v8/test/cctest/wasm/test-wasm-serialization.cc
Clemens Backes 2547e1cece [wasm] Fix tier down after deserialization
Since the compilation progress was never initialized on deserialization,
tier down was always skipped on such modules.
By initializing to the expected state after deserialization (i.e. all
code as TurboFan code), we make sure that later recompilation works as
expected.

Drive-by: Fix an unnecessary copy of a {shared_ptr} in deserialization.

R=thibaudm@chromium.org

Bug: chromium:1110258
Change-Id: Ia12af888e4b11aabfb8cd4e1201e9fa3cd2ceb47
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2323355
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Reviewed-by: Thibaud Michaud <thibaudm@chromium.org>
Cr-Commit-Position: refs/heads/master@{#69458}
2020-08-18 15:15:53 +00:00

356 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));
{
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 =
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_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