Backed out 3 changesets (bug 1290156) for 10.10 debug cpp test failures CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Mon, 01 Aug 2016 14:45:43 -0700
changeset 351170 8786fe9a1993a54f1dd379fb7b287aae1609e26f
parent 351169 a86df78500c32345a9fdb2b3fc8fd7d0182c6447
child 351171 a000a5db67aaeb2a820e734d2825e63864e2c193
push id1324
push usermtabara@mozilla.com
push dateMon, 16 Jan 2017 13:07:44 +0000
treeherdermozilla-release@a01c49833940 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1290156
milestone50.0a1
backs out41096329fbed09dd0b344483f896068cc8c802eb
1bc0a14de00a86e2a198e40c1eb7a75442d60ce5
573529c879adbdfa560d00b70528e522d32bee97
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
Backed out 3 changesets (bug 1290156) for 10.10 debug cpp test failures CLOSED TREE Backed out changeset 41096329fbed (bug 1290156) Backed out changeset 1bc0a14de00a (bug 1290156) Backed out changeset 573529c879ad (bug 1290156)
js/src/asmjs/WasmGenerator.cpp
js/src/gc/Allocator.cpp
js/src/gc/GCRuntime.h
js/src/gc/Nursery.cpp
js/src/jit/Ion.cpp
js/src/jit/Ion.h
js/src/jit/arm64/vixl/MozSimulator-vixl.cpp
js/src/jit/arm64/vixl/Simulator-vixl.h
js/src/jsgc.cpp
js/src/jsgc.h
js/src/vm/HelperThreads.cpp
js/src/vm/HelperThreads.h
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
--- a/js/src/asmjs/WasmGenerator.cpp
+++ b/js/src/asmjs/WasmGenerator.cpp
@@ -71,27 +71,27 @@ ModuleGenerator::ModuleGenerator(ImportV
 
 ModuleGenerator::~ModuleGenerator()
 {
     if (parallel_) {
         // Wait for any outstanding jobs to fail or complete.
         if (outstanding_) {
             AutoLockHelperThreadState lock;
             while (true) {
-                IonCompileTaskPtrVector& worklist = HelperThreadState().wasmWorklist(lock);
+                IonCompileTaskPtrVector& worklist = HelperThreadState().wasmWorklist();
                 MOZ_ASSERT(outstanding_ >= worklist.length());
                 outstanding_ -= worklist.length();
                 worklist.clear();
 
-                IonCompileTaskPtrVector& finished = HelperThreadState().wasmFinishedList(lock);
+                IonCompileTaskPtrVector& finished = HelperThreadState().wasmFinishedList();
                 MOZ_ASSERT(outstanding_ >= finished.length());
                 outstanding_ -= finished.length();
                 finished.clear();
 
-                uint32_t numFailed = HelperThreadState().harvestFailedWasmJobs(lock);
+                uint32_t numFailed = HelperThreadState().harvestFailedWasmJobs();
                 MOZ_ASSERT(outstanding_ >= numFailed);
                 outstanding_ -= numFailed;
 
                 if (!outstanding_)
                     break;
 
                 HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
             }
@@ -194,22 +194,22 @@ ModuleGenerator::finishOutstandingTask()
     MOZ_ASSERT(parallel_);
 
     IonCompileTask* task = nullptr;
     {
         AutoLockHelperThreadState lock;
         while (true) {
             MOZ_ASSERT(outstanding_ > 0);
 
-            if (HelperThreadState().wasmFailed(lock))
+            if (HelperThreadState().wasmFailed())
                 return false;
 
-            if (!HelperThreadState().wasmFinishedList(lock).empty()) {
+            if (!HelperThreadState().wasmFinishedList().empty()) {
                 outstanding_--;
-                task = HelperThreadState().wasmFinishedList(lock).popCopy();
+                task = HelperThreadState().wasmFinishedList().popCopy();
                 break;
             }
 
             HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
         }
     }
 
     return finishTask(task);
@@ -785,19 +785,19 @@ ModuleGenerator::startFuncDefs()
     GlobalHelperThreadState& threads = HelperThreadState();
     MOZ_ASSERT(threads.threadCount > 1);
 
     uint32_t numTasks;
     if (CanUseExtraThreads() && 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());
+            MOZ_ASSERT(!HelperThreadState().wasmFailed());
+            MOZ_ASSERT(HelperThreadState().wasmWorklist().empty());
+            MOZ_ASSERT(HelperThreadState().wasmFinishedList().empty());
         }
 #endif
         parallel_ = true;
         numTasks = threads.maxWasmCompilationThreads();
     } else {
         numTasks = 1;
     }
 
--- a/js/src/gc/Allocator.cpp
+++ b/js/src/gc/Allocator.cpp
@@ -289,17 +289,17 @@ GCRuntime::startBackgroundAllocTaskIfIdl
 {
     AutoLockHelperThreadState helperLock;
     if (allocTask.isRunningWithLockHeld(helperLock))
         return;
 
     // Join the previous invocation of the task. This will return immediately
     // if the thread has never been started.
     allocTask.joinWithLockHeld(helperLock);
-    allocTask.startWithLockHeld(helperLock);
+    allocTask.startWithLockHeld();
 }
 
 /* static */ TenuredCell*
 GCRuntime::refillFreeListFromAnyThread(ExclusiveContext* cx, AllocKind thingKind, size_t thingSize)
 {
     cx->arenas()->checkEmptyFreeList(thingKind);
 
     if (cx->isJSContext())
@@ -385,16 +385,17 @@ ArenaLists::allocateFromArena(JS::Zone* 
         return nullptr;
 
     // Although our chunk should definitely have enough space for another arena,
     // there are other valid reasons why Chunk::allocateArena() may fail.
     arena = rt->gc.allocateArena(chunk, zone, thingKind, maybeLock.ref());
     if (!arena)
         return nullptr;
 
+    MOZ_ASSERT(!maybeLock->wasUnlocked());
     MOZ_ASSERT(al.isCursorAtEnd());
     al.insertBeforeCursor(arena);
 
     return allocateFromArenaInner(zone, arena, thingKind);
 }
 
 inline TenuredCell*
 ArenaLists::allocateFromArenaInner(JS::Zone* zone, Arena* arena, AllocKind kind)
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -696,24 +696,42 @@ class GCRuntime
     void waitBackgroundSweepOrAllocEnd() {
         helperState.waitBackgroundSweepEnd();
         allocTask.cancel(GCParallelTask::CancelAndWait);
     }
 
     void requestMinorGC(JS::gcreason::Reason reason);
 
 #ifdef DEBUG
+
     bool onBackgroundThread() { return helperState.onBackgroundThread(); }
+
+    bool currentThreadOwnsGCLock() {
+        return lockOwner == PR_GetCurrentThread();
+    }
+
 #endif // DEBUG
 
+    void assertCanLock() {
+        MOZ_ASSERT(!currentThreadOwnsGCLock());
+    }
+
     void lockGC() {
         lock.lock();
+#ifdef DEBUG
+        MOZ_ASSERT(!lockOwner);
+        lockOwner = PR_GetCurrentThread();
+#endif
     }
 
     void unlockGC() {
+#ifdef DEBUG
+        MOZ_ASSERT(lockOwner == PR_GetCurrentThread());
+        lockOwner = nullptr;
+#endif
         lock.unlock();
     }
 
 #ifdef DEBUG
     bool isAllocAllowed() { return noGCOrAllocationCheck == 0; }
     void disallowAlloc() { ++noGCOrAllocationCheck; }
     void allowAlloc() {
         MOZ_ASSERT(!isAllocAllowed());
@@ -1352,16 +1370,19 @@ class GCRuntime
 
     size_t noGCOrAllocationCheck;
     size_t noNurseryAllocationCheck;
 #endif
 
     /* Synchronize GC heap access between main thread and GCHelperState. */
     friend class js::AutoLockGC;
     js::Mutex lock;
+#ifdef DEBUG
+    mozilla::Atomic<PRThread*> lockOwner;
+#endif
 
     BackgroundAllocTask allocTask;
     BackgroundDecommitTask decommitTask;
     GCHelperState helperState;
 
     /*
      * During incremental sweeping, this field temporarily holds the arenas of
      * the current AllocKind being swept in order of increasing free space.
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -709,17 +709,17 @@ js::Nursery::freeMallocedBuffers()
     if (mallocedBuffers.empty())
         return;
 
     bool started;
     {
         AutoLockHelperThreadState lock;
         freeMallocedBuffersTask->joinWithLockHeld(lock);
         freeMallocedBuffersTask->transferBuffersToFree(mallocedBuffers, lock);
-        started = freeMallocedBuffersTask->startWithLockHeld(lock);
+        started = freeMallocedBuffersTask->startWithLockHeld();
     }
 
     if (!started)
         freeMallocedBuffersTask->runFromMainThread(runtime());
 
     MOZ_ASSERT(mallocedBuffers.empty());
 }
 
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -469,19 +469,20 @@ JitCompartment::ensureIonStubsExist(JSCo
         if (!stringConcatStub_)
             return false;
     }
 
     return true;
 }
 
 void
-jit::FinishOffThreadBuilder(JSRuntime* runtime, IonBuilder* builder,
-                            const AutoLockHelperThreadState& locked)
+jit::FinishOffThreadBuilder(JSRuntime* runtime, IonBuilder* builder)
 {
+    MOZ_ASSERT(HelperThreadState().isLocked());
+
     // Clean the references to the pending IonBuilder, if we just finished it.
     if (builder->script()->baselineScript()->hasPendingIonBuilder() &&
         builder->script()->baselineScript()->pendingIonBuilder() == builder)
     {
         builder->script()->baselineScript()->removePendingIonBuilder(builder->script());
     }
 
     // If the builder is still in one of the helper thread list, then remove it.
@@ -509,22 +510,22 @@ jit::FinishOffThreadBuilder(JSRuntime* r
     js_delete(builder->backgroundCodegen());
     js_delete(builder->alloc().lifoAlloc());
 }
 
 static inline void
 FinishAllOffThreadCompilations(JSCompartment* comp)
 {
     AutoLockHelperThreadState lock;
-    GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList(lock);
+    GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList();
 
     for (size_t i = 0; i < finished.length(); i++) {
         IonBuilder* builder = finished[i];
         if (builder->compartment == CompileCompartment::get(comp)) {
-            FinishOffThreadBuilder(nullptr, builder, lock);
+            FinishOffThreadBuilder(nullptr, builder);
             HelperThreadState().remove(finished, &i);
         }
     }
 }
 
 static bool
 LinkCodeGen(JSContext* cx, IonBuilder* builder, CodeGenerator *codegen)
 {
@@ -584,17 +585,17 @@ jit::LinkIonScript(JSContext* cx, Handle
 
             // Reset the TypeZone's compiler output for this script, if any.
             InvalidateCompilerOutputsForScript(cx, calleeScript);
         }
     }
 
     {
         AutoLockHelperThreadState lock;
-        FinishOffThreadBuilder(cx->runtime(), builder, lock);
+        FinishOffThreadBuilder(cx->runtime(), builder);
     }
 }
 
 uint8_t*
 jit::LazyLinkTopActivation(JSContext* cx)
 {
     // First frame should be an exit frame.
     JitFrameIterator it(cx);
@@ -2046,17 +2047,17 @@ AttachFinishedCompilations(JSContext* cx
 {
     JitCompartment* ion = cx->compartment()->jitCompartment();
     if (!ion)
         return;
 
     {
         AutoLockHelperThreadState lock;
 
-        GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList(lock);
+        GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList();
 
         // Incorporate any off thread compilations for the compartment which have
         // finished, failed or have been cancelled.
         while (true) {
             // Find a finished builder for the compartment.
             IonBuilder* builder = GetFinishedBuilder(cx, finished);
             if (!builder)
                 break;
@@ -3533,8 +3534,9 @@ jit::JitSupportsAtomics()
 #else
     return true;
 #endif
 }
 
 // If you change these, please also change the comment in TempAllocator.
 /* static */ const size_t TempAllocator::BallastSize            = 16 * 1024;
 /* static */ const size_t TempAllocator::PreferredLifoChunkSize = 32 * 1024;
+
--- a/js/src/jit/Ion.h
+++ b/js/src/jit/Ion.h
@@ -148,18 +148,17 @@ class LIRGraph;
 class CodeGenerator;
 
 MOZ_MUST_USE bool OptimizeMIR(MIRGenerator* mir);
 LIRGraph* GenerateLIR(MIRGenerator* mir);
 CodeGenerator* GenerateCode(MIRGenerator* mir, LIRGraph* lir);
 CodeGenerator* CompileBackEnd(MIRGenerator* mir);
 
 void AttachFinishedCompilations(JSContext* cx);
-void FinishOffThreadBuilder(JSRuntime* runtime, IonBuilder* builder,
-                            const AutoLockHelperThreadState& lock);
+void FinishOffThreadBuilder(JSRuntime* runtime, IonBuilder* builder);
 void StopAllOffThreadCompilations(Zone* zone);
 void StopAllOffThreadCompilations(JSCompartment* comp);
 
 void LinkIonScript(JSContext* cx, HandleScript calleescript);
 uint8_t* LazyLinkTopActivation(JSContext* cx);
 
 static inline bool
 IsIonEnabled(JSContext* cx)
--- a/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp
+++ b/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp
@@ -139,16 +139,19 @@ void Simulator::init(Decoder* decoder, F
     return;
   }
 
   // Print a warning about exclusive-access instructions, but only the first
   // time they are encountered. This warning can be silenced using
   // SilenceExclusiveAccessWarning().
   print_exclusive_access_warning_ = true;
 
+#ifdef DEBUG
+  lockOwner_ = nullptr;
+#endif
   redirection_ = nullptr;
 }
 
 
 Simulator* Simulator::Current() {
   return js::TlsPerThreadData.get()->simulator();
 }
 
@@ -290,18 +293,33 @@ int64_t Simulator::call(uint8_t* entry, 
 class AutoLockSimulatorCache : public js::LockGuard<js::Mutex>
 {
   friend class Simulator;
   using Base = js::LockGuard<js::Mutex>;
 
  public:
   explicit AutoLockSimulatorCache(Simulator* sim)
     : Base(sim->lock_)
+    , sim_(sim)
   {
+    VIXL_ASSERT(!sim_->lockOwner_);
+#ifdef DEBUG
+    sim_->lockOwner_ = PR_GetCurrentThread();
+#endif
   }
+
+  ~AutoLockSimulatorCache() {
+#ifdef DEBUG
+    VIXL_ASSERT(sim_->lockOwner_ == PR_GetCurrentThread());
+    sim_->lockOwner_ = nullptr;
+#endif
+  }
+
+ private:
+   Simulator* const sim_;
 };
 
 
 // When the generated code calls a VM function (masm.callWithABI) we need to
 // call that function instead of trying to execute it with the simulator
 // (because it's x64 code instead of AArch64 code). We do that by redirecting the VM
 // call to a svc (Supervisor Call) instruction that is handled by the
 // simulator. We write the original destination of the jump just at a known
@@ -361,16 +379,17 @@ class Redirection
   void* nativeFunction_;
   uint32_t svcInstruction_;
   ABIFunctionType type_;
   Redirection* next_;
 };
 
 
 void Simulator::setRedirection(Redirection* redirection) {
+  // VIXL_ASSERT(lockOwner_); TODO
   redirection_ = redirection;
 }
 
 
 Redirection* Simulator::redirection() const {
   return redirection_;
 }
 
--- a/js/src/jit/arm64/vixl/Simulator-vixl.h
+++ b/js/src/jit/arm64/vixl/Simulator-vixl.h
@@ -2663,15 +2663,18 @@ class Simulator : public DecoderVisitor 
 
  public:
   // True if the simulator ran out of memory during or after construction.
   bool oom() const { return oom_; }
 
  protected:
   // Moz: Synchronizes access between main thread and compilation threads.
   js::Mutex lock_;
+#ifdef DEBUG
+  PRThread* lockOwner_;
+#endif
   Redirection* redirection_;
   mozilla::Vector<int64_t, 0, js::SystemAllocPolicy> spStack_;
 };
 }  // namespace vixl
 
 #endif  // JS_SIMULATOR_ARM64
 #endif  // VIXL_A64_SIMULATOR_A64_H_
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -3272,89 +3272,98 @@ js::GetCPUCount()
 void
 GCHelperState::finish()
 {
     // Wait for any lingering background sweeping to finish.
     waitBackgroundSweepEnd();
 }
 
 GCHelperState::State
-GCHelperState::state(const AutoLockGC&)
-{
+GCHelperState::state()
+{
+    MOZ_ASSERT(rt->gc.currentThreadOwnsGCLock());
     return state_;
 }
 
 void
-GCHelperState::setState(State state, const AutoLockGC&)
-{
+GCHelperState::setState(State state)
+{
+    MOZ_ASSERT(rt->gc.currentThreadOwnsGCLock());
     state_ = state;
 }
 
 void
-GCHelperState::startBackgroundThread(State newState, const AutoLockGC& lock,
-                                     const AutoLockHelperThreadState& helperLock)
-{
-    MOZ_ASSERT(!thread && state(lock) == IDLE && newState != IDLE);
-    setState(newState, lock);
+GCHelperState::startBackgroundThread(State newState)
+{
+    MOZ_ASSERT(!thread && state() == IDLE && newState != IDLE);
+    setState(newState);
 
     {
         AutoEnterOOMUnsafeRegion noOOM;
-        if (!HelperThreadState().gcHelperWorklist(helperLock).append(this))
+        if (!HelperThreadState().gcHelperWorklist().append(this))
             noOOM.crash("Could not add to pending GC helpers list");
     }
 
-    HelperThreadState().notifyAll(GlobalHelperThreadState::PRODUCER, helperLock);
+    HelperThreadState().notifyAll(GlobalHelperThreadState::PRODUCER);
 }
 
 void
 GCHelperState::waitForBackgroundThread(js::AutoLockGC& lock)
 {
+    MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
+
+#ifdef DEBUG
+    rt->gc.lockOwner = nullptr;
+#endif
     done.wait(lock.guard());
+#ifdef DEBUG
+    rt->gc.lockOwner = PR_GetCurrentThread();
+#endif
 }
 
 void
 GCHelperState::work()
 {
     MOZ_ASSERT(CanUseExtraThreads());
 
     AutoLockGC lock(rt);
 
     MOZ_ASSERT(!thread);
     thread = PR_GetCurrentThread();
 
     TraceLoggerThread* logger = TraceLoggerForCurrentThread();
 
-    switch (state(lock)) {
+    switch (state()) {
 
       case IDLE:
         MOZ_CRASH("GC helper triggered on idle state");
         break;
 
       case SWEEPING: {
         AutoTraceLog logSweeping(logger, TraceLogger_GCSweeping);
         doSweep(lock);
-        MOZ_ASSERT(state(lock) == SWEEPING);
+        MOZ_ASSERT(state() == SWEEPING);
         break;
       }
 
     }
 
-    setState(IDLE, lock);
+    setState(IDLE);
     thread = nullptr;
 
     done.notify_all();
 }
 
 void
 GCRuntime::queueZonesForBackgroundSweep(ZoneList& zones)
 {
     AutoLockHelperThreadState helperLock;
     AutoLockGC lock(rt);
     backgroundSweepZones.transferFrom(zones);
-    helperState.maybeStartBackgroundSweep(lock, helperLock);
+    helperState.maybeStartBackgroundSweep(lock);
 }
 
 void
 GCRuntime::freeUnusedLifoBlocksAfterSweeping(LifoAlloc* lifo)
 {
     MOZ_ASSERT(rt->isHeapBusy());
     AutoLockGC lock(rt);
     blocksToFreeAfterSweeping.transferUnusedFrom(lifo);
@@ -3371,30 +3380,29 @@ GCRuntime::freeAllLifoBlocksAfterSweepin
 void
 GCRuntime::freeAllLifoBlocksAfterMinorGC(LifoAlloc* lifo)
 {
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt));
     blocksToFreeAfterMinorGC.transferFrom(lifo);
 }
 
 void
-GCHelperState::maybeStartBackgroundSweep(const AutoLockGC& lock,
-                                         const AutoLockHelperThreadState& helperLock)
+GCHelperState::maybeStartBackgroundSweep(const AutoLockGC& lock)
 {
     MOZ_ASSERT(CanUseExtraThreads());
 
-    if (state(lock) == IDLE)
-        startBackgroundThread(SWEEPING, lock, helperLock);
+    if (state() == IDLE)
+        startBackgroundThread(SWEEPING);
 }
 
 void
 GCHelperState::waitBackgroundSweepEnd()
 {
     AutoLockGC lock(rt);
-    while (state(lock) == SWEEPING)
+    while (state() == SWEEPING)
         waitForBackgroundThread(lock);
     if (!rt->gc.isIncrementalGCInProgress())
         rt->gc.assertBackgroundSweepingFinished();
 }
 
 void
 GCHelperState::doSweep(AutoLockGC& lock)
 {
@@ -4880,17 +4888,18 @@ SweepMiscTask::run()
         c->sweepSelfHostingScriptSource();
         c->sweepNativeIterators();
     }
 }
 
 void
 GCRuntime::startTask(GCParallelTask& task, gcstats::Phase phase, AutoLockHelperThreadState& locked)
 {
-    if (!task.startWithLockHeld(locked)) {
+    MOZ_ASSERT(HelperThreadState().isLocked());
+    if (!task.startWithLockHeld()) {
         AutoUnlockHelperThreadState unlock(locked);
         gcstats::AutoPhase ap(stats, phase);
         task.runFromMainThread(rt);
     }
 }
 
 void
 GCRuntime::joinTask(GCParallelTask& task, gcstats::Phase phase, AutoLockHelperThreadState& locked)
@@ -5594,17 +5603,17 @@ AutoTraceSession::~AutoTraceSession()
 {
     MOZ_ASSERT(runtime->isHeapBusy());
 
     if (runtime->exclusiveThreadsPresent()) {
         AutoLockHelperThreadState lock;
         runtime->heapState_ = prevState;
 
         // Notify any helper threads waiting for the trace session to end.
-        HelperThreadState().notifyAll(GlobalHelperThreadState::PRODUCER, lock);
+        HelperThreadState().notifyAll(GlobalHelperThreadState::PRODUCER);
     } else {
         runtime->heapState_ = prevState;
     }
 }
 
 void
 GCRuntime::resetIncrementalGC(const char* reason, AutoLockForExclusiveAccess& lock)
 {
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -860,22 +860,21 @@ class GCHelperState
     js::ConditionVariable done;
 
     // Activity for the helper to do, protected by the GC lock.
     State state_;
 
     // Thread which work is being performed on, or null.
     PRThread* thread;
 
-    void startBackgroundThread(State newState, const AutoLockGC& lock,
-                               const AutoLockHelperThreadState& helperLock);
+    void startBackgroundThread(State newState);
     void waitForBackgroundThread(js::AutoLockGC& lock);
 
-    State state(const AutoLockGC&);
-    void setState(State state, const AutoLockGC&);
+    State state();
+    void setState(State state);
 
     friend class js::gc::ArenaLists;
 
     static void freeElementsAndArray(void** array, void** end) {
         MOZ_ASSERT(array <= end);
         for (void** p = array; p != end; ++p)
             js_free(*p);
         js_free(array);
@@ -890,18 +889,17 @@ class GCHelperState
         state_(IDLE),
         thread(nullptr)
     { }
 
     void finish();
 
     void work();
 
-    void maybeStartBackgroundSweep(const AutoLockGC& lock,
-                                   const AutoLockHelperThreadState& helperLock);
+    void maybeStartBackgroundSweep(const AutoLockGC& lock);
     void startBackgroundShrink(const AutoLockGC& lock);
 
     /* Must be called without the GC lock taken. */
     void waitBackgroundSweepEnd();
 
     bool onBackgroundThread();
 
     /*
@@ -952,17 +950,17 @@ class GCParallelTask
     int64_t duration() const { return duration_; }
 
     // 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(AutoLockHelperThreadState& locked);
+    bool startWithLockHeld();
     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) {
--- a/js/src/vm/HelperThreads.cpp
+++ b/js/src/vm/HelperThreads.cpp
@@ -81,48 +81,48 @@ js::SetFakeCPUCount(size_t count)
 }
 
 bool
 js::StartOffThreadWasmCompile(wasm::IonCompileTask* task)
 {
     AutoLockHelperThreadState lock;
 
     // Don't append this task if another failed.
-    if (HelperThreadState().wasmFailed(lock))
+    if (HelperThreadState().wasmFailed())
         return false;
 
-    if (!HelperThreadState().wasmWorklist(lock).append(task))
+    if (!HelperThreadState().wasmWorklist().append(task))
         return false;
 
-    HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
+    HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER);
     return true;
 }
 
 bool
 js::StartOffThreadIonCompile(JSContext* cx, jit::IonBuilder* builder)
 {
     AutoLockHelperThreadState lock;
 
-    if (!HelperThreadState().ionWorklist(lock).append(builder))
+    if (!HelperThreadState().ionWorklist().append(builder))
         return false;
 
-    HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
+    HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER);
     return true;
 }
 
 /*
  * Move an IonBuilder for which compilation has either finished, failed, or
  * been cancelled into the global finished compilation list. All off thread
  * compilations which are started must eventually be finished.
  */
 static void
-FinishOffThreadIonCompile(jit::IonBuilder* builder, const AutoLockHelperThreadState& lock)
+FinishOffThreadIonCompile(jit::IonBuilder* builder)
 {
     AutoEnterOOMUnsafeRegion oomUnsafe;
-    if (!HelperThreadState().ionFinishedList(lock).append(builder))
+    if (!HelperThreadState().ionFinishedList().append(builder))
         oomUnsafe.crash("FinishOffThreadIonCompile");
 }
 
 static inline bool
 CompiledScriptMatches(JSCompartment* compartment, JSScript* script, JSScript* target)
 {
     if (script)
         return target == script;
@@ -138,59 +138,59 @@ js::CancelOffThreadIonCompile(JSCompartm
         return;
 
     AutoLockHelperThreadState lock;
 
     if (!HelperThreadState().threads)
         return;
 
     /* Cancel any pending entries for which processing hasn't started. */
-    GlobalHelperThreadState::IonBuilderVector& worklist = HelperThreadState().ionWorklist(lock);
+    GlobalHelperThreadState::IonBuilderVector& worklist = HelperThreadState().ionWorklist();
     for (size_t i = 0; i < worklist.length(); i++) {
         jit::IonBuilder* builder = worklist[i];
         if (CompiledScriptMatches(compartment, script, builder->script())) {
-            FinishOffThreadIonCompile(builder, lock);
+            FinishOffThreadIonCompile(builder);
             HelperThreadState().remove(worklist, &i);
         }
     }
 
     /* Wait for in progress entries to finish up. */
     for (size_t i = 0; i < HelperThreadState().threadCount; i++) {
         HelperThread& helper = HelperThreadState().threads[i];
         while (helper.ionBuilder() &&
                CompiledScriptMatches(compartment, script, helper.ionBuilder()->script()))
         {
             helper.ionBuilder()->cancel();
             if (helper.pause) {
                 helper.pause = false;
-                HelperThreadState().notifyAll(GlobalHelperThreadState::PAUSE, lock);
+                HelperThreadState().notifyAll(GlobalHelperThreadState::PAUSE);
             }
             HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
         }
     }
 
     /* Cancel code generation for any completed entries. */
-    GlobalHelperThreadState::IonBuilderVector& finished = HelperThreadState().ionFinishedList(lock);
+    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())) {
-            jit::FinishOffThreadBuilder(nullptr, builder, lock);
+            jit::FinishOffThreadBuilder(nullptr, builder);
             HelperThreadState().remove(finished, &i);
         }
     }
 
     /* Cancel lazy linking for pending builders (attached to the ionScript). */
     if (discardLazyLinkList) {
         MOZ_ASSERT(compartment);
         JSRuntime* runtime = compartment->runtimeFromMainThread();
         jit::IonBuilder* builder = runtime->ionLazyLinkList().getFirst();
         while (builder) {
             jit::IonBuilder* next = builder->getNext();
             if (CompiledScriptMatches(compartment, script, builder->script()))
-                jit::FinishOffThreadBuilder(runtime, builder, lock);
+                jit::FinishOffThreadBuilder(runtime, builder);
             builder = next;
         }
     }
 }
 
 static const JSClassOps parseTaskGlobalClassOps = {
     nullptr, nullptr, nullptr, nullptr,
     nullptr, nullptr, nullptr, nullptr,
@@ -303,28 +303,27 @@ void
 js::CancelOffThreadParses(JSRuntime* rt)
 {
     AutoLockHelperThreadState lock;
 
     if (!HelperThreadState().threads)
         return;
 
 #ifdef DEBUG
-    GlobalHelperThreadState::ParseTaskVector& waitingOnGC =
-        HelperThreadState().parseWaitingOnGC(lock);
+    GlobalHelperThreadState::ParseTaskVector& waitingOnGC = HelperThreadState().parseWaitingOnGC();
     for (size_t i = 0; i < waitingOnGC.length(); i++)
         MOZ_ASSERT(!waitingOnGC[i]->runtimeMatches(rt));
 #endif
 
     // Instead of forcibly canceling pending parse tasks, just wait for all scheduled
     // and in progress ones to complete. Otherwise the final GC may not collect
     // everything due to zones being used off thread.
     while (true) {
         bool pending = false;
-        GlobalHelperThreadState::ParseTaskVector& worklist = HelperThreadState().parseWorklist(lock);
+        GlobalHelperThreadState::ParseTaskVector& worklist = HelperThreadState().parseWorklist();
         for (size_t i = 0; i < worklist.length(); i++) {
             ParseTask* task = worklist[i];
             if (task->runtimeMatches(rt))
                 pending = true;
         }
         if (!pending) {
             bool inProgress = false;
             for (size_t i = 0; i < HelperThreadState().threadCount; i++) {
@@ -334,17 +333,17 @@ js::CancelOffThreadParses(JSRuntime* rt)
             }
             if (!inProgress)
                 break;
         }
         HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
     }
 
     // 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();
     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(lock);
                 HelperThreadState().cancelParseTask(rt->contextFromMainThread(), task->kind, task);
@@ -444,29 +443,29 @@ CreateGlobalForOffThreadParse(JSContext*
     return global;
 }
 
 static bool
 QueueOffThreadParseTask(JSContext* cx, ParseTask* task)
 {
     if (OffThreadParsingMustWaitForGC(cx->runtime())) {
         AutoLockHelperThreadState lock;
-        if (!HelperThreadState().parseWaitingOnGC(lock).append(task)) {
+        if (!HelperThreadState().parseWaitingOnGC().append(task)) {
             ReportOutOfMemory(cx);
             return false;
         }
     } else {
         AutoLockHelperThreadState lock;
-        if (!HelperThreadState().parseWorklist(lock).append(task)) {
+        if (!HelperThreadState().parseWorklist().append(task)) {
             ReportOutOfMemory(cx);
             return false;
         }
 
         task->activate(cx->runtime());
-        HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
+        HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER);
     }
 
     return true;
 }
 
 bool
 js::StartOffThreadParseScript(JSContext* cx, const ReadOnlyCompileOptions& options,
                               const char16_t* chars, size_t length,
@@ -544,18 +543,17 @@ js::StartOffThreadParseModule(JSContext*
 void
 js::EnqueuePendingParseTasksAfterGC(JSRuntime* rt)
 {
     MOZ_ASSERT(!OffThreadParsingMustWaitForGC(rt));
 
     GlobalHelperThreadState::ParseTaskVector newTasks;
     {
         AutoLockHelperThreadState lock;
-        GlobalHelperThreadState::ParseTaskVector& waiting =
-            HelperThreadState().parseWaitingOnGC(lock);
+        GlobalHelperThreadState::ParseTaskVector& waiting = HelperThreadState().parseWaitingOnGC();
 
         for (size_t i = 0; i < waiting.length(); i++) {
             ParseTask* task = waiting[i];
             if (task->runtimeMatches(rt)) {
                 AutoEnterOOMUnsafeRegion oomUnsafe;
                 if (!newTasks.append(task))
                     oomUnsafe.crash("EnqueuePendingParseTasksAfterGC");
                 HelperThreadState().remove(waiting, &i);
@@ -571,21 +569,21 @@ js::EnqueuePendingParseTasksAfterGC(JSRu
 
     for (size_t i = 0; i < newTasks.length(); i++)
         newTasks[i]->activate(rt);
 
     AutoLockHelperThreadState lock;
 
     {
         AutoEnterOOMUnsafeRegion oomUnsafe;
-        if (!HelperThreadState().parseWorklist(lock).appendAll(newTasks))
+        if (!HelperThreadState().parseWorklist().appendAll(newTasks))
             oomUnsafe.crash("EnqueuePendingParseTasksAfterGC");
     }
 
-    HelperThreadState().notifyAll(GlobalHelperThreadState::PRODUCER, lock);
+    HelperThreadState().notifyAll(GlobalHelperThreadState::PRODUCER);
 }
 
 static const uint32_t kDefaultHelperStackSize = 2048 * 1024;
 static const uint32_t kDefaultHelperStackQuota = 1800 * 1024;
 
 // TSan enforces a minimum stack size that's just slightly larger than our
 // default helper stack size.  It does this to store blobs of TSan-specific
 // data on each thread's stack.  Unfortunately, that means that even though
@@ -664,48 +662,74 @@ GlobalHelperThreadState::finishThreads()
         threads[i].destroy();
     js_free(threads);
     threads = nullptr;
 }
 
 void
 GlobalHelperThreadState::lock()
 {
+    MOZ_ASSERT(!isLocked());
     AssertCurrentThreadCanLock(HelperThreadStateLock);
     helperLock.lock();
+#ifdef DEBUG
+    lockOwner = PR_GetCurrentThread();
+#endif
 }
 
 void
 GlobalHelperThreadState::unlock()
 {
+    MOZ_ASSERT(isLocked());
+#ifdef DEBUG
+    lockOwner = nullptr;
+#endif
     helperLock.unlock();
 }
 
+#ifdef DEBUG
+bool
+GlobalHelperThreadState::isLocked()
+{
+    return lockOwner == PR_GetCurrentThread();
+}
+#endif
+
 void
 GlobalHelperThreadState::wait(AutoLockHelperThreadState& locked, CondVar which,
                               TimeDuration timeout /* = TimeDuration::Forever() */)
 {
+    MOZ_ASSERT(isLocked());
+#ifdef DEBUG
+    lockOwner = nullptr;
+#endif
     whichWakeup(which).wait_for(locked, timeout);
+#ifdef DEBUG
+    lockOwner = PR_GetCurrentThread();
+#endif
 }
 
 void
-GlobalHelperThreadState::notifyAll(CondVar which, const AutoLockHelperThreadState&)
+GlobalHelperThreadState::notifyAll(CondVar which)
 {
+    MOZ_ASSERT(isLocked());
     whichWakeup(which).notify_all();
 }
 
 void
-GlobalHelperThreadState::notifyOne(CondVar which, const AutoLockHelperThreadState&)
+GlobalHelperThreadState::notifyOne(CondVar which)
 {
+    MOZ_ASSERT(isLocked());
     whichWakeup(which).notify_one();
 }
 
 bool
-GlobalHelperThreadState::hasActiveThreads(const AutoLockHelperThreadState&)
+GlobalHelperThreadState::hasActiveThreads()
 {
+    MOZ_ASSERT(isLocked());
     if (!threads)
         return false;
 
     for (size_t i = 0; i < threadCount; i++) {
         if (!threads[i].idle())
             return true;
     }
 
@@ -713,17 +737,17 @@ GlobalHelperThreadState::hasActiveThread
 }
 
 void
 GlobalHelperThreadState::waitForAllThreads()
 {
     CancelOffThreadIonCompile(nullptr, nullptr, /* discardLazyLinkList = */ false);
 
     AutoLockHelperThreadState lock;
-    while (hasActiveThreads(lock))
+    while (hasActiveThreads())
         wait(lock, CONSUMER);
 }
 
 template <typename T>
 bool
 GlobalHelperThreadState::checkTaskThreadLimit(size_t maxThreads) const
 {
     if (maxThreads >= threadCount)
@@ -807,20 +831,21 @@ size_t
 GlobalHelperThreadState::maxGCParallelThreads() const
 {
     if (IsHelperThreadSimulatingOOM(js::oom::THREAD_TYPE_GCPARALLEL))
         return 1;
     return threadCount;
 }
 
 bool
-GlobalHelperThreadState::canStartWasmCompile(const AutoLockHelperThreadState& lock)
+GlobalHelperThreadState::canStartWasmCompile()
 {
     // Don't execute an wasm job if an earlier one failed.
-    if (wasmWorklist(lock).empty() || numWasmFailedJobs)
+    MOZ_ASSERT(isLocked());
+    if (wasmWorklist().empty() || numWasmFailedJobs)
         return false;
 
     // Honor the maximum allowed threads to compile wasm jobs at once,
     // to avoid oversaturating the machine.
     if (!checkTaskThreadLimit<wasm::IonCompileTask*>(maxWasmCompilationThreads()))
         return false;
 
     return true;
@@ -841,48 +866,49 @@ IonBuilderHasHigherPriority(jit::IonBuil
         return !first->scriptHasIonScript();
 
     // A higher warm-up counter indicates a higher priority.
     return first->script()->getWarmUpCount() / first->script()->length() >
            second->script()->getWarmUpCount() / second->script()->length();
 }
 
 bool
-GlobalHelperThreadState::canStartIonCompile(const AutoLockHelperThreadState& lock)
+GlobalHelperThreadState::canStartIonCompile()
 {
-    return !ionWorklist(lock).empty() &&
+    return !ionWorklist().empty() &&
            checkTaskThreadLimit<jit::IonBuilder*>(maxIonCompilationThreads());
 }
 
 jit::IonBuilder*
-GlobalHelperThreadState::highestPriorityPendingIonCompile(const AutoLockHelperThreadState& lock,
-                                                          bool remove /* = false */)
+GlobalHelperThreadState::highestPriorityPendingIonCompile(bool remove /* = false */)
 {
-    auto& worklist = ionWorklist(lock);
-    if (worklist.empty()) {
+    MOZ_ASSERT(isLocked());
+
+    if (ionWorklist().empty()) {
         MOZ_ASSERT(!remove);
         return nullptr;
     }
 
     // Get the highest priority IonBuilder which has not started compilation yet.
     size_t index = 0;
-    for (size_t i = 1; i < worklist.length(); i++) {
-        if (IonBuilderHasHigherPriority(worklist[i], worklist[index]))
+    for (size_t i = 1; i < ionWorklist().length(); i++) {
+        if (IonBuilderHasHigherPriority(ionWorklist()[i], ionWorklist()[index]))
             index = i;
     }
-    jit::IonBuilder* builder = worklist[index];
+    jit::IonBuilder* builder = ionWorklist()[index];
     if (remove)
-        worklist.erase(&worklist[index]);
+        ionWorklist().erase(&ionWorklist()[index]);
     return builder;
 }
 
 HelperThread*
-GlobalHelperThreadState::lowestPriorityUnpausedIonCompileAtThreshold(
-    const AutoLockHelperThreadState& lock)
+GlobalHelperThreadState::lowestPriorityUnpausedIonCompileAtThreshold()
 {
+    MOZ_ASSERT(isLocked());
+
     // Get the lowest priority IonBuilder which has started compilation and
     // isn't paused, unless there are still fewer than the maximum number of
     // such builders permitted.
     size_t numBuilderThreads = 0;
     HelperThread* thread = nullptr;
     for (size_t i = 0; i < threadCount; i++) {
         if (threads[i].ionBuilder() && !threads[i].pause) {
             numBuilderThreads++;
@@ -891,130 +917,138 @@ GlobalHelperThreadState::lowestPriorityU
         }
     }
     if (numBuilderThreads < maxUnpausedIonCompilationThreads())
         return nullptr;
     return thread;
 }
 
 HelperThread*
-GlobalHelperThreadState::highestPriorityPausedIonCompile(const AutoLockHelperThreadState& lock)
+GlobalHelperThreadState::highestPriorityPausedIonCompile()
 {
+    MOZ_ASSERT(isLocked());
+
     // Get the highest priority IonBuilder which has started compilation but
     // which was subsequently paused.
     HelperThread* thread = nullptr;
     for (size_t i = 0; i < threadCount; i++) {
         if (threads[i].pause) {
             // Currently, only threads with IonBuilders can be paused.
             MOZ_ASSERT(threads[i].ionBuilder());
             if (!thread || IonBuilderHasHigherPriority(threads[i].ionBuilder(), thread->ionBuilder()))
                 thread = &threads[i];
         }
     }
     return thread;
 }
 
 bool
-GlobalHelperThreadState::pendingIonCompileHasSufficientPriority(
-    const AutoLockHelperThreadState& lock)
+GlobalHelperThreadState::pendingIonCompileHasSufficientPriority()
 {
+    MOZ_ASSERT(isLocked());
+
     // Can't compile anything if there are no scripts to compile.
-    if (!canStartIonCompile(lock))
+    if (!canStartIonCompile())
         return false;
 
     // Count the number of threads currently compiling scripts, and look for
     // the thread with the lowest priority.
-    HelperThread* lowestPriorityThread = lowestPriorityUnpausedIonCompileAtThreshold(lock);
+    HelperThread* lowestPriorityThread = lowestPriorityUnpausedIonCompileAtThreshold();
 
     // If the number of threads building scripts is less than the maximum, the
     // compilation can start immediately.
     if (!lowestPriorityThread)
         return true;
 
     // If there is a builder in the worklist with higher priority than some
     // builder currently being compiled, then that current compilation can be
     // paused, so allow the compilation.
-    if (IonBuilderHasHigherPriority(highestPriorityPendingIonCompile(lock),
+    if (IonBuilderHasHigherPriority(highestPriorityPendingIonCompile(),
                                     lowestPriorityThread->ionBuilder()))
         return true;
 
     // Compilation will have to wait until one of the active compilations finishes.
     return false;
 }
 
 bool
-GlobalHelperThreadState::canStartParseTask(const AutoLockHelperThreadState& lock)
+GlobalHelperThreadState::canStartParseTask()
 {
-    return !parseWorklist(lock).empty() && checkTaskThreadLimit<ParseTask*>(maxParseThreads());
+    MOZ_ASSERT(isLocked());
+    return !parseWorklist().empty() && checkTaskThreadLimit<ParseTask*>(maxParseThreads());
 }
 
 bool
-GlobalHelperThreadState::canStartCompressionTask(const AutoLockHelperThreadState& lock)
+GlobalHelperThreadState::canStartCompressionTask()
 {
-    return !compressionWorklist(lock).empty() &&
+    return !compressionWorklist().empty() &&
            checkTaskThreadLimit<SourceCompressionTask*>(maxCompressionThreads());
 }
 
 bool
-GlobalHelperThreadState::canStartGCHelperTask(const AutoLockHelperThreadState& lock)
+GlobalHelperThreadState::canStartGCHelperTask()
 {
-    return !gcHelperWorklist(lock).empty() &&
+    return !gcHelperWorklist().empty() &&
            checkTaskThreadLimit<GCHelperState*>(maxGCHelperThreads());
 }
 
 bool
-GlobalHelperThreadState::canStartGCParallelTask(const AutoLockHelperThreadState& lock)
+GlobalHelperThreadState::canStartGCParallelTask()
 {
-    return !gcParallelWorklist(lock).empty() &&
+    return !gcParallelWorklist().empty() &&
            checkTaskThreadLimit<GCParallelTask*>(maxGCParallelThreads());
 }
 
 js::GCParallelTask::~GCParallelTask()
 {
     // Only most-derived classes' destructors may do the join: base class
     // destructors run after those for derived classes' members, so a join in a
     // base class can't ensure that the task is done using the members. All we
     // can do now is check that someone has previously stopped the task.
 #ifdef DEBUG
     AutoLockHelperThreadState helperLock;
     MOZ_ASSERT(state == NotStarted);
 #endif
 }
 
 bool
-js::GCParallelTask::startWithLockHeld(AutoLockHelperThreadState& lock)
+js::GCParallelTask::startWithLockHeld()
 {
+    MOZ_ASSERT(HelperThreadState().isLocked());
+
     // Tasks cannot be started twice.
     MOZ_ASSERT(state == NotStarted);
 
     // If we do the shutdown GC before running anything, we may never
     // have initialized the helper threads. Just use the serial path
     // since we cannot safely intialize them at this point.
     if (!HelperThreadState().threads)
         return false;
 
-    if (!HelperThreadState().gcParallelWorklist(lock).append(this))
+    if (!HelperThreadState().gcParallelWorklist().append(this))
         return false;
     state = Dispatched;
 
-    HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
+    HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER);
 
     return true;
 }
 
 bool
 js::GCParallelTask::start()
 {
     AutoLockHelperThreadState helperLock;
-    return startWithLockHeld(helperLock);
+    return startWithLockHeld();
 }
 
 void
 js::GCParallelTask::joinWithLockHeld(AutoLockHelperThreadState& locked)
 {
+    MOZ_ASSERT(HelperThreadState().isLocked());
+
     if (state == NotStarted)
         return;
 
     while (state != Finished)
         HelperThreadState().wait(locked, GlobalHelperThreadState::CONSUMER);
     state = NotStarted;
     cancel_ = false;
 }
@@ -1034,53 +1068,57 @@ js::GCParallelTask::runFromMainThread(JS
     uint64_t timeStart = PRMJ_Now();
     run();
     duration_ = PRMJ_Now() - timeStart;
 }
 
 void
 js::GCParallelTask::runFromHelperThread(AutoLockHelperThreadState& locked)
 {
+    MOZ_ASSERT(HelperThreadState().isLocked());
+
     {
         AutoUnlockHelperThreadState parallelSection(locked);
         uint64_t timeStart = PRMJ_Now();
         run();
         duration_ = PRMJ_Now() - timeStart;
     }
 
     state = Finished;
-    HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
+    HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
 }
 
 bool
 js::GCParallelTask::isRunningWithLockHeld(const AutoLockHelperThreadState& locked) const
 {
+    MOZ_ASSERT(HelperThreadState().isLocked());
     return state == Dispatched;
 }
 
 bool
 js::GCParallelTask::isRunning() const
 {
     AutoLockHelperThreadState helperLock;
     return isRunningWithLockHeld(helperLock);
 }
 
 void
 HelperThread::handleGCParallelWorkload(AutoLockHelperThreadState& locked)
 {
-    MOZ_ASSERT(HelperThreadState().canStartGCParallelTask(locked));
+    MOZ_ASSERT(HelperThreadState().isLocked());
+    MOZ_ASSERT(HelperThreadState().canStartGCParallelTask());
     MOZ_ASSERT(idle());
 
     TraceLoggerThread* logger = TraceLoggerForCurrentThread();
     AutoTraceLog logCompile(logger, TraceLogger_GC);
 
-    currentTask.emplace(HelperThreadState().gcParallelWorklist(locked).popCopy());
+    currentTask.emplace(HelperThreadState().gcParallelWorklist().popCopy());
     gcParallelTask()->runFromHelperThread(locked);
     currentTask.reset();
-    HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
+    HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
 }
 
 static void
 LeaveParseTaskZone(JSRuntime* rt, ParseTask* task)
 {
     // Mark the zone as no longer in use by an ExclusiveContext, and available
     // to be collected by the GC.
     task->cx->leaveCompartment(task->cx->compartment());
@@ -1089,17 +1127,17 @@ LeaveParseTaskZone(JSRuntime* rt, ParseT
 
 ParseTask*
 GlobalHelperThreadState::removeFinishedParseTask(ParseTaskKind kind, void* token)
 {
     // The token is a ParseTask* which should be in the finished list.
     // Find and remove its entry.
 
     AutoLockHelperThreadState lock;
-    ParseTaskVector& finished = parseFinishedList(lock);
+    ParseTaskVector& finished = parseFinishedList();
 
     for (size_t i = 0; i < finished.length(); i++) {
         if (finished[i] == token) {
             ParseTask* parseTask = finished[i];
             remove(finished, &i);
             MOZ_ASSERT(parseTask);
             MOZ_ASSERT(parseTask->kind == kind);
             return parseTask;
@@ -1267,17 +1305,17 @@ void
 HelperThread::destroy()
 {
     if (thread) {
         {
             AutoLockHelperThreadState lock;
             terminate = true;
 
             /* Notify all helpers, to ensure that this thread wakes up. */
-            HelperThreadState().notifyAll(GlobalHelperThreadState::PRODUCER, lock);
+            HelperThreadState().notifyAll(GlobalHelperThreadState::PRODUCER);
         }
 
         PR_JoinThread(thread);
     }
 
     threadData.reset();
 }
 
@@ -1308,59 +1346,60 @@ HelperThread::ThreadMain(void* arg)
     FIX_FPU();
 
     static_cast<HelperThread*>(arg)->threadLoop();
 }
 
 void
 HelperThread::handleWasmWorkload(AutoLockHelperThreadState& locked)
 {
-    MOZ_ASSERT(HelperThreadState().canStartWasmCompile(locked));
+    MOZ_ASSERT(HelperThreadState().isLocked());
+    MOZ_ASSERT(HelperThreadState().canStartWasmCompile());
     MOZ_ASSERT(idle());
 
-    currentTask.emplace(HelperThreadState().wasmWorklist(locked).popCopy());
+    currentTask.emplace(HelperThreadState().wasmWorklist().popCopy());
     bool success = false;
 
     wasm::IonCompileTask* task = wasmTask();
     {
         AutoUnlockHelperThreadState unlock(locked);
         success = wasm::CompileFunction(task);
     }
 
     // On success, try to move work to the finished list.
     if (success)
-        success = HelperThreadState().wasmFinishedList(locked).append(task);
+        success = HelperThreadState().wasmFinishedList().append(task);
 
     // On failure, note the failure for harvesting by the parent.
     if (!success)
-        HelperThreadState().noteWasmFailure(locked);
+        HelperThreadState().noteWasmFailure();
 
     // Notify the main thread in case it's waiting.
-    HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
+    HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
     currentTask.reset();
 }
 
 void
 HelperThread::handleIonWorkload(AutoLockHelperThreadState& locked)
 {
-    MOZ_ASSERT(HelperThreadState().canStartIonCompile(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 =
-        HelperThreadState().highestPriorityPendingIonCompile(locked, /* remove = */ true);
+        HelperThreadState().highestPriorityPendingIonCompile(/* remove = */ true);
 
     // If there are now too many threads with active IonBuilders, indicate to
     // the one with the lowest priority that it should pause. Note that due to
     // builder priorities changing since pendingIonCompileHasSufficientPriority
     // was called, the builder we are pausing may actually be higher priority
     // than the one we are about to start. Oh well.
-    HelperThread* other = HelperThreadState().lowestPriorityUnpausedIonCompileAtThreshold(locked);
-    if (other) {
+    if (HelperThread* other = HelperThreadState().lowestPriorityUnpausedIonCompileAtThreshold()) {
         MOZ_ASSERT(other->ionBuilder() && !other->pause);
         other->pause = true;
     }
 
     currentTask.emplace(builder);
     builder->setPauseFlag(&pause);
 
     JSRuntime* rt = builder->script()->compartment()->runtimeFromAnyThread();
@@ -1376,48 +1415,48 @@ HelperThread::handleIonWorkload(AutoLock
         PerThreadData::AutoEnterRuntime enter(threadData.ptr(),
                                               builder->script()->runtimeFromAnyThread());
         jit::JitContext jctx(jit::CompileRuntime::get(rt),
                              jit::CompileCompartment::get(builder->script()->compartment()),
                              &builder->alloc());
         builder->setBackgroundCodegen(jit::CompileBackEnd(builder));
     }
 
-    FinishOffThreadIonCompile(builder, locked);
+    FinishOffThreadIonCompile(builder);
     currentTask.reset();
     pause = false;
 
     // Ping the main thread so that the compiled code can be incorporated
     // at the next interrupt callback. Don't interrupt Ion code for this, as
     // this incorporation can be delayed indefinitely without affecting
     // performance as long as the main thread is actually executing Ion code.
     rt->requestInterrupt(JSRuntime::RequestInterruptCanWait);
 
     // Notify the main thread in case it is waiting for the compilation to finish.
-    HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
+    HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
 
     // When finishing Ion compilation jobs, we can start unpausing compilation
     // threads that were paused to restrict the number of active compilations.
     // Only unpause one at a time, to make sure we don't exceed the restriction.
     // Since threads are currently only paused for Ion compilations, this
     // strategy will eventually unpause all paused threads, regardless of how
     // many there are, since each thread we unpause will eventually finish and
     // end up back here.
-    if (HelperThread* other = HelperThreadState().highestPriorityPausedIonCompile(locked)) {
+    if (HelperThread* other = HelperThreadState().highestPriorityPausedIonCompile()) {
         MOZ_ASSERT(other->ionBuilder() && other->pause);
 
         // Only unpause the other thread if there isn't a higher priority
         // builder which this thread or another can start on.
-        jit::IonBuilder* builder = HelperThreadState().highestPriorityPendingIonCompile(locked);
+        jit::IonBuilder* builder = HelperThreadState().highestPriorityPendingIonCompile();
         if (!builder || IonBuilderHasHigherPriority(other->ionBuilder(), builder)) {
             other->pause = false;
 
             // Notify all paused threads, to make sure the one we just
             // unpaused wakes up.
-            HelperThreadState().notifyAll(GlobalHelperThreadState::PAUSE, locked);
+            HelperThreadState().notifyAll(GlobalHelperThreadState::PAUSE);
         }
     }
 }
 
 static HelperThread*
 CurrentHelperThread()
 {
     PRThread* prThread = PR_GetCurrentThread();
@@ -1477,20 +1516,21 @@ ExclusiveContext::addPendingOutOfMemory(
     // Keep in sync with recoverFromOutOfMemory.
     if (helperThread()->parseTask())
         helperThread()->parseTask()->outOfMemory = true;
 }
 
 void
 HelperThread::handleParseWorkload(AutoLockHelperThreadState& locked)
 {
-    MOZ_ASSERT(HelperThreadState().canStartParseTask(locked));
+    MOZ_ASSERT(HelperThreadState().isLocked());
+    MOZ_ASSERT(HelperThreadState().canStartParseTask());
     MOZ_ASSERT(idle());
 
-    currentTask.emplace(HelperThreadState().parseWorklist(locked).popCopy());
+    currentTask.emplace(HelperThreadState().parseWorklist().popCopy());
     ParseTask* task = parseTask();
     task->cx->setHelperThread(this);
 
     {
         AutoUnlockHelperThreadState unlock(locked);
         PerThreadData::AutoEnterRuntime enter(threadData.ptr(),
                                               task->exclusiveContextGlobal->runtimeFromAnyThread());
         task->parse();
@@ -1498,73 +1538,74 @@ HelperThread::handleParseWorkload(AutoLo
 
     // The callback is invoked while we are still off the main thread.
     task->callback(task, task->callbackData);
 
     // FinishOffThreadScript will need to be called on the script to
     // migrate it into the correct compartment.
     {
         AutoEnterOOMUnsafeRegion oomUnsafe;
-        if (!HelperThreadState().parseFinishedList(locked).append(task))
+        if (!HelperThreadState().parseFinishedList().append(task))
             oomUnsafe.crash("handleParseWorkload");
     }
 
     currentTask.reset();
 
     // Notify the main thread in case it is waiting for the parse/emit to finish.
-    HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
+    HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
 }
 
 void
 HelperThread::handleCompressionWorkload(AutoLockHelperThreadState& locked)
 {
-    MOZ_ASSERT(HelperThreadState().canStartCompressionTask(locked));
+    MOZ_ASSERT(HelperThreadState().isLocked());
+    MOZ_ASSERT(HelperThreadState().canStartCompressionTask());
     MOZ_ASSERT(idle());
 
-    currentTask.emplace(HelperThreadState().compressionWorklist(locked).popCopy());
+    currentTask.emplace(HelperThreadState().compressionWorklist().popCopy());
     SourceCompressionTask* task = compressionTask();
     task->helperThread = this;
 
     {
         AutoUnlockHelperThreadState unlock(locked);
 
         TraceLoggerThread* logger = TraceLoggerForCurrentThread();
         AutoTraceLog logCompile(logger, TraceLogger_CompressSource);
 
         task->result = task->work();
     }
 
     task->helperThread = nullptr;
     currentTask.reset();
 
     // Notify the main thread in case it is waiting for the compression to finish.
-    HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
+    HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
 }
 
 bool
 js::StartOffThreadCompression(ExclusiveContext* cx, SourceCompressionTask* task)
 {
     AutoLockHelperThreadState lock;
 
-    if (!HelperThreadState().compressionWorklist(lock).append(task)) {
+    if (!HelperThreadState().compressionWorklist().append(task)) {
         if (JSContext* maybecx = cx->maybeJSContext())
             ReportOutOfMemory(maybecx);
         return false;
     }
 
-    HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER, lock);
+    HelperThreadState().notifyOne(GlobalHelperThreadState::PRODUCER);
     return true;
 }
 
 bool
-GlobalHelperThreadState::compressionInProgress(SourceCompressionTask* task,
-                                               const AutoLockHelperThreadState& lock)
+GlobalHelperThreadState::compressionInProgress(SourceCompressionTask* task)
 {
-    for (size_t i = 0; i < compressionWorklist(lock).length(); i++) {
-        if (compressionWorklist(lock)[i] == task)
+    MOZ_ASSERT(isLocked());
+    for (size_t i = 0; i < compressionWorklist().length(); i++) {
+        if (compressionWorklist()[i] == task)
             return true;
     }
     for (size_t i = 0; i < threadCount; i++) {
         if (threads[i].compressionTask() == task)
             return true;
     }
     return false;
 }
@@ -1572,17 +1613,17 @@ GlobalHelperThreadState::compressionInPr
 bool
 SourceCompressionTask::complete()
 {
     if (!active())
         return true;
 
     {
         AutoLockHelperThreadState lock;
-        while (HelperThreadState().compressionInProgress(this, lock))
+        while (HelperThreadState().compressionInProgress(this))
             HelperThreadState().wait(lock, GlobalHelperThreadState::CONSUMER);
     }
 
     if (result == Success) {
         MOZ_ASSERT(resultString);
         ss->setCompressedSource(mozilla::Move(*resultString), ss->length());
     } else {
         if (result == OOM)
@@ -1591,48 +1632,49 @@ SourceCompressionTask::complete()
 
     ss = nullptr;
     MOZ_ASSERT(!active());
 
     return result != OOM;
 }
 
 SourceCompressionTask*
-GlobalHelperThreadState::compressionTaskForSource(ScriptSource* ss,
-                                                  const AutoLockHelperThreadState& lock)
+GlobalHelperThreadState::compressionTaskForSource(ScriptSource* ss)
 {
-    for (size_t i = 0; i < compressionWorklist(lock).length(); i++) {
-        SourceCompressionTask* task = compressionWorklist(lock)[i];
+    MOZ_ASSERT(isLocked());
+    for (size_t i = 0; i < compressionWorklist().length(); i++) {
+        SourceCompressionTask* task = compressionWorklist()[i];
         if (task->source() == ss)
             return task;
     }
     for (size_t i = 0; i < threadCount; i++) {
         SourceCompressionTask* task = threads[i].compressionTask();
         if (task && task->source() == ss)
             return task;
     }
     return nullptr;
 }
 
 void
 HelperThread::handleGCHelperWorkload(AutoLockHelperThreadState& locked)
 {
-    MOZ_ASSERT(HelperThreadState().canStartGCHelperTask(locked));
+    MOZ_ASSERT(HelperThreadState().isLocked());
+    MOZ_ASSERT(HelperThreadState().canStartGCHelperTask());
     MOZ_ASSERT(idle());
 
-    currentTask.emplace(HelperThreadState().gcHelperWorklist(locked).popCopy());
+    currentTask.emplace(HelperThreadState().gcHelperWorklist().popCopy());
     GCHelperState* task = gcHelperTask();
 
     {
         AutoUnlockHelperThreadState unlock(locked);
         task->work();
     }
 
     currentTask.reset();
-    HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, locked);
+    HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER);
 }
 
 void
 HelperThread::threadLoop()
 {
     MOZ_ASSERT(CanUseExtraThreads());
 
     JS::AutoSuppressGCAnalysis nogc;
@@ -1655,44 +1697,44 @@ HelperThread::threadLoop()
 
         // Block until a task is available. Save the value of whether we are
         // going to do an Ion compile, in case the value returned by the method
         // changes.
         bool ionCompile = false;
         while (true) {
             if (terminate)
                 return;
-            if (HelperThreadState().canStartWasmCompile(lock) ||
-                (ionCompile = HelperThreadState().pendingIonCompileHasSufficientPriority(lock)) ||
-                HelperThreadState().canStartParseTask(lock) ||
-                HelperThreadState().canStartCompressionTask(lock) ||
-                HelperThreadState().canStartGCHelperTask(lock) ||
-                HelperThreadState().canStartGCParallelTask(lock))
+            if (HelperThreadState().canStartWasmCompile() ||
+                (ionCompile = HelperThreadState().pendingIonCompileHasSufficientPriority()) ||
+                HelperThreadState().canStartParseTask() ||
+                HelperThreadState().canStartCompressionTask() ||
+                HelperThreadState().canStartGCHelperTask() ||
+                HelperThreadState().canStartGCParallelTask())
             {
                 break;
             }
             HelperThreadState().wait(lock, GlobalHelperThreadState::PRODUCER);
         }
 
         // Dispatch tasks, prioritizing wasm work.
-        if (HelperThreadState().canStartWasmCompile(lock)) {
+        if (HelperThreadState().canStartWasmCompile()) {
             js::oom::SetThreadType(js::oom::THREAD_TYPE_ASMJS);
             handleWasmWorkload(lock);
         } else if (ionCompile) {
             js::oom::SetThreadType(js::oom::THREAD_TYPE_ION);
             handleIonWorkload(lock);
-        } else if (HelperThreadState().canStartParseTask(lock)) {
+        } else if (HelperThreadState().canStartParseTask()) {
             js::oom::SetThreadType(js::oom::THREAD_TYPE_PARSE);
             handleParseWorkload(lock);
-        } else if (HelperThreadState().canStartCompressionTask(lock)) {
+        } else if (HelperThreadState().canStartCompressionTask()) {
             js::oom::SetThreadType(js::oom::THREAD_TYPE_COMPRESS);
             handleCompressionWorkload(lock);
-        } else if (HelperThreadState().canStartGCHelperTask(lock)) {
+        } else if (HelperThreadState().canStartGCHelperTask()) {
             js::oom::SetThreadType(js::oom::THREAD_TYPE_GCHELPER);
             handleGCHelperWorkload(lock);
-        } else if (HelperThreadState().canStartGCParallelTask(lock)) {
+        } else if (HelperThreadState().canStartGCParallelTask()) {
             js::oom::SetThreadType(js::oom::THREAD_TYPE_GCPARALLEL);
             handleGCParallelWorkload(lock);
         } else {
             MOZ_CRASH("No task to perform");
         }
     }
 }
--- a/js/src/vm/HelperThreads.h
+++ b/js/src/vm/HelperThreads.h
@@ -116,104 +116,119 @@ class GlobalHelperThreadState
 
     bool ensureInitialized();
     void finish();
     void finishThreads();
 
     void lock();
     void unlock();
 
+#ifdef DEBUG
+    bool isLocked();
+#endif
+
     enum CondVar {
         // For notifying threads waiting for work that they may be able to make progress.
         CONSUMER,
 
         // 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(AutoLockHelperThreadState& locked, CondVar which,
               mozilla::TimeDuration timeout = mozilla::TimeDuration::Forever());
-    void notifyAll(CondVar which, const AutoLockHelperThreadState&);
-    void notifyOne(CondVar which, const AutoLockHelperThreadState&);
+    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();
         vector.popBack();
     }
 
-    IonBuilderVector& ionWorklist(const AutoLockHelperThreadState&) {
+    IonBuilderVector& ionWorklist() {
+        MOZ_ASSERT(isLocked());
         return ionWorklist_;
     }
-    IonBuilderVector& ionFinishedList(const AutoLockHelperThreadState&) {
+    IonBuilderVector& ionFinishedList() {
+        MOZ_ASSERT(isLocked());
         return ionFinishedList_;
     }
 
-    wasm::IonCompileTaskPtrVector& wasmWorklist(const AutoLockHelperThreadState&) {
+    wasm::IonCompileTaskPtrVector& wasmWorklist() {
+        MOZ_ASSERT(isLocked());
         return wasmWorklist_;
     }
-    wasm::IonCompileTaskPtrVector& wasmFinishedList(const AutoLockHelperThreadState&) {
+    wasm::IonCompileTaskPtrVector& wasmFinishedList() {
+        MOZ_ASSERT(isLocked());
         return wasmFinishedList_;
     }
 
-    ParseTaskVector& parseWorklist(const AutoLockHelperThreadState&) {
+    ParseTaskVector& parseWorklist() {
+        MOZ_ASSERT(isLocked());
         return parseWorklist_;
     }
-    ParseTaskVector& parseFinishedList(const AutoLockHelperThreadState&) {
+    ParseTaskVector& parseFinishedList() {
+        MOZ_ASSERT(isLocked());
         return parseFinishedList_;
     }
-    ParseTaskVector& parseWaitingOnGC(const AutoLockHelperThreadState&) {
+    ParseTaskVector& parseWaitingOnGC() {
+        MOZ_ASSERT(isLocked());
         return parseWaitingOnGC_;
     }
 
-    SourceCompressionTaskVector& compressionWorklist(const AutoLockHelperThreadState&) {
+    SourceCompressionTaskVector& compressionWorklist() {
+        MOZ_ASSERT(isLocked());
         return compressionWorklist_;
     }
 
-    GCHelperStateVector& gcHelperWorklist(const AutoLockHelperThreadState&) {
+    GCHelperStateVector& gcHelperWorklist() {
+        MOZ_ASSERT(isLocked());
         return gcHelperWorklist_;
     }
 
-    GCParallelTaskVector& gcParallelWorklist(const AutoLockHelperThreadState&) {
+    GCParallelTaskVector& gcParallelWorklist() {
+        MOZ_ASSERT(isLocked());
         return gcParallelWorklist_;
     }
 
-    bool canStartWasmCompile(const AutoLockHelperThreadState& lock);
-    bool canStartIonCompile(const AutoLockHelperThreadState& lock);
-    bool canStartParseTask(const AutoLockHelperThreadState& lock);
-    bool canStartCompressionTask(const AutoLockHelperThreadState& lock);
-    bool canStartGCHelperTask(const AutoLockHelperThreadState& lock);
-    bool canStartGCParallelTask(const AutoLockHelperThreadState& lock);
+    bool canStartWasmCompile();
+    bool canStartIonCompile();
+    bool canStartParseTask();
+    bool canStartCompressionTask();
+    bool canStartGCHelperTask();
+    bool canStartGCParallelTask();
 
     // Unlike the methods above, the value returned by this method can change
     // over time, even if the helper thread state lock is held throughout.
-    bool pendingIonCompileHasSufficientPriority(const AutoLockHelperThreadState& lock);
+    bool pendingIonCompileHasSufficientPriority();
 
-    jit::IonBuilder* highestPriorityPendingIonCompile(const AutoLockHelperThreadState& lock,
-                                                      bool remove = false);
-    HelperThread* lowestPriorityUnpausedIonCompileAtThreshold(
-        const AutoLockHelperThreadState& lock);
-    HelperThread* highestPriorityPausedIonCompile(const AutoLockHelperThreadState& lock);
+    jit::IonBuilder* highestPriorityPendingIonCompile(bool remove = false);
+    HelperThread* lowestPriorityUnpausedIonCompileAtThreshold();
+    HelperThread* highestPriorityPausedIonCompile();
 
-    uint32_t harvestFailedWasmJobs(const AutoLockHelperThreadState&) {
+    uint32_t harvestFailedWasmJobs() {
+        MOZ_ASSERT(isLocked());
         uint32_t n = numWasmFailedJobs;
         numWasmFailedJobs = 0;
         return n;
     }
-    void noteWasmFailure(const AutoLockHelperThreadState&) {
+    void noteWasmFailure() {
         // Be mindful to signal the main thread after calling this function.
+        MOZ_ASSERT(isLocked());
         numWasmFailedJobs++;
     }
-    bool wasmFailed(const AutoLockHelperThreadState&) {
+    bool wasmFailed() {
+        MOZ_ASSERT(isLocked());
         return bool(numWasmFailedJobs);
     }
 
     JSScript* finishParseTask(JSContext* cx, ParseTaskKind kind, void* token);
     void cancelParseTask(JSContext* cx, ParseTaskKind kind, void* token);
 
     void mergeParseTaskCompartment(JSContext* cx, ParseTask* parseTask,
                                    Handle<GlobalObject*> global,
@@ -224,32 +239,35 @@ class GlobalHelperThreadState
      * Number of wasm jobs that encountered failure for the active module.
      * Their parent is logically the main thread, and this number serves for harvesting.
      */
     uint32_t numWasmFailedJobs;
 
   public:
     JSScript* finishScriptParseTask(JSContext* cx, void* token);
     JSObject* finishModuleParseTask(JSContext* cx, void* token);
-    bool compressionInProgress(SourceCompressionTask* task, const AutoLockHelperThreadState& lock);
-    SourceCompressionTask* compressionTaskForSource(ScriptSource* ss, const AutoLockHelperThreadState& lock);
+    bool compressionInProgress(SourceCompressionTask* task);
+    SourceCompressionTask* compressionTaskForSource(ScriptSource* ss);
 
-    bool hasActiveThreads(const AutoLockHelperThreadState&);
+    bool hasActiveThreads();
     void waitForAllThreads();
 
     template <typename T>
     bool checkTaskThreadLimit(size_t maxThreads) const;
 
   private:
 
     /*
      * Lock protecting all mutable shared state accessed by helper threads, and
      * used by all condition variables.
      */
     js::Mutex helperLock;
+#ifdef DEBUG
+    mozilla::Atomic<PRThread*> lockOwner;
+#endif
 
     /* Condvars for threads waiting/notifying each other. */
     js::ConditionVariable consumerWakeup;
     js::ConditionVariable producerWakeup;
     js::ConditionVariable pauseWakeup;
 
     js::ConditionVariable& whichWakeup(CondVar which) {
         switch (which) {
@@ -436,32 +454,57 @@ class MOZ_RAII AutoLockHelperThreadState
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 
   public:
     explicit AutoLockHelperThreadState(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM)
       : Base(HelperThreadState().helperLock)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+
+#ifdef DEBUG
+        HelperThreadState().lockOwner = PR_GetCurrentThread();
+#endif
+    }
+
+    ~AutoLockHelperThreadState() {
+#ifdef DEBUG
+        HelperThreadState().lockOwner = nullptr;
+#endif
     }
 };
 
-class MOZ_RAII AutoUnlockHelperThreadState : public UnlockGuard<Mutex>
+class MOZ_RAII AutoUnlockHelperThreadState
 {
-    using Base = UnlockGuard<Mutex>;
+    // This is only in a Maybe so that we can update the DEBUG-only lockOwner
+    // thread in the proper order.
+    mozilla::Maybe<UnlockGuard<Mutex>> unlocked;
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 
   public:
 
     explicit AutoUnlockHelperThreadState(AutoLockHelperThreadState& locked
                                          MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
-      : Base(locked)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+
+#ifdef DEBUG
+        HelperThreadState().lockOwner = nullptr;
+#endif
+
+        unlocked.emplace(locked);
+    }
+
+    ~AutoUnlockHelperThreadState() {
+        unlocked.reset();
+
+#ifdef DEBUG
+        HelperThreadState().lockOwner = PR_GetCurrentThread();
+#endif
     }
 };
 
 struct ParseTask
 {
     ParseTaskKind kind;
     ExclusiveContext* cx;
     OwningCompileOptions options;
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -897,18 +897,20 @@ JSRuntime::assertCanLock(RuntimeLock whi
     // In the switch below, each case falls through to the one below it. None
     // of the runtime locks are reentrant, and when multiple locks are acquired
     // it must be done in the order below.
     switch (which) {
       case ExclusiveAccessLock:
         MOZ_ASSERT(exclusiveAccessOwner != PR_GetCurrentThread());
         MOZ_FALLTHROUGH;
       case HelperThreadStateLock:
+        MOZ_ASSERT(!HelperThreadState().isLocked());
         MOZ_FALLTHROUGH;
       case GCLock:
+        gc.assertCanLock();
         break;
       default:
         MOZ_CRASH();
     }
 }
 
 void
 js::AssertCurrentThreadCanLock(RuntimeLock which)
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -1417,42 +1417,63 @@ FreeOp::appendJitPoisonRange(const jit::
  * passed a non-const reference to this class.
  */
 class MOZ_RAII AutoLockGC
 {
   public:
     explicit AutoLockGC(JSRuntime* rt
                         MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : runtime_(rt)
+#ifdef DEBUG
+      , wasUnlocked_(false)
+#endif
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
         lock();
     }
 
     ~AutoLockGC() {
         unlock();
     }
 
     void lock() {
         MOZ_ASSERT(lockGuard_.isNothing());
         lockGuard_.emplace(runtime_->gc.lock);
+#ifdef DEBUG
+        runtime_->gc.lockOwner = PR_GetCurrentThread();
+#endif
     }
 
     void unlock() {
         MOZ_ASSERT(lockGuard_.isSome());
+#ifdef DEBUG
+        runtime_->gc.lockOwner = nullptr;
+#endif
         lockGuard_.reset();
+#ifdef DEBUG
+        wasUnlocked_ = true;
+#endif
     }
 
     js::LockGuard<js::Mutex>& guard() {
         return lockGuard_.ref();
     }
 
+#ifdef DEBUG
+    bool wasUnlocked() {
+        return wasUnlocked_;
+    }
+#endif
+
   private:
     JSRuntime* runtime_;
     mozilla::Maybe<js::LockGuard<js::Mutex>> lockGuard_;
+#ifdef DEBUG
+    bool wasUnlocked_;
+#endif
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 
     AutoLockGC(const AutoLockGC&) = delete;
     AutoLockGC& operator=(const AutoLockGC&) = delete;
 };
 
 class MOZ_RAII AutoUnlockGC
 {