Skip to content

Commit 5f025d1

Browse files
backesV8 LUCI CQ
authored andcommitted
[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. [email protected] Bug: v8:13858 Change-Id: I5398ef370da2e7f212ca772fd1f87f659929dd6d Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4437531 Commit-Queue: Clemens Backes <[email protected]> Reviewed-by: Jakob Kummerow <[email protected]> Cr-Commit-Position: refs/heads/main@{#87143}
1 parent 739c33a commit 5f025d1

1 file changed

Lines changed: 48 additions & 22 deletions

File tree

src/wasm/module-compiler.cc

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1827,7 +1827,8 @@ void CompileNativeModule(Isolate* isolate,
18271827
class BaseCompileJSToWasmWrapperJob : public JobTask {
18281828
public:
18291829
explicit BaseCompileJSToWasmWrapperJob(size_t compilation_units)
1830-
: outstanding_units_(compilation_units) {}
1830+
: outstanding_units_(compilation_units),
1831+
total_units_(compilation_units) {}
18311832

18321833
size_t GetMaxConcurrency(size_t worker_count) const override {
18331834
size_t flag_limit = static_cast<size_t>(
@@ -1840,12 +1841,20 @@ class BaseCompileJSToWasmWrapperJob : public JobTask {
18401841
}
18411842

18421843
protected:
1843-
// Returns the index of the next unit to process.
1844-
size_t GetNextUnitIndex() {
1845-
// |unit_index_| may exceeed |compilation_units|, but only by the number of
1846-
// workers at worst, thus it can't exceed 2 * |compilation_units| and
1847-
// overflow shouldn't happen.
1848-
return unit_index_.fetch_add(1, std::memory_order_relaxed);
1844+
// Returns {true} and places the index of the next unit to process in
1845+
// {index_out} if there are still units to be processed. Returns {false}
1846+
// otherwise.
1847+
bool GetNextUnitIndex(size_t* index_out) {
1848+
size_t next_index = unit_index_.fetch_add(1, std::memory_order_relaxed);
1849+
if (next_index >= total_units_) {
1850+
// {unit_index_} may exceeed {total_units_}, but only by the number of
1851+
// workers at worst, thus it can't exceed 2 * {total_units_} and overflow
1852+
// shouldn't happen.
1853+
DCHECK_GE(2 * total_units_, next_index);
1854+
return false;
1855+
}
1856+
*index_out = next_index;
1857+
return true;
18491858
}
18501859

18511860
// Returns true if the last unit was completed.
@@ -1856,9 +1865,29 @@ class BaseCompileJSToWasmWrapperJob : public JobTask {
18561865
return outstanding_units == 1;
18571866
}
18581867

1868+
// When external cancellation is detected, call this method to bump
1869+
// {unit_index_} and reset {outstanding_units_} such that no more tasks are
1870+
// being scheduled for this job and all tasks exit as soon as possible.
1871+
void FlushRemainingUnits() {
1872+
// After being cancelled, make sure to reduce outstanding_units_ to
1873+
// *basically* zero, but leave the count positive if other workers are still
1874+
// running, to avoid underflow in {CompleteUnit}.
1875+
size_t next_undone_unit =
1876+
unit_index_.exchange(total_units_, std::memory_order_relaxed);
1877+
size_t undone_units =
1878+
next_undone_unit >= total_units_ ? 0 : total_units_ - next_undone_unit;
1879+
// Note that the caller requested one unit that we also still need to remove
1880+
// from {outstanding_units_}.
1881+
++undone_units;
1882+
size_t previous_outstanding_units =
1883+
outstanding_units_.fetch_sub(undone_units, std::memory_order_relaxed);
1884+
CHECK_LE(undone_units, previous_outstanding_units);
1885+
}
1886+
18591887
private:
18601888
std::atomic<size_t> unit_index_{0};
18611889
std::atomic<size_t> outstanding_units_;
1890+
const size_t total_units_;
18621891
};
18631892

18641893
class AsyncCompileJSToWasmWrapperJob final
@@ -1868,8 +1897,7 @@ class AsyncCompileJSToWasmWrapperJob final
18681897
std::weak_ptr<NativeModule> native_module, size_t compilation_units)
18691898
: BaseCompileJSToWasmWrapperJob(compilation_units),
18701899
native_module_(std::move(native_module)),
1871-
engine_barrier_(GetWasmEngine()->GetBarrierForBackgroundCompile()),
1872-
compilation_units_size_(compilation_units) {}
1900+
engine_barrier_(GetWasmEngine()->GetBarrierForBackgroundCompile()) {}
18731901

18741902
void Run(JobDelegate* delegate) override {
18751903
auto engine_scope = engine_barrier_->TryLock();
@@ -1879,18 +1907,18 @@ class AsyncCompileJSToWasmWrapperJob final
18791907
OperationsBarrier::Token wrapper_compilation_token;
18801908
Isolate* isolate;
18811909

1882-
size_t index = GetNextUnitIndex();
1883-
if (index >= compilation_units_size_) return;
1910+
size_t index;
1911+
if (!GetNextUnitIndex(&index)) return;
18841912
{
18851913
BackgroundCompileScope compile_scope(native_module_);
1886-
if (compile_scope.cancelled()) return;
1914+
if (compile_scope.cancelled()) return FlushRemainingUnits();
18871915
wrapper_unit =
18881916
compile_scope.compilation_state()->GetJSToWasmWrapperCompilationUnit(
18891917
index);
18901918
isolate = wrapper_unit->isolate();
18911919
wrapper_compilation_token =
18921920
wasm::GetWasmEngine()->StartWrapperCompilation(isolate);
1893-
if (!wrapper_compilation_token) return;
1921+
if (!wrapper_compilation_token) return FlushRemainingUnits();
18941922
}
18951923

18961924
TRACE_EVENT0("v8.wasm", "wasm.JSToWasmWrapperCompilation");
@@ -1903,11 +1931,11 @@ class AsyncCompileJSToWasmWrapperJob final
19031931

19041932
BackgroundCompileScope compile_scope(native_module_);
19051933
if (compile_scope.cancelled()) return;
1906-
if (complete_last_unit)
1934+
if (complete_last_unit) {
19071935
compile_scope.compilation_state()->OnFinishedJSToWasmWrapperUnits();
1936+
}
19081937
if (yield) return;
1909-
size_t index = GetNextUnitIndex();
1910-
if (index >= compilation_units_size_) return;
1938+
if (!GetNextUnitIndex(&index)) return;
19111939
wrapper_unit =
19121940
compile_scope.compilation_state()->GetJSToWasmWrapperCompilationUnit(
19131941
index);
@@ -1917,8 +1945,6 @@ class AsyncCompileJSToWasmWrapperJob final
19171945
private:
19181946
std::weak_ptr<NativeModule> native_module_;
19191947
std::shared_ptr<OperationsBarrier> engine_barrier_;
1920-
// Number of wrappers to be compiled.
1921-
const size_t compilation_units_size_;
19221948
};
19231949

19241950
class BackgroundCompileJob final : public JobTask {
@@ -3726,8 +3752,9 @@ void CompilationStateImpl::WaitForCompilationEvent(
37263752
// Waiting on other CompilationEvent doesn't make sense.
37273753
UNREACHABLE();
37283754
}
3729-
if (js_to_wasm_wrapper_job_ && js_to_wasm_wrapper_job_->IsValid())
3755+
if (js_to_wasm_wrapper_job_ && js_to_wasm_wrapper_job_->IsValid()) {
37303756
js_to_wasm_wrapper_job_->Join();
3757+
}
37313758
#ifdef DEBUG
37323759
base::EnumSet<CompilationEvent> events{expect_event,
37333760
CompilationEvent::kFailedCompilation};
@@ -3789,9 +3816,8 @@ class CompileJSToWasmWrapperJob final : public BaseCompileJSToWasmWrapperJob {
37893816
compilation_units_(compilation_units) {}
37903817

37913818
void Run(JobDelegate* delegate) override {
3792-
while (true) {
3793-
size_t index = GetNextUnitIndex();
3794-
if (index >= compilation_units_->size()) return;
3819+
size_t index;
3820+
while (GetNextUnitIndex(&index)) {
37953821
JSToWasmWrapperCompilationUnit* unit =
37963822
(*compilation_units_)[index].second.get();
37973823
unit->Execute();

0 commit comments

Comments
 (0)