f47e59e045
Because of LocalHeap safepoints, our existing assert scopes don't necessarily maintain the same guarantees as desired. In particular, DisallowHeapAllocation no longer guarantees that objects don't move. This patch transitions DisallowHeapAllocation to DisallowGarbageCollection, to ensure that code using this scope is also protected against safepoints. Change-Id: I0411425884f6849982611205fb17bb072881c722 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2540547 Commit-Queue: Leszek Swirski <leszeks@chromium.org> Reviewed-by: Peter Marshall <petermarshall@chromium.org> Reviewed-by: Jakob Gruber <jgruber@chromium.org> Reviewed-by: Clemens Backes <clemensb@chromium.org> Reviewed-by: Ulan Degenbaev <ulan@chromium.org> Reviewed-by: Tobias Tebbi <tebbi@chromium.org> Reviewed-by: Igor Sheludko <ishell@chromium.org> Cr-Commit-Position: refs/heads/master@{#71319}
357 lines
12 KiB
C++
357 lines
12 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 "src/wasm/wasm-serialization.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 {
|
|
|
|
// 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 = zone->New<WasmModuleBuilder>(zone);
|
|
TestSignatures sigs;
|
|
|
|
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_i());
|
|
byte code[] = {WASM_GET_LOCAL(0), kExprI32Const, 1, kExprI32Add, kExprEnd};
|
|
f->EmitCode(code, sizeof(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()) +
|
|
WasmSerializer::kVersionHashOffset);
|
|
*slot = Version::Hash() + 1;
|
|
}
|
|
|
|
void InvalidateWireBytes() {
|
|
memset(const_cast<uint8_t*>(wire_bytes_.data()), 0, wire_bytes_.size() / 2);
|
|
}
|
|
|
|
void InvalidateNumFunctions() {
|
|
Address num_functions_slot =
|
|
reinterpret_cast<Address>(serialized_bytes_.data()) +
|
|
WasmSerializer::kHeaderSize;
|
|
CHECK_EQ(1, base::ReadUnalignedValue<uint32_t>(num_functions_slot));
|
|
base::WriteUnalignedValue<uint32_t>(num_functions_slot, 0);
|
|
}
|
|
|
|
MaybeHandle<WasmModuleObject> Deserialize(
|
|
Vector<const char> source_url = {}) {
|
|
return DeserializeNativeModule(CcTest::i_isolate(),
|
|
VectorOf(serialized_bytes_),
|
|
VectorOf(wire_bytes_), source_url);
|
|
}
|
|
|
|
void DeserializeAndRun() {
|
|
ErrorThrower thrower(CcTest::i_isolate(), "");
|
|
Handle<WasmModuleObject> module_object;
|
|
CHECK(Deserialize().ToHandle(&module_object));
|
|
{
|
|
DisallowGarbageCollection 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 =
|
|
CcTest::i_isolate()
|
|
->wasm_engine()
|
|
->SyncInstantiate(CcTest::i_isolate(), &thrower, module_object,
|
|
Handle<JSReceiver>::null(),
|
|
MaybeHandle<JSArrayBuffer>())
|
|
.ToHandleChecked();
|
|
Handle<Object> params[1] = {
|
|
Handle<Object>(Smi::FromInt(41), CcTest::i_isolate())};
|
|
int32_t result = testing::CallWasmFunctionForTesting(
|
|
CcTest::i_isolate(), instance, kFunctionName, 1, params);
|
|
CHECK_EQ(42, result);
|
|
}
|
|
|
|
void CollectGarbage() {
|
|
// Try hard to collect all garbage and will therefore also invoke all weak
|
|
// callbacks of actually unreachable persistent handles.
|
|
CcTest::i_isolate()->heap()->CollectAllAvailableGarbage(
|
|
GarbageCollectionReason::kTesting);
|
|
}
|
|
|
|
private:
|
|
static const char* kFunctionName;
|
|
|
|
Zone* zone() { return &zone_; }
|
|
|
|
void SetUp() {
|
|
CcTest::InitIsolateOnce();
|
|
ZoneBuffer buffer(&zone_);
|
|
WasmSerializationTest::BuildWireBytes(zone(), &buffer);
|
|
|
|
v8::Isolate::CreateParams create_params;
|
|
create_params.array_buffer_allocator =
|
|
CcTest::i_isolate()->array_buffer_allocator();
|
|
|
|
v8::Isolate* serialization_v8_isolate = v8::Isolate::New(create_params);
|
|
Isolate* serialization_isolate =
|
|
reinterpret_cast<Isolate*>(serialization_v8_isolate);
|
|
ErrorThrower thrower(serialization_isolate, "");
|
|
// Keep a weak pointer so we can check that the native module dies after
|
|
// serialization (when the isolate is disposed).
|
|
std::weak_ptr<NativeModule> weak_native_module;
|
|
{
|
|
HandleScope scope(serialization_isolate);
|
|
v8::Local<v8::Context> serialization_context =
|
|
v8::Context::New(serialization_v8_isolate);
|
|
serialization_context->Enter();
|
|
|
|
auto enabled_features = WasmFeatures::FromIsolate(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();
|
|
weak_native_module = module_object->shared_native_module();
|
|
// Check that the native module exists at this point.
|
|
CHECK(weak_native_module.lock());
|
|
|
|
auto* native_module = module_object->native_module();
|
|
native_module->compilation_state()->WaitForTopTierFinished();
|
|
DCHECK(!native_module->compilation_state()->failed());
|
|
|
|
v8::Local<v8::Object> v8_module_obj =
|
|
v8::Utils::ToLocal(Handle<JSObject>::cast(module_object));
|
|
CHECK(v8_module_obj->IsWasmModuleObject());
|
|
|
|
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();
|
|
CHECK_LT(0, data_.size);
|
|
}
|
|
// Dispose of serialization isolate to destroy the reference to the
|
|
// NativeModule, which removes it from the module cache in the wasm engine
|
|
// and forces de-serialization in the new isolate.
|
|
serialization_v8_isolate->Dispose();
|
|
|
|
// Busy-wait for the NativeModule to really die. Background threads might
|
|
// temporarily keep it alive (happens very rarely, see
|
|
// https://crbug.com/v8/10148).
|
|
while (weak_native_module.lock()) {
|
|
}
|
|
|
|
serialized_bytes_ = {data_.buffer.get(), data_.size};
|
|
|
|
v8::HandleScope new_scope(CcTest::isolate());
|
|
v8::Local<v8::Context> deserialization_context =
|
|
v8::Context::New(CcTest::isolate());
|
|
deserialization_context->Enter();
|
|
}
|
|
|
|
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};
|
|
};
|
|
|
|
const char* WasmSerializationTest::kFunctionName = "increment";
|
|
|
|
TEST(DeserializeValidModule) {
|
|
WasmSerializationTest test;
|
|
{
|
|
HandleScope scope(CcTest::i_isolate());
|
|
test.DeserializeAndRun();
|
|
}
|
|
test.CollectGarbage();
|
|
}
|
|
|
|
TEST(DeserializeWithSourceUrl) {
|
|
WasmSerializationTest test;
|
|
{
|
|
HandleScope scope(CcTest::i_isolate());
|
|
const std::string url = "http://example.com/example.wasm";
|
|
Handle<WasmModuleObject> module_object;
|
|
CHECK(test.Deserialize(VectorOf(url)).ToHandle(&module_object));
|
|
String source_url = String::cast(module_object->script().source_url());
|
|
CHECK_EQ(url, source_url.ToCString().get());
|
|
}
|
|
test.CollectGarbage();
|
|
}
|
|
|
|
TEST(DeserializeMismatchingVersion) {
|
|
WasmSerializationTest test;
|
|
{
|
|
HandleScope scope(CcTest::i_isolate());
|
|
test.InvalidateVersion();
|
|
CHECK(test.Deserialize().is_null());
|
|
}
|
|
test.CollectGarbage();
|
|
}
|
|
|
|
TEST(DeserializeNoSerializedData) {
|
|
WasmSerializationTest test;
|
|
{
|
|
HandleScope scope(CcTest::i_isolate());
|
|
test.ClearSerializedData();
|
|
CHECK(test.Deserialize().is_null());
|
|
}
|
|
test.CollectGarbage();
|
|
}
|
|
|
|
TEST(DeserializeInvalidNumFunctions) {
|
|
WasmSerializationTest test;
|
|
{
|
|
HandleScope scope(CcTest::i_isolate());
|
|
test.InvalidateNumFunctions();
|
|
CHECK(test.Deserialize().is_null());
|
|
}
|
|
test.CollectGarbage();
|
|
}
|
|
|
|
TEST(DeserializeWireBytesAndSerializedDataInvalid) {
|
|
WasmSerializationTest test;
|
|
{
|
|
HandleScope scope(CcTest::i_isolate());
|
|
test.InvalidateVersion();
|
|
test.InvalidateWireBytes();
|
|
CHECK(test.Deserialize().is_null());
|
|
}
|
|
test.CollectGarbage();
|
|
}
|
|
|
|
bool False(v8::Local<v8::Context> context, v8::Local<v8::String> source) {
|
|
return false;
|
|
}
|
|
|
|
TEST(BlockWasmCodeGenAtDeserialization) {
|
|
WasmSerializationTest test;
|
|
{
|
|
HandleScope scope(CcTest::i_isolate());
|
|
CcTest::isolate()->SetAllowWasmCodeGenerationCallback(False);
|
|
CHECK(test.Deserialize().is_null());
|
|
}
|
|
test.CollectGarbage();
|
|
}
|
|
|
|
UNINITIALIZED_TEST(CompiledWasmModulesTransfer) {
|
|
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::CompiledWasmModule> 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, "TestCompiledWasmModulesTransfer");
|
|
auto enabled_features = WasmFeatures::FromIsolate(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->GetCompiledModule());
|
|
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::FromCompiledModule(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();
|
|
CHECK_EQ(original_native_module, transferred_native_module);
|
|
}
|
|
to_isolate->Dispose();
|
|
}
|
|
original_native_module.reset();
|
|
from_isolate->Dispose();
|
|
}
|
|
|
|
TEST(TierDownAfterDeserialization) {
|
|
WasmSerializationTest test;
|
|
|
|
Isolate* isolate = CcTest::i_isolate();
|
|
HandleScope scope(isolate);
|
|
Handle<WasmModuleObject> module_object;
|
|
CHECK(test.Deserialize().ToHandle(&module_object));
|
|
|
|
auto* native_module = module_object->native_module();
|
|
CHECK_EQ(1, native_module->module()->functions.size());
|
|
WasmCodeRefScope code_ref_scope;
|
|
auto* turbofan_code = native_module->GetCode(0);
|
|
CHECK_NOT_NULL(turbofan_code);
|
|
CHECK_EQ(ExecutionTier::kTurbofan, turbofan_code->tier());
|
|
|
|
isolate->wasm_engine()->TierDownAllModulesPerIsolate(isolate);
|
|
|
|
auto* liftoff_code = native_module->GetCode(0);
|
|
CHECK_EQ(ExecutionTier::kLiftoff, liftoff_code->tier());
|
|
}
|
|
|
|
} // namespace test_wasm_serialization
|
|
} // namespace wasm
|
|
} // namespace internal
|
|
} // namespace v8
|