Bug 1283229 - Thread `AutoLockHelperThreadState&` parameters through various `HelperThread`-related functions; r=terrence
authorNick Fitzgerald <fitzgen@gmail.com>
Thu, 30 Jun 2016 13:13:30 -0700
changeset 303315 e5ad05d9cad4ac105f5f580887202a5041e197b4
parent 303314 452564a0c5b64f976bdc646e34a95a6aee29379a
child 303316 c62dc1a368c81054c835b419f6ec5d88405654e9
push id19839
push usercbook@mozilla.com
push dateFri, 01 Jul 2016 09:19:59 +0000
treeherderfx-team@499d8875de7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersterrence
bugs1283229
milestone50.0a1
Bug 1283229 - Thread `AutoLockHelperThreadState&` parameters through various `HelperThread`-related functions; r=terrence This is a pre-requisite for moving the `HelperThread` infrastructure over from using `PRLock` and `PRCondVar` to using `js::Mutex` and `js::ConditionVariable`. Its needed because waiting on a `js::ConditionVariable` takes the `LockGuard` for the condvar's mutex as a parameter, so we need to have that accessible. When we do the conversion, `AutoLockHelperThreadState` will become derived from `LockGuard` and specialized for the helper thread state lock, and so the guard will therefore be accessible.
js/src/asmjs/WasmGenerator.cpp
js/src/gc/Allocator.cpp
js/src/gc/GCRuntime.h
js/src/gc/Nursery.cpp
js/src/jsgc.cpp
js/src/jsgc.h
js/src/vm/HelperThreads.cpp
js/src/vm/HelperThreads.h
--- a/js/src/asmjs/WasmGenerator.cpp
+++ b/js/src/asmjs/WasmGenerator.cpp
@@ -80,17 +80,17 @@ ModuleGenerator::~ModuleGenerator()
 
                 uint32_t numFailed = HelperThreadState().harvestFailedWasmJobs();
                 MOZ_ASSERT(outstanding_ >= numFailed);
                 outstanding_ -= numFailed;
 
                 if (!outstanding_)
                     break;
 
-                HelperThreadState().wait(GlobalHelperThreadState::CONSUMER);
+                HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
             }
         }
 
         MOZ_ASSERT(HelperThreadState().wasmCompilationInProgress);
         HelperThreadState().wasmCompilationInProgress = false;
     } else {
         MOZ_ASSERT(!outstanding_);
     }
@@ -177,17 +177,17 @@ ModuleGenerator::finishOutstandingTask()
                 return false;
 
             if (!HelperThreadState().wasmFinishedList().empty()) {
                 outstanding_--;
                 task = HelperThreadState().wasmFinishedList().popCopy();
                 break;
             }
 
-            HelperThreadState().wait(GlobalHelperThreadState::CONSUMER);
+            HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
         }
     }
 
     return finishTask(task);
 }
 
 static const uint32_t BadCodeRange = UINT32_MAX;
 
--- a/js/src/gc/Allocator.cpp
+++ b/js/src/gc/Allocator.cpp
@@ -289,17 +289,17 @@ void
 GCRuntime::startBackgroundAllocTaskIfIdle()
 {
     AutoLockHelperThreadState helperLock;
     if (allocTask.isRunning())
         return;
 
     // Join the previous invocation of the task. This will return immediately
     // if the thread has never been started.
-    allocTask.joinWithLockHeld();
+    allocTask.joinWithLockHeld(helperLock);
     allocTask.startWithLockHeld();
 }
 
 /* static */ TenuredCell*
 GCRuntime::refillFreeListFromAnyThread(ExclusiveContext* cx, AllocKind thingKind, size_t thingSize)
 {
     cx->arenas()->checkEmptyFreeList(thingKind);
 
@@ -330,17 +330,17 @@ GCRuntime::refillFreeListOffMainThread(E
 
     AutoMaybeStartBackgroundAllocation maybeStartBGAlloc;
 
     // If we're off the main thread, we try to allocate once and return
     // whatever value we get. We need to first ensure the main thread is not in
     // a GC session.
     AutoLockHelperThreadState lock;
     while (rt->isHeapBusy())
-        HelperThreadState().wait(GlobalHelperThreadState::PRODUCER);
+        HelperThreadState().wait(lock, GlobalHelperThreadState::PRODUCER);
 
     return arenas->allocateFromArena(zone, thingKind, maybeStartBGAlloc);
 }
 
 /* static */ TenuredCell*
 GCRuntime::refillFreeListInGC(Zone* zone, AllocKind thingKind)
 {
     /*
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -18,16 +18,17 @@
 #include "gc/Statistics.h"
 #include "gc/StoreBuffer.h"
 #include "gc/Tracer.h"
 #include "js/GCAnnotations.h"
 
 namespace js {
 
 class AutoLockGC;
+class AutoLockHelperThreadState;
 class VerifyPreTracer;
 
 namespace gc {
 
 typedef Vector<JS::Zone*, 4, SystemAllocPolicy> ZoneVector;
 
 class AutoMaybeStartBackgroundAllocation;
 class MarkingValidator;
@@ -1193,18 +1194,18 @@ class GCRuntime
     unsigned finalizePhase;
     JS::Zone* sweepZone;
     AllocKind sweepKind;
     bool abortSweepAfterCurrentGroup;
 
     /*
      * Concurrent sweep infrastructure.
      */
-    void startTask(GCParallelTask& task, gcstats::Phase phase);
-    void joinTask(GCParallelTask& task, gcstats::Phase phase);
+    void startTask(GCParallelTask& task, gcstats::Phase phase, AutoLockHelperThreadState& locked);
+    void joinTask(GCParallelTask& task, gcstats::Phase phase, AutoLockHelperThreadState& locked);
 
     /*
      * List head of arenas allocated during the sweep phase.
      */
     Arena* arenasAllocatedDuringSweep;
 
     /*
      * Incremental compacting state.
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -608,17 +608,17 @@ void
 js::Nursery::freeMallocedBuffers()
 {
     if (mallocedBuffers.empty())
         return;
 
     bool started;
     {
         AutoLockHelperThreadState lock;
-        freeMallocedBuffersTask->joinWithLockHeld();
+        freeMallocedBuffersTask->joinWithLockHeld(lock);
         freeMallocedBuffersTask->transferBuffersToFree(mallocedBuffers);
         started = freeMallocedBuffersTask->startWithLockHeld();
     }
 
     if (!started)
         freeMallocedBuffersTask->runFromMainThread(runtime());
 
     MOZ_ASSERT(mallocedBuffers.empty());
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2437,28 +2437,28 @@ GCRuntime::updateCellPointers(MovingTrac
 
     {
         AutoLockHelperThreadState lock;
 
         fgTask.emplace(rt, &fgArenas, lock);
 
         for (size_t i = 0; i < bgTaskCount && !bgArenas.done(); i++) {
             bgTasks[i].emplace(rt, &bgArenas, lock);
-            startTask(*bgTasks[i], gcstats::PHASE_COMPACT_UPDATE_CELLS);
+            startTask(*bgTasks[i], gcstats::PHASE_COMPACT_UPDATE_CELLS, lock);
             tasksStarted = i;
         }
     }
 
     fgTask->runFromMainThread(rt);
 
     {
         AutoLockHelperThreadState lock;
 
         for (size_t i = 0; i < tasksStarted; i++)
-            joinTask(*bgTasks[i], gcstats::PHASE_COMPACT_UPDATE_CELLS);
+            joinTask(*bgTasks[i], gcstats::PHASE_COMPACT_UPDATE_CELLS, lock);
     }
 }
 
 // Pointer updates run in three phases because of depdendencies between the
 // different types of GC thing. The most important consideration is the
 // dependency:
 //
 //    object ---> shape ---> base shape
@@ -4797,19 +4797,19 @@ GCRuntime::endMarkingZoneGroup()
         zone->setGCState(Zone::Mark);
     }
     MOZ_ASSERT(marker.isDrained());
     marker.setMarkColorBlack();
 }
 
 class GCSweepTask : public GCParallelTask
 {
-    virtual void runFromHelperThread() override {
+    virtual void runFromHelperThread(AutoLockHelperThreadState& locked) override {
         AutoSetThreadIsSweeping threadIsSweeping;
-        GCParallelTask::runFromHelperThread();
+        GCParallelTask::runFromHelperThread(locked);
     }
     GCSweepTask(const GCSweepTask&) = delete;
 
   protected:
     JSRuntime* runtime;
 
   public:
     explicit GCSweepTask(JSRuntime* rt) : runtime(rt) {}
@@ -4893,31 +4893,31 @@ SweepMiscTask::run()
     for (GCCompartmentGroupIter c(runtime); !c.done(); c.next()) {
         c->sweepSavedStacks();
         c->sweepSelfHostingScriptSource();
         c->sweepNativeIterators();
     }
 }
 
 void
-GCRuntime::startTask(GCParallelTask& task, gcstats::Phase phase)
+GCRuntime::startTask(GCParallelTask& task, gcstats::Phase phase, AutoLockHelperThreadState& locked)
 {
     MOZ_ASSERT(HelperThreadState().isLocked());
     if (!task.startWithLockHeld()) {
-        AutoUnlockHelperThreadState unlock;
+        AutoUnlockHelperThreadState unlock(locked);
         gcstats::AutoPhase ap(stats, phase);
         task.runFromMainThread(rt);
     }
 }
 
 void
-GCRuntime::joinTask(GCParallelTask& task, gcstats::Phase phase)
+GCRuntime::joinTask(GCParallelTask& task, gcstats::Phase phase, AutoLockHelperThreadState& locked)
 {
     gcstats::AutoPhase ap(stats, task, phase);
-    task.joinWithLockHeld();
+    task.joinWithLockHeld(locked);
 }
 
 using WeakCacheTaskVector = mozilla::Vector<SweepWeakCacheTask, 0, SystemAllocPolicy>;
 
 static void
 SweepWeakCachesFromMainThread(JSRuntime* rt)
 {
     for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) {
@@ -5010,32 +5010,32 @@ GCRuntime::beginSweepingZoneGroup(AutoLo
                 for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
                     callWeakPointerCompartmentCallbacks(comp);
             }
         }
     }
 
     if (sweepingAtoms) {
         AutoLockHelperThreadState helperLock;
-        startTask(sweepAtomsTask, gcstats::PHASE_SWEEP_ATOMS);
+        startTask(sweepAtomsTask, gcstats::PHASE_SWEEP_ATOMS, helperLock);
     }
 
     {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_COMPARTMENTS);
         gcstats::AutoSCC scc(stats, zoneGroupIndex);
 
         {
             AutoLockHelperThreadState helperLock;
-            startTask(sweepInnerViewsTask, gcstats::PHASE_SWEEP_INNER_VIEWS);
-            startTask(sweepCCWrappersTask, gcstats::PHASE_SWEEP_CC_WRAPPER);
-            startTask(sweepObjectGroupsTask, gcstats::PHASE_SWEEP_TYPE_OBJECT);
-            startTask(sweepRegExpsTask, gcstats::PHASE_SWEEP_REGEXP);
-            startTask(sweepMiscTask, gcstats::PHASE_SWEEP_MISC);
+            startTask(sweepInnerViewsTask, gcstats::PHASE_SWEEP_INNER_VIEWS, helperLock);
+            startTask(sweepCCWrappersTask, gcstats::PHASE_SWEEP_CC_WRAPPER, helperLock);
+            startTask(sweepObjectGroupsTask, gcstats::PHASE_SWEEP_TYPE_OBJECT, helperLock);
+            startTask(sweepRegExpsTask, gcstats::PHASE_SWEEP_REGEXP, helperLock);
+            startTask(sweepMiscTask, gcstats::PHASE_SWEEP_MISC, helperLock);
             for (auto& task : sweepCacheTasks)
-                startTask(task, gcstats::PHASE_SWEEP_MISC);
+                startTask(task, gcstats::PHASE_SWEEP_MISC, helperLock);
         }
 
         // The remainder of the of the tasks run in parallel on the main
         // thread until we join, below.
         {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_MISC);
 
             for (GCCompartmentGroupIter c(rt); !c.done(); c.next()) {
@@ -5091,31 +5091,31 @@ GCRuntime::beginSweepingZoneGroup(AutoLo
     if (sweepingAtoms) {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_SYMBOL_REGISTRY);
         rt->symbolRegistry(lock).sweep();
     }
 
     // Rejoin our off-main-thread tasks.
     if (sweepingAtoms) {
         AutoLockHelperThreadState helperLock;
-        joinTask(sweepAtomsTask, gcstats::PHASE_SWEEP_ATOMS);
+        joinTask(sweepAtomsTask, gcstats::PHASE_SWEEP_ATOMS, helperLock);
     }
 
     {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_COMPARTMENTS);
         gcstats::AutoSCC scc(stats, zoneGroupIndex);
 
         AutoLockHelperThreadState helperLock;
-        joinTask(sweepInnerViewsTask, gcstats::PHASE_SWEEP_INNER_VIEWS);
-        joinTask(sweepCCWrappersTask, gcstats::PHASE_SWEEP_CC_WRAPPER);
-        joinTask(sweepObjectGroupsTask, gcstats::PHASE_SWEEP_TYPE_OBJECT);
-        joinTask(sweepRegExpsTask, gcstats::PHASE_SWEEP_REGEXP);
-        joinTask(sweepMiscTask, gcstats::PHASE_SWEEP_MISC);
+        joinTask(sweepInnerViewsTask, gcstats::PHASE_SWEEP_INNER_VIEWS, helperLock);
+        joinTask(sweepCCWrappersTask, gcstats::PHASE_SWEEP_CC_WRAPPER, helperLock);
+        joinTask(sweepObjectGroupsTask, gcstats::PHASE_SWEEP_TYPE_OBJECT, helperLock);
+        joinTask(sweepRegExpsTask, gcstats::PHASE_SWEEP_REGEXP, helperLock);
+        joinTask(sweepMiscTask, gcstats::PHASE_SWEEP_MISC, helperLock);
         for (auto& task : sweepCacheTasks)
-            joinTask(task, gcstats::PHASE_SWEEP_MISC);
+            joinTask(task, gcstats::PHASE_SWEEP_MISC, helperLock);
     }
 
     /*
      * Queue all GC things in all zones for sweeping, either in the
      * foreground or on the background thread.
      *
      * Note that order is important here for the background case.
      *
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -19,16 +19,17 @@
 #include "js/GCAPI.h"
 #include "js/SliceBudget.h"
 #include "js/Vector.h"
 #include "threading/ConditionVariable.h"
 #include "vm/NativeObject.h"
 
 namespace js {
 
+class AutoLockHelperThreadState;
 unsigned GetCPUCount();
 
 enum ThreadType
 {
     MainThread,
     BackgroundThread
 };
 
@@ -951,17 +952,17 @@ class GCParallelTask
 
     // The simple interface to a parallel task works exactly like pthreads.
     bool start();
     void join();
 
     // If multiple tasks are to be started or joined at once, it is more
     // efficient to take the helper thread lock once and use these methods.
     bool startWithLockHeld();
-    void joinWithLockHeld();
+    void joinWithLockHeld(AutoLockHelperThreadState& locked);
 
     // Instead of dispatching to a helper, run the task on the main thread.
     void runFromMainThread(JSRuntime* rt);
 
     // Dispatch a cancelation request.
     enum CancelMode { CancelNoWait, CancelAndWait};
     void cancel(CancelMode mode = CancelNoWait) {
         cancel_ = true;
@@ -970,17 +971,17 @@ class GCParallelTask
     }
 
     // Check if a task is actively running.
     bool isRunning() const;
 
     // This should be friended to HelperThread, but cannot be because it
     // would introduce several circular dependencies.
   public:
-    virtual void runFromHelperThread();
+    virtual void runFromHelperThread(AutoLockHelperThreadState& locked);
 };
 
 typedef void (*IterateChunkCallback)(JSRuntime* rt, void* data, gc::Chunk* chunk);
 typedef void (*IterateZoneCallback)(JSRuntime* rt, void* data, JS::Zone* zone);
 typedef void (*IterateArenaCallback)(JSRuntime* rt, void* data, gc::Arena* arena,
                                      JS::TraceKind traceKind, size_t thingSize);
 typedef void (*IterateCellCallback)(JSRuntime* rt, void* data, void* thing,
                                     JS::TraceKind traceKind, size_t thingSize);
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -158,17 +158,17 @@ js::CancelOffThreadIonCompile(JSCompartm
         while (helper.ionBuilder() &&
                CompiledScriptMatches(compartment, script, helper.ionBuilder()->script()))
         {
             helper.ionBuilder()->cancel();
             if (helper.pause) {
                 helper.pause = false;
                 HelperThreadState().notifyAll(GlobalHelperThreadState::PAUSE);
             }
-            HelperThreadState().wait(GlobalHelperThreadState::CONSUMER);
+            HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
         }
     }
 
     /* Cancel code generation for any completed entries. */
     GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList();
     for (size_t i = 0; i < finished.length(); i++) {
         jit::IonBuilder* builder = finished[i];
         if (CompiledScriptMatches(compartment, script, builder->script())) {
@@ -323,28 +323,28 @@ js::CancelOffThreadParses(JSRuntime* rt)
             for (size_t i = 0; i < HelperThreadState().threadCount; i++) {
                 ParseTask* task = HelperThreadState().threads[i].parseTask();
                 if (task && task->runtimeMatches(rt))
                     inProgress = true;
             }
             if (!inProgress)
                 break;
         }
-        HelperThreadState().wait(GlobalHelperThreadState::CONSUMER);
+        HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
     }
 
     // Clean up any parse tasks which haven't been finished by the main thread.
     GlobalHelperThreadState::ParseTaskVector& finished = HelperThreadState().parseFinishedList();
     while (true) {
         bool found = false;
         for (size_t i = 0; i < finished.length(); i++) {
             ParseTask* task = finished[i];
             if (task->runtimeMatches(rt)) {
                 found = true;
-                AutoUnlockHelperThreadState unlock;
+                AutoUnlockHelperThreadState unlock(lock);
                 HelperThreadState().finishParseTask(/* maybecx = */ nullptr, rt, task->kind,
                                                     task);
             }
         }
         if (!found)
             break;
     }
 }
@@ -698,17 +698,18 @@ GlobalHelperThreadState::unlock()
 bool
 GlobalHelperThreadState::isLocked()
 {
     return lockOwner == PR_GetCurrentThread();
 }
 #endif
 
 void
-GlobalHelperThreadState::wait(CondVar which, TimeDuration timeout /* = TimeDuration::Forever() */)
+GlobalHelperThreadState::wait(AutoLockHelperThreadState& locked, CondVar which,
+                              TimeDuration timeout /* = TimeDuration::Forever() */)
 {
     MOZ_ASSERT(isLocked());
 #ifdef DEBUG
     lockOwner = nullptr;
 #endif
     DebugOnly<PRStatus> status = PR_WaitCondVar(whichWakeup(which), DurationToPRInterval(timeout));
     MOZ_ASSERT(status == PR_SUCCESS);
 #ifdef DEBUG
@@ -747,17 +748,17 @@ GlobalHelperThreadState::hasActiveThread
 
 void
 GlobalHelperThreadState::waitForAllThreads()
 {
     CancelOffThreadIonCompile(nullptr, nullptr, /* discardLazyLinkList = */ false);
 
     AutoLockHelperThreadState lock;
     while (hasActiveThreads())
-        wait(CONSUMER);
+        wait(lock, CONSUMER);
 }
 
 template <typename T>
 bool
 GlobalHelperThreadState::checkTaskThreadLimit(size_t maxThreads) const
 {
     if (maxThreads >= threadCount)
         return true;
@@ -1044,53 +1045,53 @@ js::GCParallelTask::startWithLockHeld()
 bool
 js::GCParallelTask::start()
 {
     AutoLockHelperThreadState helperLock;
     return startWithLockHeld();
 }
 
 void
-js::GCParallelTask::joinWithLockHeld()
+js::GCParallelTask::joinWithLockHeld(AutoLockHelperThreadState& locked)
 {
     MOZ_ASSERT(HelperThreadState().isLocked());
 
     if (state == NotStarted)
         return;
 
     while (state != Finished)
-        HelperThreadState().wait(GlobalHelperThreadState::CONSUMER);
+        HelperThreadState().wait(locked, GlobalHelperThreadState::CONSUMER);
     state = NotStarted;
     cancel_ = false;
 }
 
 void
 js::GCParallelTask::join()
 {
     AutoLockHelperThreadState helperLock;
-    joinWithLockHeld();
+    joinWithLockHeld(helperLock);
 }
 
 void
 js::GCParallelTask::runFromMainThread(JSRuntime* rt)
 {
     MOZ_ASSERT(state == NotStarted);
     MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(rt));
     uint64_t timeStart = PRMJ_Now();
     run();
     duration_ = PRMJ_Now() - timeStart;
 }
 
 void
-js::GCParallelTask::runFromHelperThread()
+js::GCParallelTask::runFromHelperThread(AutoLockHelperThreadState& locked)
 {
     MOZ_ASSERT(HelperThreadState().isLocked());
 
     {
-        AutoUnlockHelperThreadState parallelSection;
+        AutoUnlockHelperThreadState parallelSection(locked);
         uint64_t timeStart = PRMJ_Now();
         run();
         duration_ = PRMJ_Now() - timeStart;
     }
 
     state = Finished;
     HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
 }
@@ -1098,27 +1099,27 @@ js::GCParallelTask::runFromHelperThread(
 bool
 js::GCParallelTask::isRunning() const
 {
     MOZ_ASSERT(HelperThreadState().isLocked());
     return state == Dispatched;
 }
 
 void
-HelperThread::handleGCParallelWorkload()
+HelperThread::handleGCParallelWorkload(AutoLockHelperThreadState& locked)
 {
     MOZ_ASSERT(HelperThreadState().isLocked());
     MOZ_ASSERT(HelperThreadState().canStartGCParallelTask());
     MOZ_ASSERT(idle());
 
     TraceLoggerThread* logger = TraceLoggerForCurrentThread();
     AutoTraceLog logCompile(logger, TraceLogger_GC);
 
     currentTask.emplace(HelperThreadState().gcParallelWorklist().popCopy());
-    gcParallelTask()->runFromHelperThread();
+    gcParallelTask()->runFromHelperThread(locked);
     currentTask.reset();
     HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
 }
 
 static void
 LeaveParseTaskZone(JSRuntime* rt, ParseTask* task)
 {
     // Mark the zone as no longer in use by an ExclusiveContext, and available
@@ -1343,28 +1344,28 @@ HelperThread::ThreadMain(void* arg)
     //computations on this thread may use incorrect precision rules during
     //Ion compilation.
     FIX_FPU();
 
     static_cast<HelperThread*>(arg)->threadLoop();
 }
 
 void
-HelperThread::handleWasmWorkload()
+HelperThread::handleWasmWorkload(AutoLockHelperThreadState& locked)
 {
     MOZ_ASSERT(HelperThreadState().isLocked());
     MOZ_ASSERT(HelperThreadState().canStartWasmCompile());
     MOZ_ASSERT(idle());
 
     currentTask.emplace(HelperThreadState().wasmWorklist().popCopy());
     bool success = false;
 
     wasm::IonCompileTask* task = wasmTask();
     {
-        AutoUnlockHelperThreadState unlock;
+        AutoUnlockHelperThreadState unlock(locked);
 
         TraceLoggerThread* logger = TraceLoggerForCurrentThread();
         AutoTraceLog logCompile(logger, TraceLogger_WasmCompilation);
 
         PerThreadData::AutoEnterRuntime enter(threadData.ptr(), task->runtime());
         success = wasm::CompileFunction(task);
     }
 
@@ -1377,17 +1378,17 @@ HelperThread::handleWasmWorkload()
         HelperThreadState().noteWasmFailure();
 
     // Notify the main thread in case it's waiting.
     HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
     currentTask.reset();
 }
 
 void
-HelperThread::handleIonWorkload()
+HelperThread::handleIonWorkload(AutoLockHelperThreadState& locked)
 {
     MOZ_ASSERT(HelperThreadState().isLocked());
     MOZ_ASSERT(HelperThreadState().canStartIonCompile());
     MOZ_ASSERT(idle());
 
     // Find the IonBuilder in the worklist with the highest priority, and
     // remove it from the worklist.
     jit::IonBuilder* builder =
@@ -1404,17 +1405,17 @@ HelperThread::handleIonWorkload()
     }
 
     currentTask.emplace(builder);
     builder->setPauseFlag(&pause);
 
     JSRuntime* rt = builder->script()->compartment()->runtimeFromAnyThread();
 
     {
-        AutoUnlockHelperThreadState unlock;
+        AutoUnlockHelperThreadState unlock(locked);
 
         TraceLoggerThread* logger = TraceLoggerForCurrentThread();
         TraceLoggerEvent event(logger, TraceLogger_AnnotateScripts, builder->script());
         AutoTraceLog logScript(logger, event);
         AutoTraceLog logCompile(logger, TraceLogger_IonCompilation);
 
         PerThreadData::AutoEnterRuntime enter(threadData.ptr(),
                                               builder->script()->runtimeFromAnyThread());
@@ -1480,17 +1481,17 @@ js::PauseCurrentHelperThread()
 {
     TraceLoggerThread* logger = TraceLoggerForCurrentThread();
     AutoTraceLog logPaused(logger, TraceLogger_IonCompilationPaused);
 
     HelperThread* thread = CurrentHelperThread();
 
     AutoLockHelperThreadState lock;
     while (thread->pause)
-        HelperThreadState().wait(GlobalHelperThreadState::PAUSE);
+        HelperThreadState().wait(lock, GlobalHelperThreadState::PAUSE);
 }
 
 void
 ExclusiveContext::setHelperThread(HelperThread* thread)
 {
     helperThread_ = thread;
     perThreadData = thread->threadData.ptr();
 }
@@ -1518,28 +1519,28 @@ void
 ExclusiveContext::addPendingOutOfMemory()
 {
     // Keep in sync with recoverFromOutOfMemory.
     if (helperThread()->parseTask())
         helperThread()->parseTask()->outOfMemory = true;
 }
 
 void
-HelperThread::handleParseWorkload()
+HelperThread::handleParseWorkload(AutoLockHelperThreadState& locked)
 {
     MOZ_ASSERT(HelperThreadState().isLocked());
     MOZ_ASSERT(HelperThreadState().canStartParseTask());
     MOZ_ASSERT(idle());
 
     currentTask.emplace(HelperThreadState().parseWorklist().popCopy());
     ParseTask* task = parseTask();
     task->cx->setHelperThread(this);
 
     {
-        AutoUnlockHelperThreadState unlock;
+        AutoUnlockHelperThreadState unlock(locked);
         PerThreadData::AutoEnterRuntime enter(threadData.ptr(),
                                               task->exclusiveContextGlobal->runtimeFromAnyThread());
         task->parse();
     }
 
     // The callback is invoked while we are still off the main thread.
     task->callback(task, task->callbackData);
 
@@ -1553,28 +1554,28 @@ HelperThread::handleParseWorkload()
 
     currentTask.reset();
 
     // Notify the main thread in case it is waiting for the parse/emit to finish.
     HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
 }
 
 void
-HelperThread::handleCompressionWorkload()
+HelperThread::handleCompressionWorkload(AutoLockHelperThreadState& locked)
 {
     MOZ_ASSERT(HelperThreadState().isLocked());
     MOZ_ASSERT(HelperThreadState().canStartCompressionTask());
     MOZ_ASSERT(idle());
 
     currentTask.emplace(HelperThreadState().compressionWorklist().popCopy());
     SourceCompressionTask* task = compressionTask();
     task->helperThread = this;
 
     {
-        AutoUnlockHelperThreadState unlock;
+        AutoUnlockHelperThreadState unlock(locked);
 
         TraceLoggerThread* logger = TraceLoggerForCurrentThread();
         AutoTraceLog logCompile(logger, TraceLogger_CompressSource);
 
         task->result = task->work();
     }
 
     task->helperThread = nullptr;
@@ -1618,17 +1619,17 @@ bool
 SourceCompressionTask::complete()
 {
     if (!active())
         return true;
 
     {
         AutoLockHelperThreadState lock;
         while (HelperThreadState().compressionInProgress(this))
-            HelperThreadState().wait(GlobalHelperThreadState::CONSUMER);
+            HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
     }
 
     if (result == Success) {
         MOZ_ASSERT(resultString);
         ss->setCompressedSource(mozilla::Move(*resultString), ss->length());
     } else {
         if (result == OOM)
             ReportOutOfMemory(cx);
@@ -1653,27 +1654,27 @@ GlobalHelperThreadState::compressionTask
         SourceCompressionTask* task = threads[i].compressionTask();
         if (task && task->source() == ss)
             return task;
     }
     return nullptr;
 }
 
 void
-HelperThread::handleGCHelperWorkload()
+HelperThread::handleGCHelperWorkload(AutoLockHelperThreadState& locked)
 {
     MOZ_ASSERT(HelperThreadState().isLocked());
     MOZ_ASSERT(HelperThreadState().canStartGCHelperTask());
     MOZ_ASSERT(idle());
 
     currentTask.emplace(HelperThreadState().gcHelperWorklist().popCopy());
     GCHelperState* task = gcHelperTask();
 
     {
-        AutoUnlockHelperThreadState unlock;
+        AutoUnlockHelperThreadState unlock(locked);
         task->work();
     }
 
     currentTask.reset();
     HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
 }
 
 void
@@ -1710,35 +1711,35 @@ HelperThread::threadLoop()
                 (ionCompile = HelperThreadState().pendingIonCompileHasSufficientPriority()) ||
                 HelperThreadState().canStartParseTask() ||
                 HelperThreadState().canStartCompressionTask() ||
                 HelperThreadState().canStartGCHelperTask() ||
                 HelperThreadState().canStartGCParallelTask())
             {
                 break;
             }
-            HelperThreadState().wait(GlobalHelperThreadState::PRODUCER);
+            HelperThreadState().wait(lock, GlobalHelperThreadState::PRODUCER);
         }
 
         // Dispatch tasks, prioritizing wasm work.
         if (HelperThreadState().canStartWasmCompile()) {
             js::oom::SetThreadType(js::oom::THREAD_TYPE_ASMJS);
-            handleWasmWorkload();
+            handleWasmWorkload(lock);
         } else if (ionCompile) {
             js::oom::SetThreadType(js::oom::THREAD_TYPE_ION);
-            handleIonWorkload();
+            handleIonWorkload(lock);
         } else if (HelperThreadState().canStartParseTask()) {
             js::oom::SetThreadType(js::oom::THREAD_TYPE_PARSE);
-            handleParseWorkload();
+            handleParseWorkload(lock);
         } else if (HelperThreadState().canStartCompressionTask()) {
             js::oom::SetThreadType(js::oom::THREAD_TYPE_COMPRESS);
-            handleCompressionWorkload();
+            handleCompressionWorkload(lock);
         } else if (HelperThreadState().canStartGCHelperTask()) {
             js::oom::SetThreadType(js::oom::THREAD_TYPE_GCHELPER);
-            handleGCHelperWorkload();
+            handleGCHelperWorkload(lock);
         } else if (HelperThreadState().canStartGCParallelTask()) {
             js::oom::SetThreadType(js::oom::THREAD_TYPE_GCPARALLEL);
-            handleGCParallelWorkload();
+            handleGCParallelWorkload(lock);
         } else {
             MOZ_CRASH("No task to perform");
         }
     }
 }
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -18,19 +18,22 @@
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Variant.h"
 
 #include "jscntxt.h"
 #include "jslock.h"
 
 #include "frontend/TokenStream.h"
 #include "jit/Ion.h"
+#include "threading/ConditionVariable.h"
+#include "threading/Mutex.h"
 
 namespace js {
 
+class AutoLockHelperThreadState;
 struct HelperThread;
 struct ParseTask;
 namespace jit {
   class IonBuilder;
 } // namespace jit
 namespace wasm {
   class FuncIR;
   class FunctionCompileResults;
@@ -123,17 +126,18 @@ class GlobalHelperThreadState
         // For notifying threads doing work that they may be able to make progress.
         PRODUCER,
 
         // For notifying threads doing work which are paused that they may be
         // able to resume making progress.
         PAUSE
     };
 
-    void wait(CondVar which, mozilla::TimeDuration timeout = mozilla::TimeDuration::Forever());
+    void wait(AutoLockHelperThreadState& locked, CondVar which,
+              mozilla::TimeDuration timeout = mozilla::TimeDuration::Forever());
     void notifyAll(CondVar which);
     void notifyOne(CondVar which);
 
     // Helper method for removing items from the vectors below while iterating over them.
     template <typename T>
     void remove(T& vector, size_t* index)
     {
         vector[(*index)--] = vector.back();
@@ -257,17 +261,17 @@ class GlobalHelperThreadState
     PRCondVar* producerWakeup;
     PRCondVar* pauseWakeup;
 
     PRCondVar* whichWakeup(CondVar which) {
         switch (which) {
           case CONSUMER: return consumerWakeup;
           case PRODUCER: return producerWakeup;
           case PAUSE: return pauseWakeup;
-          default: MOZ_CRASH();
+          default: MOZ_CRASH("Invalid CondVar in |whichWakeup|");
         }
     }
 };
 
 static inline GlobalHelperThreadState&
 HelperThreadState()
 {
     extern GlobalHelperThreadState* gHelperThreadState;
@@ -346,22 +350,22 @@ struct HelperThread
     template <typename T>
     T maybeCurrentTaskAs() {
         if (currentTask.isSome() && currentTask->is<T>())
             return currentTask->as<T>();
 
         return nullptr;
     }
 
-    void handleWasmWorkload();
-    void handleIonWorkload();
-    void handleParseWorkload();
-    void handleCompressionWorkload();
-    void handleGCHelperWorkload();
-    void handleGCParallelWorkload();
+    void handleWasmWorkload(AutoLockHelperThreadState& locked);
+    void handleIonWorkload(AutoLockHelperThreadState& locked);
+    void handleParseWorkload(AutoLockHelperThreadState& locked);
+    void handleCompressionWorkload(AutoLockHelperThreadState& locked);
+    void handleGCHelperWorkload(AutoLockHelperThreadState& locked);
+    void handleGCParallelWorkload(AutoLockHelperThreadState& locked);
 };
 
 /* Methods for interacting with helper threads. */
 
 // Create data structures used by helper threads.
 bool
 CreateHelperThreadsState();
 
@@ -453,17 +457,18 @@ class MOZ_RAII AutoLockHelperThreadState
 };
 
 class MOZ_RAII AutoUnlockHelperThreadState
 {
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 
   public:
 
-    explicit AutoUnlockHelperThreadState(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM)
+    explicit AutoUnlockHelperThreadState(AutoLockHelperThreadState& locked
+                                         MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
         HelperThreadState().unlock();
     }
 
     ~AutoUnlockHelperThreadState()
     {
         HelperThreadState().lock();