Skip to content

Commit

Permalink
deps: V8: cherry-pick 5f025d1ca2ca
Browse files Browse the repository at this point in the history
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.

    [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}

Refs: v8/v8@5f025d1
  • Loading branch information
targos committed Apr 19, 2023
1 parent c49ba9c commit f0564fe
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 23 deletions.
2 changes: 1 addition & 1 deletion common.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -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.5',
'v8_embedder_string': '-node.6',

##### V8 defaults for Node.js #####

Expand Down
70 changes: 48 additions & 22 deletions deps/v8/src/wasm/module-compiler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<size_t>(
Expand All @@ -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.
Expand All @@ -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<size_t> unit_index_{0};
std::atomic<size_t> outstanding_units_;
const size_t total_units_;
};

class AsyncCompileJSToWasmWrapperJob final
Expand All @@ -1867,8 +1896,7 @@ class AsyncCompileJSToWasmWrapperJob final
std::weak_ptr<NativeModule> 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();
Expand All @@ -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");
Expand All @@ -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);
Expand All @@ -1916,8 +1944,6 @@ class AsyncCompileJSToWasmWrapperJob final
private:
std::weak_ptr<NativeModule> native_module_;
std::shared_ptr<OperationsBarrier> engine_barrier_;
// Number of wrappers to be compiled.
const size_t compilation_units_size_;
};

class BackgroundCompileJob final : public JobTask {
Expand Down Expand Up @@ -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<CompilationEvent> events{expect_event,
CompilationEvent::kFailedCompilation};
Expand Down Expand Up @@ -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();
Expand Down

0 comments on commit f0564fe

Please sign in to comment.