diff --git a/src/flags/flag-definitions.h b/src/flags/flag-definitions.h index 414a603867..218dc97d12 100644 --- a/src/flags/flag-definitions.h +++ b/src/flags/flag-definitions.h @@ -1036,6 +1036,14 @@ DEFINE_INT(wasm_tier_mask_for_testing, 0, DEFINE_INT(wasm_debug_mask_for_testing, 0, "bitmask of functions to compile for debugging, only applies if the " "tier is Liftoff") +// TODO(clemensb): Introduce experimental_wasm_pgo to read from a custom section +// instead of from a local file. +DEFINE_BOOL( + experimental_wasm_pgo_to_file, false, + "experimental: dump Wasm PGO information to a local file (for testing)") +DEFINE_BOOL( + experimental_wasm_pgo_from_file, false, + "experimental: read and use Wasm PGO data from a local file (for testing)") DEFINE_BOOL(validate_asm, true, "validate asm.js modules before compiling") // asm.js validation is disabled since it triggers wasm code generation. @@ -1102,15 +1110,10 @@ DEFINE_BOOL(trace_wasm_speculative_inlining, false, DEFINE_BOOL(trace_wasm_typer, false, "trace wasm typer") DEFINE_BOOL(wasm_type_canonicalization, false, "apply isorecursive canonicalization on wasm types") -DEFINE_IMPLICATION(wasm_speculative_inlining, wasm_dynamic_tiering) DEFINE_IMPLICATION(wasm_speculative_inlining, wasm_inlining) DEFINE_WEAK_IMPLICATION(experimental_wasm_gc, wasm_speculative_inlining) DEFINE_WEAK_IMPLICATION(experimental_wasm_typed_funcref, wasm_type_canonicalization) -// Speculative inlining needs type feedback from Liftoff and compilation in -// Turbofan. -DEFINE_NEG_NEG_IMPLICATION(liftoff, wasm_speculative_inlining) -DEFINE_NEG_IMPLICATION(liftoff_only, wasm_speculative_inlining) DEFINE_BOOL(wasm_loop_unrolling, true, "enable loop unrolling for wasm functions") diff --git a/src/wasm/graph-builder-interface.cc b/src/wasm/graph-builder-interface.cc index eb46432c5f..3219580a97 100644 --- a/src/wasm/graph-builder-interface.cc +++ b/src/wasm/graph-builder-interface.cc @@ -196,6 +196,9 @@ class WasmGraphBuildingInterface { void StartFunctionBody(FullDecoder* decoder, Control* block) {} void FinishFunction(FullDecoder*) { + if (v8_flags.wasm_speculative_inlining) { + DCHECK_EQ(feedback_instruction_index_, type_feedback_.size()); + } if (inlined_status_ == kRegularFunction) { builder_->PatchInStackCheckIfNeeded(); } diff --git a/src/wasm/module-compiler.cc b/src/wasm/module-compiler.cc index 5cab9c46c8..ce046834ec 100644 --- a/src/wasm/module-compiler.cc +++ b/src/wasm/module-compiler.cc @@ -1422,6 +1422,7 @@ void TransitiveTypeFeedbackProcessor::ProcessFunction(int func_index) { base::Vector call_direct_targets = module_->type_feedback.feedback_for_function[func_index] .call_targets.as_vector(); + DCHECK_EQ(feedback.length(), call_direct_targets.size() * 2); FeedbackMaker fm(instance_, func_index, feedback.length() / 2); for (int i = 0; i < feedback.length(); i += 2) { Object value = feedback.get(i); diff --git a/src/wasm/module-instantiate.cc b/src/wasm/module-instantiate.cc index 62a61757bc..58869f581b 100644 --- a/src/wasm/module-instantiate.cc +++ b/src/wasm/module-instantiate.cc @@ -452,7 +452,8 @@ MaybeHandle InstantiateToInstanceObject( memory_buffer); auto instance = builder.Build(); if (!instance.is_null()) { - // Post tasks for lazy compilation metrics before we call the start function + // Post tasks for lazy compilation metrics before we call the start + // function. if (v8_flags.wasm_lazy_compilation && module_object->native_module() ->ShouldLazyCompilationMetricsBeReported()) { diff --git a/src/wasm/wasm-code-manager.cc b/src/wasm/wasm-code-manager.cc index fa77fdad5d..35a9a23dab 100644 --- a/src/wasm/wasm-code-manager.cc +++ b/src/wasm/wasm-code-manager.cc @@ -1884,6 +1884,11 @@ NativeModule::~NativeModule() { // {owned_code_}. The destructor of {WasmImportWrapperCache} still needs to // decrease reference counts on the {WasmCode} objects. import_wrapper_cache_.reset(); + + // If experimental PGO support is enabled, serialize the PGO data now. + if (V8_UNLIKELY(FLAG_experimental_wasm_pgo_to_file)) { + DumpProfileToFile(module_.get(), wire_bytes()); + } } WasmCodeManager::WasmCodeManager() diff --git a/src/wasm/wasm-engine.cc b/src/wasm/wasm-engine.cc index bc5ac6c285..2c549596b1 100644 --- a/src/wasm/wasm-engine.cc +++ b/src/wasm/wasm-engine.cc @@ -530,21 +530,30 @@ MaybeHandle WasmEngine::SyncCompile( TRACE_EVENT1("v8.wasm", "wasm.SyncCompile", "id", compilation_id); v8::metrics::Recorder::ContextId context_id = isolate->GetOrRegisterRecorderContextId(isolate->native_context()); - ModuleResult result = - DecodeWasmModule(enabled, bytes.start(), bytes.end(), false, kWasmOrigin, - isolate->counters(), isolate->metrics_recorder(), - context_id, DecodingMethod::kSync, allocator()); - if (result.failed()) { - thrower->CompileFailed(result.error()); - return {}; + std::shared_ptr module; + { + ModuleResult result = DecodeWasmModule( + enabled, bytes.start(), bytes.end(), false, kWasmOrigin, + isolate->counters(), isolate->metrics_recorder(), context_id, + DecodingMethod::kSync, allocator()); + if (result.failed()) { + thrower->CompileFailed(result.error()); + return {}; + } + module = std::move(result).value(); + } + + // If experimental PGO via files is enabled, load profile information now. + if (V8_UNLIKELY(FLAG_experimental_wasm_pgo_from_file)) { + LoadProfileFromFile(module.get(), bytes.module_bytes()); } // Transfer ownership of the WasmModule to the {Managed} generated // in {CompileToNativeModule}. Handle export_wrappers; - std::shared_ptr native_module = CompileToNativeModule( - isolate, enabled, thrower, std::move(result).value(), bytes, - &export_wrappers, compilation_id, context_id); + std::shared_ptr native_module = + CompileToNativeModule(isolate, enabled, thrower, std::move(module), bytes, + &export_wrappers, compilation_id, context_id); if (!native_module) return {}; #ifdef DEBUG diff --git a/src/wasm/wasm-module.cc b/src/wasm/wasm-module.cc index c807468eee..85a415c2ef 100644 --- a/src/wasm/wasm-module.cc +++ b/src/wasm/wasm-module.cc @@ -16,6 +16,7 @@ #include "src/wasm/wasm-code-manager.h" #include "src/wasm/wasm-init-expr.h" #include "src/wasm/wasm-js.h" +#include "src/wasm/wasm-module-builder.h" // For {ZoneBuffer}. #include "src/wasm/wasm-objects-inl.h" #include "src/wasm/wasm-result.h" #include "src/wasm/wasm-subtyping.h" @@ -688,4 +689,146 @@ size_t GetWireBytesHash(base::Vector wire_bytes) { kZeroHashSeed); } +base::OwnedVector GetProfileData(const WasmModule* module) { + const TypeFeedbackStorage& type_feedback = module->type_feedback; + AccountingAllocator allocator; + Zone zone{&allocator, "wasm::GetProfileData"}; + ZoneBuffer buffer{&zone}; + base::MutexGuard mutex_guard{&type_feedback.mutex}; + + // Get an ordered list of function indexes, so we generate deterministic data. + std::vector ordered_func_indexes; + ordered_func_indexes.reserve(type_feedback.feedback_for_function.size()); + for (const auto& entry : type_feedback.feedback_for_function) { + ordered_func_indexes.push_back(entry.first); + } + std::sort(ordered_func_indexes.begin(), ordered_func_indexes.end()); + + buffer.write_u32v(static_cast(ordered_func_indexes.size())); + for (const uint32_t func_index : ordered_func_indexes) { + buffer.write_u32v(func_index); + // Serialize {feedback_vector}. + const FunctionTypeFeedback& feedback = + type_feedback.feedback_for_function.at(func_index); + buffer.write_u32v(static_cast(feedback.feedback_vector.size())); + for (const CallSiteFeedback& call_site_feedback : + feedback.feedback_vector) { + int cases = call_site_feedback.num_cases(); + buffer.write_i32v(cases); + for (int i = 0; i < cases; ++i) { + buffer.write_i32v(call_site_feedback.function_index(i)); + buffer.write_i32v(call_site_feedback.call_count(i)); + } + } + // Serialize {call_targets}. + buffer.write_u32v(static_cast(feedback.call_targets.size())); + for (uint32_t call_target : feedback.call_targets) { + buffer.write_u32v(call_target); + } + } + return base::OwnedVector::Of(buffer); +} + +void RestoreProfileData(WasmModule* module, + base::Vector profile_data) { + TypeFeedbackStorage& type_feedback = module->type_feedback; + Decoder decoder{profile_data.begin(), profile_data.end()}; + uint32_t num_entries = decoder.consume_u32v("num function entries"); + CHECK_LE(num_entries, module->num_declared_functions); + for (uint32_t missing_entries = num_entries; missing_entries > 0; + --missing_entries) { + uint32_t function_index = decoder.consume_u32v("function index"); + CHECK(!type_feedback.feedback_for_function.count(function_index)); + FunctionTypeFeedback& feedback = + type_feedback.feedback_for_function[function_index]; + // Deserialize {feedback_vector}. + uint32_t feedback_vector_size = + decoder.consume_u32v("feedback vector size"); + feedback.feedback_vector.resize(feedback_vector_size); + for (CallSiteFeedback& feedback : feedback.feedback_vector) { + int num_cases = decoder.consume_i32v("num cases"); + if (num_cases == 0) continue; // no feedback + if (num_cases == 1) { // monomorphic + int called_function_index = decoder.consume_i32v("function index"); + int call_count = decoder.consume_i32v("call count"); + feedback = CallSiteFeedback{called_function_index, call_count}; + } else { // polymorphic + auto* polymorphic = new CallSiteFeedback::PolymorphicCase[num_cases]; + for (int i = 0; i < num_cases; ++i) { + polymorphic[i].function_index = + decoder.consume_i32v("function index"); + polymorphic[i].absolute_call_frequency = + decoder.consume_i32v("call count"); + } + feedback = CallSiteFeedback{polymorphic, num_cases}; + } + } + // Deserialize {call_targets}. + uint32_t num_call_targets = decoder.consume_u32v("num call targets"); + feedback.call_targets = + base::OwnedVector::NewForOverwrite(num_call_targets); + for (uint32_t& call_target : feedback.call_targets) { + call_target = decoder.consume_u32v("call target"); + } + } + CHECK(decoder.ok()); + CHECK_EQ(decoder.pc(), decoder.end()); +} + +void DumpProfileToFile(const WasmModule* module, + base::Vector wire_bytes) { + CHECK(!wire_bytes.empty()); + // File are named `profile-wasm-`. + // We use the same hash as for reported scripts, to make it easier to + // correlate files to wasm modules (see {CreateWasmScript}). + uint32_t hash = static_cast(GetWireBytesHash(wire_bytes)); + base::EmbeddedVector filename; + SNPrintF(filename, "profile-wasm-%08x", hash); + base::OwnedVector profile_data = GetProfileData(module); + PrintF("Dumping Wasm PGO data to file '%s' (%zu bytes)\n", filename.begin(), + profile_data.size()); + if (FILE* file = base::OS::FOpen(filename.begin(), "wb")) { + CHECK_EQ(profile_data.size(), + fwrite(profile_data.begin(), 1, profile_data.size(), file)); + base::Fclose(file); + } +} + +void LoadProfileFromFile(WasmModule* module, + base::Vector wire_bytes) { + CHECK(!wire_bytes.empty()); + // File are named `profile-wasm-`. + // We use the same hash as for reported scripts, to make it easier to + // correlate files to wasm modules (see {CreateWasmScript}). + uint32_t hash = static_cast(GetWireBytesHash(wire_bytes)); + base::EmbeddedVector filename; + SNPrintF(filename, "profile-wasm-%08x", hash); + + FILE* file = base::OS::FOpen(filename.begin(), "rb"); + if (!file) { + PrintF("No Wasm PGO data found: Cannot open file '%s'\n", filename.begin()); + return; + } + + fseek(file, 0, SEEK_END); + size_t size = ftell(file); + rewind(file); + + PrintF("Loading Wasm PGO data from file '%s' (%zu bytes)\n", filename.begin(), + size); + base::OwnedVector profile_data = + base::OwnedVector::NewForOverwrite(size); + for (size_t read = 0; read < size;) { + read += fread(profile_data.begin() + read, 1, size - read, file); + CHECK(!ferror(file)); + } + + base::Fclose(file); + + RestoreProfileData(module, profile_data.as_vector()); + + // Check that the generated profile is deterministic. + DCHECK_EQ(profile_data.as_vector(), GetProfileData(module).as_vector()); +} + } // namespace v8::internal::wasm diff --git a/src/wasm/wasm-module.h b/src/wasm/wasm-module.h index 018cc8bdbb..14a667759a 100644 --- a/src/wasm/wasm-module.h +++ b/src/wasm/wasm-module.h @@ -450,6 +450,7 @@ class CallSiteFeedback { bool is_polymorphic() const { return index_or_count_ <= -2; } bool is_invalid() const { return index_or_count_ == -1; } const PolymorphicCase* polymorphic_storage() const { + DCHECK(is_polymorphic()); return reinterpret_cast(frequency_or_ool_); } @@ -477,7 +478,7 @@ struct FunctionTypeFeedback { struct TypeFeedbackStorage { std::unordered_map feedback_for_function; // Accesses to {feedback_for_function} are guarded by this mutex. - base::Mutex mutex; + mutable base::Mutex mutex; }; struct WasmTable; @@ -794,6 +795,12 @@ size_t PrintSignature(base::Vector buffer, const wasm::FunctionSig*, V8_EXPORT_PRIVATE size_t GetWireBytesHash(base::Vector wire_bytes); +void DumpProfileToFile(const WasmModule* module, + base::Vector wire_bytes); + +void LoadProfileFromFile(WasmModule* module, + base::Vector wire_bytes); + } // namespace v8::internal::wasm #endif // V8_WASM_WASM_MODULE_H_ diff --git a/test/mjsunit/regress/wasm/regress-12874.js b/test/mjsunit/regress/wasm/regress-12874.js index 4f8a3be91b..2a74508708 100644 --- a/test/mjsunit/regress/wasm/regress-12874.js +++ b/test/mjsunit/regress/wasm/regress-12874.js @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Flags: --experimental-wasm-gc --no-liftoff --no-wasm-inlining +// Flags: --experimental-wasm-gc +// Flags: --no-liftoff --no-wasm-speculative-inlining --no-wasm-inlining d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");