// 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 #include "src/execution/microtask-queue.h" #include "src/objects/objects-inl.h" #include "src/wasm/function-compiler.h" #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: explicit SharedEngine(size_t max_committed = kMaxWasmCodeMemory) : wasm_engine_(base::make_unique()) {} ~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(wasm_engine_.use_count()) - 1; } std::shared_ptr ExportEngineForSharing() { return wasm_engine_; } private: std::shared_ptr wasm_engine_; }; // Helper type definition representing a WebAssembly module shared between // multiple Isolates with implicit reference counting. using SharedModule = std::shared_ptr; // 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()) { isolate()->SetWasmEngine(engine->ExportEngineForSharing()); 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_); } Handle CompileAndInstantiate(ZoneBuffer* buffer) { ErrorThrower thrower(isolate(), "CompileAndInstantiate"); MaybeHandle instance = testing::CompileAndInstantiateForTesting( isolate(), &thrower, ModuleWireBytes(buffer->begin(), buffer->end())); return instance.ToHandleChecked(); } Handle ImportInstance(SharedModule shared_module) { Handle module_object = isolate()->wasm_engine()->ImportNativeModule(isolate(), shared_module); ErrorThrower thrower(isolate(), "ImportInstance"); MaybeHandle instance = isolate()->wasm_engine()->SyncInstantiate(isolate(), &thrower, module_object, {}, {}); return instance.ToHandleChecked(); } SharedModule ExportInstance(Handle instance) { return instance->module_object().shared_native_module(); } int32_t Run(Handle instance) { return testing::RunWasmModuleForTesting(isolate(), instance, 0, nullptr); } private: v8::Isolate* isolate_; std::unique_ptr zone_; }; // 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, std::function callback) : Thread(Options("SharedEngineThread")), engine_(engine), callback_(callback) {} void Run() override { SharedEngineIsolate isolate(engine_); callback_(isolate); } private: SharedEngine* engine_; std::function callback_; }; namespace { ZoneBuffer* BuildReturnConstantModule(Zone* zone, int constant) { TestSignatures sigs; ZoneBuffer* buffer = new (zone) ZoneBuffer(zone); WasmModuleBuilder* builder = new (zone) WasmModuleBuilder(zone); 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); builder->WriteTo(buffer); return buffer; } class MockInstantiationResolver : public InstantiationResultResolver { public: explicit MockInstantiationResolver(Handle* out_instance) : out_instance_(out_instance) {} void OnInstantiationSucceeded(Handle result) override { *out_instance_->location() = result->ptr(); } void OnInstantiationFailed(Handle error_reason) override { UNREACHABLE(); } private: Handle* out_instance_; }; class MockCompilationResolver : public CompilationResultResolver { public: MockCompilationResolver( SharedEngineIsolate& isolate, // NOLINT(runtime/references) Handle* out_instance) : isolate_(isolate), out_instance_(out_instance) {} void OnCompilationSucceeded(Handle result) override { isolate_.isolate()->wasm_engine()->AsyncInstantiate( isolate_.isolate(), base::make_unique(out_instance_), result, {}); } void OnCompilationFailed(Handle error_reason) override { UNREACHABLE(); } private: SharedEngineIsolate& isolate_; Handle* out_instance_; }; void PumpMessageLoop( SharedEngineIsolate& isolate) { // NOLINT(runtime/references) v8::platform::PumpMessageLoop(i::V8::GetCurrentPlatform(), isolate.v8_isolate(), platform::MessageLoopBehavior::kWaitForWork); isolate.isolate()->default_microtask_queue()->RunMicrotasks( isolate.isolate()); } Handle CompileAndInstantiateAsync( SharedEngineIsolate& isolate, // NOLINT(runtime/references) ZoneBuffer* buffer) { Handle maybe_instance = handle(Smi::kZero, isolate.isolate()); auto enabled_features = WasmFeaturesFromIsolate(isolate.isolate()); constexpr const char* kAPIMethodName = "Test.CompileAndInstantiateAsync"; isolate.isolate()->wasm_engine()->AsyncCompile( isolate.isolate(), enabled_features, base::make_unique(isolate, &maybe_instance), ModuleWireBytes(buffer->begin(), buffer->end()), true, kAPIMethodName); while (!maybe_instance->IsWasmInstanceObject()) PumpMessageLoop(isolate); Handle instance = Handle::cast(maybe_instance); return instance; } } // 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 instance = isolate.CompileAndInstantiate(buffer); CHECK_EQ(23, isolate.Run(instance)); } { SharedEngineIsolate isolate(&engine); HandleScope scope(isolate.isolate()); ZoneBuffer* buffer = BuildReturnConstantModule(isolate.zone(), 42); Handle 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 instance = isolate.CompileAndInstantiate(buffer); module = isolate.ExportInstance(instance); CHECK_EQ(23, isolate.Run(instance)); } { SharedEngineIsolate isolate(&engine); HandleScope scope(isolate.isolate()); Handle instance = isolate.ImportInstance(module); CHECK_EQ(23, isolate.Run(instance)); } } TEST(SharedEngineRunThreadedBuildingSync) { SharedEngine engine; SharedEngineThread thread1(&engine, [](SharedEngineIsolate& isolate) { HandleScope scope(isolate.isolate()); ZoneBuffer* buffer = BuildReturnConstantModule(isolate.zone(), 23); Handle instance = isolate.CompileAndInstantiate(buffer); CHECK_EQ(23, isolate.Run(instance)); }); SharedEngineThread thread2(&engine, [](SharedEngineIsolate& isolate) { HandleScope scope(isolate.isolate()); ZoneBuffer* buffer = BuildReturnConstantModule(isolate.zone(), 42); Handle instance = isolate.CompileAndInstantiate(buffer); CHECK_EQ(42, isolate.Run(instance)); }); thread1.Start(); thread2.Start(); thread1.Join(); thread2.Join(); } TEST(SharedEngineRunThreadedBuildingAsync) { SharedEngine engine; SharedEngineThread thread1(&engine, [](SharedEngineIsolate& isolate) { HandleScope scope(isolate.isolate()); ZoneBuffer* buffer = BuildReturnConstantModule(isolate.zone(), 23); Handle instance = CompileAndInstantiateAsync(isolate, buffer); CHECK_EQ(23, isolate.Run(instance)); }); SharedEngineThread thread2(&engine, [](SharedEngineIsolate& isolate) { HandleScope scope(isolate.isolate()); ZoneBuffer* buffer = BuildReturnConstantModule(isolate.zone(), 42); Handle instance = CompileAndInstantiateAsync(isolate, buffer); CHECK_EQ(42, isolate.Run(instance)); }); thread1.Start(); thread2.Start(); thread1.Join(); thread2.Join(); } TEST(SharedEngineRunThreadedExecution) { SharedEngine engine; SharedModule module; { SharedEngineIsolate isolate(&engine); HandleScope scope(isolate.isolate()); ZoneBuffer* buffer = BuildReturnConstantModule(isolate.zone(), 23); Handle instance = isolate.CompileAndInstantiate(buffer); module = isolate.ExportInstance(instance); } SharedEngineThread thread1(&engine, [module](SharedEngineIsolate& isolate) { HandleScope scope(isolate.isolate()); Handle instance = isolate.ImportInstance(module); CHECK_EQ(23, isolate.Run(instance)); }); SharedEngineThread thread2(&engine, [module](SharedEngineIsolate& isolate) { HandleScope scope(isolate.isolate()); Handle instance = isolate.ImportInstance(module); CHECK_EQ(23, isolate.Run(instance)); }); thread1.Start(); thread2.Start(); thread1.Join(); thread2.Join(); } TEST(SharedEngineRunThreadedTierUp) { SharedEngine engine; SharedModule module; { SharedEngineIsolate isolate(&engine); HandleScope scope(isolate.isolate()); ZoneBuffer* buffer = BuildReturnConstantModule(isolate.zone(), 23); Handle instance = isolate.CompileAndInstantiate(buffer); module = isolate.ExportInstance(instance); } constexpr int kNumberOfThreads = 5; std::list threads; for (int i = 0; i < kNumberOfThreads; ++i) { threads.emplace_back(&engine, [module](SharedEngineIsolate& isolate) { constexpr int kNumberOfIterations = 100; HandleScope scope(isolate.isolate()); Handle instance = isolate.ImportInstance(module); for (int j = 0; j < kNumberOfIterations; ++j) { CHECK_EQ(23, isolate.Run(instance)); } }); } threads.emplace_back(&engine, [module](SharedEngineIsolate& isolate) { HandleScope scope(isolate.isolate()); Handle instance = isolate.ImportInstance(module); WasmFeatures detected = kNoWasmFeatures; WasmCompilationUnit::CompileWasmFunction( isolate.isolate(), module.get(), &detected, &module->module()->functions[0], ExecutionTier::kTurbofan); CHECK_EQ(23, isolate.Run(instance)); }); for (auto& thread : threads) thread.Start(); for (auto& thread : threads) thread.Join(); } } // namespace test_wasm_shared_engine } // namespace wasm } // namespace internal } // namespace v8