Bug 1395587 - Baldr: allow multiple concurrent, parallel compilations (r=lth)
authorLuke Wagner <luke@mozilla.com>
Wed, 06 Sep 2017 08:31:27 -0500
changeset 428810 3bd70f5f356be8f9aaaab1f09f5dde758b565782
parent 428809 32df4db6c1503deeb20e50d6e76d0c8cd0d2d5ef
child 428811 735938b553b5ccd369522442ad25c8b1e17eeff4
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslth
bugs1395587
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1395587 - Baldr: allow multiple concurrent, parallel compilations (r=lth) MozReview-Commit-ID: I8CUt271hdf
js/src/jsutil.h
js/src/threading/ConditionVariable.h
js/src/threading/ExclusiveData.h
js/src/vm/HelperThreads.cpp
js/src/vm/HelperThreads.h
js/src/vm/MutexIDs.h
js/src/wasm/WasmGenerator.cpp
js/src/wasm/WasmGenerator.h
--- a/js/src/jsutil.h
+++ b/js/src/jsutil.h
@@ -94,16 +94,38 @@ Reverse(T* beg, T* end)
             return;
         T tmp = *beg;
         *beg = *end;
         *end = tmp;
         ++beg;
     }
 }
 
+template <class T, class Pred>
+static inline T*
+RemoveIf(T* begin, T* end, Pred pred)
+{
+    T* result = begin;
+    for (T* p = begin; p != end; p++) {
+        if (!pred(*p))
+            *result++ = *p;
+    }
+    return result;
+}
+
+template <class Container, class Pred>
+static inline size_t
+EraseIf(Container& c, Pred pred)
+{
+    auto newEnd = RemoveIf(c.begin(), c.end(), pred);
+    size_t removed = c.end() - newEnd;
+    c.shrinkBy(removed);
+    return removed;
+}
+
 template <class T>
 static inline T*
 Find(T* beg, T* end, const T& v)
 {
     for (T* p = beg; p != end; ++p) {
         if (*p == v)
             return p;
     }
--- a/js/src/threading/ConditionVariable.h
+++ b/js/src/threading/ConditionVariable.h
@@ -17,16 +17,18 @@
 # include <pthread.h>
 #endif
 
 #include "threading/LockGuard.h"
 #include "threading/Mutex.h"
 
 namespace js {
 
+template <class T> class ExclusiveData;
+
 enum class CVStatus {
   NoTimeout,
   Timeout
 };
 
 template <typename T> using UniqueLock = LockGuard<T>;
 
 // A poly-fill for std::condition_variable.
@@ -110,15 +112,16 @@ public:
     return wait_until(lock, mozilla::TimeStamp::Now() + rel_time,
                       mozilla::Move(pred));
   }
 
 
 private:
   ConditionVariable(const ConditionVariable&) = delete;
   ConditionVariable& operator=(const ConditionVariable&) = delete;
+  template <class T> friend class ExclusiveData;
 
   mozilla::detail::ConditionVariableImpl impl_;
 };
 
 } // namespace js
 
 #endif // threading_ConditionVariable_h
--- a/js/src/threading/ExclusiveData.h
+++ b/js/src/threading/ExclusiveData.h
@@ -6,16 +6,17 @@
 
 #ifndef threading_ExclusiveData_h
 #define threading_ExclusiveData_h
 
 #include "mozilla/Alignment.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Move.h"
 
+#include "threading/ConditionVariable.h"
 #include "threading/Mutex.h"
 
 namespace js {
 
 /**
  * A mutual exclusion lock class.
  *
  * `ExclusiveData` provides an RAII guard to automatically lock and unlock when
@@ -173,16 +174,20 @@ class ExclusiveData
         operator T& () const { return get(); }
         T* operator->() const { return &get(); }
 
         const ExclusiveData<T>* parent() const {
             MOZ_ASSERT(parent_);
             return parent_;
         }
 
+        void wait(ConditionVariable& cond) {
+            cond.impl_.wait(parent_->lock_);
+        }
+
         ~Guard() {
             if (parent_)
                 parent_->release();
         }
     };
 
     /**
      * Access the protected inner `T` value for exclusive reading and writing.
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -967,21 +967,17 @@ GlobalHelperThreadState::ensureInitializ
 
     return true;
 }
 
 GlobalHelperThreadState::GlobalHelperThreadState()
  : cpuCount(0),
    threadCount(0),
    threads(nullptr),
-   wasmCompilationInProgress_tier1(false),
-   wasmCompilationInProgress_tier2(false),
    wasmTier2GeneratorsFinished_(0),
-   numWasmFailedJobs_tier1(0),
-   numWasmFailedJobs_tier2(0),
    helperLock(mutexid::GlobalHelperThreadState)
 {
     cpuCount = GetCPUCount();
     threadCount = ThreadCountForCPUCount(cpuCount);
 
     MOZ_ASSERT(cpuCount > 0, "GetCPUCount() seems broken");
 }
 
@@ -1222,18 +1218,17 @@ GlobalHelperThreadState::maxGCParallelTh
         return 1;
     return threadCount;
 }
 
 bool
 GlobalHelperThreadState::canStartWasmCompile(const AutoLockHelperThreadState& lock,
                                              wasm::CompileMode mode)
 {
-    // Don't execute a wasm job if an earlier one failed.
-    if (wasmWorklist(lock, mode).empty() || wasmFailed(lock, mode))
+    if (wasmWorklist(lock, mode).empty())
         return false;
 
     // For Tier1 and Once compilation, honor the maximum allowed threads to
     // compile wasm jobs at once, to avoid oversaturating the machine.
     //
     // For Tier2 compilation we need to allow other things to happen too, so for
     // now we only allow one thread.
     //
@@ -1879,37 +1874,24 @@ HelperThread::ThreadMain(void* arg)
 
 void
 HelperThread::handleWasmWorkload(AutoLockHelperThreadState& locked, wasm::CompileMode mode)
 {
     MOZ_ASSERT(HelperThreadState().canStartWasmCompile(locked, mode));
     MOZ_ASSERT(idle());
 
     currentTask.emplace(HelperThreadState().wasmWorklist(locked, mode).popCopy());
-    bool success = false;
-    UniqueChars error;
 
     wasm::CompileTask* task = wasmTask();
     {
         AutoUnlockHelperThreadState unlock(locked);
-        success = wasm::CompileFunction(task, &error);
+        wasm::ExecuteCompileTaskFromHelperThread(task);
     }
 
-    // On success, try to move work to the finished list.
-    if (success)
-        success = HelperThreadState().wasmFinishedList(locked, mode).append(task);
-
-    // On failure, note the failure for harvesting by the parent.
-    if (!success) {
-        HelperThreadState().noteWasmFailure(locked, mode);
-        HelperThreadState().setWasmError(locked, mode, Move(error));
-    }
-
-    // Notify the active thread in case it's waiting.
-    HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
+    // No active thread should be waiting on the CONSUMER mutex.
     currentTask.reset();
 }
 
 void
 HelperThread::handleWasmTier2GeneratorWorkload(AutoLockHelperThreadState& locked)
 {
     MOZ_ASSERT(HelperThreadState().canStartWasmTier2Generator(locked));
     MOZ_ASSERT(idle());
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -101,45 +101,24 @@ class GlobalHelperThreadState
     UniquePtr<HelperThreadVector> threads;
 
   private:
     // The lists below are all protected by |lock|.
 
     // Ion compilation worklist and finished jobs.
     IonBuilderVector ionWorklist_, ionFinishedList_, ionFreeList_;
 
-    // wasm worklist and finished jobs.
-    wasm::CompileTaskPtrVector wasmWorklist_tier1_, wasmFinishedList_tier1_;
-    wasm::CompileTaskPtrVector wasmWorklist_tier2_, wasmFinishedList_tier2_;
+    // wasm worklists.
+    wasm::CompileTaskPtrVector wasmWorklist_tier1_;
+    wasm::CompileTaskPtrVector wasmWorklist_tier2_;
     wasm::Tier2GeneratorTaskPtrVector wasmTier2GeneratorWorklist_;
 
-    // For now, only allow a single parallel wasm compilation at each tier to
-    // happen at a time.  This avoids race conditions on
-    // wasmWorklist/wasmFinishedList/etc, for which there is one for each tier.
-    //
-    // TODO: remove this restriction by making the work and finish lists
-    // per-compilation state, not part of the global state.
-
-    mozilla::Atomic<bool> wasmCompilationInProgress_tier1;
-    mozilla::Atomic<bool> wasmCompilationInProgress_tier2;
+    // Count of finished Tier2Generator tasks.
+    uint32_t wasmTier2GeneratorsFinished_;
 
-  public:
-    mozilla::Atomic<bool>& wasmCompilationInProgress(wasm::CompileMode m) {
-        switch (m) {
-          case wasm::CompileMode::Once:
-          case wasm::CompileMode::Tier1:
-            return wasmCompilationInProgress_tier1;
-          case wasm::CompileMode::Tier2:
-            return wasmCompilationInProgress_tier2;
-          default:
-            MOZ_CRASH();
-        }
-    }
-
-  private:
     // Async tasks that, upon completion, are dispatched back to the JSContext's
     // owner thread via embedding callbacks instead of a finished list.
     PromiseHelperTaskVector promiseHelperTasks_;
 
     // Script parsing/emitting worklist and finished jobs.
     ParseTaskVector parseWorklist_, parseFinishedList_;
 
     // Parse tasks waiting for an atoms-zone GC to complete.
@@ -155,19 +134,16 @@ class GlobalHelperThreadState
     SourceCompressionTaskVector compressionFinishedList_;
 
     // Runtimes which have sweeping / allocating work to do.
     GCHelperStateVector gcHelperWorklist_;
 
     // GC tasks needing to be done in parallel.
     GCParallelTaskVector gcParallelWorklist_;
 
-    // Count of finished Tier2Generator tasks.
-    uint32_t wasmTier2GeneratorsFinished_;
-
     ParseTask* removeFinishedParseTask(ParseTaskKind kind, void* token);
 
   public:
     size_t maxIonCompilationThreads() const;
     size_t maxUnpausedIonCompilationThreads() const;
     size_t maxWasmCompilationThreads() const;
     size_t maxWasmTier2GeneratorThreads() const;
     size_t maxParseThreads() const;
@@ -235,27 +211,16 @@ class GlobalHelperThreadState
           case wasm::CompileMode::Tier1:
             return wasmWorklist_tier1_;
           case wasm::CompileMode::Tier2:
             return wasmWorklist_tier2_;
           default:
             MOZ_CRASH();
         }
     }
-    wasm::CompileTaskPtrVector& wasmFinishedList(const AutoLockHelperThreadState&, wasm::CompileMode m) {
-        switch (m) {
-          case wasm::CompileMode::Once:
-          case wasm::CompileMode::Tier1:
-            return wasmFinishedList_tier1_;
-          case wasm::CompileMode::Tier2:
-            return wasmFinishedList_tier2_;
-          default:
-            MOZ_CRASH();
-        }
-    }
 
     wasm::Tier2GeneratorTaskPtrVector& wasmTier2GeneratorWorklist(const AutoLockHelperThreadState&) {
         return wasmTier2GeneratorWorklist_;
     }
 
     void incWasmTier2GeneratorsFinished(const AutoLockHelperThreadState&) {
         wasmTier2GeneratorsFinished_++;
     }
@@ -321,85 +286,16 @@ class GlobalHelperThreadState
     bool pendingIonCompileHasSufficientPriority(const AutoLockHelperThreadState& lock);
 
     jit::IonBuilder* highestPriorityPendingIonCompile(const AutoLockHelperThreadState& lock,
                                                       bool remove = false);
     HelperThread* lowestPriorityUnpausedIonCompileAtThreshold(
         const AutoLockHelperThreadState& lock);
     HelperThread* highestPriorityPausedIonCompile(const AutoLockHelperThreadState& lock);
 
-    uint32_t harvestFailedWasmJobs(const AutoLockHelperThreadState&, wasm::CompileMode m) {
-        switch (m) {
-          case wasm::CompileMode::Once:
-          case wasm::CompileMode::Tier1: {
-              uint32_t n = numWasmFailedJobs_tier1;
-              numWasmFailedJobs_tier1 = 0;
-              return n;
-          }
-          case wasm::CompileMode::Tier2: {
-              uint32_t n = numWasmFailedJobs_tier2;
-              numWasmFailedJobs_tier2 = 0;
-              return n;
-          }
-          default:
-            MOZ_CRASH();
-        }
-    }
-    UniqueChars harvestWasmError(const AutoLockHelperThreadState&, wasm::CompileMode m) {
-        switch (m) {
-          case wasm::CompileMode::Once:
-          case wasm::CompileMode::Tier1:
-            return Move(firstWasmError_tier1);
-          case wasm::CompileMode::Tier2:
-            return Move(firstWasmError_tier2);
-          default:
-            MOZ_CRASH();
-        }
-    }
-    void noteWasmFailure(const AutoLockHelperThreadState&, wasm::CompileMode m) {
-        // Be mindful to signal the active thread after calling this function.
-        switch (m) {
-          case wasm::CompileMode::Once:
-          case wasm::CompileMode::Tier1:
-            numWasmFailedJobs_tier1++;
-            break;
-          case wasm::CompileMode::Tier2:
-            numWasmFailedJobs_tier2++;
-            break;
-          default:
-            MOZ_CRASH();
-        }
-    }
-    void setWasmError(const AutoLockHelperThreadState&, wasm::CompileMode m, UniqueChars error) {
-        switch (m) {
-          case wasm::CompileMode::Once:
-          case wasm::CompileMode::Tier1:
-            if (!firstWasmError_tier1)
-                firstWasmError_tier1 = Move(error);
-            break;
-          case wasm::CompileMode::Tier2:
-            if (!firstWasmError_tier2)
-                firstWasmError_tier2 = Move(error);
-            break;
-          default:
-            MOZ_CRASH();
-        }
-    }
-    bool wasmFailed(const AutoLockHelperThreadState&, wasm::CompileMode m) {
-        switch (m) {
-          case wasm::CompileMode::Once:
-          case wasm::CompileMode::Tier1:
-            return bool(numWasmFailedJobs_tier1);
-          case wasm::CompileMode::Tier2:
-            return bool(numWasmFailedJobs_tier2);
-          default:
-            MOZ_CRASH();
-        }
-    }
-
     template <
         typename F,
         typename = typename mozilla::EnableIf<
             // Matches when the type is a function or lambda with the signature `bool(ParseTask*)`
             mozilla::IsSame<bool, decltype((*(F*)nullptr)((ParseTask*)nullptr))>::value
         >::Type
     >
     bool finishParseTask(JSContext* cx, ParseTaskKind kind, void* token, F&& finishCallback);
@@ -411,31 +307,16 @@ class GlobalHelperThreadState
     void cancelParseTask(JSRuntime* rt, ParseTaskKind kind, void* token);
 
     void mergeParseTaskCompartment(JSContext* cx, ParseTask* parseTask,
                                    Handle<GlobalObject*> global,
                                    JSCompartment* dest);
 
     void trace(JSTracer* trc);
 
-  private:
-    /*
-     * Number of wasm jobs that encountered failure for the active module.
-     * Their parent is logically the active thread, and this number serves for harvesting.
-     */
-    uint32_t numWasmFailedJobs_tier1;
-    uint32_t numWasmFailedJobs_tier2;
-    /*
-     * Error string from wasm validation. Arbitrarily choose to keep the first one that gets
-     * reported. Nondeterministic if multiple threads have errors.
-     */
-    UniqueChars firstWasmError_tier1;
-    UniqueChars firstWasmError_tier2;
-
-  public:
     JSScript* finishScriptParseTask(JSContext* cx, void* token);
     JSScript* finishScriptDecodeTask(JSContext* cx, void* token);
     bool finishMultiScriptsDecodeTask(JSContext* cx, void* token, MutableHandle<ScriptVector> scripts);
     JSObject* finishModuleParseTask(JSContext* cx, void* token);
 
     bool hasActiveThreads(const AutoLockHelperThreadState&);
     void waitForAllThreadsLocked(AutoLockHelperThreadState&);
 
@@ -597,18 +478,18 @@ PauseCurrentHelperThread();
 
 // Enqueues a wasm compilation task.
 bool
 StartOffThreadWasmCompile(wasm::CompileTask* task, wasm::CompileMode mode);
 
 namespace wasm {
 
 // Called on a helper thread after StartOffThreadWasmCompile.
-MOZ_MUST_USE bool
-CompileFunction(CompileTask* task, UniqueChars* error);
+void
+ExecuteCompileTaskFromHelperThread(CompileTask* task);
 
 }
 
 // Enqueues a wasm compilation task.
 void
 StartOffThreadWasmTier2Generator(wasm::UniqueTier2GeneratorTask task);
 
 // Cancel all background Wasm Tier-2 compilations.
--- a/js/src/vm/MutexIDs.h
+++ b/js/src/vm/MutexIDs.h
@@ -39,16 +39,17 @@
   _(CacheIRSpewer,               500) \
   _(TraceLoggerThreadState,      500) \
   _(DateTimeInfoMutex,           500) \
   _(IcuTimeZoneStateMutex,       500) \
   _(ProcessExecutableRegion,     500) \
   _(WasmCodeProfilingLabels,     500) \
   _(OffThreadPromiseState,       500) \
   _(WasmModuleTieringLock,       500) \
+  _(WasmCompileTaskState,        500) \
                                       \
   _(TraceLoggerGraphState,       600) \
   _(VTuneLock,                   600)
 
 namespace js {
 namespace mutexid {
 
 #define DEFINE_MUTEX_ID(name, order)  \
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -48,16 +48,17 @@ static const uint32_t BAD_CODE_RANGE = U
 ModuleGenerator::ModuleGenerator(const CompileArgs& args, ModuleEnvironment* env,
                                  Atomic<bool>* cancelled, UniqueChars* error)
   : compileArgs_(&args),
     error_(error),
     cancelled_(cancelled),
     env_(env),
     linkDataTier_(nullptr),
     metadataTier_(nullptr),
+    taskState_(mutexid::WasmCompileTaskState),
     numSigs_(0),
     numTables_(0),
     lifo_(GENERATOR_LIFO_DEFAULT_CHUNK_SIZE),
     masmAlloc_(&lifo_),
     masm_(MacroAssembler::WasmToken(), masmAlloc_),
     lastPatchedCallsite_(0),
     startOfUnpatchedCallsites_(0),
     parallel_(false),
@@ -68,49 +69,57 @@ ModuleGenerator::ModuleGenerator(const C
     finishedFuncDefs_(false),
     numFinishedFuncDefs_(0)
 {
     MOZ_ASSERT(IsCompilingWasm());
 }
 
 ModuleGenerator::~ModuleGenerator()
 {
+    MOZ_ASSERT_IF(finishedFuncDefs_, !batchedBytecode_);
+    MOZ_ASSERT_IF(finishedFuncDefs_, !currentTask_);
+
     if (parallel_) {
-        // Wait for any outstanding jobs to fail or complete.
         if (outstanding_) {
-            AutoLockHelperThreadState lock;
-            while (true) {
+            // Remove any pending compilation tasks from the worklist.
+            {
+                AutoLockHelperThreadState lock;
                 CompileTaskPtrVector& worklist = HelperThreadState().wasmWorklist(lock, mode());
-                MOZ_ASSERT(outstanding_ >= worklist.length());
-                outstanding_ -= worklist.length();
-                worklist.clear();
+                auto pred = [this](CompileTask* task) { return &task->state() == &taskState_; };
+                size_t removed = EraseIf(worklist, pred);
+                MOZ_ASSERT(outstanding_ >= removed);
+                outstanding_ -= removed;
+            }
 
-                CompileTaskPtrVector& finished = HelperThreadState().wasmFinishedList(lock, mode());
-                MOZ_ASSERT(outstanding_ >= finished.length());
-                outstanding_ -= finished.length();
-                finished.clear();
+            // Wait until all active compilation tasks have finished.
+            {
+                auto taskState = taskState_.lock();
+                while (true) {
+                    MOZ_ASSERT(outstanding_ >= taskState->finished.length());
+                    outstanding_ -= taskState->finished.length();
+                    taskState->finished.clear();
 
-                uint32_t numFailed = HelperThreadState().harvestFailedWasmJobs(lock, mode());
-                MOZ_ASSERT(outstanding_ >= numFailed);
-                outstanding_ -= numFailed;
+                    MOZ_ASSERT(outstanding_ >= taskState->numFailed);
+                    outstanding_ -= taskState->numFailed;
+                    taskState->numFailed = 0;
 
-                if (!outstanding_)
-                    break;
+                    if (!outstanding_)
+                        break;
 
-                HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
+                    taskState.wait(taskState->failedOrFinished);
+                }
             }
         }
-
-        MOZ_ASSERT(HelperThreadState().wasmCompilationInProgress(mode()));
-        HelperThreadState().wasmCompilationInProgress(mode()) = false;
     } else {
         MOZ_ASSERT(!outstanding_);
     }
-    MOZ_ASSERT_IF(finishedFuncDefs_, !batchedBytecode_);
-    MOZ_ASSERT_IF(finishedFuncDefs_, !currentTask_);
+
+    // Propagate error state.
+    if (error_ && !*error_)
+        *error_ = Move(taskState_.lock()->errorMessage);
 }
 
 bool
 ModuleGenerator::initAsmJS(Metadata* asmJSMetadata)
 {
     MOZ_ASSERT(env_->isAsmJS());
 
     if (!linkData_.initTier1(Tier::Ion, *asmJSMetadata))
@@ -233,48 +242,16 @@ ModuleGenerator::init(Metadata* maybeAsm
         if (!metadata_->filename)
             return false;
     }
 
     return true;
 }
 
 bool
-ModuleGenerator::finishOutstandingTask()
-{
-    MOZ_ASSERT(parallel_);
-
-    CompileTask* task = nullptr;
-    {
-        AutoLockHelperThreadState lock;
-        while (true) {
-            MOZ_ASSERT(outstanding_ > 0);
-
-            if (HelperThreadState().wasmFailed(lock, mode())) {
-                if (error_) {
-                    MOZ_ASSERT(!*error_, "Should have stopped earlier");
-                    *error_ = Move(HelperThreadState().harvestWasmError(lock, mode()));
-                }
-                return false;
-            }
-
-            if (!HelperThreadState().wasmFinishedList(lock, mode()).empty()) {
-                outstanding_--;
-                task = HelperThreadState().wasmFinishedList(lock, mode()).popCopy();
-                break;
-            }
-
-            HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
-        }
-    }
-
-    return finishTask(task);
-}
-
-bool
 ModuleGenerator::funcIsCompiled(uint32_t funcIndex) const
 {
     return funcToCodeRange_[funcIndex] != BAD_CODE_RANGE;
 }
 
 const CodeRange&
 ModuleGenerator::funcCodeRange(uint32_t funcIndex) const
 {
@@ -829,94 +806,140 @@ ModuleGenerator::funcSig(uint32_t funcIn
 }
 
 bool
 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.
-
     GlobalHelperThreadState& threads = HelperThreadState();
     MOZ_ASSERT(threads.threadCount > 1);
 
     uint32_t numTasks;
-    if (CanUseExtraThreads() &&
-        threads.cpuCount > 1 &&
-        threads.wasmCompilationInProgress(mode()).compareExchange(false, true))
-    {
-#ifdef DEBUG
-        {
-            AutoLockHelperThreadState lock;
-            MOZ_ASSERT(!HelperThreadState().wasmFailed(lock, mode()));
-            MOZ_ASSERT(HelperThreadState().wasmWorklist(lock, mode()).empty());
-            MOZ_ASSERT(HelperThreadState().wasmFinishedList(lock, mode()).empty());
-        }
-#endif
+    if (CanUseExtraThreads() && threads.cpuCount > 1) {
         parallel_ = true;
         numTasks = 2 * threads.maxWasmCompilationThreads();
     } else {
         numTasks = 1;
     }
 
     if (!tasks_.initCapacity(numTasks))
         return false;
     for (size_t i = 0; i < numTasks; i++)
-        tasks_.infallibleEmplaceBack(*env_, COMPILATION_LIFO_DEFAULT_CHUNK_SIZE);
+        tasks_.infallibleEmplaceBack(*env_, taskState_, COMPILATION_LIFO_DEFAULT_CHUNK_SIZE);
 
     if (!freeTasks_.reserve(numTasks))
         return false;
     for (size_t i = 0; i < numTasks; i++)
         freeTasks_.infallibleAppend(&tasks_[i]);
 
     startedFuncDefs_ = true;
     MOZ_ASSERT(!finishedFuncDefs_);
     return true;
 }
 
+static bool
+ExecuteCompileTask(CompileTask* task, UniqueChars* error)
+{
+    switch (task->tier()) {
+      case Tier::Ion:
+        for (FuncCompileUnit& unit : task->units()) {
+            if (!IonCompileFunction(task, &unit, error))
+                return false;
+        }
+        break;
+      case Tier::Baseline:
+        for (FuncCompileUnit& unit : task->units()) {
+            if (!BaselineCompileFunction(task, &unit, error))
+                return false;
+        }
+        break;
+    }
+    return true;
+}
+
+void
+wasm::ExecuteCompileTaskFromHelperThread(CompileTask* task)
+{
+    TraceLoggerThread* logger = TraceLoggerForCurrentThread();
+    AutoTraceLog logCompile(logger, TraceLogger_WasmCompilation);
+
+    UniqueChars error;
+    bool ok = ExecuteCompileTask(task, &error);
+
+    auto taskState = task->state().lock();
+
+    if (!ok || !taskState->finished.append(task)) {
+        taskState->numFailed++;
+        if (!taskState->errorMessage)
+            taskState->errorMessage = Move(error);
+    }
+
+    taskState->failedOrFinished.notify_one();
+}
+
 bool
 ModuleGenerator::launchBatchCompile()
 {
     MOZ_ASSERT(currentTask_);
 
     if (cancelled_ && *cancelled_)
         return false;
 
     size_t numBatchedFuncs = currentTask_->units().length();
     MOZ_ASSERT(numBatchedFuncs);
 
     if (parallel_) {
         if (!StartOffThreadWasmCompile(currentTask_, mode()))
             return false;
         outstanding_++;
     } else {
-        if (!CompileFunction(currentTask_, error_))
+        if (!ExecuteCompileTask(currentTask_, error_))
             return false;
         if (!finishTask(currentTask_))
             return false;
     }
 
     currentTask_ = nullptr;
     batchedBytecode_ = 0;
 
     numFinishedFuncDefs_ += numBatchedFuncs;
     return true;
 }
 
 bool
+ModuleGenerator::finishOutstandingTask()
+{
+    MOZ_ASSERT(parallel_);
+
+    CompileTask* task = nullptr;
+    {
+        auto taskState = taskState_.lock();
+        while (true) {
+            MOZ_ASSERT(outstanding_ > 0);
+
+            if (taskState->numFailed > 0)
+                return false;
+
+            if (!taskState->finished.empty()) {
+                outstanding_--;
+                task = taskState->finished.popCopy();
+                break;
+            }
+
+            taskState.wait(taskState->failedOrFinished);
+        }
+    }
+
+    // Call outside of the compilation lock.
+    return finishTask(task);
+}
+
+bool
 ModuleGenerator::compileFuncDef(uint32_t funcIndex, uint32_t lineOrBytecode,
                                 Bytes&& bytes, const uint8_t* begin, const uint8_t* end,
                                 Uint32Vector&& lineNums)
 {
     MOZ_ASSERT(startedFuncDefs_);
     MOZ_ASSERT(!finishedFuncDefs_);
     MOZ_ASSERT_IF(mode() == CompileMode::Tier1, funcIndex < env_->numFuncs());
 
@@ -1261,34 +1284,8 @@ ModuleGenerator::finishTier2(Module& mod
         return false;
 
     module.finishTier2(linkData_.takeLinkData(tier()),
                        metadata_->takeMetadata(tier()),
                        Move(codeSegment),
                        env_);
     return true;
 }
-
-bool
-wasm::CompileFunction(CompileTask* task, UniqueChars* error)
-{
-    TraceLoggerThread* logger = TraceLoggerForCurrentThread();
-    AutoTraceLog logCompile(logger, TraceLogger_WasmCompilation);
-
-    switch (task->tier()) {
-      case Tier::Ion:
-        for (FuncCompileUnit& unit : task->units()) {
-            if (!IonCompileFunction(task, &unit, error))
-                return false;
-        }
-        break;
-      case Tier::Baseline:
-        for (FuncCompileUnit& unit : task->units()) {
-            if (!BaselineCompileFunction(task, &unit, error))
-                return false;
-        }
-        break;
-      default:
-        MOZ_CRASH("Invalid tier value");
-    }
-
-    return true;
-}
--- a/js/src/wasm/WasmGenerator.h
+++ b/js/src/wasm/WasmGenerator.h
@@ -72,53 +72,76 @@ class FuncCompileUnit
         MOZ_ASSERT(!finished_);
         offsets_ = offsets;
         finished_ = true;
     }
 };
 
 typedef Vector<FuncCompileUnit, 8, SystemAllocPolicy> FuncCompileUnitVector;
 
+// The CompileTaskState of a ModuleGenerator contains the mutable state shared
+// between helper threads executing CompileTasks. Each CompileTask started on a
+// helper thread eventually either ends up in the 'finished' list or increments
+// 'numFailed'.
+
+struct CompileTaskState
+{
+    ConditionVariable    failedOrFinished;
+    CompileTaskPtrVector finished;
+    uint32_t             numFailed;
+    UniqueChars          errorMessage;
+
+    CompileTaskState() : numFailed(0) {}
+    ~CompileTaskState() { MOZ_ASSERT(finished.empty()); MOZ_ASSERT(!numFailed); }
+};
+
+typedef ExclusiveData<CompileTaskState> ExclusiveCompileTaskState;
+
 // A CompileTask represents the task of compiling a batch of functions. It is
 // filled with a certain number of function's bodies that are sent off to a
 // compilation helper thread, which fills in the resulting code offsets, and
 // finally sent back to the validation thread.
 
 class CompileTask
 {
     const ModuleEnvironment&   env_;
+    ExclusiveCompileTaskState& state_;
     LifoAlloc                  lifo_;
     Maybe<jit::TempAllocator>  alloc_;
     Maybe<jit::MacroAssembler> masm_;
     FuncCompileUnitVector      units_;
 
     CompileTask(const CompileTask&) = delete;
     CompileTask& operator=(const CompileTask&) = delete;
 
     void init() {
         alloc_.emplace(&lifo_);
         masm_.emplace(jit::MacroAssembler::WasmToken(), *alloc_);
     }
 
   public:
-    CompileTask(const ModuleEnvironment& env, size_t defaultChunkSize)
+    CompileTask(const ModuleEnvironment& env, ExclusiveCompileTaskState& state, size_t defaultChunkSize)
       : env_(env),
+        state_(state),
         lifo_(defaultChunkSize)
     {
         init();
     }
     LifoAlloc& lifo() {
         return lifo_;
     }
     jit::TempAllocator& alloc() {
         return *alloc_;
     }
     const ModuleEnvironment& env() const {
         return env_;
     }
+    const ExclusiveCompileTaskState& state() const {
+        return state_;
+    }
     jit::MacroAssembler& masm() {
         return *masm_;
     }
     FuncCompileUnitVector& units() {
         return units_;
     }
     Tier tier() const {
         return env_.tier;
@@ -134,18 +157,16 @@ class CompileTask
         masm_.reset();
         alloc_.reset();
         lifo_.releaseAll();
         init();
         return true;
     }
 };
 
-struct Tier2GeneratorTask;
-
 // A ModuleGenerator encapsulates the creation of a wasm module. During the
 // lifetime of a ModuleGenerator, a sequence of FunctionGenerators are created
 // and destroyed to compile the individual function bodies. After generating all
 // functions, ModuleGenerator::finish() must be called to complete the
 // compilation and extract the resulting wasm module.
 
 class MOZ_STACK_CLASS ModuleGenerator
 {
@@ -164,16 +185,17 @@ class MOZ_STACK_CLASS ModuleGenerator
     Assumptions                     assumptions_;
     LinkDataTier*                   linkDataTier_; // Owned by linkData_
     LinkData                        linkData_;
     MetadataTier*                   metadataTier_; // Owned by metadata_
     MutableMetadata                 metadata_;
     UniqueJumpTable                 jumpTable_;
 
     // Data scoped to the ModuleGenerator's lifetime
+    ExclusiveCompileTaskState       taskState_;
     uint32_t                        numSigs_;
     uint32_t                        numTables_;
     LifoAlloc                       lifo_;
     jit::JitContext                 jcx_;
     jit::TempAllocator              masmAlloc_;
     jit::MacroAssembler             masm_;
     Uint32Vector                    funcToCodeRange_;
     Uint32Set                       exportedFuncs_;