[wasm] Enable dynamic tiering in serialization tests

Some tests disabled dynamic tiering in order to actually serialize
TurboFan code. This makes the tests a bit simpler, but does not reflect
real-world usage.
This CL enables dynamic tiering in those tests and when needed executes
the code until tier-up was observed.

R=ahaas@chromium.org

Bug: v8:12899
Change-Id: I34cb8cedbc5908d9e6ca09d56c51609d0c8b2d6a
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3660262
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Reviewed-by: Andreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/main@{#80849}
This commit is contained in:
Clemens Backes 2022-05-31 12:19:23 +02:00 committed by V8 LUCI CQ
parent b5ccb0ef90
commit a1c96c7b8a
2 changed files with 149 additions and 89 deletions

View File

@ -5,6 +5,7 @@
#include "include/libplatform/libplatform.h"
#include "src/api/api-inl.h"
#include "src/base/vector.h"
#include "src/handles/global-handles-inl.h"
#include "src/init/v8.h"
#include "src/objects/managed.h"
#include "src/objects/objects-inl.h"
@ -23,9 +24,7 @@
#include "test/common/wasm/wasm-macro-gen.h"
#include "test/common/wasm/wasm-module-runner.h"
namespace v8 {
namespace internal {
namespace wasm {
namespace v8::internal::wasm {
class MockPlatform final : public TestPlatform {
public:
@ -153,17 +152,15 @@ class TestResolver : public CompilationResultResolver {
public:
TestResolver(i::Isolate* isolate, CompilationState* state,
std::string* error_message,
std::shared_ptr<NativeModule>* native_module)
Handle<WasmModuleObject>* module_object)
: isolate_(isolate),
state_(state),
error_message_(error_message),
native_module_(native_module) {}
module_object_(module_object) {}
void OnCompilationSucceeded(i::Handle<i::WasmModuleObject> module) override {
*state_ = CompilationState::kFinished;
if (!module.is_null()) {
*native_module_ = module->shared_native_module();
}
*module_object_ = isolate_->global_handles()->Create(*module);
}
void OnCompilationFailed(i::Handle<i::Object> error_reason) override {
@ -177,14 +174,13 @@ class TestResolver : public CompilationResultResolver {
i::Isolate* isolate_;
CompilationState* const state_;
std::string* const error_message_;
std::shared_ptr<NativeModule>* const native_module_;
Handle<WasmModuleObject>* const module_object_;
};
class StreamTester {
public:
explicit StreamTester(v8::Isolate* isolate)
: zone_(&allocator_, "StreamTester"),
internal_scope_(reinterpret_cast<i::Isolate*>(isolate)) {
: zone_(&allocator_, "StreamTester") {
Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
v8::Local<v8::Context> context = isolate->GetCurrentContext();
@ -192,13 +188,24 @@ class StreamTester {
i_isolate, WasmFeatures::All(), v8::Utils::OpenHandle(*context),
"WebAssembly.compileStreaming()",
std::make_shared<TestResolver>(i_isolate, &state_, &error_message_,
&native_module_));
&module_object_));
}
std::shared_ptr<StreamingDecoder> stream() { return stream_; }
std::shared_ptr<StreamingDecoder> stream() const { return stream_; }
// Compiled module object, valid after successful compile.
Handle<WasmModuleObject> module_object() const {
CHECK(!module_object_.is_null());
return module_object_;
}
// Compiled native module, valid after successful compile.
std::shared_ptr<NativeModule> native_module() { return native_module_; }
NativeModule* native_module() const {
return module_object()->native_module();
}
std::shared_ptr<NativeModule> shared_native_module() const {
return module_object()->shared_native_module();
}
// Run all compiler tasks, both foreground and background tasks.
void RunCompilerTasks() {
@ -228,19 +235,20 @@ class StreamTester {
private:
AccountingAllocator allocator_;
Zone zone_;
i::HandleScope internal_scope_;
CompilationState state_ = CompilationState::kPending;
std::string error_message_;
std::shared_ptr<NativeModule> native_module_;
Handle<WasmModuleObject> module_object_;
std::shared_ptr<StreamingDecoder> stream_;
};
} // namespace
#define RUN_STREAM(name) \
v8::Isolate* isolate = CcTest::isolate(); \
v8::HandleScope handle_scope(isolate); \
v8::Local<v8::Context> context = v8::Context::New(isolate); \
v8::Context::Scope context_scope(context); \
#define RUN_STREAM(name) \
v8::Isolate* isolate = CcTest::isolate(); \
v8::HandleScope handle_scope(isolate); \
v8::Local<v8::Context> context = v8::Context::New(isolate); \
v8::Context::Scope context_scope(context); \
/* Reduce tiering budget so we do not need to execute too long. */ \
i::FlagScope<int> reduced_tiering_budget(&i::FLAG_wasm_tiering_budget, 10); \
RunStream_##name(&platform, isolate);
#define STREAM_TEST(name) \
@ -253,25 +261,20 @@ class StreamTester {
} \
void RunStream_##name(MockPlatform* platform, v8::Isolate* isolate)
constexpr const char* kExportNames[] = {"a", "b", "c"};
// Create a valid module with 3 functions.
ZoneBuffer GetValidModuleBytes(Zone* zone) {
ZoneBuffer buffer(zone);
TestSignatures sigs;
WasmModuleBuilder builder(zone);
{
uint8_t i = 0;
for (const char* export_name : kExportNames) {
WasmFunctionBuilder* f = builder.AddFunction(sigs.i_iii());
uint8_t code[] = {kExprLocalGet, 0, kExprEnd};
f->EmitCode(code, arraysize(code));
}
{
WasmFunctionBuilder* f = builder.AddFunction(sigs.i_iii());
uint8_t code[] = {kExprLocalGet, 1, kExprEnd};
f->EmitCode(code, arraysize(code));
}
{
WasmFunctionBuilder* f = builder.AddFunction(sigs.i_iii());
uint8_t code[] = {kExprLocalGet, 2, kExprEnd};
uint8_t code[] = {kExprLocalGet, i, kExprEnd};
f->EmitCode(code, arraysize(code));
CHECK_GE(3, ++i);
builder.AddExport(base::CStrVector(export_name), f);
}
builder.WriteTo(&buffer);
return buffer;
@ -287,11 +290,47 @@ ZoneBuffer GetValidCompiledModuleBytes(v8::Isolate* isolate, Zone* zone,
tester.FinishStream();
tester.RunCompilerTasks();
CHECK(tester.IsPromiseFulfilled());
NativeModule* native_module = tester.native_module();
CHECK_NOT_NULL(native_module);
auto* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
ErrorThrower thrower{i_isolate, "GetValidCompiledModuleBytes"};
Handle<WasmInstanceObject> instance =
GetWasmEngine()
->SyncInstantiate(i_isolate, &thrower, tester.module_object(), {}, {})
.ToHandleChecked();
CHECK(!thrower.error());
// Call the exported functions repeatedly until they are all tiered up.
std::vector<Handle<WasmExportedFunction>> exported_functions;
for (const char* export_name : kExportNames) {
exported_functions.push_back(
testing::GetExportedFunction(i_isolate, instance, export_name)
.ToHandleChecked());
}
while (true) {
WasmCodeRefScope code_ref_scope;
std::vector<WasmCode*> all_code = native_module->SnapshotCodeTable();
if (std::all_of(all_code.begin(), all_code.end(), [](const WasmCode* code) {
return code->tier() == ExecutionTier::kTurbofan;
})) {
break;
}
for (Handle<WasmExportedFunction> exported_function : exported_functions) {
Handle<Object> return_value =
Execution::Call(i_isolate, exported_function,
ReadOnlyRoots{i_isolate}.undefined_value_handle(), 0,
nullptr)
.ToHandleChecked();
CHECK(return_value->IsSmi());
CHECK_EQ(0, Smi::cast(*return_value).value());
}
tester.RunCompilerTasks();
}
// Serialize the NativeModule.
std::shared_ptr<NativeModule> native_module = tester.native_module();
CHECK(native_module);
native_module->compilation_state()->WaitForTopTierFinished();
i::wasm::WasmSerializer serializer(native_module.get());
i::wasm::WasmSerializer serializer(native_module);
size_t size = serializer.GetSerializedNativeModuleSize();
std::vector<byte> buffer(size);
CHECK(serializer.SerializeNativeModule(base::VectorOf(buffer)));
@ -1101,7 +1140,6 @@ STREAM_TEST(TestModuleWithImportedFunction) {
}
STREAM_TEST(TestIncrementalCaching) {
FLAG_VALUE_SCOPE(wasm_dynamic_tiering, true);
FLAG_VALUE_SCOPE(wasm_tier_up, false);
constexpr int threshold = 10;
FlagScope<int> caching_treshold(&FLAG_wasm_caching_threshold, threshold);
@ -1149,10 +1187,10 @@ STREAM_TEST(TestIncrementalCaching) {
constexpr base::Vector<const char> kNoSourceUrl{"", 0};
Isolate* i_isolate = reinterpret_cast<i::Isolate*>(isolate);
Handle<Script> script = GetWasmEngine()->GetOrCreateScript(
i_isolate, tester.native_module(), kNoSourceUrl);
i_isolate, tester.shared_native_module(), kNoSourceUrl);
Handle<FixedArray> export_wrappers = i_isolate->factory()->NewFixedArray(3);
Handle<WasmModuleObject> module_object = WasmModuleObject::New(
i_isolate, tester.native_module(), script, export_wrappers);
i_isolate, tester.shared_native_module(), script, export_wrappers);
ErrorThrower thrower(i_isolate, "Instantiation");
// We instantiated before, so the second instantiation must also succeed:
Handle<WasmInstanceObject> instance =
@ -1175,7 +1213,7 @@ STREAM_TEST(TestIncrementalCaching) {
CHECK_EQ(1, call_cache_counter);
size_t serialized_size;
{
i::wasm::WasmSerializer serializer(tester.native_module().get());
i::wasm::WasmSerializer serializer(tester.native_module());
serialized_size = serializer.GetSerializedNativeModuleSize();
}
i::wasm::TriggerTierUp(*instance, 1);
@ -1185,7 +1223,7 @@ STREAM_TEST(TestIncrementalCaching) {
CHECK(tester.native_module()->GetCode(2)->is_liftoff());
CHECK_EQ(2, call_cache_counter);
{
i::wasm::WasmSerializer serializer(tester.native_module().get());
i::wasm::WasmSerializer serializer(tester.native_module());
CHECK_LT(serialized_size, serializer.GetSerializedNativeModuleSize());
}
}
@ -1226,7 +1264,6 @@ STREAM_TEST(TestModuleWithErrorAfterDataSection) {
// Test that cached bytes work.
STREAM_TEST(TestDeserializationBypassesCompilation) {
FlagScope<bool> no_wasm_dynamic_tiering(&FLAG_wasm_dynamic_tiering, false);
StreamTester tester(isolate);
ZoneBuffer wire_bytes = GetValidModuleBytes(tester.zone());
ZoneBuffer module_bytes =
@ -1242,7 +1279,6 @@ STREAM_TEST(TestDeserializationBypassesCompilation) {
// Test that bad cached bytes don't cause compilation of wire bytes to fail.
STREAM_TEST(TestDeserializationFails) {
FlagScope<bool> no_wasm_dynamic_tiering(&FLAG_wasm_dynamic_tiering, false);
StreamTester tester(isolate);
ZoneBuffer wire_bytes = GetValidModuleBytes(tester.zone());
ZoneBuffer module_bytes =
@ -1286,7 +1322,9 @@ STREAM_TEST(TestFunctionSectionWithoutCodeSection) {
}
STREAM_TEST(TestSetModuleCompiledCallback) {
FlagScope<bool> no_wasm_dynamic_tiering(&FLAG_wasm_dynamic_tiering, false);
// Reduce the caching threshold so that our three small functions trigger
// caching.
FlagScope<int> caching_treshold(&FLAG_wasm_caching_threshold, 10);
StreamTester tester(isolate);
bool callback_called = false;
tester.stream()->SetModuleCompiledCallback(
@ -1295,36 +1333,62 @@ STREAM_TEST(TestSetModuleCompiledCallback) {
});
uint8_t code[] = {
U32V_1(4), // body size
U32V_1(0), // locals count
kExprLocalGet, 0, kExprEnd // body
ADD_COUNT(U32V_1(0), // locals count
kExprLocalGet, 0, kExprEnd) // body
};
const uint8_t bytes[] = {
WASM_MODULE_HEADER, // module header
kTypeSectionCode, // section code
U32V_1(1 + SIZEOF_SIG_ENTRY_x_x), // section size
U32V_1(1), // type count
SIG_ENTRY_x_x(kI32Code, kI32Code), // signature entry
kFunctionSectionCode, // section code
U32V_1(1 + 3), // section size
U32V_1(3), // functions count
0, // signature index
0, // signature index
0, // signature index
kCodeSectionCode, // section code
U32V_1(1 + arraysize(code) * 3), // section size
U32V_1(3), // functions count
WASM_MODULE_HEADER, // module header
SECTION(Type,
ENTRY_COUNT(1), // type count
SIG_ENTRY_x_x(kI32Code, kI32Code)), // signature entry
SECTION(Function, ENTRY_COUNT(3), SIG_INDEX(0), SIG_INDEX(0),
SIG_INDEX(0)),
SECTION(Export, ENTRY_COUNT(3), // 3 exports
ADD_COUNT('a'), kExternalFunction, FUNC_INDEX(0), // "a" (0)
ADD_COUNT('b'), kExternalFunction, FUNC_INDEX(1), // "b" (1)
ADD_COUNT('c'), kExternalFunction, FUNC_INDEX(2)), // "c" (2)
kCodeSectionCode, // section code
U32V_1(1 + arraysize(code) * 3), // section size
U32V_1(3), // functions count
};
tester.OnBytesReceived(bytes, arraysize(bytes));
tester.OnBytesReceived(code, arraysize(code));
tester.OnBytesReceived(code, arraysize(code));
tester.OnBytesReceived(code, arraysize(code));
tester.FinishStream();
tester.RunCompilerTasks();
CHECK(tester.IsPromiseFulfilled());
CHECK(callback_called);
// Continue executing functions (eventually triggering tier-up) until the
// callback is called at least once.
auto* i_isolate = CcTest::i_isolate();
ErrorThrower thrower{i_isolate, "TestSetModuleCompiledCallback"};
Handle<WasmInstanceObject> instance =
GetWasmEngine()
->SyncInstantiate(i_isolate, &thrower, tester.module_object(), {}, {})
.ToHandleChecked();
CHECK(!thrower.error());
Handle<WasmExportedFunction> exported_functions[]{
testing::GetExportedFunction(i_isolate, instance, "a").ToHandleChecked(),
testing::GetExportedFunction(i_isolate, instance, "b").ToHandleChecked(),
testing::GetExportedFunction(i_isolate, instance, "c").ToHandleChecked()};
// If Liftoff is enabled, then the callback should only be called after
// tiering up.
CHECK_IMPLIES(FLAG_liftoff, !callback_called);
while (!callback_called) {
for (Handle<WasmExportedFunction> exported_function : exported_functions) {
Execution::Call(i_isolate, exported_function,
ReadOnlyRoots{i_isolate}.undefined_value_handle(), 0,
nullptr)
.Check();
}
tester.RunCompilerTasks();
}
}
// Test that a compile error contains the name of the function, even if the name
@ -1481,6 +1545,4 @@ STREAM_TEST(TierDownWithError) {
#undef STREAM_TEST
} // namespace wasm
} // namespace internal
} // namespace v8
} // namespace v8::internal::wasm

View File

@ -23,21 +23,18 @@
#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 v8::internal::wasm {
// Approximate gtest TEST_F style, in case we adopt gtest.
class WasmSerializationTest {
public:
WasmSerializationTest()
: zone_(&allocator_, ZONE_NAME),
no_wasm_dynamic_tiering_(&FLAG_wasm_dynamic_tiering, false) {
WasmSerializationTest() : zone_(&allocator_, ZONE_NAME) {
// Don't call here if we move to gtest.
SetUp();
}
static constexpr const char* kFunctionName = "increment";
static void BuildWireBytes(Zone* zone, ZoneBuffer* buffer) {
WasmModuleBuilder* builder = zone->New<WasmModuleBuilder>(zone);
TestSignatures sigs;
@ -50,7 +47,7 @@ class WasmSerializationTest {
builder->WriteTo(buffer);
}
void ClearSerializedData() { serialized_bytes_ = {nullptr, 0}; }
void ClearSerializedData() { serialized_bytes_ = {}; }
void InvalidateVersion() {
uint32_t* slot = reinterpret_cast<uint32_t*>(
@ -104,8 +101,6 @@ class WasmSerializationTest {
}
private:
static const char* kFunctionName;
Zone* zone() { return &zone_; }
void SetUp() {
@ -141,10 +136,6 @@ class WasmSerializationTest {
// 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());
@ -158,8 +149,19 @@ class WasmSerializationTest {
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();
// Run the code until tier-up (of the single function) was observed.
Handle<WasmInstanceObject> instance =
GetWasmEngine()
->SyncInstantiate(serialization_isolate, &thrower, module_object,
{}, {})
.ToHandleChecked();
CHECK_EQ(0, data_.size);
while (data_.size == 0) {
testing::CallWasmFunctionForTesting(serialization_isolate, instance,
kFunctionName, 0, nullptr);
data_ = compiled_module.Serialize();
}
CHECK_LT(0, data_.size);
}
// Dispose of serialization isolate to destroy the reference to the
@ -186,11 +188,8 @@ class WasmSerializationTest {
v8::OwnedBuffer data_;
v8::MemorySpan<const uint8_t> wire_bytes_ = {nullptr, 0};
v8::MemorySpan<const uint8_t> serialized_bytes_ = {nullptr, 0};
FlagScope<bool> no_wasm_dynamic_tiering_;
};
const char* WasmSerializationTest::kFunctionName = "increment";
TEST(DeserializeValidModule) {
WasmSerializationTest test;
{
@ -323,6 +322,8 @@ TEST(TierDownAfterDeserialization) {
auto* native_module = module_object->native_module();
CHECK_EQ(1, native_module->module()->functions.size());
WasmCodeRefScope code_ref_scope;
// The deserialized code must be TurboFan (we wait for tier-up before
// serializing).
auto* turbofan_code = native_module->GetCode(0);
CHECK_NOT_NULL(turbofan_code);
CHECK_EQ(ExecutionTier::kTurbofan, turbofan_code->tier());
@ -363,7 +364,4 @@ TEST(SerializeLiftoffModuleFails) {
CHECK(!wasm_serializer.SerializeNativeModule({buffer.get(), buffer_size}));
}
} // namespace test_wasm_serialization
} // namespace wasm
} // namespace internal
} // namespace v8
} // namespace v8::internal::wasm