62e168308c
This reverts commit8fdb23873b
. Original change's description: > "Reland x4 [arraybuffer] Rearchitect backing store ownership" > > This is a reland ofbc33f5aeba
> > Contributed by titzer@chromium.org > > Original change's description: > > [arraybuffer] Rearchitect backing store ownership > > > > This CL completely rearchitects the ownership of array buffer backing stores, > > consolidating ownership into a {BackingStore} C++ object that is tracked > > throughout V8 using unique_ptr and shared_ptr where appropriate. > > > > Overall, lifetime management is simpler and more explicit. The numerous > > ways that array buffers were initialized have been streamlined to one > > Attach() method on JSArrayBuffer. The array buffer tracker in the > > GC implementation now manages std::shared_ptr<BackingStore> pointers, > > and the construction and destruction of the BackingStore object itself > > handles the underlying page or embedder-allocated memory. > > > > The embedder API remains unchanged for now. We use the > > v8::ArrayBuffer::Contents struct to hide an additional shared_ptr to > > keep the backing store alive properly, even in the case of aliases > > from live heap objects. Thus the embedder has a lower chance of making > > a mistake. Long-term, we should move the embedder to a model where they > > manage backing stores using shared_ptr to an opaque backing store object. > > TBR=yangguo@chromium.org > > BUG=v8:9380,v8:9221,chromium:986318 > > Change-Id: If671a4a9ca0476e8f084efae46e0d2bf99ed99ef > Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1731005 > Commit-Queue: Ulan Degenbaev <ulan@chromium.org> > Reviewed-by: Clemens Hammacher <clemensh@chromium.org> > Reviewed-by: Michael Starzinger <mstarzinger@chromium.org> > Cr-Commit-Position: refs/heads/master@{#63041} TBR=yangguo@chromium.org,clemensh@chromium.org,mstarzinger@chromium.org Change-Id: Iba55c7ab71e5642b5cb6aeb699d6fc9cf9061486 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1771795 Reviewed-by: Michael Lippautz <mlippautz@chromium.org> Commit-Queue: Ulan Degenbaev <ulan@chromium.org> Cr-Commit-Position: refs/heads/master@{#63461}
351 lines
11 KiB
C++
351 lines
11 KiB
C++
// Copyright 2015 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 <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "src/api/api-inl.h"
|
|
#include "src/objects/objects-inl.h"
|
|
#include "src/snapshot/code-serializer.h"
|
|
#include "src/utils/version.h"
|
|
#include "src/wasm/module-decoder.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 "src/wasm/wasm-opcodes.h"
|
|
|
|
#include "test/cctest/cctest.h"
|
|
#include "test/common/wasm/flag-utils.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_serialization {
|
|
|
|
namespace {
|
|
void Cleanup(Isolate* isolate = CcTest::InitIsolateOnce()) {
|
|
// By sending a low memory notifications, we will try hard to collect all
|
|
// garbage and will therefore also invoke all weak callbacks of actually
|
|
// unreachable persistent handles.
|
|
reinterpret_cast<v8::Isolate*>(isolate)->LowMemoryNotification();
|
|
}
|
|
|
|
#define EMIT_CODE_WITH_END(f, code) \
|
|
do { \
|
|
f->EmitCode(code, sizeof(code)); \
|
|
f->Emit(kExprEnd); \
|
|
} while (false)
|
|
|
|
} // namespace
|
|
|
|
// Approximate gtest TEST_F style, in case we adopt gtest.
|
|
class WasmSerializationTest {
|
|
public:
|
|
WasmSerializationTest() : zone_(&allocator_, ZONE_NAME) {
|
|
// Don't call here if we move to gtest.
|
|
SetUp();
|
|
}
|
|
|
|
static void BuildWireBytes(Zone* zone, ZoneBuffer* buffer) {
|
|
WasmModuleBuilder* builder = new (zone) WasmModuleBuilder(zone);
|
|
TestSignatures sigs;
|
|
|
|
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_i());
|
|
byte code[] = {WASM_GET_LOCAL(0), kExprI32Const, 1, kExprI32Add};
|
|
EMIT_CODE_WITH_END(f, code);
|
|
builder->AddExport(CStrVector(kFunctionName), f);
|
|
|
|
builder->WriteTo(buffer);
|
|
}
|
|
|
|
void ClearSerializedData() { serialized_bytes_ = {nullptr, 0}; }
|
|
|
|
void InvalidateVersion() {
|
|
uint32_t* slot = reinterpret_cast<uint32_t*>(
|
|
const_cast<uint8_t*>(serialized_bytes_.data()) +
|
|
SerializedCodeData::kVersionHashOffset);
|
|
*slot = Version::Hash() + 1;
|
|
}
|
|
|
|
void InvalidateWireBytes() {
|
|
memset(const_cast<uint8_t*>(wire_bytes_.data()), 0, wire_bytes_.size() / 2);
|
|
}
|
|
|
|
void InvalidateLength() {
|
|
uint32_t* slot = reinterpret_cast<uint32_t*>(
|
|
const_cast<uint8_t*>(serialized_bytes_.data()) +
|
|
SerializedCodeData::kPayloadLengthOffset);
|
|
*slot = 0u;
|
|
}
|
|
|
|
v8::MaybeLocal<v8::WasmModuleObject> Deserialize() {
|
|
ErrorThrower thrower(current_isolate(), "");
|
|
v8::MaybeLocal<v8::WasmModuleObject> deserialized =
|
|
v8::WasmModuleObject::DeserializeOrCompile(
|
|
current_isolate_v8(), serialized_bytes_, wire_bytes_);
|
|
return deserialized;
|
|
}
|
|
|
|
void DeserializeAndRun() {
|
|
ErrorThrower thrower(current_isolate(), "");
|
|
v8::Local<v8::WasmModuleObject> deserialized_module;
|
|
CHECK(Deserialize().ToLocal(&deserialized_module));
|
|
Handle<WasmModuleObject> module_object = Handle<WasmModuleObject>::cast(
|
|
v8::Utils::OpenHandle(*deserialized_module));
|
|
{
|
|
DisallowHeapAllocation assume_no_gc;
|
|
Vector<const byte> deserialized_module_wire_bytes =
|
|
module_object->native_module()->wire_bytes();
|
|
CHECK_EQ(deserialized_module_wire_bytes.size(), wire_bytes_.size());
|
|
CHECK_EQ(memcmp(deserialized_module_wire_bytes.begin(),
|
|
wire_bytes_.data(), wire_bytes_.size()),
|
|
0);
|
|
}
|
|
Handle<WasmInstanceObject> instance =
|
|
current_isolate()
|
|
->wasm_engine()
|
|
->SyncInstantiate(current_isolate(), &thrower, module_object,
|
|
Handle<JSReceiver>::null(),
|
|
MaybeHandle<JSArrayBuffer>())
|
|
.ToHandleChecked();
|
|
Handle<Object> params[1] = {
|
|
Handle<Object>(Smi::FromInt(41), current_isolate())};
|
|
int32_t result = testing::CallWasmFunctionForTesting(
|
|
current_isolate(), instance, &thrower, kFunctionName, 1, params);
|
|
CHECK_EQ(42, result);
|
|
}
|
|
|
|
Isolate* current_isolate() {
|
|
return reinterpret_cast<Isolate*>(current_isolate_v8_);
|
|
}
|
|
|
|
~WasmSerializationTest() {
|
|
// Don't call from here if we move to gtest
|
|
TearDown();
|
|
}
|
|
|
|
v8::Isolate* current_isolate_v8() { return current_isolate_v8_; }
|
|
|
|
private:
|
|
static const char* kFunctionName;
|
|
|
|
Zone* zone() { return &zone_; }
|
|
|
|
void SetUp() {
|
|
ZoneBuffer buffer(&zone_);
|
|
WasmSerializationTest::BuildWireBytes(zone(), &buffer);
|
|
|
|
Isolate* serialization_isolate = CcTest::InitIsolateOnce();
|
|
ErrorThrower thrower(serialization_isolate, "");
|
|
{
|
|
HandleScope scope(serialization_isolate);
|
|
testing::SetupIsolateForWasmModule(serialization_isolate);
|
|
|
|
auto enabled_features = WasmFeaturesFromIsolate(serialization_isolate);
|
|
MaybeHandle<WasmModuleObject> maybe_module_object =
|
|
serialization_isolate->wasm_engine()->SyncCompile(
|
|
serialization_isolate, enabled_features, &thrower,
|
|
ModuleWireBytes(buffer.begin(), buffer.end()));
|
|
Handle<WasmModuleObject> module_object =
|
|
maybe_module_object.ToHandleChecked();
|
|
|
|
v8::Local<v8::Object> v8_module_obj =
|
|
v8::Utils::ToLocal(Handle<JSObject>::cast(module_object));
|
|
CHECK(v8_module_obj->IsWebAssemblyCompiledModule());
|
|
|
|
v8::Local<v8::WasmModuleObject> v8_module_object =
|
|
v8_module_obj.As<v8::WasmModuleObject>();
|
|
v8::CompiledWasmModule compiled_module =
|
|
v8_module_object->GetCompiledModule();
|
|
v8::MemorySpan<const uint8_t> uncompiled_bytes =
|
|
compiled_module.GetWireBytesRef();
|
|
uint8_t* bytes_copy = zone()->NewArray<uint8_t>(uncompiled_bytes.size());
|
|
memcpy(bytes_copy, uncompiled_bytes.data(), uncompiled_bytes.size());
|
|
wire_bytes_ = {bytes_copy, uncompiled_bytes.size()};
|
|
// keep alive data_ until the end
|
|
data_ = compiled_module.Serialize();
|
|
}
|
|
|
|
serialized_bytes_ = {data_.buffer.get(), data_.size};
|
|
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator =
|
|
serialization_isolate->array_buffer_allocator();
|
|
|
|
current_isolate_v8_ = v8::Isolate::New(create_params);
|
|
v8::HandleScope new_scope(current_isolate_v8());
|
|
v8::Local<v8::Context> deserialization_context =
|
|
v8::Context::New(current_isolate_v8());
|
|
deserialization_context->Enter();
|
|
testing::SetupIsolateForWasmModule(current_isolate());
|
|
}
|
|
|
|
void TearDown() {
|
|
current_isolate_v8()->Dispose();
|
|
current_isolate_v8_ = nullptr;
|
|
}
|
|
|
|
v8::internal::AccountingAllocator allocator_;
|
|
Zone zone_;
|
|
v8::OwnedBuffer data_;
|
|
v8::MemorySpan<const uint8_t> wire_bytes_ = {nullptr, 0};
|
|
v8::MemorySpan<const uint8_t> serialized_bytes_ = {nullptr, 0};
|
|
v8::Isolate* current_isolate_v8_;
|
|
};
|
|
|
|
const char* WasmSerializationTest::kFunctionName = "increment";
|
|
|
|
TEST(DeserializeValidModule) {
|
|
WasmSerializationTest test;
|
|
{
|
|
HandleScope scope(test.current_isolate());
|
|
test.DeserializeAndRun();
|
|
}
|
|
Cleanup(test.current_isolate());
|
|
Cleanup();
|
|
}
|
|
|
|
TEST(DeserializeMismatchingVersion) {
|
|
WasmSerializationTest test;
|
|
{
|
|
HandleScope scope(test.current_isolate());
|
|
test.InvalidateVersion();
|
|
test.DeserializeAndRun();
|
|
}
|
|
Cleanup(test.current_isolate());
|
|
Cleanup();
|
|
}
|
|
|
|
TEST(DeserializeNoSerializedData) {
|
|
WasmSerializationTest test;
|
|
{
|
|
HandleScope scope(test.current_isolate());
|
|
test.ClearSerializedData();
|
|
test.DeserializeAndRun();
|
|
}
|
|
Cleanup(test.current_isolate());
|
|
Cleanup();
|
|
}
|
|
|
|
TEST(DeserializeInvalidLength) {
|
|
WasmSerializationTest test;
|
|
{
|
|
HandleScope scope(test.current_isolate());
|
|
test.InvalidateLength();
|
|
test.DeserializeAndRun();
|
|
}
|
|
Cleanup(test.current_isolate());
|
|
Cleanup();
|
|
}
|
|
|
|
TEST(DeserializeWireBytesAndSerializedDataInvalid) {
|
|
WasmSerializationTest test;
|
|
{
|
|
HandleScope scope(test.current_isolate());
|
|
test.InvalidateVersion();
|
|
test.InvalidateWireBytes();
|
|
test.Deserialize();
|
|
}
|
|
Cleanup(test.current_isolate());
|
|
Cleanup();
|
|
}
|
|
|
|
bool False(v8::Local<v8::Context> context, v8::Local<v8::String> source) {
|
|
return false;
|
|
}
|
|
|
|
TEST(BlockWasmCodeGenAtDeserialization) {
|
|
WasmSerializationTest test;
|
|
{
|
|
HandleScope scope(test.current_isolate());
|
|
test.current_isolate_v8()->SetAllowCodeGenerationFromStringsCallback(False);
|
|
v8::MaybeLocal<v8::WasmModuleObject> nothing = test.Deserialize();
|
|
CHECK(nothing.IsEmpty());
|
|
}
|
|
Cleanup(test.current_isolate());
|
|
Cleanup();
|
|
}
|
|
|
|
namespace {
|
|
|
|
void TestTransferrableWasmModules(bool should_share) {
|
|
i::wasm::WasmEngine::InitializeOncePerProcess();
|
|
v8::internal::AccountingAllocator allocator;
|
|
Zone zone(&allocator, ZONE_NAME);
|
|
|
|
ZoneBuffer buffer(&zone);
|
|
WasmSerializationTest::BuildWireBytes(&zone, &buffer);
|
|
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator = CcTest::array_buffer_allocator();
|
|
v8::Isolate* from_isolate = v8::Isolate::New(create_params);
|
|
std::vector<v8::WasmModuleObject::TransferrableModule> store;
|
|
std::shared_ptr<NativeModule> original_native_module;
|
|
{
|
|
v8::HandleScope scope(from_isolate);
|
|
LocalContext env(from_isolate);
|
|
|
|
Isolate* from_i_isolate = reinterpret_cast<Isolate*>(from_isolate);
|
|
testing::SetupIsolateForWasmModule(from_i_isolate);
|
|
ErrorThrower thrower(from_i_isolate, "TestTransferrableWasmModules");
|
|
auto enabled_features = WasmFeaturesFromIsolate(from_i_isolate);
|
|
MaybeHandle<WasmModuleObject> maybe_module_object =
|
|
from_i_isolate->wasm_engine()->SyncCompile(
|
|
from_i_isolate, enabled_features, &thrower,
|
|
ModuleWireBytes(buffer.begin(), buffer.end()));
|
|
Handle<WasmModuleObject> module_object =
|
|
maybe_module_object.ToHandleChecked();
|
|
v8::Local<v8::WasmModuleObject> v8_module =
|
|
v8::Local<v8::WasmModuleObject>::Cast(
|
|
v8::Utils::ToLocal(Handle<JSObject>::cast(module_object)));
|
|
store.push_back(v8_module->GetTransferrableModule());
|
|
original_native_module = module_object->shared_native_module();
|
|
}
|
|
|
|
{
|
|
v8::Isolate* to_isolate = v8::Isolate::New(create_params);
|
|
{
|
|
v8::HandleScope scope(to_isolate);
|
|
LocalContext env(to_isolate);
|
|
|
|
v8::MaybeLocal<v8::WasmModuleObject> transferred_module =
|
|
v8::WasmModuleObject::FromTransferrableModule(to_isolate, store[0]);
|
|
CHECK(!transferred_module.IsEmpty());
|
|
Handle<WasmModuleObject> module_object = Handle<WasmModuleObject>::cast(
|
|
v8::Utils::OpenHandle(*transferred_module.ToLocalChecked()));
|
|
std::shared_ptr<NativeModule> transferred_native_module =
|
|
module_object->shared_native_module();
|
|
bool is_sharing = (original_native_module == transferred_native_module);
|
|
CHECK_EQ(should_share, is_sharing);
|
|
}
|
|
to_isolate->Dispose();
|
|
}
|
|
original_native_module.reset();
|
|
from_isolate->Dispose();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
UNINITIALIZED_TEST(TransferrableWasmModulesCloned) {
|
|
FlagScope<bool> flag_scope_code(&FLAG_wasm_shared_code, false);
|
|
TestTransferrableWasmModules(false);
|
|
}
|
|
|
|
UNINITIALIZED_TEST(TransferrableWasmModulesShared) {
|
|
FlagScope<bool> flag_scope_engine(&FLAG_wasm_shared_engine, true);
|
|
FlagScope<bool> flag_scope_code(&FLAG_wasm_shared_code, true);
|
|
TestTransferrableWasmModules(true);
|
|
}
|
|
|
|
#undef EMIT_CODE_WITH_END
|
|
|
|
} // namespace test_wasm_serialization
|
|
} // namespace wasm
|
|
} // namespace internal
|
|
} // namespace v8
|