test-wasm-shared-engine.cc 12.9 KB
Newer Older
1 2 3 4 5 6
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>

7
#include "src/execution/microtask-queue.h"
8
#include "src/objects/objects-inl.h"
9
#include "src/wasm/function-compiler.h"
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-module-builder.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-objects-inl.h"

#include "test/cctest/cctest.h"
#include "test/common/wasm/test-signatures.h"
#include "test/common/wasm/wasm-macro-gen.h"
#include "test/common/wasm/wasm-module-runner.h"

namespace v8 {
namespace internal {
namespace wasm {
namespace test_wasm_shared_engine {

// Helper class representing a WebAssembly engine that is capable of being
// shared between multiple Isolates, sharing the underlying generated code.
class SharedEngine {
 public:
  ~SharedEngine() {
    // Ensure no remaining uses exist.
    CHECK(wasm_engine_.unique());
  }

  WasmEngine* engine() const { return wasm_engine_.get(); }
  WasmCodeManager* code_manager() const { return engine()->code_manager(); }

  int NumberOfExportedEngineUses() const {
    // This class holds one implicit use itself, which we discount.
    return static_cast<int>(wasm_engine_.use_count()) - 1;
  }

  std::shared_ptr<WasmEngine> ExportEngineForSharing() { return wasm_engine_; }

 private:
45
  std::shared_ptr<WasmEngine> wasm_engine_ = std::make_unique<WasmEngine>();
46 47 48 49 50 51 52 53 54 55 56 57
};

// Helper type definition representing a WebAssembly module shared between
// multiple Isolates with implicit reference counting.
using SharedModule = std::shared_ptr<NativeModule>;

// Helper class representing an Isolate based on a given shared WebAssembly
// engine available at construction time.
class SharedEngineIsolate {
 public:
  explicit SharedEngineIsolate(SharedEngine* engine)
      : isolate_(v8::Isolate::Allocate()) {
58
    isolate()->SetWasmEngine(engine->ExportEngineForSharing());
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
    v8::Isolate::CreateParams create_params;
    create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
    v8::Isolate::Initialize(isolate_, create_params);
    v8::HandleScope handle_scope(v8_isolate());
    v8::Context::New(v8_isolate())->Enter();
    testing::SetupIsolateForWasmModule(isolate());
    zone_.reset(new Zone(isolate()->allocator(), ZONE_NAME));
  }
  ~SharedEngineIsolate() {
    zone_.reset();
    isolate_->Dispose();
  }

  Zone* zone() const { return zone_.get(); }
  v8::Isolate* v8_isolate() { return isolate_; }
  Isolate* isolate() { return reinterpret_cast<Isolate*>(isolate_); }

  Handle<WasmInstanceObject> CompileAndInstantiate(ZoneBuffer* buffer) {
    ErrorThrower thrower(isolate(), "CompileAndInstantiate");
    MaybeHandle<WasmInstanceObject> instance =
        testing::CompileAndInstantiateForTesting(
            isolate(), &thrower,
            ModuleWireBytes(buffer->begin(), buffer->end()));
    return instance.ToHandleChecked();
  }

  Handle<WasmInstanceObject> ImportInstance(SharedModule shared_module) {
86
    Handle<WasmModuleObject> module_object =
87 88
        isolate()->wasm_engine()->ImportNativeModule(isolate(), shared_module,
                                                     {});
89 90 91 92 93 94 95 96
    ErrorThrower thrower(isolate(), "ImportInstance");
    MaybeHandle<WasmInstanceObject> instance =
        isolate()->wasm_engine()->SyncInstantiate(isolate(), &thrower,
                                                  module_object, {}, {});
    return instance.ToHandleChecked();
  }

  SharedModule ExportInstance(Handle<WasmInstanceObject> instance) {
97
    return instance->module_object().shared_native_module();
98 99 100
  }

  int32_t Run(Handle<WasmInstanceObject> instance) {
101 102
    return testing::CallWasmFunctionForTesting(isolate(), instance, "main", 0,
                                               nullptr);
103 104 105 106 107 108 109
  }

 private:
  v8::Isolate* isolate_;
  std::unique_ptr<Zone> zone_;
};

110 111 112 113 114
// Helper class representing a Thread running its own instance of an Isolate
// with a shared WebAssembly engine available at construction time.
class SharedEngineThread : public v8::base::Thread {
 public:
  SharedEngineThread(SharedEngine* engine,
115
                     std::function<void(SharedEngineIsolate*)> callback)
116 117 118 119
      : Thread(Options("SharedEngineThread")),
        engine_(engine),
        callback_(callback) {}

120
  void Run() override {
121
    SharedEngineIsolate isolate(engine_);
122
    callback_(&isolate);
123 124 125 126
  }

 private:
  SharedEngine* engine_;
127
  std::function<void(SharedEngineIsolate*)> callback_;
128 129
};

130 131 132 133
namespace {

ZoneBuffer* BuildReturnConstantModule(Zone* zone, int constant) {
  TestSignatures sigs;
134 135
  ZoneBuffer* buffer = zone->New<ZoneBuffer>(zone);
  WasmModuleBuilder* builder = zone->New<WasmModuleBuilder>(zone);
136 137 138 139 140
  WasmFunctionBuilder* f = builder->AddFunction(sigs.i_v());
  f->builder()->AddExport(CStrVector("main"), f);
  byte code[] = {WASM_I32V_2(constant)};
  f->EmitCode(code, sizeof(code));
  f->Emit(kExprEnd);
141
  builder->WriteTo(buffer);
142 143 144
  return buffer;
}

145 146 147 148
class MockInstantiationResolver : public InstantiationResultResolver {
 public:
  explicit MockInstantiationResolver(Handle<Object>* out_instance)
      : out_instance_(out_instance) {}
149
  void OnInstantiationSucceeded(Handle<WasmInstanceObject> result) override {
150
    *out_instance_->location() = result->ptr();
151
  }
152
  void OnInstantiationFailed(Handle<Object> error_reason) override {
153 154 155 156 157 158 159 160 161
    UNREACHABLE();
  }

 private:
  Handle<Object>* out_instance_;
};

class MockCompilationResolver : public CompilationResultResolver {
 public:
162 163
  MockCompilationResolver(SharedEngineIsolate* isolate,
                          Handle<Object>* out_instance)
164
      : isolate_(isolate), out_instance_(out_instance) {}
165
  void OnCompilationSucceeded(Handle<WasmModuleObject> result) override {
166 167
    isolate_->isolate()->wasm_engine()->AsyncInstantiate(
        isolate_->isolate(),
168
        std::make_unique<MockInstantiationResolver>(out_instance_), result, {});
169
  }
170
  void OnCompilationFailed(Handle<Object> error_reason) override {
171 172 173 174
    UNREACHABLE();
  }

 private:
175
  SharedEngineIsolate* isolate_;
176 177 178
  Handle<Object>* out_instance_;
};

179
void PumpMessageLoop(SharedEngineIsolate* isolate) {
180
  v8::platform::PumpMessageLoop(i::V8::GetCurrentPlatform(),
181
                                isolate->v8_isolate(),
182
                                platform::MessageLoopBehavior::kWaitForWork);
183 184
  isolate->isolate()->default_microtask_queue()->RunMicrotasks(
      isolate->isolate());
185 186 187
}

Handle<WasmInstanceObject> CompileAndInstantiateAsync(
188
    SharedEngineIsolate* isolate, ZoneBuffer* buffer) {
189
  Handle<Object> maybe_instance = handle(Smi::zero(), isolate->isolate());
190
  auto enabled_features = WasmFeatures::FromIsolate(isolate->isolate());
191
  constexpr const char* kAPIMethodName = "Test.CompileAndInstantiateAsync";
192 193
  isolate->isolate()->wasm_engine()->AsyncCompile(
      isolate->isolate(), enabled_features,
194
      std::make_unique<MockCompilationResolver>(isolate, &maybe_instance),
195
      ModuleWireBytes(buffer->begin(), buffer->end()), true, kAPIMethodName);
196 197 198 199 200 201
  while (!maybe_instance->IsWasmInstanceObject()) PumpMessageLoop(isolate);
  Handle<WasmInstanceObject> instance =
      Handle<WasmInstanceObject>::cast(maybe_instance);
  return instance;
}

202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
}  // namespace

TEST(SharedEngineUseCount) {
  SharedEngine engine;
  CHECK_EQ(0, engine.NumberOfExportedEngineUses());
  {
    SharedEngineIsolate isolate(&engine);
    CHECK_EQ(1, engine.NumberOfExportedEngineUses());
  }
  CHECK_EQ(0, engine.NumberOfExportedEngineUses());
  {
    SharedEngineIsolate isolate1(&engine);
    CHECK_EQ(1, engine.NumberOfExportedEngineUses());
    SharedEngineIsolate isolate2(&engine);
    CHECK_EQ(2, engine.NumberOfExportedEngineUses());
  }
  CHECK_EQ(0, engine.NumberOfExportedEngineUses());
}

TEST(SharedEngineRunSeparated) {
  SharedEngine engine;
  {
    SharedEngineIsolate isolate(&engine);
    HandleScope scope(isolate.isolate());
    ZoneBuffer* buffer = BuildReturnConstantModule(isolate.zone(), 23);
    Handle<WasmInstanceObject> instance = isolate.CompileAndInstantiate(buffer);
    CHECK_EQ(23, isolate.Run(instance));
  }
  {
    SharedEngineIsolate isolate(&engine);
    HandleScope scope(isolate.isolate());
    ZoneBuffer* buffer = BuildReturnConstantModule(isolate.zone(), 42);
    Handle<WasmInstanceObject> instance = isolate.CompileAndInstantiate(buffer);
    CHECK_EQ(42, isolate.Run(instance));
  }
}

TEST(SharedEngineRunImported) {
  SharedEngine engine;
  SharedModule module;
  {
    SharedEngineIsolate isolate(&engine);
    HandleScope scope(isolate.isolate());
    ZoneBuffer* buffer = BuildReturnConstantModule(isolate.zone(), 23);
    Handle<WasmInstanceObject> instance = isolate.CompileAndInstantiate(buffer);
247
    module = isolate.ExportInstance(instance);
248 249
    CHECK_EQ(23, isolate.Run(instance));
  }
250 251 252 253 254 255
  {
    SharedEngineIsolate isolate(&engine);
    HandleScope scope(isolate.isolate());
    Handle<WasmInstanceObject> instance = isolate.ImportInstance(module);
    CHECK_EQ(23, isolate.Run(instance));
  }
256 257
}

258
TEST(SharedEngineRunThreadedBuildingSync) {
259
  SharedEngine engine;
260 261 262 263 264 265
  SharedEngineThread thread1(&engine, [](SharedEngineIsolate* isolate) {
    HandleScope scope(isolate->isolate());
    ZoneBuffer* buffer = BuildReturnConstantModule(isolate->zone(), 23);
    Handle<WasmInstanceObject> instance =
        isolate->CompileAndInstantiate(buffer);
    CHECK_EQ(23, isolate->Run(instance));
266
  });
267 268 269 270 271 272
  SharedEngineThread thread2(&engine, [](SharedEngineIsolate* isolate) {
    HandleScope scope(isolate->isolate());
    ZoneBuffer* buffer = BuildReturnConstantModule(isolate->zone(), 42);
    Handle<WasmInstanceObject> instance =
        isolate->CompileAndInstantiate(buffer);
    CHECK_EQ(42, isolate->Run(instance));
273
  });
274 275
  CHECK(thread1.Start());
  CHECK(thread2.Start());
276 277 278 279
  thread1.Join();
  thread2.Join();
}

280 281
TEST(SharedEngineRunThreadedBuildingAsync) {
  SharedEngine engine;
282 283 284
  SharedEngineThread thread1(&engine, [](SharedEngineIsolate* isolate) {
    HandleScope scope(isolate->isolate());
    ZoneBuffer* buffer = BuildReturnConstantModule(isolate->zone(), 23);
285 286
    Handle<WasmInstanceObject> instance =
        CompileAndInstantiateAsync(isolate, buffer);
287
    CHECK_EQ(23, isolate->Run(instance));
288
  });
289 290 291
  SharedEngineThread thread2(&engine, [](SharedEngineIsolate* isolate) {
    HandleScope scope(isolate->isolate());
    ZoneBuffer* buffer = BuildReturnConstantModule(isolate->zone(), 42);
292 293
    Handle<WasmInstanceObject> instance =
        CompileAndInstantiateAsync(isolate, buffer);
294
    CHECK_EQ(42, isolate->Run(instance));
295
  });
296 297
  CHECK(thread1.Start());
  CHECK(thread2.Start());
298 299 300 301
  thread1.Join();
  thread2.Join();
}

302 303 304 305 306 307 308 309 310 311
TEST(SharedEngineRunThreadedExecution) {
  SharedEngine engine;
  SharedModule module;
  {
    SharedEngineIsolate isolate(&engine);
    HandleScope scope(isolate.isolate());
    ZoneBuffer* buffer = BuildReturnConstantModule(isolate.zone(), 23);
    Handle<WasmInstanceObject> instance = isolate.CompileAndInstantiate(buffer);
    module = isolate.ExportInstance(instance);
  }
312 313 314 315
  SharedEngineThread thread1(&engine, [module](SharedEngineIsolate* isolate) {
    HandleScope scope(isolate->isolate());
    Handle<WasmInstanceObject> instance = isolate->ImportInstance(module);
    CHECK_EQ(23, isolate->Run(instance));
316
  });
317 318 319 320
  SharedEngineThread thread2(&engine, [module](SharedEngineIsolate* isolate) {
    HandleScope scope(isolate->isolate());
    Handle<WasmInstanceObject> instance = isolate->ImportInstance(module);
    CHECK_EQ(23, isolate->Run(instance));
321
  });
322 323
  CHECK(thread1.Start());
  CHECK(thread2.Start());
324 325 326 327
  thread1.Join();
  thread2.Join();
}

328 329 330 331 332 333 334 335 336 337 338 339 340
TEST(SharedEngineRunThreadedTierUp) {
  SharedEngine engine;
  SharedModule module;
  {
    SharedEngineIsolate isolate(&engine);
    HandleScope scope(isolate.isolate());
    ZoneBuffer* buffer = BuildReturnConstantModule(isolate.zone(), 23);
    Handle<WasmInstanceObject> instance = isolate.CompileAndInstantiate(buffer);
    module = isolate.ExportInstance(instance);
  }
  constexpr int kNumberOfThreads = 5;
  std::list<SharedEngineThread> threads;
  for (int i = 0; i < kNumberOfThreads; ++i) {
341
    threads.emplace_back(&engine, [module](SharedEngineIsolate* isolate) {
342
      constexpr int kNumberOfIterations = 100;
343 344
      HandleScope scope(isolate->isolate());
      Handle<WasmInstanceObject> instance = isolate->ImportInstance(module);
345
      for (int j = 0; j < kNumberOfIterations; ++j) {
346
        CHECK_EQ(23, isolate->Run(instance));
347 348 349
      }
    });
  }
350 351 352
  threads.emplace_back(&engine, [module](SharedEngineIsolate* isolate) {
    HandleScope scope(isolate->isolate());
    Handle<WasmInstanceObject> instance = isolate->ImportInstance(module);
353
    WasmFeatures detected = WasmFeatures::None();
354
    WasmCompilationUnit::CompileWasmFunction(
355
        isolate->isolate(), module.get(), &detected,
356
        &module->module()->functions[0], ExecutionTier::kTurbofan);
357
    CHECK_EQ(23, isolate->Run(instance));
358
  });
359
  for (auto& thread : threads) CHECK(thread.Start());
360 361 362
  for (auto& thread : threads) thread.Join();
}

363 364 365 366
}  // namespace test_wasm_shared_engine
}  // namespace wasm
}  // namespace internal
}  // namespace v8