From 995ff8e86cef8fde4fbd86a82b913865387af04c Mon Sep 17 00:00:00 2001 From: roytam1 Date: Tue, 19 May 2026 23:25:43 +0800 Subject: [PATCH] ported from UXP: Issue #3092 - Refactor WASM compilation handling (a7a75b78) (some portions are from forward commits to make it able to be compiled) --- js/src/vm/HelperThreads.cpp | 24 +++++------ js/src/vm/HelperThreads.h | 4 +- js/src/wasm/WasmGenerator.cpp | 76 +++++++++++++++++------------------ js/src/wasm/WasmGenerator.h | 25 +++++++++++- 4 files changed, 75 insertions(+), 54 deletions(-) diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index d4b4df2e2..5880e3f50 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -20,6 +20,7 @@ #include "vm/SharedImmutableStringsCache.h" #include "vm/Time.h" #include "vm/TraceLogging.h" +#include "wasm/WasmGenerator.h" #include "jscntxtinlines.h" #include "jscompartmentinlines.h" @@ -473,7 +474,8 @@ js::CancelOffThreadParses(JSRuntime* rt) } // Clean up any parse tasks which haven't been finished by the main thread. - GlobalHelperThreadState::ParseTaskVector& finished = HelperThreadState().parseFinishedList(lock); + GlobalHelperThreadState::ParseTaskVector& finished = + HelperThreadState().parseFinishedList(lock); while (true) { bool found = false; for (size_t i = 0; i < finished.length(); i++) { @@ -481,7 +483,8 @@ js::CancelOffThreadParses(JSRuntime* rt) if (task->runtimeMatches(rt)) { found = true; AutoUnlockHelperThreadState unlock(lock); - HelperThreadState().cancelParseTask(rt->contextFromMainThread(), task->kind, task); + HelperThreadState().cancelParseTask(rt->contextFromMainThread(), + task->kind, task); } } if (!found) @@ -961,8 +964,7 @@ GlobalHelperThreadState::maxGCParallelThreads() const bool GlobalHelperThreadState::canStartWasmCompile(const AutoLockHelperThreadState& lock) { - // Don't execute an wasm job if an earlier one failed. - if (wasmWorklist(lock).empty() || numWasmFailedJobs) + if (wasmWorklist(lock).empty()) return false; // Honor the maximum allowed threads to compile wasm jobs at once, @@ -1414,15 +1416,13 @@ HelperThread::handleWasmWorkload(AutoLockHelperThreadState& locked) success = wasm::CompileFunction(task, &error); } - // On success, try to move work to the finished list. - if (success) - success = HelperThreadState().wasmFinishedList(locked).append(task); + // Append the task to the finished queue owned by its module generator. + if (!success) + task->setFailed(); - // On failure, note the failure for harvesting by the parent. - if (!success) { - HelperThreadState().noteWasmFailure(locked); - HelperThreadState().setWasmError(locked, Move(error)); - } + AutoEnterOOMUnsafeRegion oomUnsafe; + if (!task->finishedList()->append(task)) + oomUnsafe.crash("HelperThread::handleWasmWorkload"); // Notify the main thread in case it's waiting. HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked); diff --git a/js/src/vm/HelperThreads.h b/js/src/vm/HelperThreads.h index 1cbd01a65..afaf21e4e 100644 --- a/js/src/vm/HelperThreads.h +++ b/js/src/vm/HelperThreads.h @@ -90,8 +90,8 @@ class GlobalHelperThreadState wasm::CompileTaskPtrVector wasmWorklist_, wasmFinishedList_; public: - // For now, only allow a single parallel wasm compilation to happen at a - // time. This avoids race conditions on wasmWorklist/wasmFinishedList/etc. + // Helper-thread initiated wasm compilations are serialized to avoid the + // deadlock scenario described in WasmGenerator.cpp. mozilla::Atomic wasmCompilationInProgress; private: diff --git a/js/src/wasm/WasmGenerator.cpp b/js/src/wasm/WasmGenerator.cpp index ad30903e5..04c636d1f 100644 --- a/js/src/wasm/WasmGenerator.cpp +++ b/js/src/wasm/WasmGenerator.cpp @@ -55,6 +55,7 @@ ModuleGenerator::ModuleGenerator(UniqueChars* error) lastPatchedCallsite_(0), startOfUnpatchedCallsites_(0), parallel_(false), + parallelCompilationInProgressOnHelperThread_(false), outstanding_(0), currentTask_(nullptr), batchedBytecode_(0), @@ -74,18 +75,21 @@ ModuleGenerator::~ModuleGenerator() AutoLockHelperThreadState lock; while (true) { CompileTaskPtrVector& worklist = HelperThreadState().wasmWorklist(lock); - MOZ_ASSERT(outstanding_ >= worklist.length()); - outstanding_ -= worklist.length(); - worklist.clear(); + for (size_t i = worklist.length(); i > 0;) { + if (worklist[i - 1]->finishedList() == &finishedTasks_) { + HelperThreadState().remove(worklist, &i); + MOZ_ASSERT(outstanding_ > 0); + outstanding_--; + } else { + i--; + } + } - CompileTaskPtrVector& finished = HelperThreadState().wasmFinishedList(lock); - MOZ_ASSERT(outstanding_ >= finished.length()); - outstanding_ -= finished.length(); - finished.clear(); - - uint32_t numFailed = HelperThreadState().harvestFailedWasmJobs(lock); - MOZ_ASSERT(outstanding_ >= numFailed); - outstanding_ -= numFailed; + for (size_t i = finishedTasks_.length(); i > 0;) { + HelperThreadState().remove(finishedTasks_, &i); + MOZ_ASSERT(outstanding_ > 0); + outstanding_--; + } if (!outstanding_) break; @@ -94,8 +98,10 @@ ModuleGenerator::~ModuleGenerator() } } - MOZ_ASSERT(HelperThreadState().wasmCompilationInProgress); - HelperThreadState().wasmCompilationInProgress = false; + if (parallelCompilationInProgressOnHelperThread_) { + MOZ_ASSERT(HelperThreadState().wasmCompilationInProgress); + HelperThreadState().wasmCompilationInProgress = false; + } } else { MOZ_ASSERT(!outstanding_); } @@ -252,17 +258,9 @@ ModuleGenerator::finishOutstandingTask() while (true) { MOZ_ASSERT(outstanding_ > 0); - if (HelperThreadState().wasmFailed(lock)) { - if (error_) { - MOZ_ASSERT(!*error_, "Should have stopped earlier"); - *error_ = Move(HelperThreadState().harvestWasmError(lock)); - } - return false; - } - - if (!HelperThreadState().wasmFinishedList(lock).empty()) { + if (!finishedTasks_.empty()) { outstanding_--; - task = HelperThreadState().wasmFinishedList(lock).popCopy(); + task = finishedTasks_.popCopy(); break; } @@ -270,6 +268,9 @@ ModuleGenerator::finishOutstandingTask() } } + if (task->failed()) + return false; + return finishTask(task); } @@ -435,6 +436,9 @@ ModuleGenerator::patchFarJumps(const TrapExitOffsetArray& trapExits, const Offse bool ModuleGenerator::finishTask(CompileTask* task) { + if (task->failed()) + return false; + masm_.haltingAlign(CodeAlignment); // Before merging in the new function's code, if calls in a prior function @@ -865,34 +869,25 @@ ModuleGenerator::startFuncDefs() MOZ_ASSERT(!startedFuncDefs_); MOZ_ASSERT(!finishedFuncDefs_); - // The wasmCompilationInProgress atomic ensures that there is only one - // parallel compilation in progress at a time. In the special case of - // asm.js, where the ModuleGenerator itself can be on a helper thread, this - // avoids the possibility of deadlock since at most 1 helper thread will be - // blocking on other helper threads and there are always >1 helper threads. - // With wasm, this restriction could be relaxed by moving the worklist state - // out of HelperThreadState since each independent compilation needs its own - // worklist pair. Alternatively, the deadlock could be avoided by having the - // ModuleGenerator thread make progress (on compile tasks) instead of - // blocking. + // Helper-thread initiated wasm compilations stay serialized so that we do + // not end up with multiple helper threads blocking on other helper threads. + // Main-thread compilations can still overlap because they drain their own + // finished-task queue and do not steal tasks from other generators. GlobalHelperThreadState& threads = HelperThreadState(); MOZ_ASSERT(threads.threadCount > 1); uint32_t numTasks; - if (CanUseExtraThreads() && - threads.cpuCount > 1 && - threads.wasmCompilationInProgress.compareExchange(false, true)) - { + if (CanUseExtraThreads() && (!CurrentHelperThread() || + threads.wasmCompilationInProgress.compareExchange(false, true))) { #ifdef DEBUG { AutoLockHelperThreadState lock; - MOZ_ASSERT(!HelperThreadState().wasmFailed(lock)); MOZ_ASSERT(HelperThreadState().wasmWorklist(lock).empty()); - MOZ_ASSERT(HelperThreadState().wasmFinishedList(lock).empty()); } #endif parallel_ = true; + parallelCompilationInProgressOnHelperThread_ = !!CurrentHelperThread(); numTasks = 2 * threads.maxWasmCompilationThreads(); } else { numTasks = 1; @@ -903,6 +898,9 @@ ModuleGenerator::startFuncDefs() for (size_t i = 0; i < numTasks; i++) tasks_.infallibleEmplaceBack(*env_, compileMode_, COMPILATION_LIFO_DEFAULT_CHUNK_SIZE); + for (auto& task : tasks_) + task.setFinishedList(&finishedTasks_); + if (!freeTasks_.reserve(numTasks)) return false; for (size_t i = 0; i < numTasks; i++) diff --git a/js/src/wasm/WasmGenerator.h b/js/src/wasm/WasmGenerator.h index 88d44ddc0..cc592abc8 100644 --- a/js/src/wasm/WasmGenerator.h +++ b/js/src/wasm/WasmGenerator.h @@ -142,6 +142,8 @@ class CompileTask Maybe masm_; FuncCompileUnitVector units_; bool debugEnabled_; + CompileTaskPtrVector* finishedList_; + bool failed_; CompileTask(const CompileTask&) = delete; CompileTask& operator=(const CompileTask&) = delete; @@ -156,10 +158,28 @@ class CompileTask CompileTask(const ModuleEnvironment& env, CompileMode mode, size_t defaultChunkSize) : env_(env), mode_(mode), - lifo_(defaultChunkSize) + lifo_(defaultChunkSize), + finishedList_(nullptr), + failed_(false) { init(); } + void setFinishedList(CompileTaskPtrVector* finishedList) { + finishedList_ = finishedList; + } + + CompileTaskPtrVector* finishedList() const { + MOZ_ASSERT(finishedList_); + return finishedList_; + } + + void setFailed() { + failed_ = true; + } + + bool failed() const { + return failed_; + } LifoAlloc& lifo() { return lifo_; } @@ -194,6 +214,7 @@ class CompileTask masm_.reset(); alloc_.reset(); lifo_.releaseAll(); + failed_ = false; init(); return true; @@ -238,9 +259,11 @@ class MOZ_STACK_CLASS ModuleGenerator // Parallel compilation bool parallel_; + bool parallelCompilationInProgressOnHelperThread_; uint32_t outstanding_; CompileTaskVector tasks_; CompileTaskPtrVector freeTasks_; + CompileTaskPtrVector finishedTasks_; UniqueFuncBytesVector freeFuncBytes_; CompileTask* currentTask_; uint32_t batchedBytecode_;