diff --git a/src/wasm/compilation-environment.h b/src/wasm/compilation-environment.h index 8a870532c8..629ebe913e 100644 --- a/src/wasm/compilation-environment.h +++ b/src/wasm/compilation-environment.h @@ -92,7 +92,8 @@ class WireBytesStorage { enum class CompilationEvent : uint8_t { kFinishedBaselineCompilation, kFinishedTopTierCompilation, - kFailedCompilation + kFailedCompilation, + kFinishedRecompilation }; // The implementation of {CompilationState} lives in module-compiler.cc. @@ -117,6 +118,7 @@ class CompilationState { bool failed() const; V8_EXPORT_PRIVATE bool baseline_compilation_finished() const; V8_EXPORT_PRIVATE bool top_tier_compilation_finished() const; + V8_EXPORT_PRIVATE bool recompilation_finished() const; // Override {operator delete} to avoid implicit instantiation of {operator // delete} with {size_t} argument. The {size_t} argument would be incorrect. diff --git a/src/wasm/module-compiler.cc b/src/wasm/module-compiler.cc index f15df9d3d6..c0a8e21d61 100644 --- a/src/wasm/module-compiler.cc +++ b/src/wasm/module-compiler.cc @@ -381,6 +381,9 @@ class CompilationStateImpl { // is invoked which triggers background compilation. void InitializeCompilationProgress(bool lazy_module, int num_wrappers); + // Initialize compilation progress for recompilation of the whole module. + void InitializeRecompilationProgress(ExecutionTier tier); + // Add the callback function to be called on compilation events. Needs to be // set before {AddCompilationUnits} is run to ensure that it receives all // events. The callback object must support being deleted from any thread. @@ -404,7 +407,8 @@ class CompilationStateImpl { void OnFinishedUnits(Vector); void OnFinishedJSToWasmWrapperUnits(int num); void TriggerCallbacks(bool completes_baseline_compilation, - bool completes_top_tier_compilation); + bool completes_top_tier_compilation, + bool completes_recompilation = false); void OnBackgroundTaskStopped(int task_id, const WasmFeatures& detected); void UpdateDetectedFeatures(const WasmFeatures& detected); @@ -427,6 +431,11 @@ class CompilationStateImpl { return outstanding_top_tier_functions_ == 0; } + bool recompilation_finished() const { + base::MutexGuard guard(&callbacks_mutex_); + return outstanding_recompilation_functions_ == 0; + } + CompileMode compile_mode() const { return compile_mode_; } Counters* counters() const { return async_counters_.get(); } WasmFeatures* detected_features() { return &detected_features_; } @@ -536,6 +545,7 @@ class CompilationStateImpl { int outstanding_top_tier_functions_ = 0; std::vector compilation_progress_; + int outstanding_recompilation_functions_ = 0; // End of fields protected by {callbacks_mutex_}. ////////////////////////////////////////////////////////////////////////////// @@ -597,6 +607,10 @@ bool CompilationState::top_tier_compilation_finished() const { return Impl(this)->top_tier_compilation_finished(); } +bool CompilationState::recompilation_finished() const { + return Impl(this)->recompilation_finished(); +} + // static std::unique_ptr CompilationState::New( const std::shared_ptr& native_module, @@ -747,6 +761,10 @@ class CompilationUnitBuilder { tiering_units_.emplace_back(func_index, tiers.top_tier); } + void AddBaselineUnit(int func_index) { + baseline_units_.emplace_back(func_index, ExecutionTier::kLiftoff); + } + bool Commit() { if (baseline_units_.empty() && tiering_units_.empty() && js_to_wasm_wrapper_units_.empty()) { @@ -1195,6 +1213,19 @@ void InitializeCompilationUnits(Isolate* isolate, NativeModule* native_module) { builder.Commit(); } +void AddBaselineCompilationUnits(NativeModule* native_module) { + CompilationUnitBuilder builder(native_module); + auto* module = native_module->module(); + + uint32_t start = module->num_imported_functions; + uint32_t end = start + module->num_declared_functions; + for (uint32_t func_index = start; func_index < end; func_index++) { + builder.AddBaselineUnit(func_index); + } + + builder.Commit(); +} + bool MayCompriseLazyFunctions(const WasmModule* module, const WasmFeatures& enabled_features, bool lazy_module) { @@ -1388,6 +1419,40 @@ std::shared_ptr CompileToNativeModule( return native_module; } +void RecompileNativeModule(Isolate* isolate, NativeModule* native_module, + ExecutionTier tier) { + // Install a callback to notify us once background recompilation finished. + auto recompilation_finished_semaphore = std::make_shared(0); + auto* compilation_state = Impl(native_module->compilation_state()); + DCHECK_EQ(tier, ExecutionTier::kLiftoff); + // The callback captures a shared ptr to the semaphore. + compilation_state->AddCallback( + [recompilation_finished_semaphore](CompilationEvent event) { + if (event == CompilationEvent::kFinishedRecompilation) { + recompilation_finished_semaphore->Signal(); + } + }); + + // Initialize the compilation units and kick off background compile tasks. + compilation_state->InitializeRecompilationProgress(tier); + AddBaselineCompilationUnits(native_module); + + // The main thread contributes to the compilation, except if we need + // deterministic compilation; in that case, the single background task will + // execute all compilation. + if (!NeedsDeterministicCompile()) { + while (ExecuteCompilationUnits( + compilation_state->background_compile_token(), isolate->counters(), + kMainThreadTaskId, kBaselineOnly)) { + // Continue executing compilation units. + } + } + + // Now wait until baseline recompilation finished. + recompilation_finished_semaphore->Wait(); + DCHECK(!compilation_state->failed()); +} + AsyncCompileJob::AsyncCompileJob( Isolate* isolate, const WasmFeatures& enabled, std::unique_ptr bytes_copy, size_t length, Handle context, @@ -1630,7 +1695,7 @@ class AsyncCompileJob::CompilationStateCallback { // At this point, the job will already be gone, thus do not access it // here. break; - case CompilationEvent::kFailedCompilation: { + case CompilationEvent::kFailedCompilation: DCHECK(!last_event_.has_value()); if (job_->DecrementAndCheckFinisherCount()) { // TODO(v8:6847): Also share streaming compilation result. @@ -1641,7 +1706,8 @@ class AsyncCompileJob::CompilationStateCallback { job_->DoSync(); } break; - } + case CompilationEvent::kFinishedRecompilation: + break; default: UNREACHABLE(); } @@ -1960,6 +2026,7 @@ class SampleTopTierCodeSizeCallback { void operator()(CompilationEvent event) { // This callback is registered after baseline compilation finished, so the // only possible event to follow is {kFinishedTopTierCompilation}. + if (event == CompilationEvent::kFinishedRecompilation) return; DCHECK_EQ(CompilationEvent::kFinishedTopTierCompilation, event); if (std::shared_ptr native_module = native_module_.lock()) { native_module->engine()->SampleTopTierCodeSizeInAllIsolates( @@ -2352,6 +2419,37 @@ void CompilationStateImpl::InitializeCompilationProgress(bool lazy_module, } } +void CompilationStateImpl::InitializeRecompilationProgress(ExecutionTier tier) { + DCHECK(!failed()); + auto* module = native_module_->module(); + + base::MutexGuard guard(&callbacks_mutex_); + // Ensure that we don't trigger recompilation if another recompilation is + // already happening. + DCHECK_EQ(0, outstanding_recompilation_functions_); + int start = module->num_imported_functions; + int end = start + module->num_declared_functions; + for (int function_index = start; function_index < end; function_index++) { + int slot_index = function_index - start; + DCHECK_LT(slot_index, compilation_progress_.size()); + ExecutionTier reached_tier = + ReachedTierField::decode(compilation_progress_[slot_index]); + if (reached_tier != tier) { + outstanding_recompilation_functions_++; + } + } + DCHECK_LE(0, outstanding_recompilation_functions_); + DCHECK_LE(outstanding_recompilation_functions_, + module->num_declared_functions); + + // Trigger callbacks if module needs no recompilation. + if (outstanding_recompilation_functions_ == 0) { + for (auto& callback : callbacks_) { + callback(CompilationEvent::kFinishedRecompilation); + } + } +} + void CompilationStateImpl::AddCallback(CompilationState::callback_t callback) { base::MutexGuard callbacks_guard(&callbacks_mutex_); callbacks_.emplace_back(std::move(callback)); @@ -2423,7 +2521,8 @@ void CompilationStateImpl::OnFinishedUnits(Vector code_vector) { // This is especially important for lazy modules that were deserialized. // Compilation progress was not set up in these cases. if (outstanding_baseline_units_ == 0 && - outstanding_top_tier_functions_ == 0) { + outstanding_top_tier_functions_ == 0 && + outstanding_recompilation_functions_ == 0) { return; } @@ -2439,6 +2538,7 @@ void CompilationStateImpl::OnFinishedUnits(Vector code_vector) { bool completes_baseline_compilation = false; bool completes_top_tier_compilation = false; + bool completes_recompilation = false; for (WasmCode* code : code_vector) { DCHECK_NOT_NULL(code); @@ -2486,17 +2586,34 @@ void CompilationStateImpl::OnFinishedUnits(Vector code_vector) { } } - // Update function's compilation progress. - if (code->tier() > reached_tier) { - compilation_progress_[slot_index] = ReachedTierField::update( - compilation_progress_[slot_index], code->tier()); + // If there is recompilation in progress, we would only count the + // functions which are not Liftoff already, and would only decrement the + // counter once a function reaches Liftoff. + if (outstanding_recompilation_functions_ > 0) { + // TODO(duongn): extend this logic for tier up. + if (code->tier() == ExecutionTier::kLiftoff && + reached_tier != ExecutionTier::kLiftoff) { + outstanding_recompilation_functions_--; + // Update function's compilation progress. + compilation_progress_[slot_index] = ReachedTierField::update( + compilation_progress_[slot_index], code->tier()); + if (outstanding_recompilation_functions_ == 0) { + completes_recompilation = true; + } + } + } else { + // Update function's compilation progress. + if (code->tier() > reached_tier) { + compilation_progress_[slot_index] = ReachedTierField::update( + compilation_progress_[slot_index], code->tier()); + } } DCHECK_LE(0, outstanding_baseline_units_); } } TriggerCallbacks(completes_baseline_compilation, - completes_top_tier_compilation); + completes_top_tier_compilation, completes_recompilation); } void CompilationStateImpl::OnFinishedJSToWasmWrapperUnits(int num) { @@ -2507,8 +2624,14 @@ void CompilationStateImpl::OnFinishedJSToWasmWrapperUnits(int num) { TriggerCallbacks(completes_baseline_compilation, false); } -void CompilationStateImpl::TriggerCallbacks( - bool completes_baseline_compilation, bool completes_top_tier_compilation) { +void CompilationStateImpl::TriggerCallbacks(bool completes_baseline_compilation, + bool completes_top_tier_compilation, + bool completes_recompilation) { + if (completes_recompilation) { + for (auto& callback : callbacks_) { + callback(CompilationEvent::kFinishedRecompilation); + } + } if (completes_baseline_compilation) { TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("v8.wasm"), "BaselineFinished"); for (auto& callback : callbacks_) { @@ -2523,6 +2646,10 @@ void CompilationStateImpl::TriggerCallbacks( for (auto& callback : callbacks_) { callback(CompilationEvent::kFinishedTopTierCompilation); } + } + if (outstanding_baseline_units_ == 0 && + outstanding_top_tier_functions_ == 0 && + outstanding_recompilation_functions_ == 0) { // Clear the callbacks because no more events will be delivered. callbacks_.clear(); } @@ -2589,7 +2716,7 @@ void CompilationStateImpl::RestartBackgroundTasks() { } } - if (baseline_compilation_finished()) { + if (baseline_compilation_finished() && recompilation_finished()) { for (auto& task : new_tasks) { V8::GetCurrentPlatform()->CallLowPriorityTaskOnWorkerThread( std::move(task)); diff --git a/src/wasm/module-compiler.h b/src/wasm/module-compiler.h index f3a561da1a..b60c12702f 100644 --- a/src/wasm/module-compiler.h +++ b/src/wasm/module-compiler.h @@ -44,6 +44,9 @@ std::shared_ptr CompileToNativeModule( std::shared_ptr module, const ModuleWireBytes& wire_bytes, Handle* export_wrappers_out); +void RecompileNativeModule(Isolate* isolate, NativeModule* native_module, + ExecutionTier tier); + V8_EXPORT_PRIVATE void CompileJsToWasmWrappers(Isolate* isolate, const WasmModule* module, Handle* export_wrappers_out); diff --git a/src/wasm/wasm-code-manager.cc b/src/wasm/wasm-code-manager.cc index 178659b42d..27e36aaabe 100644 --- a/src/wasm/wasm-code-manager.cc +++ b/src/wasm/wasm-code-manager.cc @@ -1818,13 +1818,8 @@ void NativeModule::TierDown(Isolate* isolate) { tier_down_ = true; } // Tier down all functions. - // TODO(duongn): parallelize this eventually. - for (uint32_t index = module_->num_imported_functions; - index < num_functions(); index++) { - isolate->wasm_engine()->CompileFunction(isolate, this, index, - ExecutionTier::kLiftoff); - DCHECK(!compilation_state()->failed()); - } + isolate->wasm_engine()->RecompileAllFunctions(isolate, this, + ExecutionTier::kLiftoff); } void NativeModule::TierUp(Isolate* isolate) { diff --git a/src/wasm/wasm-engine.cc b/src/wasm/wasm-engine.cc index 5f16def0b1..28b0aa0ca5 100644 --- a/src/wasm/wasm-engine.cc +++ b/src/wasm/wasm-engine.cc @@ -488,6 +488,12 @@ void WasmEngine::CompileFunction(Isolate* isolate, NativeModule* native_module, &native_module->module()->functions[function_index], tier); } +void WasmEngine::RecompileAllFunctions(Isolate* isolate, + NativeModule* native_module, + ExecutionTier tier) { + RecompileNativeModule(isolate, native_module, tier); +} + std::shared_ptr WasmEngine::ExportNativeModule( Handle module_object) { return module_object->shared_native_module(); diff --git a/src/wasm/wasm-engine.h b/src/wasm/wasm-engine.h index 94bee77663..073d617b31 100644 --- a/src/wasm/wasm-engine.h +++ b/src/wasm/wasm-engine.h @@ -148,6 +148,10 @@ class V8_EXPORT_PRIVATE WasmEngine { void CompileFunction(Isolate* isolate, NativeModule* native_module, uint32_t function_index, ExecutionTier tier); + // Recompiles all functions at a specific compilation tier. + void RecompileAllFunctions(Isolate* isolate, NativeModule* native_module, + ExecutionTier tier); + // Exports the sharable parts of the given module object so that they can be // transferred to a different Context/Isolate using the same engine. std::shared_ptr ExportNativeModule( diff --git a/test/mjsunit/wasm/tier-down-to-liftoff.js b/test/mjsunit/wasm/tier-down-to-liftoff.js index 6baf8fea98..bfd973c607 100644 --- a/test/mjsunit/wasm/tier-down-to-liftoff.js +++ b/test/mjsunit/wasm/tier-down-to-liftoff.js @@ -2,17 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Flags: --allow-natives-syntax --wasm-tier-up +// Flags: --allow-natives-syntax --liftoff --wasm-tier-up --no-stress-opt load('test/mjsunit/wasm/wasm-module-builder.js'); const num_functions = 2; -function create_builder() { +function create_builder(delta = 0) { const builder = new WasmModuleBuilder(); for (let i = 0; i < num_functions; ++i) { builder.addFunction('f' + i, kSig_i_v) - .addBody(wasmI32Const(i)) + .addBody(wasmI32Const(i + delta)) .exportFunc(); } return builder; @@ -41,10 +41,10 @@ function check(instance) { check(instance); })(); - +// Use slightly different module for this test to avoid sharing native module. async function testTierDownToLiftoffAsync() { print(arguments.callee.name); - const instance = await create_builder().asyncInstantiate(); + const instance = await create_builder(num_functions).asyncInstantiate(); check(instance); }