blob: ad9a72f0ce1ef4f1020439f69d7f128ed652cd8e [file] [log] [blame]
// 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>
#include "src/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<WasmEngine>()) {}
~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:
std::shared_ptr<WasmEngine> wasm_engine_;
};
// 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()) {
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*>(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) {
Handle<WasmModuleObject> module_object =
isolate()->wasm_engine()->ImportNativeModule(isolate(), shared_module);
ErrorThrower thrower(isolate(), "ImportInstance");
MaybeHandle<WasmInstanceObject> instance =
isolate()->wasm_engine()->SyncInstantiate(isolate(), &thrower,
module_object, {}, {});
return instance.ToHandleChecked();
}
SharedModule ExportInstance(Handle<WasmInstanceObject> instance) {
return instance->module_object()->managed_native_module()->get();
}
int32_t Run(Handle<WasmInstanceObject> instance) {
return testing::RunWasmModuleForTesting(isolate(), instance, 0, nullptr);
}
private:
v8::Isolate* isolate_;
std::unique_ptr<Zone> 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<void(SharedEngineIsolate&)> callback)
: Thread(Options("SharedEngineThread")),
engine_(engine),
callback_(callback) {}
void Run() override {
SharedEngineIsolate isolate(engine_);
callback_(isolate);
}
private:
SharedEngine* engine_;
std::function<void(SharedEngineIsolate&)> 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<Object>* out_instance)
: out_instance_(out_instance) {}
void OnInstantiationSucceeded(Handle<WasmInstanceObject> result) override {
*out_instance_->location() = result->ptr();
}
void OnInstantiationFailed(Handle<Object> error_reason) override {
UNREACHABLE();
}
private:
Handle<Object>* out_instance_;
};
class MockCompilationResolver : public CompilationResultResolver {
public:
MockCompilationResolver(SharedEngineIsolate& isolate,
Handle<Object>* out_instance)
: isolate_(isolate), out_instance_(out_instance) {}
void OnCompilationSucceeded(Handle<WasmModuleObject> result) override {
isolate_.isolate()->wasm_engine()->AsyncInstantiate(
isolate_.isolate(),
base::make_unique<MockInstantiationResolver>(out_instance_), result,
{});
}
void OnCompilationFailed(Handle<Object> error_reason) override {
UNREACHABLE();
}
private:
SharedEngineIsolate& isolate_;
Handle<Object>* out_instance_;
};
void PumpMessageLoop(SharedEngineIsolate& isolate) {
v8::platform::PumpMessageLoop(i::V8::GetCurrentPlatform(),
isolate.v8_isolate(),
platform::MessageLoopBehavior::kWaitForWork);
isolate.isolate()->RunMicrotasks();
}
Handle<WasmInstanceObject> CompileAndInstantiateAsync(
SharedEngineIsolate& isolate, ZoneBuffer* buffer) {
Handle<Object> maybe_instance = handle(Smi::kZero, isolate.isolate());
auto enabled_features = WasmFeaturesFromIsolate(isolate.isolate());
isolate.isolate()->wasm_engine()->AsyncCompile(
isolate.isolate(), enabled_features,
base::make_unique<MockCompilationResolver>(isolate, &maybe_instance),
ModuleWireBytes(buffer->begin(), buffer->end()), true);
while (!maybe_instance->IsWasmInstanceObject()) PumpMessageLoop(isolate);
Handle<WasmInstanceObject> instance =
Handle<WasmInstanceObject>::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<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);
module = isolate.ExportInstance(instance);
CHECK_EQ(23, isolate.Run(instance));
CHECK_EQ(2, module.use_count());
}
CHECK_EQ(1, module.use_count());
{
SharedEngineIsolate isolate(&engine);
HandleScope scope(isolate.isolate());
Handle<WasmInstanceObject> instance = isolate.ImportInstance(module);
CHECK_EQ(23, isolate.Run(instance));
CHECK_EQ(2, module.use_count());
}
CHECK_EQ(1, module.use_count());
}
TEST(SharedEngineRunThreadedBuildingSync) {
SharedEngine engine;
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));
});
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));
});
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<WasmInstanceObject> 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<WasmInstanceObject> 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<WasmInstanceObject> instance = isolate.CompileAndInstantiate(buffer);
module = isolate.ExportInstance(instance);
}
SharedEngineThread thread1(&engine, [module](SharedEngineIsolate& isolate) {
HandleScope scope(isolate.isolate());
Handle<WasmInstanceObject> instance = isolate.ImportInstance(module);
CHECK_EQ(23, isolate.Run(instance));
});
SharedEngineThread thread2(&engine, [module](SharedEngineIsolate& isolate) {
HandleScope scope(isolate.isolate());
Handle<WasmInstanceObject> 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<WasmInstanceObject> instance = isolate.CompileAndInstantiate(buffer);
module = isolate.ExportInstance(instance);
}
constexpr int kNumberOfThreads = 5;
std::list<SharedEngineThread> threads;
for (int i = 0; i < kNumberOfThreads; ++i) {
threads.emplace_back(&engine, [module](SharedEngineIsolate& isolate) {
constexpr int kNumberOfIterations = 100;
HandleScope scope(isolate.isolate());
Handle<WasmInstanceObject> 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<WasmInstanceObject> instance = isolate.ImportInstance(module);
WasmFeatures detected = kNoWasmFeatures;
WasmCompilationUnit::CompileWasmFunction(
isolate.isolate(), module.get(), &detected,
&module->module()->functions[0], ExecutionTier::kOptimized);
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