From a9e53d6e448f247827dc89a17d5656a31cd6ae6c Mon Sep 17 00:00:00 2001 From: Andreas Haas Date: Wed, 16 Nov 2022 17:09:23 +0100 Subject: [PATCH] [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 Reviewed-by: Clemens Backes Cr-Commit-Position: refs/heads/main@{#84320} --- src/wasm/wasm-code-manager.h | 2 +- src/wasm/wasm-serialization.cc | 26 ++++++++ test/cctest/wasm/test-wasm-serialization.cc | 72 +++++++++++++++++++-- 3 files changed, 94 insertions(+), 6 deletions(-) diff --git a/src/wasm/wasm-code-manager.h b/src/wasm/wasm-code-manager.h index 08d4dcb76a..84a52ab717 100644 --- a/src/wasm/wasm-code-manager.h +++ b/src/wasm/wasm-code-manager.h @@ -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(); } diff --git a/src/wasm/wasm-serialization.cc b/src/wasm/wasm-serialization.cc index a28b8defc9..f3d20ba172 100644 --- a/src/wasm/wasm-serialization.cc +++ b/src/wasm/wasm-serialization.cc @@ -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 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::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 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 serialized_budget = + reader->ReadVector(size_of_tiering_budget); + + memcpy(native_module_->tiering_budget_array(), serialized_budget.begin(), + size_of_tiering_budget); +} + void NativeModuleDeserializer::Publish(std::vector batch) { DCHECK(!batch.empty()); std::vector> codes; diff --git a/test/cctest/wasm/test-wasm-serialization.cc b/test/cctest/wasm/test-wasm-serialization.cc index de065835c8..ce90755e54 100644 --- a/test/cctest/wasm/test-wasm-serialization.cc +++ b/test/cctest/wasm/test-wasm-serialization.cc @@ -39,9 +39,14 @@ class WasmSerializationTest { WasmModuleBuilder* builder = zone->New(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(wire_bytes_.data()), 0, wire_bytes_.size() / 2); } + void PartlyDropTieringBudget() { + serialized_bytes_ = {serialized_bytes_.data(), + serialized_bytes_.size() - 1}; + } + MaybeHandle Deserialize( base::Vector source_url = {}) { return DeserializeNativeModule(CcTest::i_isolate(), @@ -99,6 +109,8 @@ class WasmSerializationTest { CcTest::CollectAllAvailableGarbage(); } + v8::MemorySpan 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 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_module_obj = + v8::Utils::ToLocal(Handle::cast(module_object)); + CHECK(v8_module_obj->IsWasmModuleObject()); + + v8::Local v8_module_object = + v8_module_obj.As(); + 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 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