[wasm] Cache the tiering budget with the code

With dynamic tiering, each WebAssembly function has a tiering budget,
and the function gets optimized once the tiering budget is reached. So
far the tiering budget exists per process, which means that whenever
a web application got loaded, it started with a full tiering budget.
As a result, functions that only get called few times during startup
and never reach the tiering budget would never get optimized.

With this CL the tiering budget gets written to the cache. Given that
caching events are happening, this means that also startup functions get
optimized eventually as long as the web application gets visited often
enough.

R=clemensb@chromium.org
Bug: chromium:1384530

Change-Id: I5066bc8f3daf457159b6eb785d2e17eda43c8c4c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4026769
Commit-Queue: Andreas Haas <ahaas@chromium.org>
Reviewed-by: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/main@{#84320}
This commit is contained in:
Andreas Haas 2022-11-16 17:09:23 +01:00 committed by V8 LUCI CQ
parent 145853f5c1
commit a9e53d6e44
3 changed files with 94 additions and 6 deletions

View File

@ -868,7 +868,7 @@ class V8_EXPORT_PRIVATE NativeModule final {
// Get or create the NamesProvider. Requires {HasWireBytes()}.
NamesProvider* GetNamesProvider();
uint32_t* tiering_budget_array() { return tiering_budgets_.get(); }
uint32_t* tiering_budget_array() const { return tiering_budgets_.get(); }
Counters* counters() const { return code_allocator_.counters(); }

View File

@ -286,6 +286,7 @@ class V8_EXPORT_PRIVATE NativeModuleSerializer {
size_t MeasureCode(const WasmCode*) const;
void WriteHeader(Writer*, size_t total_code_size);
void WriteCode(const WasmCode*, Writer*);
void WriteTieringBudget(Writer* writer);
const NativeModule* const native_module_;
const base::Vector<WasmCode* const> code_table_;
@ -318,6 +319,9 @@ size_t NativeModuleSerializer::Measure() const {
for (WasmCode* code : code_table_) {
size += MeasureCode(code);
}
// Add the size of the tiering budget.
size += native_module_->module()->num_declared_functions * sizeof(uint32_t);
return size;
}
@ -446,6 +450,12 @@ void NativeModuleSerializer::WriteCode(const WasmCode* code, Writer* writer) {
total_written_code_ += code_size;
}
void NativeModuleSerializer::WriteTieringBudget(Writer* writer) {
writer->WriteVector(base::Vector<const byte>::cast(
base::VectorOf(native_module_->tiering_budget_array(),
native_module_->module()->num_declared_functions)));
}
bool NativeModuleSerializer::Write(Writer* writer) {
DCHECK(!write_called_);
write_called_ = true;
@ -468,6 +478,7 @@ bool NativeModuleSerializer::Write(Writer* writer) {
// Make sure that the serialized total code size was correct.
CHECK_EQ(total_written_code_, total_code_size);
WriteTieringBudget(writer);
return true;
}
@ -561,6 +572,7 @@ class V8_EXPORT_PRIVATE NativeModuleDeserializer {
void ReadHeader(Reader* reader);
DeserializationUnit ReadCode(int fn_index, Reader* reader);
void ReadTieringBudget(Reader* reader);
void CopyAndRelocate(const DeserializationUnit& unit);
void Publish(std::vector<DeserializationUnit> batch);
@ -700,6 +712,7 @@ bool NativeModuleDeserializer::Read(Reader* reader) {
// Wait for all tasks to finish, while participating in their work.
job_handle->Join();
ReadTieringBudget(reader);
return reader->current_size() == 0;
}
@ -823,6 +836,19 @@ void NativeModuleDeserializer::CopyAndRelocate(
unit.code->instructions().size());
}
void NativeModuleDeserializer::ReadTieringBudget(Reader* reader) {
size_t size_of_tiering_budget =
native_module_->module()->num_declared_functions * sizeof(uint32_t);
if (size_of_tiering_budget > reader->current_size()) {
return;
}
base::Vector<const byte> serialized_budget =
reader->ReadVector<const byte>(size_of_tiering_budget);
memcpy(native_module_->tiering_budget_array(), serialized_budget.begin(),
size_of_tiering_budget);
}
void NativeModuleDeserializer::Publish(std::vector<DeserializationUnit> batch) {
DCHECK(!batch.empty());
std::vector<std::unique_ptr<WasmCode>> codes;

View File

@ -39,9 +39,14 @@ class WasmSerializationTest {
WasmModuleBuilder* builder = zone->New<WasmModuleBuilder>(zone);
TestSignatures sigs;
WasmFunctionBuilder* f = builder->AddFunction(sigs.i_i());
byte code[] = {WASM_LOCAL_GET(0), kExprI32Const, 1, kExprI32Add, kExprEnd};
f->EmitCode(code, sizeof(code));
// Generate 3 functions, and export the last one with the name "increment".
WasmFunctionBuilder* f;
for (int i = 0; i < 3; ++i) {
f = builder->AddFunction(sigs.i_i());
byte code[] = {WASM_LOCAL_GET(0), kExprI32Const, 1, kExprI32Add,
kExprEnd};
f->EmitCode(code, sizeof(code));
}
builder->AddExport(base::CStrVector(kFunctionName), f);
builder->WriteTo(buffer);
@ -60,6 +65,11 @@ class WasmSerializationTest {
memset(const_cast<uint8_t*>(wire_bytes_.data()), 0, wire_bytes_.size() / 2);
}
void PartlyDropTieringBudget() {
serialized_bytes_ = {serialized_bytes_.data(),
serialized_bytes_.size() - 1};
}
MaybeHandle<WasmModuleObject> Deserialize(
base::Vector<const char> source_url = {}) {
return DeserializeNativeModule(CcTest::i_isolate(),
@ -99,6 +109,8 @@ class WasmSerializationTest {
CcTest::CollectAllAvailableGarbage();
}
v8::MemorySpan<const uint8_t> wire_bytes() const { return wire_bytes_; }
private:
Zone* zone() { return &zone_; }
@ -322,11 +334,11 @@ TEST(TierDownAfterDeserialization) {
CHECK(test.Deserialize().ToHandle(&module_object));
auto* native_module = module_object->native_module();
CHECK_EQ(1, native_module->module()->functions.size());
CHECK_EQ(3, 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);
auto* turbofan_code = native_module->GetCode(2);
CHECK_NOT_NULL(turbofan_code);
CHECK_EQ(ExecutionTier::kTurbofan, turbofan_code->tier());
@ -366,4 +378,54 @@ TEST(SerializeLiftoffModuleFails) {
CHECK(!wasm_serializer.SerializeNativeModule({buffer.get(), buffer_size}));
}
TEST(SerializeTieringBudget) {
WasmSerializationTest test;
Isolate* isolate = CcTest::i_isolate();
v8::OwnedBuffer serialized_bytes;
uint32_t mock_budget[3]{1, 2, 3};
{
HandleScope scope(isolate);
Handle<WasmModuleObject> module_object;
CHECK(test.Deserialize().ToHandle(&module_object));
auto* native_module = module_object->native_module();
memcpy(native_module->tiering_budget_array(), mock_budget,
arraysize(mock_budget) * sizeof(uint32_t));
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>();
serialized_bytes = v8_module_object->GetCompiledModule().Serialize();
// Change one entry in the tiering budget after serialization to make sure
// the module gets deserialized and not just loaded from the module cache.
native_module->tiering_budget_array()[0]++;
}
test.CollectGarbage();
HandleScope scope(isolate);
Handle<WasmModuleObject> module_object;
CHECK(DeserializeNativeModule(isolate,
base::VectorOf(serialized_bytes.buffer.get(),
serialized_bytes.size),
base::VectorOf(test.wire_bytes()), {})
.ToHandle(&module_object));
auto* native_module = module_object->native_module();
for (size_t i = 0; i < arraysize(mock_budget); ++i) {
CHECK_EQ(mock_budget[i], native_module->tiering_budget_array()[i]);
}
}
TEST(DeserializeTieringBudgetPartlyMissing) {
WasmSerializationTest test;
{
HandleScope scope(CcTest::i_isolate());
test.PartlyDropTieringBudget();
CHECK(test.Deserialize().is_null());
}
test.CollectGarbage();
}
} // namespace v8::internal::wasm