From c63d47c2b0a969306b318fa91feaded9c1144e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Tue, 18 Apr 2023 18:05:46 +0200 Subject: [PATCH 1/2] deps: V8: cherry-pick a8a11a87cb72 Original commit message: [wasm] Simplify CompileJSToWasmWrapperJob This CL merges some of AsyncCompileJSToWasmWrapperJob and CompileJSToWasmWrapperJob into a common base class. Both jobs now loop on a vector with an atomic index. Bug: chromium:1423615 Change-Id: I7bb9cb2a57d8b659766c01e20e38d1b859d3a987 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4347597 Reviewed-by: Clemens Backes Commit-Queue: Etienne Pierre-Doray Cr-Commit-Position: refs/heads/main@{#86675} Refs: https://github.com/v8/v8/commit/a8a11a87cb72c698cd35a5df3a23f0d08340b6d1 --- common.gypi | 2 +- deps/v8/src/wasm/module-compiler.cc | 263 ++++++++++++++-------------- 2 files changed, 128 insertions(+), 137 deletions(-) diff --git a/common.gypi b/common.gypi index 2104bba30e2ebb..a94b35dbdd2a27 100644 --- a/common.gypi +++ b/common.gypi @@ -36,7 +36,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.6', + 'v8_embedder_string': '-node.7', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/src/wasm/module-compiler.cc b/deps/v8/src/wasm/module-compiler.cc index 96abaa0a042a66..7feb697aad948e 100644 --- a/deps/v8/src/wasm/module-compiler.cc +++ b/deps/v8/src/wasm/module-compiler.cc @@ -5,6 +5,7 @@ #include "src/wasm/module-compiler.h" #include +#include #include #include @@ -537,7 +538,7 @@ class CompilationStateImpl { std::shared_ptr async_counters, DynamicTiering dynamic_tiering); ~CompilationStateImpl() { - if (js_to_wasm_wrapper_job_->IsValid()) + if (js_to_wasm_wrapper_job_ && js_to_wasm_wrapper_job_->IsValid()) js_to_wasm_wrapper_job_->CancelAndDetach(); if (baseline_compile_job_->IsValid()) baseline_compile_job_->CancelAndDetach(); @@ -607,18 +608,17 @@ class CompilationStateImpl { CompilationUnitQueues::Queue*, CompilationTier tier); std::shared_ptr - GetNextJSToWasmWrapperCompilationUnit(); + GetJSToWasmWrapperCompilationUnit(size_t index); void FinalizeJSToWasmWrappers(Isolate* isolate, const WasmModule* module); void OnFinishedUnits(base::Vector); - void OnFinishedJSToWasmWrapperUnits(int num); + void OnFinishedJSToWasmWrapperUnits(); void OnCompilationStopped(WasmFeatures detected); void PublishDetectedFeatures(Isolate*); void SchedulePublishCompilationResults( std::vector> unpublished_code); - size_t NumOutstandingExportWrappers() const; size_t NumOutstandingCompilations(CompilationTier tier) const; void SetError(); @@ -642,7 +642,7 @@ class CompilationStateImpl { bool baseline_compilation_finished() const { base::MutexGuard guard(&callbacks_mutex_); return outstanding_baseline_units_ == 0 && - outstanding_export_wrappers_ == 0; + !has_outstanding_export_wrappers_; } DynamicTiering dynamic_tiering() const { return dynamic_tiering_; } @@ -698,9 +698,6 @@ class CompilationStateImpl { CompilationUnitQueues compilation_unit_queues_; - // Number of wrappers to be compiled. Initialized once, counted down in - // {GetNextJSToWasmWrapperCompilationUnit}. - std::atomic outstanding_js_to_wasm_wrappers_{0}; // Wrapper compilation units are stored in shared_ptrs so that they are kept // alive by the tasks even if the NativeModule dies. std::vector> @@ -754,7 +751,7 @@ class CompilationStateImpl { base::EnumSet finished_events_; int outstanding_baseline_units_ = 0; - int outstanding_export_wrappers_ = 0; + bool has_outstanding_export_wrappers_ = false; // The amount of generated top tier code since the last // {kFinishedCompilationChunk} event. size_t bytes_since_last_chunk_ = 0; @@ -1444,44 +1441,6 @@ void RecordStats(Code code, Counters* counters) { enum CompilationExecutionResult : int8_t { kNoMoreUnits, kYield }; -CompilationExecutionResult ExecuteJSToWasmWrapperCompilationUnits( - std::weak_ptr native_module, JobDelegate* delegate) { - std::shared_ptr wrapper_unit = nullptr; - int num_processed_wrappers = 0; - - OperationsBarrier::Token wrapper_compilation_token; - Isolate* isolate; - - { - BackgroundCompileScope compile_scope(native_module); - if (compile_scope.cancelled()) return kYield; - wrapper_unit = compile_scope.compilation_state() - ->GetNextJSToWasmWrapperCompilationUnit(); - if (!wrapper_unit) return kNoMoreUnits; - isolate = wrapper_unit->isolate(); - wrapper_compilation_token = - wasm::GetWasmEngine()->StartWrapperCompilation(isolate); - if (!wrapper_compilation_token) return kNoMoreUnits; - } - - TRACE_EVENT0("v8.wasm", "wasm.JSToWasmWrapperCompilation"); - while (true) { - DCHECK_EQ(isolate, wrapper_unit->isolate()); - wrapper_unit->Execute(); - ++num_processed_wrappers; - bool yield = delegate && delegate->ShouldYield(); - BackgroundCompileScope compile_scope(native_module); - if (compile_scope.cancelled()) return kYield; - if (yield || - !(wrapper_unit = compile_scope.compilation_state() - ->GetNextJSToWasmWrapperCompilationUnit())) { - compile_scope.compilation_state()->OnFinishedJSToWasmWrapperUnits( - num_processed_wrappers); - return yield ? kYield : kNoMoreUnits; - } - } -} - namespace { const char* GetCompilationEventName(const WasmCompilationUnit& unit, const CompilationEnv& env) { @@ -1864,35 +1823,101 @@ void CompileNativeModule(Isolate* isolate, } } -class AsyncCompileJSToWasmWrapperJob final : public JobTask { +class BaseCompileJSToWasmWrapperJob : public JobTask { + public: + explicit BaseCompileJSToWasmWrapperJob(size_t compilation_units) + : outstanding_units_(compilation_units) {} + + size_t GetMaxConcurrency(size_t worker_count) const override { + size_t flag_limit = static_cast( + std::max(1, v8_flags.wasm_num_compilation_tasks.value())); + // {outstanding_units_} includes the units that other workers are currently + // working on, so we can safely ignore the {worker_count} and just return + // the current number of outstanding units. + return std::min(flag_limit, + outstanding_units_.load(std::memory_order_relaxed)); + } + + protected: + // Returns the index of the next unit to process. + size_t GetNextUnitIndex() { + // |unit_index_| may exceeed |compilation_units|, but only by the number of + // workers at worst, thus it can't exceed 2 * |compilation_units| and + // overflow shouldn't happen. + return unit_index_.fetch_add(1, std::memory_order_relaxed); + } + + // Returns true if the last unit was completed. + bool CompleteUnit() { + size_t outstanding_units = + outstanding_units_.fetch_sub(1, std::memory_order_relaxed); + DCHECK_GE(outstanding_units, 1); + return outstanding_units == 1; + } + + private: + std::atomic unit_index_{0}; + std::atomic outstanding_units_; +}; + +class AsyncCompileJSToWasmWrapperJob final + : public BaseCompileJSToWasmWrapperJob { public: explicit AsyncCompileJSToWasmWrapperJob( - std::weak_ptr native_module) - : native_module_(std::move(native_module)), - engine_barrier_(GetWasmEngine()->GetBarrierForBackgroundCompile()) {} + std::weak_ptr native_module, size_t compilation_units) + : BaseCompileJSToWasmWrapperJob(compilation_units), + native_module_(std::move(native_module)), + engine_barrier_(GetWasmEngine()->GetBarrierForBackgroundCompile()), + compilation_units_size_(compilation_units) {} void Run(JobDelegate* delegate) override { auto engine_scope = engine_barrier_->TryLock(); if (!engine_scope) return; - ExecuteJSToWasmWrapperCompilationUnits(native_module_, delegate); - } + std::shared_ptr wrapper_unit = nullptr; - size_t GetMaxConcurrency(size_t worker_count) const override { - BackgroundCompileScope compile_scope(native_module_); - if (compile_scope.cancelled()) return 0; - size_t flag_limit = static_cast( - std::max(1, v8_flags.wasm_num_compilation_tasks.value())); - // NumOutstandingExportWrappers() does not reflect the units that running - // workers are processing, thus add the current worker count to that number. - return std::min( - flag_limit, - worker_count + - compile_scope.compilation_state()->NumOutstandingExportWrappers()); + OperationsBarrier::Token wrapper_compilation_token; + Isolate* isolate; + + size_t index = GetNextUnitIndex(); + if (index >= compilation_units_size_) return; + { + BackgroundCompileScope compile_scope(native_module_); + if (compile_scope.cancelled()) return; + wrapper_unit = + compile_scope.compilation_state()->GetJSToWasmWrapperCompilationUnit( + index); + isolate = wrapper_unit->isolate(); + wrapper_compilation_token = + wasm::GetWasmEngine()->StartWrapperCompilation(isolate); + if (!wrapper_compilation_token) return; + } + + TRACE_EVENT0("v8.wasm", "wasm.JSToWasmWrapperCompilation"); + while (true) { + DCHECK_EQ(isolate, wrapper_unit->isolate()); + wrapper_unit->Execute(); + bool complete_last_unit = CompleteUnit(); + bool yield = delegate && delegate->ShouldYield(); + if (yield && !complete_last_unit) return; + + BackgroundCompileScope compile_scope(native_module_); + if (compile_scope.cancelled()) return; + if (complete_last_unit) + compile_scope.compilation_state()->OnFinishedJSToWasmWrapperUnits(); + if (yield) return; + size_t index = GetNextUnitIndex(); + if (index >= compilation_units_size_) return; + wrapper_unit = + compile_scope.compilation_state()->GetJSToWasmWrapperCompilationUnit( + index); + } } private: std::weak_ptr native_module_; std::shared_ptr engine_barrier_; + // Number of wrappers to be compiled. + const size_t compilation_units_size_; }; class BackgroundCompileJob final : public JobTask { @@ -3053,14 +3078,10 @@ CompilationStateImpl::CompilationStateImpl( dynamic_tiering_(dynamic_tiering) {} void CompilationStateImpl::InitCompileJob() { - DCHECK_NULL(js_to_wasm_wrapper_job_); DCHECK_NULL(baseline_compile_job_); DCHECK_NULL(top_tier_compile_job_); // Create the job, but don't spawn workers yet. This will happen on // {NotifyConcurrencyIncrease}. - js_to_wasm_wrapper_job_ = V8::GetCurrentPlatform()->CreateJob( - TaskPriority::kUserBlocking, - std::make_unique(native_module_weak_)); baseline_compile_job_ = V8::GetCurrentPlatform()->CreateJob( TaskPriority::kUserVisible, std::make_unique( @@ -3184,7 +3205,7 @@ void CompilationStateImpl::InitializeCompilationProgress( base::MutexGuard guard(&callbacks_mutex_); DCHECK_EQ(0, outstanding_baseline_units_); - DCHECK_EQ(0, outstanding_export_wrappers_); + DCHECK(!has_outstanding_export_wrappers_); // Compute the default compilation progress for all functions, and set it. const ExecutionTierPair default_tiers = GetDefaultTiersPerModule( @@ -3215,7 +3236,7 @@ void CompilationStateImpl::InitializeCompilationProgress( // Account for outstanding wrapper compilation. outstanding_baseline_units_ += num_import_wrappers; - outstanding_export_wrappers_ = num_export_wrappers; + has_outstanding_export_wrappers_ = (num_export_wrappers > 0); // Trigger callbacks if module needs no baseline or top tier compilation. This // can be the case for an empty or fully lazy module. @@ -3373,16 +3394,14 @@ void CompilationStateImpl::CommitCompilationUnits( js_to_wasm_wrapper_units) { if (!js_to_wasm_wrapper_units.empty()) { // |js_to_wasm_wrapper_units_| will only be initialized once. - DCHECK_EQ(0, outstanding_js_to_wasm_wrappers_.load()); + DCHECK_NULL(js_to_wasm_wrapper_job_); js_to_wasm_wrapper_units_.insert(js_to_wasm_wrapper_units_.end(), js_to_wasm_wrapper_units.begin(), js_to_wasm_wrapper_units.end()); - // Use release semantics such that updates to {js_to_wasm_wrapper_units_} - // are available to other threads doing an acquire load. - outstanding_js_to_wasm_wrappers_.store(js_to_wasm_wrapper_units.size(), - std::memory_order_release); - DCHECK(js_to_wasm_wrapper_job_->IsValid()); - js_to_wasm_wrapper_job_->NotifyConcurrencyIncrease(); + js_to_wasm_wrapper_job_ = V8::GetCurrentPlatform()->PostJob( + TaskPriority::kUserBlocking, + std::make_unique( + native_module_weak_, js_to_wasm_wrapper_units_.size())); } if (!baseline_units.empty() || !top_tier_units.empty()) { compilation_unit_queues_.AddUnits(baseline_units, top_tier_units, @@ -3415,19 +3434,9 @@ void CompilationStateImpl::AddTopTierPriorityCompilationUnit( } std::shared_ptr -CompilationStateImpl::GetNextJSToWasmWrapperCompilationUnit() { - size_t outstanding_units = - outstanding_js_to_wasm_wrappers_.load(std::memory_order_relaxed); - // Use acquire semantics such that initialization of - // {js_to_wasm_wrapper_units_} is available. - while (outstanding_units && - !outstanding_js_to_wasm_wrappers_.compare_exchange_weak( - outstanding_units, outstanding_units - 1, - std::memory_order_acquire)) { - // Retry with updated {outstanding_units}. - } - if (outstanding_units == 0) return nullptr; - return js_to_wasm_wrapper_units_[outstanding_units - 1]; +CompilationStateImpl::GetJSToWasmWrapperCompilationUnit(size_t index) { + DCHECK_LT(index, js_to_wasm_wrapper_units_.size()); + return js_to_wasm_wrapper_units_[index]; } void CompilationStateImpl::FinalizeJSToWasmWrappers(Isolate* isolate, @@ -3534,11 +3543,9 @@ void CompilationStateImpl::OnFinishedUnits( TriggerCallbacks(); } -void CompilationStateImpl::OnFinishedJSToWasmWrapperUnits(int num) { - if (num == 0) return; +void CompilationStateImpl::OnFinishedJSToWasmWrapperUnits() { base::MutexGuard guard(&callbacks_mutex_); - DCHECK_GE(outstanding_export_wrappers_, num); - outstanding_export_wrappers_ -= num; + has_outstanding_export_wrappers_ = false; TriggerCallbacks(); } @@ -3546,7 +3553,7 @@ void CompilationStateImpl::TriggerCallbacks() { DCHECK(!callbacks_mutex_.TryLock()); base::EnumSet triggered_events; - if (outstanding_export_wrappers_ == 0) { + if (!has_outstanding_export_wrappers_) { triggered_events.Add(CompilationEvent::kFinishedExportWrappers); if (outstanding_baseline_units_ == 0) { triggered_events.Add(CompilationEvent::kFinishedBaselineCompilation); @@ -3592,7 +3599,7 @@ void CompilationStateImpl::TriggerCallbacks() { } } - if (outstanding_baseline_units_ == 0 && outstanding_export_wrappers_ == 0) { + if (outstanding_baseline_units_ == 0 && !has_outstanding_export_wrappers_) { auto new_end = std::remove_if( callbacks_.begin(), callbacks_.end(), [](const auto& callback) { return callback->release_after_final_event(); @@ -3688,10 +3695,6 @@ void CompilationStateImpl::SchedulePublishCompilationResults( } } -size_t CompilationStateImpl::NumOutstandingExportWrappers() const { - return outstanding_js_to_wasm_wrappers_.load(std::memory_order_relaxed); -} - size_t CompilationStateImpl::NumOutstandingCompilations( CompilationTier tier) const { return compilation_unit_queues_.GetSizeForTier(tier); @@ -3720,7 +3723,8 @@ void CompilationStateImpl::WaitForCompilationEvent( // Waiting on other CompilationEvent doesn't make sense. UNREACHABLE(); } - if (js_to_wasm_wrapper_job_->IsValid()) js_to_wasm_wrapper_job_->Join(); + if (js_to_wasm_wrapper_job_ && js_to_wasm_wrapper_job_->IsValid()) + js_to_wasm_wrapper_job_->Join(); #ifdef DEBUG base::EnumSet events{expect_event, CompilationEvent::kFailedCompilation}; @@ -3769,45 +3773,32 @@ void CompilationStateImpl::TierUpAllFunctions() { } namespace { -using JSToWasmWrapperQueue = WrapperQueue>; -using JSToWasmWrapperUnitMap = - std::unordered_map, - base::hash>; - -class CompileJSToWasmWrapperJob final : public JobTask { +using JSToWasmWrapperSet = + std::unordered_set>; +using JSToWasmWrapperUnitVector = + std::vector>>; + +class CompileJSToWasmWrapperJob final : public BaseCompileJSToWasmWrapperJob { public: - CompileJSToWasmWrapperJob(JSToWasmWrapperQueue* queue, - JSToWasmWrapperUnitMap* compilation_units) - : queue_(queue), - compilation_units_(compilation_units), - outstanding_units_(queue->size()) {} + CompileJSToWasmWrapperJob(JSToWasmWrapperUnitVector* compilation_units) + : BaseCompileJSToWasmWrapperJob(compilation_units->size()), + compilation_units_(compilation_units) {} void Run(JobDelegate* delegate) override { - while (base::Optional> key = - queue_->pop()) { + while (true) { + size_t index = GetNextUnitIndex(); + if (index >= compilation_units_->size()) return; JSToWasmWrapperCompilationUnit* unit = - (*compilation_units_)[key->first].get(); + (*compilation_units_)[index].second.get(); unit->Execute(); - outstanding_units_.fetch_sub(1, std::memory_order_relaxed); + CompleteUnit(); if (delegate && delegate->ShouldYield()) return; } } - size_t GetMaxConcurrency(size_t /* worker_count */) const override { - DCHECK_GE(v8_flags.wasm_num_compilation_tasks, 1); - // {outstanding_units_} includes the units that other workers are currently - // working on, so we can safely ignore the {worker_count} and just return - // the current number of outstanding units. - return std::min(static_cast(v8_flags.wasm_num_compilation_tasks), - outstanding_units_.load(std::memory_order_relaxed)); - } - private: - JSToWasmWrapperQueue* const queue_; - JSToWasmWrapperUnitMap* const compilation_units_; - std::atomic outstanding_units_; + JSToWasmWrapperUnitVector* const compilation_units_; }; } // namespace @@ -3817,8 +3808,8 @@ void CompileJsToWasmWrappers(Isolate* isolate, const WasmModule* module) { isolate->heap()->EnsureWasmCanonicalRttsSize(module->MaxCanonicalTypeIndex() + 1); - JSToWasmWrapperQueue queue; - JSToWasmWrapperUnitMap compilation_units; + JSToWasmWrapperSet set; + JSToWasmWrapperUnitVector compilation_units; WasmFeatures enabled_features = WasmFeatures::FromIsolate(isolate); // Prepare compilation units in the main thread. @@ -3838,12 +3829,13 @@ void CompileJsToWasmWrappers(Isolate* isolate, const WasmModule* module) { } JSToWasmWrapperKey key(function.imported, canonical_type_index); - if (queue.insert(key, nullptr)) { + const auto [it, inserted] = set.insert(key); + if (inserted) { auto unit = std::make_unique( isolate, function.sig, canonical_type_index, module, function.imported, enabled_features, JSToWasmWrapperCompilationUnit::kAllowGeneric); - compilation_units.emplace(key, std::move(unit)); + compilation_units.emplace_back(key, std::move(unit)); } } @@ -3852,8 +3844,7 @@ void CompileJsToWasmWrappers(Isolate* isolate, const WasmModule* module) { // descriptive. It's mainly to log the number of wrappers. TRACE_EVENT1("v8.wasm", "wasm.JsToWasmWrapperCompilation", "num_wrappers", compilation_units.size()); - auto job = - std::make_unique(&queue, &compilation_units); + auto job = std::make_unique(&compilation_units); if (v8_flags.wasm_num_compilation_tasks > 0) { auto job_handle = V8::GetCurrentPlatform()->CreateJob( TaskPriority::kUserVisible, std::move(job)); From a62c955b4d094058f09ac0f043a97ae6fb025349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Tue, 18 Apr 2023 18:05:58 +0200 Subject: [PATCH 2/2] deps: V8: cherry-pick 5f025d1ca2ca Original commit message: [wasm] Fix deadlock in async wrapper compilation If compilation is cancelled while wrapper compilation is running, the tasks spawned for the {AsyncCompileJSToWasmWrapperJob} will return immediately, but {GetMaxConcurrency} will still return a positive value. Hence {Join()} will spawn another task, resulting in a livelock. We could fix this by checking for cancellation in {GetMaxConcurrency}, but that requires taking the compilation state lock. So this CL fixes the issue by dropping the number of outstanding compilation units by to (basically) zero. We can't unconditionally drop to zero because another thread might concurrently execute a wrapper compilation and still call {CompleteUnit} afterwards. Hence only drop outstanding units by the amount of not-yet-started units. R=jkummerow@chromium.org Bug: v8:13858 Change-Id: I5398ef370da2e7f212ca772fd1f87f659929dd6d Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4437531 Commit-Queue: Clemens Backes Reviewed-by: Jakob Kummerow Cr-Commit-Position: refs/heads/main@{#87143} Refs: https://github.com/v8/v8/commit/5f025d1ca2ca2fe2f6cee5b6d841907a36fd32b7 --- common.gypi | 2 +- deps/v8/src/wasm/module-compiler.cc | 70 ++++++++++++++++++++--------- 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/common.gypi b/common.gypi index a94b35dbdd2a27..5cca6b4c597699 100644 --- a/common.gypi +++ b/common.gypi @@ -36,7 +36,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.7', + 'v8_embedder_string': '-node.8', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/src/wasm/module-compiler.cc b/deps/v8/src/wasm/module-compiler.cc index 7feb697aad948e..128ca572d39fe6 100644 --- a/deps/v8/src/wasm/module-compiler.cc +++ b/deps/v8/src/wasm/module-compiler.cc @@ -1826,7 +1826,8 @@ void CompileNativeModule(Isolate* isolate, class BaseCompileJSToWasmWrapperJob : public JobTask { public: explicit BaseCompileJSToWasmWrapperJob(size_t compilation_units) - : outstanding_units_(compilation_units) {} + : outstanding_units_(compilation_units), + total_units_(compilation_units) {} size_t GetMaxConcurrency(size_t worker_count) const override { size_t flag_limit = static_cast( @@ -1839,12 +1840,20 @@ class BaseCompileJSToWasmWrapperJob : public JobTask { } protected: - // Returns the index of the next unit to process. - size_t GetNextUnitIndex() { - // |unit_index_| may exceeed |compilation_units|, but only by the number of - // workers at worst, thus it can't exceed 2 * |compilation_units| and - // overflow shouldn't happen. - return unit_index_.fetch_add(1, std::memory_order_relaxed); + // Returns {true} and places the index of the next unit to process in + // {index_out} if there are still units to be processed. Returns {false} + // otherwise. + bool GetNextUnitIndex(size_t* index_out) { + size_t next_index = unit_index_.fetch_add(1, std::memory_order_relaxed); + if (next_index >= total_units_) { + // {unit_index_} may exceeed {total_units_}, but only by the number of + // workers at worst, thus it can't exceed 2 * {total_units_} and overflow + // shouldn't happen. + DCHECK_GE(2 * total_units_, next_index); + return false; + } + *index_out = next_index; + return true; } // Returns true if the last unit was completed. @@ -1855,9 +1864,29 @@ class BaseCompileJSToWasmWrapperJob : public JobTask { return outstanding_units == 1; } + // When external cancellation is detected, call this method to bump + // {unit_index_} and reset {outstanding_units_} such that no more tasks are + // being scheduled for this job and all tasks exit as soon as possible. + void FlushRemainingUnits() { + // After being cancelled, make sure to reduce outstanding_units_ to + // *basically* zero, but leave the count positive if other workers are still + // running, to avoid underflow in {CompleteUnit}. + size_t next_undone_unit = + unit_index_.exchange(total_units_, std::memory_order_relaxed); + size_t undone_units = + next_undone_unit >= total_units_ ? 0 : total_units_ - next_undone_unit; + // Note that the caller requested one unit that we also still need to remove + // from {outstanding_units_}. + ++undone_units; + size_t previous_outstanding_units = + outstanding_units_.fetch_sub(undone_units, std::memory_order_relaxed); + CHECK_LE(undone_units, previous_outstanding_units); + } + private: std::atomic unit_index_{0}; std::atomic outstanding_units_; + const size_t total_units_; }; class AsyncCompileJSToWasmWrapperJob final @@ -1867,8 +1896,7 @@ class AsyncCompileJSToWasmWrapperJob final std::weak_ptr native_module, size_t compilation_units) : BaseCompileJSToWasmWrapperJob(compilation_units), native_module_(std::move(native_module)), - engine_barrier_(GetWasmEngine()->GetBarrierForBackgroundCompile()), - compilation_units_size_(compilation_units) {} + engine_barrier_(GetWasmEngine()->GetBarrierForBackgroundCompile()) {} void Run(JobDelegate* delegate) override { auto engine_scope = engine_barrier_->TryLock(); @@ -1878,18 +1906,18 @@ class AsyncCompileJSToWasmWrapperJob final OperationsBarrier::Token wrapper_compilation_token; Isolate* isolate; - size_t index = GetNextUnitIndex(); - if (index >= compilation_units_size_) return; + size_t index; + if (!GetNextUnitIndex(&index)) return; { BackgroundCompileScope compile_scope(native_module_); - if (compile_scope.cancelled()) return; + if (compile_scope.cancelled()) return FlushRemainingUnits(); wrapper_unit = compile_scope.compilation_state()->GetJSToWasmWrapperCompilationUnit( index); isolate = wrapper_unit->isolate(); wrapper_compilation_token = wasm::GetWasmEngine()->StartWrapperCompilation(isolate); - if (!wrapper_compilation_token) return; + if (!wrapper_compilation_token) return FlushRemainingUnits(); } TRACE_EVENT0("v8.wasm", "wasm.JSToWasmWrapperCompilation"); @@ -1902,11 +1930,11 @@ class AsyncCompileJSToWasmWrapperJob final BackgroundCompileScope compile_scope(native_module_); if (compile_scope.cancelled()) return; - if (complete_last_unit) + if (complete_last_unit) { compile_scope.compilation_state()->OnFinishedJSToWasmWrapperUnits(); + } if (yield) return; - size_t index = GetNextUnitIndex(); - if (index >= compilation_units_size_) return; + if (!GetNextUnitIndex(&index)) return; wrapper_unit = compile_scope.compilation_state()->GetJSToWasmWrapperCompilationUnit( index); @@ -1916,8 +1944,6 @@ class AsyncCompileJSToWasmWrapperJob final private: std::weak_ptr native_module_; std::shared_ptr engine_barrier_; - // Number of wrappers to be compiled. - const size_t compilation_units_size_; }; class BackgroundCompileJob final : public JobTask { @@ -3723,8 +3749,9 @@ void CompilationStateImpl::WaitForCompilationEvent( // Waiting on other CompilationEvent doesn't make sense. UNREACHABLE(); } - if (js_to_wasm_wrapper_job_ && js_to_wasm_wrapper_job_->IsValid()) + if (js_to_wasm_wrapper_job_ && js_to_wasm_wrapper_job_->IsValid()) { js_to_wasm_wrapper_job_->Join(); + } #ifdef DEBUG base::EnumSet events{expect_event, CompilationEvent::kFailedCompilation}; @@ -3786,9 +3813,8 @@ class CompileJSToWasmWrapperJob final : public BaseCompileJSToWasmWrapperJob { compilation_units_(compilation_units) {} void Run(JobDelegate* delegate) override { - while (true) { - size_t index = GetNextUnitIndex(); - if (index >= compilation_units_->size()) return; + size_t index; + while (GetNextUnitIndex(&index)) { JSToWasmWrapperCompilationUnit* unit = (*compilation_units_)[index].second.get(); unit->Execute();