[wasm] First test for sharing the {WasmEngine}.
This is a first set of test cases for sharing an {WasmEngine} and the contained {WasmCode} between multiple Isolates. Currently this can only be done using internal API methods on the Isolate, an external API that is usable by embedders does not exist yet. R=clemensh@chromium.org TEST=cctest/test-wasm-shared-engine BUG=v8:7424 Change-Id: I35541a76b5aceec4519e3a46e6a9ef4d01cad22b Reviewed-on: https://chromium-review.googlesource.com/1126382 Reviewed-by: Clemens Hammacher <clemensh@chromium.org> Commit-Queue: Michael Starzinger <mstarzinger@chromium.org> Cr-Commit-Position: refs/heads/master@{#54248}
This commit is contained in:
parent
cc9d4b1484
commit
e2d7129f5f
@ -2990,16 +2990,18 @@ bool Isolate::Init(StartupDeserializer* des) {
|
||||
DCHECK(!heap_.HasBeenSetUp());
|
||||
heap_.SetUp();
|
||||
|
||||
// Setup the wasm engine. Currently, there's one per Isolate.
|
||||
wasm_engine_.reset(
|
||||
new wasm::WasmEngine(std::unique_ptr<wasm::WasmCodeManager>(
|
||||
new wasm::WasmCodeManager(kMaxWasmCodeMemory))));
|
||||
wasm_engine_->memory_tracker()->SetAllocationResultHistogram(
|
||||
counters()->wasm_memory_allocation_result());
|
||||
wasm_engine_->memory_tracker()->SetAddressSpaceUsageHistogram(
|
||||
counters()->wasm_address_space_usage_mb());
|
||||
wasm_engine_->code_manager()->SetModuleCodeSizeHistogram(
|
||||
counters()->wasm_module_code_size_mb());
|
||||
// Setup the wasm engine. Currently, there's one per Isolate by default.
|
||||
if (wasm_engine_ == nullptr) {
|
||||
wasm_engine_.reset(
|
||||
new wasm::WasmEngine(std::unique_ptr<wasm::WasmCodeManager>(
|
||||
new wasm::WasmCodeManager(kMaxWasmCodeMemory))));
|
||||
wasm_engine_->memory_tracker()->SetAllocationResultHistogram(
|
||||
counters()->wasm_memory_allocation_result());
|
||||
wasm_engine_->memory_tracker()->SetAddressSpaceUsageHistogram(
|
||||
counters()->wasm_address_space_usage_mb());
|
||||
wasm_engine_->code_manager()->SetModuleCodeSizeHistogram(
|
||||
counters()->wasm_module_code_size_mb());
|
||||
}
|
||||
|
||||
deoptimizer_data_ = new DeoptimizerData(heap());
|
||||
|
||||
|
@ -902,7 +902,6 @@ class Isolate : private HiddenFactory {
|
||||
}
|
||||
StackGuard* stack_guard() { return &stack_guard_; }
|
||||
Heap* heap() { return &heap_; }
|
||||
wasm::WasmEngine* wasm_engine() const { return wasm_engine_.get(); }
|
||||
StubCache* load_stub_cache() { return load_stub_cache_; }
|
||||
StubCache* store_stub_cache() { return store_stub_cache_; }
|
||||
DeoptimizerData* deoptimizer_data() { return deoptimizer_data_; }
|
||||
@ -1378,6 +1377,12 @@ class Isolate : private HiddenFactory {
|
||||
elements_deletion_counter_ = value;
|
||||
}
|
||||
|
||||
wasm::WasmEngine* wasm_engine() const { return wasm_engine_.get(); }
|
||||
void set_wasm_engine(std::shared_ptr<wasm::WasmEngine> engine) {
|
||||
DCHECK_NULL(wasm_engine_); // Only call once before {Init}.
|
||||
wasm_engine_ = std::move(engine);
|
||||
}
|
||||
|
||||
const v8::Context::BackupIncumbentScope* top_backup_incumbent_scope() const {
|
||||
return top_backup_incumbent_scope_;
|
||||
}
|
||||
@ -1690,7 +1695,7 @@ class Isolate : private HiddenFactory {
|
||||
|
||||
size_t elements_deletion_counter_ = 0;
|
||||
|
||||
std::unique_ptr<wasm::WasmEngine> wasm_engine_;
|
||||
std::shared_ptr<wasm::WasmEngine> wasm_engine_;
|
||||
|
||||
std::unique_ptr<TracingCpuProfilerImpl> tracing_cpu_profiler_;
|
||||
|
||||
|
@ -130,8 +130,8 @@ class WasmMemoryTracker {
|
||||
std::unordered_map<const void*, AllocationData> allocations_;
|
||||
|
||||
// Keep pointers to
|
||||
Histogram* allocation_result_;
|
||||
Histogram* address_space_usage_mb_; // in MiB
|
||||
Histogram* allocation_result_ = nullptr;
|
||||
Histogram* address_space_usage_mb_ = nullptr; // in MiB
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(WasmMemoryTracker);
|
||||
};
|
||||
|
@ -251,6 +251,7 @@ v8_source_set("cctest_sources") {
|
||||
"wasm/test-wasm-codegen.cc",
|
||||
"wasm/test-wasm-interpreter-entry.cc",
|
||||
"wasm/test-wasm-serialization.cc",
|
||||
"wasm/test-wasm-shared-engine.cc",
|
||||
"wasm/test-wasm-stack.cc",
|
||||
"wasm/test-wasm-trap-position.cc",
|
||||
"wasm/wasm-atomics-utils.h",
|
||||
|
228
test/cctest/wasm/test-wasm-shared-engine.cc
Normal file
228
test/cctest/wasm/test-wasm-shared-engine.cc
Normal file
@ -0,0 +1,228 @@
|
||||
// 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/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>(
|
||||
base::make_unique<WasmCodeManager>(max_committed))) {}
|
||||
~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()->set_wasm_engine(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();
|
||||
}
|
||||
|
||||
// TODO(mstarzinger): Turn this into a {WasmModuleObject::New} method.
|
||||
Handle<WasmModuleObject> NewWasmModuleObject(
|
||||
Handle<FixedArray> export_wrappers, Handle<Script> script,
|
||||
SharedModule shared_module) {
|
||||
Handle<JSFunction> module_cons(
|
||||
isolate()->native_context()->wasm_module_constructor(), isolate());
|
||||
auto module_object = Handle<WasmModuleObject>::cast(
|
||||
isolate()->factory()->NewJSObject(module_cons));
|
||||
module_object->set_export_wrappers(*export_wrappers);
|
||||
script->set_wasm_module_object(*module_object);
|
||||
module_object->set_script(*script);
|
||||
module_object->set_weak_instance_list(
|
||||
isolate()->heap()->empty_weak_array_list());
|
||||
|
||||
size_t native_memory_estimate =
|
||||
isolate()->wasm_engine()->code_manager()->EstimateNativeModuleSize(
|
||||
shared_module->module());
|
||||
size_t memory_estimate = EstimateWasmModuleSize(shared_module->module()) +
|
||||
native_memory_estimate;
|
||||
Handle<Managed<wasm::NativeModule>> managed_native_module =
|
||||
Managed<wasm::NativeModule>::FromSharedPtr(isolate(), memory_estimate,
|
||||
shared_module);
|
||||
module_object->set_managed_native_module(*managed_native_module);
|
||||
return module_object;
|
||||
}
|
||||
|
||||
// TODO(mstarzinger): Switch over to a public API for sharing modules via the
|
||||
// {v8::WasmCompiledModule::TransferrableModule} class once it is ready.
|
||||
Handle<WasmInstanceObject> ImportInstance(SharedModule shared_module) {
|
||||
Vector<const byte> wire_bytes = shared_module->wire_bytes();
|
||||
Handle<Script> script = CreateWasmScript(isolate(), wire_bytes);
|
||||
int export_wrappers_size =
|
||||
static_cast<int>(shared_module->module()->num_exported_functions);
|
||||
Handle<FixedArray> export_wrappers =
|
||||
isolate()->factory()->NewFixedArray(export_wrappers_size, TENURED);
|
||||
Handle<WasmModuleObject> module_object =
|
||||
NewWasmModuleObject(export_wrappers, script, shared_module);
|
||||
|
||||
// TODO(6792): Wrappers below might be cloned using {Factory::CopyCode}.
|
||||
// This requires unlocking the code space here. This should eventually be
|
||||
// moved into the allocator.
|
||||
CodeSpaceMemoryModificationScope modification_scope(isolate()->heap());
|
||||
CompileJsToWasmWrappers(isolate(), module_object);
|
||||
|
||||
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_;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
} // 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);
|
||||
// TODO(mstarzinger): Even just deferring the destruction of the native
|
||||
// module doesn't work currently because of {WasmModule::signature_zone}.
|
||||
// module = isolate.ExportInstance(instance);
|
||||
CHECK_EQ(23, isolate.Run(instance));
|
||||
}
|
||||
// TODO(mstarzinger): Tearing down the first Isolate still invalidates the
|
||||
// compilation state and hence breaks tear down of the second Isolate.
|
||||
// {
|
||||
// SharedEngineIsolate isolate(&engine);
|
||||
// HandleScope scope(isolate.isolate());
|
||||
// Handle<WasmInstanceObject> instance = isolate.ImportInstance(module);
|
||||
// CHECK_EQ(23, isolate.Run(instance));
|
||||
// }
|
||||
}
|
||||
|
||||
} // namespace test_wasm_shared_engine
|
||||
} // namespace wasm
|
||||
} // namespace internal
|
||||
} // namespace v8
|
Loading…
Reference in New Issue
Block a user