Backed out changesets 45840c6628d7 and acadb164dbc5 (bug 941805) for causing bug 966782.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 03 Feb 2014 08:34:51 -0500
changeset 166626 80286bae0d73ae97bfa9e99dddb491fda7830bf6
parent 166610 e020404995daf891ba7376cf1a3f372fcb04d273
child 166627 c90c5daad2ad332a345605c6bc5d1996cd441911
child 166632 aa5a42f85d508e41b3c2f9224fa3ef5876ca65f4
push id4807
push userryanvm@gmail.com
push dateMon, 03 Feb 2014 20:47:31 +0000
treeherderfx-team@ba2cc1eda988 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs941805, 966782
milestone29.0a1
backs out45840c6628d7bd1853ec535d1be010d99596e808
acadb164dbc5947de50aff7643ed7120b44bd08f
Backed out changesets 45840c6628d7 and acadb164dbc5 (bug 941805) for causing bug 966782.
js/src/builtin/TestingFunctions.cpp
js/src/jit/AsmJS.cpp
js/src/jit/Ion.cpp
js/src/jit/JitCompartment.h
js/src/jsapi.cpp
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jsgc.cpp
js/src/jsscript.cpp
js/src/jsworkers.cpp
js/src/jsworkers.h
js/src/shell/js.cpp
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/vm/ThreadPool.cpp
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1400,21 +1400,17 @@ Neuter(JSContext *cx, unsigned argc, jsv
     args.rval().setUndefined();
     return true;
 }
 
 static bool
 WorkerThreadCount(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-#ifdef JS_THREADSAFE
-    args.rval().setInt32(cx->runtime()->useHelperThreads() ? WorkerThreadState().threadCount : 0);
-#else
-    args.rval().setInt32(0);
-#endif
+    args.rval().setNumber(static_cast<double>(cx->runtime()->workerThreadCount()));
     return true;
 }
 
 static const JSFunctionSpecWithHelp TestingFunctions[] = {
     JS_FN_HELP("gc", ::GC, 0, 0,
 "gc([obj] | 'compartment')",
 "  Run the garbage collector. When obj is given, GC only its compartment.\n"
 "  If 'compartment' is given, GC any compartments that were scheduled for\n"
--- a/js/src/jit/AsmJS.cpp
+++ b/js/src/jit/AsmJS.cpp
@@ -5359,75 +5359,74 @@ CheckFunctionsSequential(ModuleCompiler 
 
 #ifdef JS_THREADSAFE
 
 // Currently, only one asm.js parallel compilation is allowed at a time.
 // This RAII class attempts to claim this parallel compilation using atomic ops
 // on rt->workerThreadState->asmJSCompilationInProgress.
 class ParallelCompilationGuard
 {
-    bool parallelState_;
+    WorkerThreadState *parallelState_;
   public:
-    ParallelCompilationGuard() : parallelState_(false) {}
+    ParallelCompilationGuard() : parallelState_(nullptr) {}
     ~ParallelCompilationGuard() {
         if (parallelState_) {
-            JS_ASSERT(WorkerThreadState().asmJSCompilationInProgress == true);
-            WorkerThreadState().asmJSCompilationInProgress = false;
+            JS_ASSERT(parallelState_->asmJSCompilationInProgress == true);
+            parallelState_->asmJSCompilationInProgress = false;
         }
     }
-    bool claim() {
+    bool claim(WorkerThreadState *state) {
         JS_ASSERT(!parallelState_);
-        if (!WorkerThreadState().asmJSCompilationInProgress.compareExchange(false, true))
+        if (!state->asmJSCompilationInProgress.compareExchange(false, true))
             return false;
-        parallelState_ = true;
+        parallelState_ = state;
         return true;
     }
 };
 
 static bool
 ParallelCompilationEnabled(ExclusiveContext *cx)
 {
     // If 'cx' isn't a JSContext, then we are already off the main thread so
     // off-thread compilation must be enabled. However, since there are a fixed
     // number of worker threads and one is already being consumed by this
     // parsing task, ensure that there another free thread to avoid deadlock.
     // (Note: there is at most one thread used for parsing so we don't have to
     // worry about general dining philosophers.)
-    if (WorkerThreadState().threadCount <= 1)
-        return false;
-
     if (!cx->isJSContext())
-        return true;
+        return cx->workerThreadState()->numThreads > 1;
+
     return cx->asJSContext()->runtime()->canUseParallelIonCompilation();
 }
 
 // State of compilation as tracked and updated by the main thread.
 struct ParallelGroupState
 {
+    WorkerThreadState &state;
     js::Vector<AsmJSParallelTask> &tasks;
     int32_t outstandingJobs; // Good work, jobs!
     uint32_t compiledJobs;
 
-    ParallelGroupState(js::Vector<AsmJSParallelTask> &tasks)
-      : tasks(tasks), outstandingJobs(0), compiledJobs(0)
+    ParallelGroupState(WorkerThreadState &state, js::Vector<AsmJSParallelTask> &tasks)
+      : state(state), tasks(tasks), outstandingJobs(0), compiledJobs(0)
     { }
 };
 
 // Block until a worker-assigned LifoAlloc becomes finished.
 static AsmJSParallelTask *
 GetFinishedCompilation(ModuleCompiler &m, ParallelGroupState &group)
 {
-    AutoLockWorkerThreadState lock;
-
-    while (!WorkerThreadState().asmJSWorkerFailed()) {
-        if (!WorkerThreadState().asmJSFinishedList().empty()) {
+    AutoLockWorkerThreadState lock(*m.cx()->workerThreadState());
+
+    while (!group.state.asmJSWorkerFailed()) {
+        if (!group.state.asmJSFinishedList.empty()) {
             group.outstandingJobs--;
-            return WorkerThreadState().asmJSFinishedList().popCopy();
+            return group.state.asmJSFinishedList.popCopy();
         }
-        WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
+        group.state.wait(WorkerThreadState::CONSUMER);
     }
 
     return nullptr;
 }
 
 static bool
 GenerateCodeForFinishedJob(ModuleCompiler &m, ParallelGroupState &group, AsmJSParallelTask **outTask)
 {
@@ -5467,39 +5466,34 @@ GetUnusedTask(ParallelGroupState &group,
         return false;
     *outTask = &group.tasks[i];
     return true;
 }
 
 static bool
 CheckFunctionsParallelImpl(ModuleCompiler &m, ParallelGroupState &group)
 {
-#ifdef DEBUG
-    {
-        AutoLockWorkerThreadState lock;
-        JS_ASSERT(WorkerThreadState().asmJSWorklist().empty());
-        JS_ASSERT(WorkerThreadState().asmJSFinishedList().empty());
-    }
-#endif
-    WorkerThreadState().resetAsmJSFailureState();
+    JS_ASSERT(group.state.asmJSWorklist.empty());
+    JS_ASSERT(group.state.asmJSFinishedList.empty());
+    group.state.resetAsmJSFailureState();
 
     for (unsigned i = 0; PeekToken(m.parser()) == TOK_FUNCTION; i++) {
         // Get exclusive access to an empty LifoAlloc from the thread group's pool.
         AsmJSParallelTask *task = nullptr;
         if (!GetUnusedTask(group, i, &task) && !GenerateCodeForFinishedJob(m, group, &task))
             return false;
 
         // Generate MIR into the LifoAlloc on the main thread.
         MIRGenerator *mir;
         ModuleCompiler::Func *func;
         if (!CheckFunction(m, task->lifo, &mir, &func))
             return false;
 
         // Perform optimizations and LIR generation on a worker thread.
-        task->init(m.cx()->compartment()->runtimeFromAnyThread(), func, mir);
+        task->init(func, mir);
         if (!StartOffThreadAsmJSCompile(m.cx(), task))
             return false;
 
         group.outstandingJobs++;
     }
 
     // Block for all outstanding workers to complete.
     while (group.outstandingJobs > 0) {
@@ -5508,101 +5502,97 @@ CheckFunctionsParallelImpl(ModuleCompile
             return false;
     }
 
     if (!CheckAllFunctionsDefined(m))
         return false;
 
     JS_ASSERT(group.outstandingJobs == 0);
     JS_ASSERT(group.compiledJobs == m.numFunctions());
-#ifdef DEBUG
-    {
-        AutoLockWorkerThreadState lock;
-        JS_ASSERT(WorkerThreadState().asmJSWorklist().empty());
-        JS_ASSERT(WorkerThreadState().asmJSFinishedList().empty());
-    }
-#endif
-    JS_ASSERT(!WorkerThreadState().asmJSWorkerFailed());
+    JS_ASSERT(group.state.asmJSWorklist.empty());
+    JS_ASSERT(group.state.asmJSFinishedList.empty());
+    JS_ASSERT(!group.state.asmJSWorkerFailed());
     return true;
 }
 
 static void
 CancelOutstandingJobs(ModuleCompiler &m, ParallelGroupState &group)
 {
     // This is failure-handling code, so it's not allowed to fail.
     // The problem is that all memory for compilation is stored in LifoAllocs
     // maintained in the scope of CheckFunctionsParallel() -- so in order
     // for that function to safely return, and thereby remove the LifoAllocs,
     // none of that memory can be in use or reachable by workers.
 
     JS_ASSERT(group.outstandingJobs >= 0);
     if (!group.outstandingJobs)
         return;
 
-    AutoLockWorkerThreadState lock;
+    AutoLockWorkerThreadState lock(*m.cx()->workerThreadState());
 
     // From the compiling tasks, eliminate those waiting for worker assignation.
-    group.outstandingJobs -= WorkerThreadState().asmJSWorklist().length();
-    WorkerThreadState().asmJSWorklist().clear();
+    group.outstandingJobs -= group.state.asmJSWorklist.length();
+    group.state.asmJSWorklist.clear();
 
     // From the compiling tasks, eliminate those waiting for codegen.
-    group.outstandingJobs -= WorkerThreadState().asmJSFinishedList().length();
-    WorkerThreadState().asmJSFinishedList().clear();
+    group.outstandingJobs -= group.state.asmJSFinishedList.length();
+    group.state.asmJSFinishedList.clear();
 
     // Eliminate tasks that failed without adding to the finished list.
-    group.outstandingJobs -= WorkerThreadState().harvestFailedAsmJSJobs();
+    group.outstandingJobs -= group.state.harvestFailedAsmJSJobs();
 
     // Any remaining tasks are therefore undergoing active compilation.
     JS_ASSERT(group.outstandingJobs >= 0);
     while (group.outstandingJobs > 0) {
-        WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
-
-        group.outstandingJobs -= WorkerThreadState().harvestFailedAsmJSJobs();
-        group.outstandingJobs -= WorkerThreadState().asmJSFinishedList().length();
-        WorkerThreadState().asmJSFinishedList().clear();
+        group.state.wait(WorkerThreadState::CONSUMER);
+
+        group.outstandingJobs -= group.state.harvestFailedAsmJSJobs();
+        group.outstandingJobs -= group.state.asmJSFinishedList.length();
+        group.state.asmJSFinishedList.clear();
     }
 
     JS_ASSERT(group.outstandingJobs == 0);
-    JS_ASSERT(WorkerThreadState().asmJSWorklist().empty());
-    JS_ASSERT(WorkerThreadState().asmJSFinishedList().empty());
+    JS_ASSERT(group.state.asmJSWorklist.empty());
+    JS_ASSERT(group.state.asmJSFinishedList.empty());
 }
 
 static const size_t LIFO_ALLOC_PARALLEL_CHUNK_SIZE = 1 << 12;
 
 static bool
 CheckFunctionsParallel(ModuleCompiler &m)
 {
     // If parallel compilation isn't enabled (not enough cores, disabled by
     // pref, etc) or another thread is currently compiling asm.js in parallel,
     // fall back to sequential compilation. (We could lift the latter
     // constraint by hoisting asmJS* state out of WorkerThreadState so multiple
     // concurrent asm.js parallel compilations don't race.)
     ParallelCompilationGuard g;
-    if (!ParallelCompilationEnabled(m.cx()) || !g.claim())
+    if (!ParallelCompilationEnabled(m.cx()) || !g.claim(m.cx()->workerThreadState()))
         return CheckFunctionsSequential(m);
 
     // Saturate all worker threads plus the main thread.
-    size_t numParallelJobs = WorkerThreadState().threadCount + 1;
+    WorkerThreadState &state = *m.cx()->workerThreadState();
+    size_t numParallelJobs = state.numThreads + 1;
 
     // Allocate scoped AsmJSParallelTask objects. Each contains a unique
     // LifoAlloc that provides all necessary memory for compilation.
     js::Vector<AsmJSParallelTask, 0> tasks(m.cx());
     if (!tasks.initCapacity(numParallelJobs))
         return false;
 
     for (size_t i = 0; i < numParallelJobs; i++)
         tasks.infallibleAppend(LIFO_ALLOC_PARALLEL_CHUNK_SIZE);
 
     // With compilation memory in-scope, dispatch worker threads.
-    ParallelGroupState group(tasks);
+    ParallelGroupState group(state, tasks);
     if (!CheckFunctionsParallelImpl(m, group)) {
         CancelOutstandingJobs(m, group);
 
         // If failure was triggered by a worker thread, report error.
-        if (void *maybeFunc = WorkerThreadState().maybeAsmJSFailedFunction()) {
+        if (void *maybeFunc = state.maybeAsmJSFailedFunction()) {
             ModuleCompiler::Func *func = reinterpret_cast<ModuleCompiler::Func *>(maybeFunc);
             return m.failOffset(func->srcOffset(), "allocation failure during compilation");
         }
 
         // Otherwise, the error occurred on the main thread and was already reported.
         return false;
     }
     return true;
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -508,30 +508,25 @@ jit::FinishOffThreadBuilder(IonBuilder *
     // destroy the builder and all other data accumulated during compilation,
     // except any final codegen (which includes an assembler and needs to be
     // explicitly destroyed).
     js_delete(builder->backgroundCodegen());
     js_delete(builder->alloc().lifoAlloc());
 }
 
 static inline void
-FinishAllOffThreadCompilations(JSCompartment *comp)
+FinishAllOffThreadCompilations(JitCompartment *ion)
 {
-#ifdef JS_THREADSAFE
-    AutoLockWorkerThreadState lock;
-    GlobalWorkerThreadState::IonBuilderVector &finished = WorkerThreadState().ionFinishedList();
-
-    for (size_t i = 0; i < finished.length(); i++) {
-        IonBuilder *builder = finished[i];
-        if (builder->compartment == CompileCompartment::get(comp)) {
-            FinishOffThreadBuilder(builder);
-            WorkerThreadState().remove(finished, &i);
-        }
+    OffThreadCompilationVector &compilations = ion->finishedOffThreadCompilations();
+
+    for (size_t i = 0; i < compilations.length(); i++) {
+        IonBuilder *builder = compilations[i];
+        FinishOffThreadBuilder(builder);
     }
-#endif
+    compilations.clear();
 }
 
 /* static */ void
 JitRuntime::Mark(JSTracer *trc)
 {
     JS_ASSERT(!trc->runtime->isHeapMinorCollecting());
     Zone *zone = trc->runtime->atomsCompartment()->zone();
     for (gc::CellIterUnderGC i(zone, gc::FINALIZE_JITCODE); !i.done(); i.next()) {
@@ -543,17 +538,17 @@ JitRuntime::Mark(JSTracer *trc)
 void
 JitCompartment::mark(JSTracer *trc, JSCompartment *compartment)
 {
     // Cancel any active or pending off thread compilations. Note that the
     // MIR graph does not hold any nursery pointers, so there's no need to
     // do this for minor GCs.
     JS_ASSERT(!trc->runtime->isHeapMinorCollecting());
     CancelOffThreadIonCompile(compartment, nullptr);
-    FinishAllOffThreadCompilations(compartment);
+    FinishAllOffThreadCompilations(this);
 
     // Free temporary OSR buffer.
     rt->freeOsrTempData();
 }
 
 void
 JitCompartment::sweep(FreeOp *fop)
 {
@@ -1512,97 +1507,79 @@ CompileBackEnd(MIRGenerator *mir, MacroA
     return GenerateCode(mir, lir, maybeMasm);
 }
 
 void
 AttachFinishedCompilations(JSContext *cx)
 {
 #ifdef JS_THREADSAFE
     JitCompartment *ion = cx->compartment()->jitCompartment();
-    if (!ion)
+    if (!ion || !cx->runtime()->workerThreadState)
         return;
 
     types::AutoEnterAnalysis enterTypes(cx);
-    AutoLockWorkerThreadState lock;
-
-    GlobalWorkerThreadState::IonBuilderVector &finished = WorkerThreadState().ionFinishedList();
-
-    // Incorporate any off thread compilations for the compartment which have
-    // finished, failed or have been cancelled.
-    while (true) {
-        IonBuilder *builder = nullptr;
-
-        // Find a finished builder for the compartment.
-        for (size_t i = 0; i < finished.length(); i++) {
-            IonBuilder *testBuilder = finished[i];
-            if (testBuilder->compartment == CompileCompartment::get(cx->compartment())) {
-                builder = testBuilder;
-                WorkerThreadState().remove(finished, &i);
-                break;
-            }
-        }
-        if (!builder)
-            break;
+    AutoLockWorkerThreadState lock(*cx->runtime()->workerThreadState);
+
+    OffThreadCompilationVector &compilations = ion->finishedOffThreadCompilations();
+
+    // Incorporate any off thread compilations which have finished, failed or
+    // have been cancelled.
+    while (!compilations.empty()) {
+        IonBuilder *builder = compilations.popCopy();
 
         if (CodeGenerator *codegen = builder->backgroundCodegen()) {
             RootedScript script(cx, builder->script());
             IonContext ictx(cx, &builder->alloc());
 
             // Root the assembler until the builder is finished below. As it
             // was constructed off thread, the assembler has not been rooted
             // previously, though any GC activity would discard the builder.
             codegen->masm.constructRoot(cx);
 
             bool success;
             {
                 // Release the worker thread lock and root the compiler for GC.
                 AutoTempAllocatorRooter root(cx, &builder->alloc());
-                AutoUnlockWorkerThreadState unlock;
+                AutoUnlockWorkerThreadState unlock(cx->runtime());
                 AutoFlushCache afc("AttachFinishedCompilations", cx->runtime()->jitRuntime());
                 success = codegen->link(cx, builder->constraints());
             }
 
             if (!success) {
                 // Silently ignore OOM during code generation, we're at an
                 // operation callback and can't propagate failures.
                 cx->clearPendingException();
             }
         } else {
             if (builder->abortReason() == AbortReason_Disable)
                 SetIonScript(builder->script(), builder->info().executionMode(), ION_DISABLED_SCRIPT);
         }
 
         FinishOffThreadBuilder(builder);
     }
+
+    compilations.clear();
 #endif
 }
 
 static const size_t BUILDER_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 1 << 12;
 
 static inline bool
 OffThreadCompilationAvailable(JSContext *cx)
 {
-#ifdef JS_THREADSAFE
     // Even if off thread compilation is enabled, compilation must still occur
     // on the main thread in some cases. Do not compile off thread during an
     // incremental GC, as this may trip incremental read barriers.
     //
-    // Require cpuCount > 1 so that Ion compilation jobs and main-thread
-    // execution are not competing for the same resources.
-    //
     // Skip off thread compilation if PC count profiling is enabled, as
     // CodeGenerator::maybeCreateScriptCounts will not attach script profiles
     // when running off thread.
     return cx->runtime()->canUseParallelIonCompilation()
-        && WorkerThreadState().cpuCount > 1
         && cx->runtime()->gcIncrementalState == gc::NO_INCREMENTAL
         && !cx->runtime()->profilingScripts;
-#else
-    return false;
-#endif
 }
 
 static void
 TrackAllProperties(JSContext *cx, JSObject *obj)
 {
     JS_ASSERT(obj->hasSingletonType());
 
     for (Shape::Range<NoGC> range(obj->lastProperty()); !range.empty(); range.popFront())
@@ -1850,22 +1827,17 @@ CheckScriptSize(JSContext *cx, JSScript*
         }
 
         return Method_Compiled;
     }
 
     if (script->length() > MAX_MAIN_THREAD_SCRIPT_SIZE ||
         numLocalsAndArgs > MAX_MAIN_THREAD_LOCALS_AND_ARGS)
     {
-#ifdef JS_THREADSAFE
-        size_t cpuCount = WorkerThreadState().cpuCount;
-#else
-        size_t cpuCount = 1;
-#endif
-        if (cx->runtime()->canUseParallelIonCompilation() && cpuCount > 1) {
+        if (cx->runtime()->canUseParallelIonCompilation()) {
             // Even if off thread compilation is enabled, there are cases where
             // compilation must still occur on the main thread. Don't compile
             // in these cases (except when profiling scripts, as compilations
             // occurring with profiling should reflect those without), but do
             // not forbid compilation so that the script may be compiled later.
             if (!OffThreadCompilationAvailable(cx) && !cx->runtime()->profilingScripts) {
                 IonSpew(IonSpew_Abort,
                         "Script too large for main thread, skipping (%u bytes) (%u locals/args)",
@@ -2512,17 +2484,17 @@ InvalidateActivation(FreeOp *fop, uint8_
 }
 
 void
 jit::StopAllOffThreadCompilations(JSCompartment *comp)
 {
     if (!comp->jitCompartment())
         return;
     CancelOffThreadIonCompile(comp, nullptr);
-    FinishAllOffThreadCompilations(comp);
+    FinishAllOffThreadCompilations(comp->jitCompartment());
 }
 
 void
 jit::InvalidateAll(FreeOp *fop, Zone *zone)
 {
     for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
         StopAllOffThreadCompilations(comp);
 
--- a/js/src/jit/JitCompartment.h
+++ b/js/src/jit/JitCompartment.h
@@ -54,16 +54,18 @@ struct EnterJitData
 };
 
 typedef void (*EnterJitCode)(void *code, unsigned argc, Value *argv, StackFrame *fp,
                              CalleeToken calleeToken, JSObject *scopeChain,
                              size_t numStackValues, Value *vp);
 
 class IonBuilder;
 
+typedef Vector<IonBuilder*, 0, SystemAllocPolicy> OffThreadCompilationVector;
+
 // ICStubSpace is an abstraction for allocation policy and storage for stub data.
 // There are two kinds of stubs: optimized stubs and fallback stubs (the latter
 // also includes stubs that can make non-tail calls that can GC).
 //
 // Optimized stubs are allocated per-compartment and are always purged when
 // JIT-code is discarded. Fallback stubs are allocated per BaselineScript and
 // are only destroyed when the BaselineScript is destroyed.
 class ICStubSpace
@@ -325,16 +327,22 @@ class JitRuntime
 
 class JitCompartment
 {
     friend class JitActivation;
 
     // Ion state for the compartment's runtime.
     JitRuntime *rt;
 
+    // Any scripts for which off thread compilation has successfully finished,
+    // failed, or been cancelled. All off thread compilations which are started
+    // will eventually appear in this list asynchronously. Protected by the
+    // runtime's analysis lock.
+    OffThreadCompilationVector finishedOffThreadCompilations_;
+
     // Map ICStub keys to ICStub shared code objects.
     typedef WeakValueCache<uint32_t, ReadBarriered<JitCode> > ICStubCodeMap;
     ICStubCodeMap *stubCodes_;
 
     // Keep track of offset into various baseline stubs' code at return
     // point from called script.
     void *baselineCallReturnAddr_;
     void *baselineGetPropReturnAddr_;
@@ -348,16 +356,20 @@ class JitCompartment
     // pointers. This has to be a weak pointer to avoid keeping the whole
     // compartment alive.
     ReadBarriered<JitCode> stringConcatStub_;
     ReadBarriered<JitCode> parallelStringConcatStub_;
 
     JitCode *generateStringConcatStub(JSContext *cx, ExecutionMode mode);
 
   public:
+    OffThreadCompilationVector &finishedOffThreadCompilations() {
+        return finishedOffThreadCompilations_;
+    }
+
     JitCode *getStubCode(uint32_t key) {
         ICStubCodeMap::AddPtr p = stubCodes_->lookupForAdd(key);
         if (p)
             return p->value();
         return nullptr;
     }
     bool putStubCode(uint32_t key, Handle<JitCode *> stubCode) {
         // Make sure to do a lookupForAdd(key) and then insert into that slot, because
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4524,17 +4524,17 @@ JS::FinishOffThreadScript(JSContext *may
 {
 #ifdef JS_THREADSAFE
     JS_ASSERT(CurrentThreadCanAccessRuntime(rt));
 
     Maybe<AutoLastFrameCheck> lfc;
     if (maybecx)
         lfc.construct(maybecx);
 
-    return WorkerThreadState().finishParseTask(maybecx, rt, token);
+    return rt->workerThreadState->finishParseTask(maybecx, rt, token);
 #else
     MOZ_ASSUME_UNREACHABLE("Off thread compilation is not available.");
 #endif
 }
 
 JS_PUBLIC_API(JSScript *)
 JS_CompileScript(JSContext *cx, JS::HandleObject obj, const char *ascii,
                  size_t length, const JS::CompileOptions &options)
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -1047,16 +1047,19 @@ js_HandleExecutionInterrupt(JSContext *c
 }
 
 js::ThreadSafeContext::ThreadSafeContext(JSRuntime *rt, PerThreadData *pt, ContextKind kind)
   : ContextFriendFields(rt),
     contextKind_(kind),
     perThreadData(pt),
     allocator_(nullptr)
 {
+#ifdef JS_THREADSAFE
+    JS_ASSERT_IF(kind == Context_Exclusive, rt->workerThreadState != nullptr);
+#endif
 }
 
 bool
 ThreadSafeContext::isForkJoinContext() const
 {
     return contextKind_ == Context_ForkJoin;
 }
 
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -277,16 +277,18 @@ struct ThreadSafeContext : ContextFriend
 
     // Accessors for immutable runtime data.
     JSAtomState &names() { return runtime_->atomState; }
     StaticStrings &staticStrings() { return runtime_->staticStrings; }
     const JS::AsmJSCacheOps &asmJSCacheOps() { return runtime_->asmJSCacheOps; }
     PropertyName *emptyString() { return runtime_->emptyString; }
     FreeOp *defaultFreeOp() { return runtime_->defaultFreeOp(); }
     bool useHelperThreads() { return runtime_->useHelperThreads(); }
+    unsigned cpuCount() { return runtime_->cpuCount(); }
+    size_t workerThreadCount() { return runtime_->workerThreadCount(); }
     void *runtimeAddressForJit() { return runtime_; }
     void *stackLimitAddress(StackKind kind) { return &runtime_->mainThread.nativeStackLimit[kind]; }
     void *stackLimitAddressForJitCode(StackKind kind);
     size_t gcSystemPageSize() { return runtime_->gcSystemPageSize; }
     bool signalHandlersInstalled() const { return runtime_->signalHandlersInstalled(); }
     bool jitSupportsFloatingPoint() const { return runtime_->jitSupportsFloatingPoint; }
 
     // Thread local data that may be accessed freely.
@@ -383,16 +385,25 @@ class ExclusiveContext : public ThreadSa
     }
     JSCompartment *atomsCompartment() {
         return runtime_->atomsCompartment();
     }
     ScriptDataTable &scriptDataTable() {
         return runtime_->scriptDataTable();
     }
 
+#ifdef JS_THREADSAFE
+    // Since JSRuntime::workerThreadState is necessarily initialized from the
+    // main thread before the first worker thread can access it, there is no
+    // possibility for a race read/writing it.
+    WorkerThreadState *workerThreadState() {
+        return runtime_->workerThreadState;
+    }
+#endif
+
     // Methods specific to any WorkerThread for the context.
     frontend::CompileError &addPendingCompileError();
     void addPendingOverRecursed();
 };
 
 inline void
 MaybeCheckStackRoots(ExclusiveContext *cx)
 {
@@ -1024,17 +1035,17 @@ bool intrinsic_NewParallelArray(JSContex
 class AutoLockForExclusiveAccess
 {
 #ifdef JS_THREADSAFE
     JSRuntime *runtime;
 
     void init(JSRuntime *rt) {
         runtime = rt;
         if (runtime->numExclusiveThreads) {
-            runtime->assertCanLock(ExclusiveAccessLock);
+            runtime->assertCanLock(JSRuntime::ExclusiveAccessLock);
             PR_Lock(runtime->exclusiveAccessLock);
 #ifdef DEBUG
             runtime->exclusiveAccessOwner = PR_GetCurrentThread();
 #endif
         } else {
             JS_ASSERT(!runtime->mainThreadHasExclusiveAccess);
             runtime->mainThreadHasExclusiveAccess = true;
         }
@@ -1079,17 +1090,17 @@ class AutoLockForExclusiveAccess
 class AutoLockForCompilation
 {
 #ifdef JS_THREADSAFE
     JSRuntime *runtime;
 
     void init(JSRuntime *rt) {
         runtime = rt;
         if (runtime->numCompilationThreads) {
-            runtime->assertCanLock(CompilationLock);
+            runtime->assertCanLock(JSRuntime::CompilationLock);
             PR_Lock(runtime->compilationLock);
 #ifdef DEBUG
             runtime->compilationLockOwner = PR_GetCurrentThread();
 #endif
         } else {
 #ifdef DEBUG
             JS_ASSERT(!runtime->mainThreadHasCompilationLock);
             runtime->mainThreadHasCompilationLock = true;
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1708,19 +1708,19 @@ ArenaLists::refillFreeList(ThreadSafeCon
              * return whatever value we get. If we aren't in a ForkJoin
              * session (i.e. we are in a worker thread async with the main
              * thread), we need to first ensure the main thread is not in a GC
              * session.
              */
             mozilla::Maybe<AutoLockWorkerThreadState> lock;
             JSRuntime *rt = zone->runtimeFromAnyThread();
             if (rt->exclusiveThreadsPresent()) {
-                lock.construct();
+                lock.construct<WorkerThreadState &>(*rt->workerThreadState);
                 while (rt->isHeapBusy())
-                    WorkerThreadState().wait(GlobalWorkerThreadState::PRODUCER);
+                    rt->workerThreadState->wait(WorkerThreadState::PRODUCER);
             }
 
             void *thing = cx->allocator()->arenas.allocateFromArenaInline(zone, thingKind);
             if (thing)
                 return thing;
 #else
             MOZ_CRASH();
 #endif
@@ -4295,37 +4295,37 @@ AutoTraceSession::AutoTraceSession(JSRun
     // this lock during GC and the other thread is waiting, make sure we hold
     // the exclusive access lock during GC sessions.
     JS_ASSERT(rt->currentThreadHasExclusiveAccess());
 
     if (rt->exclusiveThreadsPresent()) {
         // Lock the worker thread state when changing the heap state in the
         // presence of exclusive threads, to avoid racing with refillFreeList.
 #ifdef JS_THREADSAFE
-        AutoLockWorkerThreadState lock;
+        AutoLockWorkerThreadState lock(*rt->workerThreadState);
         rt->heapState = heapState;
 #else
         MOZ_CRASH();
 #endif
     } else {
         rt->heapState = heapState;
     }
 }
 
 AutoTraceSession::~AutoTraceSession()
 {
     JS_ASSERT(runtime->isHeapBusy());
 
     if (runtime->exclusiveThreadsPresent()) {
 #ifdef JS_THREADSAFE
-        AutoLockWorkerThreadState lock;
+        AutoLockWorkerThreadState lock(*runtime->workerThreadState);
         runtime->heapState = prevState;
 
         // Notify any worker threads waiting for the trace session to end.
-        WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
+        runtime->workerThreadState->notifyAll(WorkerThreadState::PRODUCER);
 #else
         MOZ_CRASH();
 #endif
     } else {
         runtime->heapState = prevState;
     }
 }
 
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -1297,25 +1297,21 @@ ScriptSource::setSourceCopy(ExclusiveCon
     // worker threads:
     //  - If we are on a worker thread, there must be another worker thread to
     //    execute our compression task.
     //  - If we are on the main thread, there must be at least two worker
     //    threads since at most one worker thread can be blocking on the main
     //    thread (see WorkerThreadState::canStartParseTask) which would cause a
     //    deadlock if there wasn't a second worker thread that could make
     //    progress on our compression task.
-#ifdef JS_THREADSAFE
-    bool canCompressOffThread =
-        WorkerThreadState().cpuCount > 1 &&
-        WorkerThreadState().threadCount >= 2;
-#else
-    bool canCompressOffThread = false;
-#endif
     const size_t HUGE_SCRIPT = 5 * 1024 * 1024;
-    if (length < HUGE_SCRIPT && canCompressOffThread) {
+    if (length < HUGE_SCRIPT &&
+        cx->cpuCount() > 1 &&
+        cx->workerThreadCount() >= 2)
+    {
         task->ss = this;
         task->chars = src;
         ready_ = false;
         if (!StartOffThreadCompression(cx, task))
             return false;
     } else {
         if (!adjustDataSize(sizeof(jschar) * length))
             return false;
--- a/js/src/jsworkers.cpp
+++ b/js/src/jsworkers.cpp
@@ -9,163 +9,175 @@
 #ifdef JS_THREADSAFE
 
 #include "mozilla/DebugOnly.h"
 
 #include "jsnativestack.h"
 #include "prmjtime.h"
 
 #include "frontend/BytecodeCompiler.h"
+#include "jit/ExecutionModeInlines.h"
 #include "jit/IonBuilder.h"
 #include "vm/Debugger.h"
 
 #include "jscntxtinlines.h"
 #include "jscompartmentinlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 using namespace js;
 
 using mozilla::ArrayLength;
 using mozilla::DebugOnly;
 
-namespace js {
-
-GlobalWorkerThreadState gWorkerThreadState;
-
-} // namespace js
-
 bool
 js::EnsureWorkerThreadsInitialized(ExclusiveContext *cx)
 {
     // If 'cx' is not a JSContext, we are already off the main thread and the
     // worker threads would have already been initialized.
-    if (!cx->isJSContext())
+    if (!cx->isJSContext()) {
+        JS_ASSERT(cx->workerThreadState() != nullptr);
+        return true;
+    }
+
+    JSRuntime *rt = cx->asJSContext()->runtime();
+    if (rt->workerThreadState)
         return true;
 
-    return WorkerThreadState().ensureInitialized();
-}
-
-static size_t
-ThreadCountForCPUCount(size_t cpuCount)
-{
-    return Max(cpuCount, (size_t)2);
-}
+    rt->workerThreadState = rt->new_<WorkerThreadState>(rt);
+    if (!rt->workerThreadState)
+        return false;
 
-void
-js::SetFakeCPUCount(size_t count)
-{
-    // This must be called before the threads have been initialized.
-    JS_ASSERT(!WorkerThreadState().threads);
+    if (!rt->workerThreadState->init()) {
+        js_delete(rt->workerThreadState);
+        rt->workerThreadState = nullptr;
+        return false;
+    }
 
-    WorkerThreadState().cpuCount = count;
-    WorkerThreadState().threadCount = ThreadCountForCPUCount(count);
+    return true;
 }
 
 #ifdef JS_ION
 
 bool
 js::StartOffThreadAsmJSCompile(ExclusiveContext *cx, AsmJSParallelTask *asmData)
 {
     // Threads already initialized by the AsmJS compiler.
+    JS_ASSERT(cx->workerThreadState() != nullptr);
     JS_ASSERT(asmData->mir);
     JS_ASSERT(asmData->lir == nullptr);
 
-    AutoLockWorkerThreadState lock;
+    WorkerThreadState &state = *cx->workerThreadState();
+    JS_ASSERT(state.numThreads);
+
+    AutoLockWorkerThreadState lock(state);
 
     // Don't append this task if another failed.
-    if (WorkerThreadState().asmJSWorkerFailed())
+    if (state.asmJSWorkerFailed())
         return false;
 
-    if (!WorkerThreadState().asmJSWorklist().append(asmData))
+    if (!state.asmJSWorklist.append(asmData))
         return false;
 
-    WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER);
+    state.notifyOne(WorkerThreadState::PRODUCER);
     return true;
 }
 
 bool
 js::StartOffThreadIonCompile(JSContext *cx, jit::IonBuilder *builder)
 {
     if (!EnsureWorkerThreadsInitialized(cx))
         return false;
 
-    AutoLockWorkerThreadState lock;
+    WorkerThreadState &state = *cx->runtime()->workerThreadState;
+    JS_ASSERT(state.numThreads);
 
-    if (!WorkerThreadState().ionWorklist().append(builder))
+    AutoLockWorkerThreadState lock(state);
+
+    if (!state.ionWorklist.append(builder))
         return false;
 
     cx->runtime()->addCompilationThread();
 
-    WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
+    state.notifyAll(WorkerThreadState::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.
+ * been cancelled into the Ion compartment's finished compilations list.
+ * All off thread compilations which are started must eventually be finished.
  */
 static void
 FinishOffThreadIonCompile(jit::IonBuilder *builder)
 {
-    WorkerThreadState().ionFinishedList().append(builder);
+    JSCompartment *compartment = builder->script()->compartment();
+    JS_ASSERT(compartment->runtimeFromAnyThread()->workerThreadState);
+    JS_ASSERT(compartment->runtimeFromAnyThread()->workerThreadState->isLocked());
+
+    compartment->jitCompartment()->finishedOffThreadCompilations().append(builder);
 }
 
 #endif // JS_ION
 
 static inline bool
 CompiledScriptMatches(JSCompartment *compartment, JSScript *script, JSScript *target)
 {
     if (script)
         return target == script;
     return target->compartment() == compartment;
 }
 
 void
 js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
 {
 #ifdef JS_ION
+    JSRuntime *rt = compartment->runtimeFromMainThread();
+
+    if (!rt->workerThreadState)
+        return;
+
+    WorkerThreadState &state = *rt->workerThreadState;
+
     jit::JitCompartment *jitComp = compartment->jitCompartment();
     if (!jitComp)
         return;
 
-    AutoLockWorkerThreadState lock;
-
-    if (!WorkerThreadState().threads)
-        return;
+    AutoLockWorkerThreadState lock(state);
 
     /* Cancel any pending entries for which processing hasn't started. */
-    GlobalWorkerThreadState::IonBuilderVector &worklist = WorkerThreadState().ionWorklist();
-    for (size_t i = 0; i < worklist.length(); i++) {
-        jit::IonBuilder *builder = worklist[i];
+    for (size_t i = 0; i < state.ionWorklist.length(); i++) {
+        jit::IonBuilder *builder = state.ionWorklist[i];
         if (CompiledScriptMatches(compartment, script, builder->script())) {
             FinishOffThreadIonCompile(builder);
-            WorkerThreadState().remove(worklist, &i);
+            state.ionWorklist[i--] = state.ionWorklist.back();
+            state.ionWorklist.popBack();
         }
     }
 
     /* Wait for in progress entries to finish up. */
-    for (size_t i = 0; i < WorkerThreadState().threadCount; i++) {
-        const WorkerThread &helper = WorkerThreadState().threads[i];
+    for (size_t i = 0; i < state.numThreads; i++) {
+        const WorkerThread &helper = state.threads[i];
         while (helper.ionBuilder &&
                CompiledScriptMatches(compartment, script, helper.ionBuilder->script()))
         {
             helper.ionBuilder->cancel();
-            WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
+            state.wait(WorkerThreadState::CONSUMER);
         }
     }
 
+    jit::OffThreadCompilationVector &compilations = jitComp->finishedOffThreadCompilations();
+
     /* Cancel code generation for any completed entries. */
-    GlobalWorkerThreadState::IonBuilderVector &finished = WorkerThreadState().ionFinishedList();
-    for (size_t i = 0; i < finished.length(); i++) {
-        jit::IonBuilder *builder = finished[i];
+    for (size_t i = 0; i < compilations.length(); i++) {
+        jit::IonBuilder *builder = compilations[i];
         if (CompiledScriptMatches(compartment, script, builder->script())) {
             jit::FinishOffThreadBuilder(builder);
-            WorkerThreadState().remove(finished, &i);
+            compilations[i--] = compilations.back();
+            compilations.popBack();
         }
     }
 #endif // JS_ION
 }
 
 static const JSClass workerGlobalClass = {
     "internal-worker-global", JSCLASS_GLOBAL_FLAGS,
     JS_PropertyStub,  JS_DeletePropertyStub,
@@ -220,65 +232,16 @@ ParseTask::~ParseTask()
 {
     // ParseTask takes over ownership of its input exclusive context.
     js_delete(cx);
 
     for (size_t i = 0; i < errors.length(); i++)
         js_delete(errors[i]);
 }
 
-void
-js::CancelOffThreadParses(JSRuntime *rt)
-{
-    AutoLockWorkerThreadState lock;
-
-    if (!WorkerThreadState().threads)
-        return;
-
-    // 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;
-        GlobalWorkerThreadState::ParseTaskVector &worklist = WorkerThreadState().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 < WorkerThreadState().threadCount; i++) {
-                ParseTask *task = WorkerThreadState().threads[i].parseTask;
-                if (task && task->runtimeMatches(rt))
-                    inProgress = true;
-            }
-            if (!inProgress)
-                break;
-        }
-        WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
-    }
-
-    // Clean up any parse tasks which haven't been finished by the main thread.
-    GlobalWorkerThreadState::ParseTaskVector &finished = WorkerThreadState().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;
-                AutoUnlockWorkerThreadState unlock;
-                WorkerThreadState().finishParseTask(/* maybecx = */ nullptr, rt, task);
-            }
-        }
-        if (!found)
-            break;
-    }
-}
-
 bool
 js::OffThreadParsingMustWaitForGC(JSRuntime *rt)
 {
     // Off thread parsing can't occur during incremental collections on the
     // atoms compartment, to avoid triggering barriers. (Outside the atoms
     // compartment, the compilation will use a new zone that is never
     // collected.) If an atoms-zone GC is in progress, hold off on executing the
     // parse task until the atoms-zone GC completes (see
@@ -348,233 +311,279 @@ js::StartOffThreadParseScript(JSContext 
     if (!task)
         return false;
 
     workercx.forget();
 
     if (!task->init(cx, options))
         return false;
 
+    WorkerThreadState &state = *cx->runtime()->workerThreadState;
+    JS_ASSERT(state.numThreads);
+
     if (OffThreadParsingMustWaitForGC(cx->runtime())) {
-        AutoLockWorkerThreadState lock;
-        if (!WorkerThreadState().parseWaitingOnGC().append(task.get()))
+        if (!state.parseWaitingOnGC.append(task.get()))
             return false;
     } else {
         task->activate(cx->runtime());
 
-        AutoLockWorkerThreadState lock;
+        AutoLockWorkerThreadState lock(state);
 
-        if (!WorkerThreadState().parseWorklist().append(task.get()))
+        if (!state.parseWorklist.append(task.get()))
             return false;
 
-        WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
+        state.notifyAll(WorkerThreadState::PRODUCER);
     }
 
     task.forget();
 
     return true;
 }
 
 void
 js::EnqueuePendingParseTasksAfterGC(JSRuntime *rt)
 {
     JS_ASSERT(!OffThreadParsingMustWaitForGC(rt));
 
-    GlobalWorkerThreadState::ParseTaskVector newTasks;
-    {
-        AutoLockWorkerThreadState lock;
-        GlobalWorkerThreadState::ParseTaskVector &waiting = WorkerThreadState().parseWaitingOnGC();
-
-        for (size_t i = 0; i < waiting.length(); i++) {
-            ParseTask *task = waiting[i];
-            if (task->runtimeMatches(rt)) {
-                newTasks.append(task);
-                WorkerThreadState().remove(waiting, &i);
-            }
-        }
-    }
-
-    if (newTasks.empty())
+    if (!rt->workerThreadState || rt->workerThreadState->parseWaitingOnGC.empty())
         return;
 
     // This logic should mirror the contents of the !activeGCInAtomsZone()
     // branch in StartOffThreadParseScript:
 
-    for (size_t i = 0; i < newTasks.length(); i++)
-        newTasks[i]->activate(rt);
+    WorkerThreadState &state = *rt->workerThreadState;
+
+    for (size_t i = 0; i < state.parseWaitingOnGC.length(); i++)
+        state.parseWaitingOnGC[i]->activate(rt);
 
-    AutoLockWorkerThreadState lock;
+    AutoLockWorkerThreadState lock(state);
+
+    JS_ASSERT(state.parseWorklist.empty());
+    state.parseWorklist.swap(state.parseWaitingOnGC);
+
+    state.notifyAll(WorkerThreadState::PRODUCER);
+}
 
-    for (size_t i = 0; i < newTasks.length(); i++)
-        WorkerThreadState().parseWorklist().append(newTasks[i]);
+void
+js::WaitForOffThreadParsingToFinish(JSRuntime *rt)
+{
+    if (!rt->workerThreadState)
+        return;
+
+    WorkerThreadState &state = *rt->workerThreadState;
+
+    AutoLockWorkerThreadState lock(state);
 
-    WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
+    while (true) {
+        if (state.parseWorklist.empty()) {
+            bool parseInProgress = false;
+            for (size_t i = 0; i < state.numThreads; i++)
+                parseInProgress |= !!state.threads[i].parseTask;
+            if (!parseInProgress)
+                break;
+        }
+        state.wait(WorkerThreadState::CONSUMER);
+    }
 }
 
 #ifdef XP_WIN
 // The default stack size for new threads on Windows is 1MB, but specifying a
 // smaller explicit size to NSPR on thread creation causes our visible memory
 // usage to increase. Just use the default stack size on Windows.
 static const uint32_t WORKER_STACK_SIZE = 0;
 #else
 static const uint32_t WORKER_STACK_SIZE = 512 * 1024;
 #endif
 
 static const uint32_t WORKER_STACK_QUOTA = 450 * 1024;
 
 bool
-GlobalWorkerThreadState::ensureInitialized()
+WorkerThreadState::init()
 {
-    JS_ASSERT(this == &WorkerThreadState());
-    AutoLockWorkerThreadState lock;
+    JS_ASSERT(numThreads == 0);
 
-    if (threads)
+    if (!runtime->useHelperThreads())
         return true;
 
-    threads = js_pod_calloc<WorkerThread>(threadCount);
+    workerLock = PR_NewLock();
+    if (!workerLock)
+        return false;
+
+    consumerWakeup = PR_NewCondVar(workerLock);
+    if (!consumerWakeup)
+        return false;
+
+    producerWakeup = PR_NewCondVar(workerLock);
+    if (!producerWakeup)
+        return false;
+
+    threads = (WorkerThread*) js_pod_calloc<WorkerThread>(runtime->workerThreadCount());
     if (!threads)
         return false;
 
-    for (size_t i = 0; i < threadCount; i++) {
+    for (size_t i = 0; i < runtime->workerThreadCount(); i++) {
         WorkerThread &helper = threads[i];
-        helper.threadData.construct(static_cast<JSRuntime *>(nullptr));
+        helper.runtime = runtime;
+        helper.threadData.construct(runtime);
+        helper.threadData.ref().addToThreadList();
         helper.thread = PR_CreateThread(PR_USER_THREAD,
                                         WorkerThread::ThreadMain, &helper,
                                         PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_JOINABLE_THREAD, WORKER_STACK_SIZE);
         if (!helper.thread || !helper.threadData.ref().init()) {
-            for (size_t j = 0; j < threadCount; j++)
+            for (size_t j = 0; j < runtime->workerThreadCount(); j++)
                 threads[j].destroy();
             js_free(threads);
             threads = nullptr;
             return false;
         }
     }
 
+    numThreads = runtime->workerThreadCount();
     resetAsmJSFailureState();
     return true;
 }
 
-GlobalWorkerThreadState::GlobalWorkerThreadState()
+void
+WorkerThreadState::cleanup()
 {
-    mozilla::PodZero(this);
+    // Do preparatory work for shutdown before the final GC has destroyed most
+    // of the GC heap.
+
+    // Join created threads, to ensure there is no in progress work.
+    if (threads) {
+        for (size_t i = 0; i < numThreads; i++)
+            threads[i].destroy();
+        js_free(threads);
+        threads = nullptr;
+        numThreads = 0;
+    }
 
-    cpuCount = GetCPUCount();
-    threadCount = ThreadCountForCPUCount(cpuCount);
+    // Clean up any parse tasks which haven't been finished yet.
+    while (!parseFinishedList.empty())
+        finishParseTask(/* maybecx = */ nullptr, runtime, parseFinishedList[0]);
+}
 
-    MOZ_ASSERT(cpuCount > 0, "GetCPUCount() seems broken");
+WorkerThreadState::~WorkerThreadState()
+{
+    JS_ASSERT(!threads);
+    JS_ASSERT(parseFinishedList.empty());
 
-    workerLock = PR_NewLock();
-    consumerWakeup = PR_NewCondVar(workerLock);
-    producerWakeup = PR_NewCondVar(workerLock);
+    if (workerLock)
+        PR_DestroyLock(workerLock);
+
+    if (consumerWakeup)
+        PR_DestroyCondVar(consumerWakeup);
+
+    if (producerWakeup)
+        PR_DestroyCondVar(producerWakeup);
 }
 
 void
-GlobalWorkerThreadState::lock()
+WorkerThreadState::lock()
 {
-    JS_ASSERT(!isLocked());
-    AssertCurrentThreadCanLock(WorkerThreadStateLock);
+    runtime->assertCanLock(JSRuntime::WorkerThreadStateLock);
     PR_Lock(workerLock);
 #ifdef DEBUG
     lockOwner = PR_GetCurrentThread();
 #endif
 }
 
 void
-GlobalWorkerThreadState::unlock()
+WorkerThreadState::unlock()
 {
     JS_ASSERT(isLocked());
 #ifdef DEBUG
     lockOwner = nullptr;
 #endif
     PR_Unlock(workerLock);
 }
 
 #ifdef DEBUG
 bool
-GlobalWorkerThreadState::isLocked()
+WorkerThreadState::isLocked()
 {
     return lockOwner == PR_GetCurrentThread();
 }
 #endif
 
 void
-GlobalWorkerThreadState::wait(CondVar which, uint32_t millis)
+WorkerThreadState::wait(CondVar which, uint32_t millis)
 {
     JS_ASSERT(isLocked());
 #ifdef DEBUG
     lockOwner = nullptr;
 #endif
     DebugOnly<PRStatus> status =
         PR_WaitCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup,
                        millis ? PR_MillisecondsToInterval(millis) : PR_INTERVAL_NO_TIMEOUT);
     JS_ASSERT(status == PR_SUCCESS);
 #ifdef DEBUG
     lockOwner = PR_GetCurrentThread();
 #endif
 }
 
 void
-GlobalWorkerThreadState::notifyAll(CondVar which)
+WorkerThreadState::notifyAll(CondVar which)
 {
     JS_ASSERT(isLocked());
     PR_NotifyAllCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup);
 }
 
 void
-GlobalWorkerThreadState::notifyOne(CondVar which)
+WorkerThreadState::notifyOne(CondVar which)
 {
     JS_ASSERT(isLocked());
     PR_NotifyCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup);
 }
 
 bool
-GlobalWorkerThreadState::canStartAsmJSCompile()
+WorkerThreadState::canStartAsmJSCompile()
 {
     // Don't execute an AsmJS job if an earlier one failed.
     JS_ASSERT(isLocked());
-    return (!asmJSWorklist().empty() && !numAsmJSFailedJobs);
+    return (!asmJSWorklist.empty() && !numAsmJSFailedJobs);
 }
 
 bool
-GlobalWorkerThreadState::canStartIonCompile()
+WorkerThreadState::canStartIonCompile()
 {
     // A worker thread can begin an Ion compilation if (a) there is some script
     // which is waiting to be compiled, and (b) no other worker thread is
     // currently compiling a script. The latter condition ensures that two
     // compilations cannot simultaneously occur.
-    if (ionWorklist().empty())
+    if (ionWorklist.empty())
         return false;
-    for (size_t i = 0; i < threadCount; i++) {
+    for (size_t i = 0; i < numThreads; i++) {
         if (threads[i].ionBuilder)
             return false;
     }
     return true;
 }
 
 bool
-GlobalWorkerThreadState::canStartParseTask()
+WorkerThreadState::canStartParseTask()
 {
     // Don't allow simultaneous off thread parses, to reduce contention on the
     // atoms table. Note that asm.js compilation depends on this to avoid
     // stalling the worker thread, as off thread parse tasks can trigger and
     // block on other off thread asm.js compilation tasks.
     JS_ASSERT(isLocked());
-    if (parseWorklist().empty())
+    if (parseWorklist.empty())
         return false;
-    for (size_t i = 0; i < threadCount; i++) {
+    for (size_t i = 0; i < numThreads; i++) {
         if (threads[i].parseTask)
             return false;
     }
     return true;
 }
 
 bool
-GlobalWorkerThreadState::canStartCompressionTask()
+WorkerThreadState::canStartCompressionTask()
 {
-    return !compressionWorklist().empty();
+    return !compressionWorklist.empty();
 }
 
 static void
 CallNewScriptHookForAllScripts(JSContext *cx, HandleScript script)
 {
     // We should never hit this, since nested scripts are also constructed via
     // BytecodeEmitter instances on the stack.
     JS_CHECK_RECURSION(cx, return);
@@ -595,29 +604,29 @@ CallNewScriptHookForAllScripts(JSContext
     }
 
     // The global new script hook is called on every script that was compiled.
     RootedFunction function(cx, script->functionNonDelazifying());
     CallNewScriptHook(cx, script, function);
 }
 
 JSScript *
-GlobalWorkerThreadState::finishParseTask(JSContext *maybecx, JSRuntime *rt, void *token)
+WorkerThreadState::finishParseTask(JSContext *maybecx, JSRuntime *rt, void *token)
 {
     ParseTask *parseTask = nullptr;
 
     // The token is a ParseTask* which should be in the finished list.
     // Find and remove its entry.
     {
-        AutoLockWorkerThreadState lock;
-        ParseTaskVector &finished = parseFinishedList();
-        for (size_t i = 0; i < finished.length(); i++) {
-            if (finished[i] == token) {
-                parseTask = finished[i];
-                remove(finished, &i);
+        AutoLockWorkerThreadState lock(*rt->workerThreadState);
+        for (size_t i = 0; i < parseFinishedList.length(); i++) {
+            if (parseFinishedList[i] == token) {
+                parseTask = parseFinishedList[i];
+                parseFinishedList[i] = parseFinishedList.back();
+                parseFinishedList.popBack();
                 break;
             }
         }
     }
     JS_ASSERT(parseTask);
 
     // Mark the zone as no longer in use by an ExclusiveContext, and available
     // to be collected by the GC.
@@ -677,137 +686,137 @@ GlobalWorkerThreadState::finishParseTask
 
     js_delete(parseTask);
     return script;
 }
 
 void
 WorkerThread::destroy()
 {
+    WorkerThreadState &state = *runtime->workerThreadState;
+
     if (thread) {
         {
-            AutoLockWorkerThreadState lock;
+            AutoLockWorkerThreadState lock(state);
             terminate = true;
 
             /* Notify all workers, to ensure that this thread wakes up. */
-            WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
+            state.notifyAll(WorkerThreadState::PRODUCER);
         }
 
         PR_JoinThread(thread);
     }
 
-    if (!threadData.empty())
+    if (!threadData.empty()) {
+        threadData.ref().removeFromThreadList();
         threadData.destroy();
+    }
 }
 
 /* static */
 void
 WorkerThread::ThreadMain(void *arg)
 {
     PR_SetCurrentThreadName("Analysis Helper");
     static_cast<WorkerThread *>(arg)->threadLoop();
 }
 
 void
-WorkerThread::handleAsmJSWorkload()
+WorkerThread::handleAsmJSWorkload(WorkerThreadState &state)
 {
 #ifdef JS_ION
-    JS_ASSERT(WorkerThreadState().isLocked());
-    JS_ASSERT(WorkerThreadState().canStartAsmJSCompile());
+    JS_ASSERT(state.isLocked());
+    JS_ASSERT(state.canStartAsmJSCompile());
     JS_ASSERT(idle());
 
-    asmData = WorkerThreadState().asmJSWorklist().popCopy();
+    asmData = state.asmJSWorklist.popCopy();
     bool success = false;
 
+    state.unlock();
     do {
-        AutoUnlockWorkerThreadState unlock;
-        PerThreadData::AutoEnterRuntime enter(threadData.addr(), asmData->runtime);
-
-        jit::IonContext icx(asmData->mir->compartment->runtime(),
-                            asmData->mir->compartment,
-                            &asmData->mir->alloc());
+        jit::IonContext icx(jit::CompileRuntime::get(runtime), asmData->mir->compartment, &asmData->mir->alloc());
 
         int64_t before = PRMJ_Now();
 
         if (!OptimizeMIR(asmData->mir))
             break;
 
         asmData->lir = GenerateLIR(asmData->mir);
         if (!asmData->lir)
             break;
 
         int64_t after = PRMJ_Now();
         asmData->compileTime = (after - before) / PRMJ_USEC_PER_MSEC;
 
         success = true;
     } while(0);
+    state.lock();
 
     // On failure, signal parent for harvesting in CancelOutstandingJobs().
     if (!success) {
-        WorkerThreadState().noteAsmJSFailure(asmData->func);
-        WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
+        state.noteAsmJSFailure(asmData->func);
+        state.notifyAll(WorkerThreadState::CONSUMER);
         asmData = nullptr;
         return;
     }
 
     // On success, move work to the finished list.
-    WorkerThreadState().asmJSFinishedList().append(asmData);
+    state.asmJSFinishedList.append(asmData);
     asmData = nullptr;
 
     // Notify the main thread in case it's blocked waiting for a LifoAlloc.
-    WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
+    state.notifyAll(WorkerThreadState::CONSUMER);
 #else
     MOZ_CRASH();
 #endif // JS_ION
 }
 
 void
-WorkerThread::handleIonWorkload()
+WorkerThread::handleIonWorkload(WorkerThreadState &state)
 {
 #ifdef JS_ION
-    JS_ASSERT(WorkerThreadState().isLocked());
-    JS_ASSERT(WorkerThreadState().canStartIonCompile());
+    JS_ASSERT(state.isLocked());
+    JS_ASSERT(state.canStartIonCompile());
     JS_ASSERT(idle());
 
-    ionBuilder = WorkerThreadState().ionWorklist().popCopy();
+    ionBuilder = state.ionWorklist.popCopy();
+
+    DebugOnly<ExecutionMode> executionMode = ionBuilder->info().executionMode();
 
 #if JS_TRACE_LOGGING
     AutoTraceLog logger(TraceLogging::getLogger(TraceLogging::ION_BACKGROUND_COMPILER),
                         TraceLogging::ION_COMPILE_START,
                         TraceLogging::ION_COMPILE_STOP,
                         ionBuilder->script());
 #endif
 
-    JSRuntime *rt = ionBuilder->script()->compartment()->runtimeFromAnyThread();
-
+    state.unlock();
     {
-        AutoUnlockWorkerThreadState unlock;
-        PerThreadData::AutoEnterRuntime enter(threadData.addr(),
-                                              ionBuilder->script()->runtimeFromAnyThread());
-        jit::IonContext ictx(jit::CompileRuntime::get(rt),
+        jit::IonContext ictx(jit::CompileRuntime::get(runtime),
                              jit::CompileCompartment::get(ionBuilder->script()->compartment()),
                              &ionBuilder->alloc());
         AutoEnterIonCompilation ionCompiling;
         bool succeeded = ionBuilder->build();
         ionBuilder->clearForBackEnd();
         if (succeeded)
             ionBuilder->setBackgroundCodegen(jit::CompileBackEnd(ionBuilder));
     }
+    state.lock();
 
     FinishOffThreadIonCompile(ionBuilder);
     ionBuilder = nullptr;
 
     // Ping the main thread so that the compiled code can be incorporated
     // at the next operation 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->triggerOperationCallback(JSRuntime::TriggerCallbackAnyThreadDontStopIon);
+    runtime->triggerOperationCallback(JSRuntime::TriggerCallbackAnyThreadDontStopIon);
 
     // Notify the main thread in case it is waiting for the compilation to finish.
-    WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
+    state.notifyAll(WorkerThreadState::CONSUMER);
 #else
     MOZ_CRASH();
 #endif // JS_ION
 }
 
 void
 ExclusiveContext::setWorkerThread(WorkerThread *workerThread)
 {
@@ -829,110 +838,110 @@ ExclusiveContext::addPendingCompileError
 void
 ExclusiveContext::addPendingOverRecursed()
 {
     if (workerThread()->parseTask)
         workerThread()->parseTask->overRecursed = true;
 }
 
 void
-WorkerThread::handleParseWorkload()
+WorkerThread::handleParseWorkload(WorkerThreadState &state)
 {
-    JS_ASSERT(WorkerThreadState().isLocked());
-    JS_ASSERT(WorkerThreadState().canStartParseTask());
+    JS_ASSERT(state.isLocked());
+    JS_ASSERT(state.canStartParseTask());
     JS_ASSERT(idle());
 
-    parseTask = WorkerThreadState().parseWorklist().popCopy();
+    parseTask = state.parseWorklist.popCopy();
     parseTask->cx->setWorkerThread(this);
 
     {
-        AutoUnlockWorkerThreadState unlock;
-        PerThreadData::AutoEnterRuntime enter(threadData.addr(),
-                                              parseTask->exclusiveContextGlobal->runtimeFromAnyThread());
+        AutoUnlockWorkerThreadState unlock(runtime);
         parseTask->script = frontend::CompileScript(parseTask->cx, &parseTask->alloc,
                                                     NullPtr(), NullPtr(),
                                                     parseTask->options,
                                                     parseTask->chars, parseTask->length);
     }
 
     // The callback is invoked while we are still off the main thread.
     parseTask->callback(parseTask, parseTask->callbackData);
 
     // FinishOffThreadScript will need to be called on the script to
     // migrate it into the correct compartment.
-    WorkerThreadState().parseFinishedList().append(parseTask);
+    state.parseFinishedList.append(parseTask);
 
     parseTask = nullptr;
 
     // Notify the main thread in case it is waiting for the parse/emit to finish.
-    WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
+    state.notifyAll(WorkerThreadState::CONSUMER);
 }
 
 void
-WorkerThread::handleCompressionWorkload()
+WorkerThread::handleCompressionWorkload(WorkerThreadState &state)
 {
-    JS_ASSERT(WorkerThreadState().isLocked());
-    JS_ASSERT(WorkerThreadState().canStartCompressionTask());
+    JS_ASSERT(state.isLocked());
+    JS_ASSERT(state.canStartCompressionTask());
     JS_ASSERT(idle());
 
-    compressionTask = WorkerThreadState().compressionWorklist().popCopy();
+    compressionTask = state.compressionWorklist.popCopy();
     compressionTask->workerThread = this;
 
     {
-        AutoUnlockWorkerThreadState unlock;
+        AutoUnlockWorkerThreadState unlock(runtime);
         if (!compressionTask->work())
             compressionTask->setOOM();
     }
 
     compressionTask->workerThread = nullptr;
     compressionTask = nullptr;
 
     // Notify the main thread in case it is waiting for the compression to finish.
-    WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
+    state.notifyAll(WorkerThreadState::CONSUMER);
 }
 
 bool
 js::StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task)
 {
     if (!EnsureWorkerThreadsInitialized(cx))
         return false;
 
-    AutoLockWorkerThreadState lock;
+    WorkerThreadState &state = *cx->workerThreadState();
+    AutoLockWorkerThreadState lock(state);
 
-    if (!WorkerThreadState().compressionWorklist().append(task))
+    if (!state.compressionWorklist.append(task))
         return false;
 
-    WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
+    state.notifyAll(WorkerThreadState::PRODUCER);
     return true;
 }
 
 bool
-GlobalWorkerThreadState::compressionInProgress(SourceCompressionTask *task)
+WorkerThreadState::compressionInProgress(SourceCompressionTask *task)
 {
     JS_ASSERT(isLocked());
-    for (size_t i = 0; i < compressionWorklist().length(); i++) {
-        if (compressionWorklist()[i] == task)
+    for (size_t i = 0; i < compressionWorklist.length(); i++) {
+        if (compressionWorklist[i] == task)
             return true;
     }
-    for (size_t i = 0; i < threadCount; i++) {
+    for (size_t i = 0; i < numThreads; i++) {
         if (threads[i].compressionTask == task)
             return true;
     }
     return false;
 }
 
 bool
 SourceCompressionTask::complete()
 {
     JS_ASSERT_IF(!ss, !chars);
     if (active()) {
-        AutoLockWorkerThreadState lock;
+        WorkerThreadState &state = *cx->workerThreadState();
+        AutoLockWorkerThreadState lock(state);
 
-        while (WorkerThreadState().compressionInProgress(this))
-            WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
+        while (state.compressionInProgress(this))
+            state.wait(WorkerThreadState::CONSUMER);
 
         ss->ready_ = true;
 
         // Update memory accounting.
         if (!oom)
             cx->updateMallocCounter(ss->computedSizeOfData());
 
         ss = nullptr;
@@ -941,25 +950,25 @@ SourceCompressionTask::complete()
     if (oom) {
         js_ReportOutOfMemory(cx);
         return false;
     }
     return true;
 }
 
 SourceCompressionTask *
-GlobalWorkerThreadState::compressionTaskForSource(ScriptSource *ss)
+WorkerThreadState::compressionTaskForSource(ScriptSource *ss)
 {
     JS_ASSERT(isLocked());
-    for (size_t i = 0; i < compressionWorklist().length(); i++) {
-        SourceCompressionTask *task = compressionWorklist()[i];
+    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++) {
+    for (size_t i = 0; i < numThreads; i++) {
         SourceCompressionTask *task = threads[i].compressionTask;
         if (task && task->source() == ss)
             return task;
     }
     return nullptr;
 }
 
 const jschar *
@@ -967,35 +976,37 @@ ScriptSource::getOffThreadCompressionCha
 {
     // If this is being compressed off thread, return its uncompressed chars.
 
     if (ready()) {
         // Compression has already finished on the source.
         return nullptr;
     }
 
-    AutoLockWorkerThreadState lock;
+    WorkerThreadState &state = *cx->workerThreadState();
+    AutoLockWorkerThreadState lock(state);
 
     // Look for a token that hasn't finished compressing and whose source is
     // the given ScriptSource.
-    if (SourceCompressionTask *task = WorkerThreadState().compressionTaskForSource(this))
+    if (SourceCompressionTask *task = state.compressionTaskForSource(this))
         return task->uncompressedChars();
 
     // Compressing has finished, so this ScriptSource is ready. Avoid future
     // queries on the worker thread state when getting the chars.
     ready_ = true;
 
     return nullptr;
 }
 
 void
 WorkerThread::threadLoop()
 {
     JS::AutoAssertNoGC nogc;
-    AutoLockWorkerThreadState lock;
+    WorkerThreadState &state = *runtime->workerThreadState;
+    AutoLockWorkerThreadState lock(state);
 
     js::TlsPerThreadData.set(threadData.addr());
 
     // Compute the thread's stack limit, for over-recursed checks.
     uintptr_t stackLimit = GetNativeStackBase();
 #if JS_STACK_GROWTH_DIRECTION > 0
     stackLimit += WORKER_STACK_QUOTA;
 #else
@@ -1006,35 +1017,35 @@ WorkerThread::threadLoop()
 
     while (true) {
         JS_ASSERT(!ionBuilder && !asmData);
 
         // Block until a task is available.
         while (true) {
             if (terminate)
                 return;
-            if (WorkerThreadState().canStartIonCompile() ||
-                WorkerThreadState().canStartAsmJSCompile() ||
-                WorkerThreadState().canStartParseTask() ||
-                WorkerThreadState().canStartCompressionTask())
+            if (state.canStartIonCompile() ||
+                state.canStartAsmJSCompile() ||
+                state.canStartParseTask() ||
+                state.canStartCompressionTask())
             {
                 break;
             }
-            WorkerThreadState().wait(GlobalWorkerThreadState::PRODUCER);
+            state.wait(WorkerThreadState::PRODUCER);
         }
 
         // Dispatch tasks, prioritizing AsmJS work.
-        if (WorkerThreadState().canStartAsmJSCompile())
-            handleAsmJSWorkload();
-        else if (WorkerThreadState().canStartIonCompile())
-            handleIonWorkload();
-        else if (WorkerThreadState().canStartParseTask())
-            handleParseWorkload();
-        else if (WorkerThreadState().canStartCompressionTask())
-            handleCompressionWorkload();
+        if (state.canStartAsmJSCompile())
+            handleAsmJSWorkload(state);
+        else if (state.canStartIonCompile())
+            handleIonWorkload(state);
+        else if (state.canStartParseTask())
+            handleParseWorkload(state);
+        else if (state.canStartCompressionTask())
+            handleCompressionWorkload(state);
         else
             MOZ_ASSUME_UNREACHABLE("No task to perform");
     }
 }
 
 #else /* JS_THREADSAFE */
 
 using namespace js;
@@ -1055,29 +1066,29 @@ js::StartOffThreadIonCompile(JSContext *
 
 #endif // JS_ION
 
 void
 js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
 {
 }
 
-void
-js::CancelOffThreadParses(JSRuntime *rt)
-{
-}
-
 bool
 js::StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &options,
                               const jschar *chars, size_t length, HandleObject scopeChain,
                               JS::OffThreadCompileCallback callback, void *callbackData)
 {
     MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds");
 }
 
+void
+js::WaitForOffThreadParsingToFinish(JSRuntime *rt)
+{
+}
+
 bool
 js::StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task)
 {
     MOZ_ASSUME_UNREACHABLE("Off thread compression not available");
 }
 
 bool
 SourceCompressionTask::complete()
--- a/js/src/jsworkers.h
+++ b/js/src/jsworkers.h
@@ -28,131 +28,80 @@ struct WorkerThread;
 struct AsmJSParallelTask;
 struct ParseTask;
 namespace jit {
   class IonBuilder;
 }
 
 #ifdef JS_THREADSAFE
 
-// Per-process state for off thread work items.
-class GlobalWorkerThreadState
+/* Per-runtime state for off thread work items. */
+class WorkerThreadState
 {
   public:
-    // Number of CPUs to treat this machine as having when creating threads.
-    // May be accessed without locking.
-    size_t cpuCount;
-
-    // Number of threads to create. May be accessed without locking.
-    size_t threadCount;
+    /* Available threads. */
+    WorkerThread *threads;
+    size_t numThreads;
 
-    typedef Vector<jit::IonBuilder*, 0, SystemAllocPolicy> IonBuilderVector;
-    typedef Vector<AsmJSParallelTask*, 0, SystemAllocPolicy> AsmJSParallelTaskVector;
-    typedef Vector<ParseTask*, 0, SystemAllocPolicy> ParseTaskVector;
-    typedef Vector<SourceCompressionTask*, 0, SystemAllocPolicy> SourceCompressionTaskVector;
+    enum CondVar {
+        /* For notifying threads waiting for work that they may be able to make progress. */
+        CONSUMER,
 
-    // List of available threads, or null if the thread state has not been initialized.
-    WorkerThread *threads;
+        /* For notifying threads doing work that they may be able to make progress. */
+        PRODUCER
+    };
+
+    /* Shared worklist for Ion worker threads. */
+    Vector<jit::IonBuilder*, 0, SystemAllocPolicy> ionWorklist;
 
-  private:
-    // The lists below are all protected by |lock|.
-
-    // Ion compilation worklist and finished jobs.
-    IonBuilderVector ionWorklist_, ionFinishedList_;
+    /* Worklist for AsmJS worker threads. */
+    Vector<AsmJSParallelTask*, 0, SystemAllocPolicy> asmJSWorklist;
 
-    // AsmJS worklist and finished jobs.
-    //
-    // Simultaneous AsmJS compilations all service the same AsmJS module.
-    // The main thread must pick up finished optimizations and perform codegen.
-    // |asmJSCompilationInProgress| is used to avoid triggering compilations
-    // for more than one module at a time.
-    AsmJSParallelTaskVector asmJSWorklist_, asmJSFinishedList_;
+    /*
+     * Finished list for AsmJS worker threads.
+     * Simultaneous AsmJS compilations all service the same AsmJS module.
+     * The main thread must pick up finished optimizations and perform codegen.
+     */
+    Vector<AsmJSParallelTask*, 0, SystemAllocPolicy> asmJSFinishedList;
 
-  public:
-    // For now, only allow a single parallel asm.js compilation to happen at a
-    // time. This avoids race conditions on asmJSWorklist/asmJSFinishedList/etc.
+    /*
+     * For now, only allow a single parallel asm.js compilation to happen at a
+     * time. This avoids race conditions on asmJSWorklist/asmJSFinishedList/etc.
+     */
     mozilla::Atomic<uint32_t> asmJSCompilationInProgress;
 
-  private:
-    // Script parsing/emitting worklist and finished jobs.
-    ParseTaskVector parseWorklist_, parseFinishedList_;
+    /* Shared worklist for parsing/emitting scripts on worker threads. */
+    Vector<ParseTask*, 0, SystemAllocPolicy> parseWorklist, parseFinishedList;
 
-    // Parse tasks waiting for an atoms-zone GC to complete.
-    ParseTaskVector parseWaitingOnGC_;
+    /* Main-thread-only list of parse tasks waiting for an atoms-zone GC to complete. */
+    Vector<ParseTask*, 0, SystemAllocPolicy> parseWaitingOnGC;
+
+    /* Worklist for source compression worker threads. */
+    Vector<SourceCompressionTask *, 0, SystemAllocPolicy> compressionWorklist;
 
-    // Source compression worklist.
-    SourceCompressionTaskVector compressionWorklist_;
+    WorkerThreadState(JSRuntime *rt) {
+        mozilla::PodZero(this);
+        runtime = rt;
+    }
+    ~WorkerThreadState();
 
-  public:
-    GlobalWorkerThreadState();
+    bool init();
+    void cleanup();
 
-    bool ensureInitialized();
     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
-    };
-
     void wait(CondVar which, uint32_t timeoutMillis = 0);
     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() {
-        JS_ASSERT(isLocked());
-        return ionWorklist_;
-    }
-    IonBuilderVector &ionFinishedList() {
-        JS_ASSERT(isLocked());
-        return ionFinishedList_;
-    }
-
-    AsmJSParallelTaskVector &asmJSWorklist() {
-        JS_ASSERT(isLocked());
-        return asmJSWorklist_;
-    }
-    AsmJSParallelTaskVector &asmJSFinishedList() {
-        JS_ASSERT(isLocked());
-        return asmJSFinishedList_;
-    }
-
-    ParseTaskVector &parseWorklist() {
-        JS_ASSERT(isLocked());
-        return parseWorklist_;
-    }
-    ParseTaskVector &parseFinishedList() {
-        JS_ASSERT(isLocked());
-        return parseFinishedList_;
-    }
-    ParseTaskVector &parseWaitingOnGC() {
-        JS_ASSERT(isLocked());
-        return parseWaitingOnGC_;
-    }
-
-    SourceCompressionTaskVector &compressionWorklist() {
-        JS_ASSERT(isLocked());
-        return compressionWorklist_;
-    }
-
     bool canStartAsmJSCompile();
     bool canStartIonCompile();
     bool canStartParseTask();
     bool canStartCompressionTask();
 
     uint32_t harvestFailedAsmJSJobs() {
         JS_ASSERT(isLocked());
         uint32_t n = numAsmJSFailedJobs;
@@ -178,16 +127,18 @@ class GlobalWorkerThreadState
     }
 
     JSScript *finishParseTask(JSContext *maybecx, JSRuntime *rt, void *token);
     bool compressionInProgress(SourceCompressionTask *task);
     SourceCompressionTask *compressionTaskForSource(ScriptSource *ss);
 
   private:
 
+    JSRuntime *runtime;
+
     /*
      * Lock protecting all mutable shared state accessed by helper threads, and
      * used by all condition variables.
      */
     PRLock *workerLock;
 
 # ifdef DEBUG
     PRThread *lockOwner;
@@ -205,26 +156,21 @@ class GlobalWorkerThreadState
 
     /*
      * Function index |i| in |Module.function(i)| of first failed AsmJS function.
      * -1 if no function has failed.
      */
     void *asmJSFailedFunction;
 };
 
-static inline GlobalWorkerThreadState &
-WorkerThreadState()
-{
-    extern GlobalWorkerThreadState gWorkerThreadState;
-    return gWorkerThreadState;
-}
-
 /* Individual helper thread, one allocated per core. */
 struct WorkerThread
 {
+    JSRuntime *runtime;
+
     mozilla::Maybe<PerThreadData> threadData;
     PRThread *thread;
 
     /* Indicate to an idle thread that it should finish executing. */
     bool terminate;
 
     /* Any builder currently being compiled by Ion on this thread. */
     jit::IonBuilder *ionBuilder;
@@ -239,38 +185,33 @@ struct WorkerThread
     SourceCompressionTask *compressionTask;
 
     bool idle() const {
         return !ionBuilder && !asmData && !parseTask && !compressionTask;
     }
 
     void destroy();
 
-    void handleAsmJSWorkload();
-    void handleIonWorkload();
-    void handleParseWorkload();
-    void handleCompressionWorkload();
+    void handleAsmJSWorkload(WorkerThreadState &state);
+    void handleIonWorkload(WorkerThreadState &state);
+    void handleParseWorkload(WorkerThreadState &state);
+    void handleCompressionWorkload(WorkerThreadState &state);
 
     static void ThreadMain(void *arg);
     void threadLoop();
 };
 
 #endif /* JS_THREADSAFE */
 
 /* Methods for interacting with worker threads. */
 
-// Initialize worker threads unless already initialized.
+/* Initialize worker threads unless already initialized. */
 bool
 EnsureWorkerThreadsInitialized(ExclusiveContext *cx);
 
-// This allows the JS shell to override GetCPUCount() when passed the
-// --thread-count=N option.
-void
-SetFakeCPUCount(size_t count);
-
 #ifdef JS_ION
 
 /* Perform MIR optimization and LIR generation on a single function. */
 bool
 StartOffThreadAsmJSCompile(ExclusiveContext *cx, AsmJSParallelTask *asmData);
 
 /*
  * Schedule an Ion compilation for a script, given a builder which has been
@@ -283,102 +224,111 @@ StartOffThreadIonCompile(JSContext *cx, 
 
 /*
  * Cancel a scheduled or in progress Ion compilation for script. If script is
  * nullptr, all compilations for the compartment are cancelled.
  */
 void
 CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script);
 
-/* Cancel all scheduled, in progress or finished parses for runtime. */
-void
-CancelOffThreadParses(JSRuntime *runtime);
-
 /*
  * Start a parse/emit cycle for a stream of source. The characters must stay
  * alive until the compilation finishes.
  */
 bool
 StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &options,
                           const jschar *chars, size_t length, HandleObject scopeChain,
                           JS::OffThreadCompileCallback callback, void *callbackData);
 
 /*
  * Called at the end of GC to enqueue any Parse tasks that were waiting on an
  * atoms-zone GC to finish.
  */
 void
 EnqueuePendingParseTasksAfterGC(JSRuntime *rt);
 
+/* Block until in progress and pending off thread parse jobs have finished. */
+void
+WaitForOffThreadParsingToFinish(JSRuntime *rt);
+
 /* Start a compression job for the specified token. */
 bool
 StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task);
 
 class AutoLockWorkerThreadState
 {
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 
 #ifdef JS_THREADSAFE
+    WorkerThreadState &state;
+
   public:
-    AutoLockWorkerThreadState(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM)
+    AutoLockWorkerThreadState(WorkerThreadState &state
+                              MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+      : state(state)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-        WorkerThreadState().lock();
+        state.lock();
     }
 
     ~AutoLockWorkerThreadState() {
-        WorkerThreadState().unlock();
+        state.unlock();
     }
 #else
   public:
-    AutoLockWorkerThreadState(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM)
+    AutoLockWorkerThreadState(WorkerThreadState &state
+                              MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     }
 #endif
 };
 
 class AutoUnlockWorkerThreadState
 {
+    JSRuntime *rt;
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 
   public:
 
-    AutoUnlockWorkerThreadState(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM)
+    AutoUnlockWorkerThreadState(JSRuntime *rt
+                                MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+      : rt(rt)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 #ifdef JS_THREADSAFE
-        WorkerThreadState().unlock();
+        JS_ASSERT(rt->workerThreadState);
+        rt->workerThreadState->unlock();
+#else
+        (void)this->rt;
 #endif
     }
 
     ~AutoUnlockWorkerThreadState()
     {
 #ifdef JS_THREADSAFE
-        WorkerThreadState().lock();
+        rt->workerThreadState->lock();
 #endif
     }
 };
 
 #ifdef JS_ION
 struct AsmJSParallelTask
 {
-    JSRuntime *runtime;     // Associated runtime.
     LifoAlloc lifo;         // Provider of all heap memory used for compilation.
     void *func;             // Really, a ModuleCompiler::Func*
     jit::MIRGenerator *mir; // Passed from main thread to worker.
     jit::LIRGraph *lir;     // Passed from worker to main thread.
     unsigned compileTime;
 
     AsmJSParallelTask(size_t defaultChunkSize)
-      : runtime(nullptr), lifo(defaultChunkSize), func(nullptr), mir(nullptr), lir(nullptr), compileTime(0)
+      : lifo(defaultChunkSize), func(nullptr), mir(nullptr), lir(nullptr), compileTime(0)
     { }
 
-    void init(JSRuntime *rt, void *func, jit::MIRGenerator *mir) {
-        this->runtime = rt;
+    void init(void *func, jit::MIRGenerator *mir) {
         this->func = func;
         this->mir = mir;
         this->lir = nullptr;
     }
 };
 #endif
 
 struct ParseTask
@@ -421,20 +371,16 @@ struct ParseTask
     ParseTask(ExclusiveContext *cx, JSObject *exclusiveContextGlobal, JSContext *initCx,
               const jschar *chars, size_t length, JSObject *scopeChain,
               JS::OffThreadCompileCallback callback, void *callbackData);
     bool init(JSContext *cx, const ReadOnlyCompileOptions &options);
 
     void activate(JSRuntime *rt);
     void finish();
 
-    bool runtimeMatches(JSRuntime *rt) {
-        return exclusiveContextGlobal->runtimeFromAnyThread() == rt;
-    }
-
     ~ParseTask();
 };
 
 #ifdef JS_THREADSAFE
 // Return whether, if a new parse task was started, it would need to wait for
 // an in-progress GC to complete before starting.
 extern bool
 OffThreadParsingMustWaitForGC(JSRuntime *rt);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -5451,17 +5451,17 @@ ProcessArgs(JSContext *cx, JSObject *obj
     if (op->getBoolOption('D')) {
         cx->runtime()->profilingScripts = true;
         enableDisassemblyDumps = true;
     }
 
 #ifdef JS_THREADSAFE
     int32_t threadCount = op->getIntOption("thread-count");
     if (threadCount >= 0)
-        SetFakeCPUCount(threadCount);
+        cx->runtime()->setFakeCPUCount(threadCount);
 #endif /* JS_THREADSAFE */
 
 #if defined(JS_ION)
     if (op->getBoolOption("no-ion")) {
         enableIon = false;
         JS::ContextOptionsRef(cx).toggleIon();
     }
     if (op->getBoolOption("no-asmjs")) {
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -86,31 +86,50 @@ PerThreadData::PerThreadData(JSRuntime *
     activeCompilations(0)
 {}
 
 PerThreadData::~PerThreadData()
 {
     if (dtoaState)
         js_DestroyDtoaState(dtoaState);
 
+    if (isInList())
+        removeFromThreadList();
+
 #ifdef JS_ARM_SIMULATOR
     js_delete(simulator_);
 #endif
 }
 
 bool
 PerThreadData::init()
 {
     dtoaState = js_NewDtoaState();
     if (!dtoaState)
         return false;
 
     return true;
 }
 
+void
+PerThreadData::addToThreadList()
+{
+    // PerThreadData which are created/destroyed off the main thread do not
+    // show up in the runtime's thread list.
+    JS_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
+    runtime_->threadList.insertBack(this);
+}
+
+void
+PerThreadData::removeFromThreadList()
+{
+    JS_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
+    removeFrom(runtime_->threadList);
+}
+
 static const JSWrapObjectCallbacks DefaultWrapObjectCallbacks = {
     TransparentObjectWrapper,
     nullptr,
     nullptr
 };
 
 JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
   : JS::shadow::Runtime(
@@ -120,16 +139,17 @@ JSRuntime::JSRuntime(JSUseHelperThreads 
     ),
     mainThread(this),
     interrupt(0),
     handlingSignal(false),
     operationCallback(nullptr),
 #ifdef JS_THREADSAFE
     operationCallbackLock(nullptr),
     operationCallbackOwner(nullptr),
+    workerThreadState(nullptr),
     exclusiveAccessLock(nullptr),
     exclusiveAccessOwner(nullptr),
     mainThreadHasExclusiveAccess(false),
     numExclusiveThreads(0),
     compilationLock(nullptr),
 #ifdef DEBUG
     compilationLockOwner(nullptr),
     mainThreadHasCompilationLock(false),
@@ -291,23 +311,30 @@ JSRuntime::JSRuntime(JSUseHelperThreads 
     jitSupportsFloatingPoint(false),
     ionPcScriptCache(nullptr),
     threadPool(this),
     defaultJSContextCallback(nullptr),
     ctypesActivityCallback(nullptr),
     parallelWarmup(0),
     ionReturnOverride_(MagicValue(JS_ARG_POISON)),
     useHelperThreads_(useHelperThreads),
+#ifdef JS_THREADSAFE
+    cpuCount_(GetCPUCount()),
+#else
+    cpuCount_(1),
+#endif
     parallelIonCompilationEnabled_(true),
     parallelParsingEnabled_(true),
     isWorkerRuntime_(false)
 #ifdef DEBUG
     , enteredPolicy(nullptr)
 #endif
 {
+    MOZ_ASSERT(cpuCount_ > 0, "GetCPUCount() seems broken");
+
     liveRuntimesCount++;
 
     setGCMode(JSGC_MODE_GLOBAL);
 
     /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
     JS_INIT_CLIST(&onNewGlobalObjectWatchers);
 
     PodZero(&debugHooks);
@@ -360,16 +387,17 @@ JSRuntime::init(uint32_t maxbytes)
     if (!compilationLock)
         return false;
 #endif
 
     if (!mainThread.init())
         return false;
 
     js::TlsPerThreadData.set(&mainThread);
+    mainThread.addToThreadList();
 
     if (!threadPool.init())
         return false;
 
     if (!js_InitGC(this, maxbytes))
         return false;
 
     if (!gcMarker.init(gcMode()))
@@ -430,25 +458,25 @@ JSRuntime::init(uint32_t maxbytes)
 
 JSRuntime::~JSRuntime()
 {
     JS_ASSERT(!isHeapBusy());
 
     /* Free source hook early, as its destructor may want to delete roots. */
     sourceHook = nullptr;
 
-    /*
-     * Cancel any pending, in progress or completed Ion compilations and
-     * parse tasks. Waiting for AsmJS and compression tasks is done
-     * synchronously (on the main thread or during parse tasks), so no
-     * explicit canceling is needed for these.
-     */
+    /* Off thread compilation and parsing depend on atoms still existing. */
     for (CompartmentsIter comp(this, SkipAtoms); !comp.done(); comp.next())
         CancelOffThreadIonCompile(comp, nullptr);
-    CancelOffThreadParses(this);
+    WaitForOffThreadParsingToFinish(this);
+
+#ifdef JS_THREADSAFE
+    if (workerThreadState)
+        workerThreadState->cleanup();
+#endif
 
     /* Poison common names before final GC. */
     FinishCommonNames(this);
 
     /* Clear debugging state to remove GC roots. */
     for (CompartmentsIter comp(this, SkipAtoms); !comp.done(); comp.next()) {
         comp->clearTraps(defaultFreeOp());
         if (WatchpointMap *wpmap = comp->watchpointMap)
@@ -471,17 +499,21 @@ JSRuntime::~JSRuntime()
     GC(this, GC_NORMAL, JS::gcreason::DESTROY_RUNTIME);
 
     /*
      * Clear the self-hosted global and delete self-hosted classes *after*
      * GC, as finalizers for objects check for clasp->finalize during GC.
      */
     finishSelfHosting();
 
+    mainThread.removeFromThreadList();
+
 #ifdef JS_THREADSAFE
+    js_delete(workerThreadState);
+
     JS_ASSERT(!exclusiveAccessOwner);
     if (exclusiveAccessLock)
         PR_DestroyLock(exclusiveAccessLock);
 
     // Avoid bogus asserts during teardown.
     JS_ASSERT(!numExclusiveThreads);
     mainThreadHasExclusiveAccess = true;
 
@@ -973,17 +1005,17 @@ JSRuntime::assertCanLock(RuntimeLock whi
 #ifdef JS_THREADSAFE
     // 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:
         JS_ASSERT(exclusiveAccessOwner != PR_GetCurrentThread());
       case WorkerThreadStateLock:
-        JS_ASSERT(!WorkerThreadState().isLocked());
+        JS_ASSERT_IF(workerThreadState, !workerThreadState->isLocked());
       case CompilationLock:
         JS_ASSERT(compilationLockOwner != PR_GetCurrentThread());
       case OperationCallbackLock:
         JS_ASSERT(!currentThreadOwnsOperationCallbackLock());
       case GCLock:
         JS_ASSERT(gcLockOwner != PR_GetCurrentThread());
         break;
       default:
@@ -1045,19 +1077,9 @@ js::CurrentThreadCanReadCompilationData(
         return true;
 
     return pt->runtime_->currentThreadHasCompilationLock();
 #else
     return true;
 #endif
 }
 
-void
-js::AssertCurrentThreadCanLock(RuntimeLock which)
-{
-#ifdef JS_THREADSAFE
-    PerThreadData *pt = TlsPerThreadData.get();
-    if (pt && pt->runtime_)
-        pt->runtime_->assertCanLock(which);
-#endif
-}
-
 #endif // DEBUG
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -79,16 +79,17 @@ namespace WTF { class BumpPointerAllocat
 namespace js {
 
 typedef Rooted<JSLinearString*> RootedLinearString;
 
 class Activation;
 class ActivationIterator;
 class AsmJSActivation;
 class MathCache;
+class WorkerThreadState;
 
 namespace jit {
 class JitRuntime;
 class JitActivation;
 struct PcScriptCache;
 class Simulator;
 class SimulatorRuntime;
 }
@@ -471,41 +472,25 @@ namespace js {
 #define NAME_OFFSET(name)       offsetof(JSAtomState, name)
 
 inline HandlePropertyName
 AtomStateOffsetToName(const JSAtomState &atomState, size_t offset)
 {
     return *(js::FixedHeapPtr<js::PropertyName>*)((char*)&atomState + offset);
 }
 
-// There are several coarse locks in the enum below. These may be either
-// per-runtime or per-process. When acquiring more than one of these locks,
-// the acquisition must be done in the order below to avoid deadlocks.
-enum RuntimeLock {
-    ExclusiveAccessLock,
-    WorkerThreadStateLock,
-    CompilationLock,
-    OperationCallbackLock,
-    GCLock
-};
-
-#ifdef DEBUG
-void AssertCurrentThreadCanLock(RuntimeLock which);
-#else
-inline void AssertCurrentThreadCanLock(RuntimeLock which) {}
-#endif
-
 /*
  * Encapsulates portions of the runtime/context that are tied to a
- * single active thread.  Instances of this structure can occur for
- * the main thread as |JSRuntime::mainThread|, for select operations
- * performed off thread, such as parsing, and for Parallel JS worker
- * threads.
+ * single active thread.  Normally, as most JS is single-threaded,
+ * there is only one instance of this struct, embedded in the
+ * JSRuntime as the field |mainThread|.  During Parallel JS sections,
+ * however, there will be one instance per worker thread.
  */
-class PerThreadData : public PerThreadDataFriendFields
+class PerThreadData : public PerThreadDataFriendFields,
+                      public mozilla::LinkedListElement<PerThreadData>
 {
     /*
      * Backpointer to the full shared JSRuntime* with which this
      * thread is associated.  This is private because accessing the
      * fields of this runtime can provoke race conditions, so the
      * intention is that access will be mediated through safe
      * functions like |runtimeFromMainThread| and |associatedWith()| below.
      */
@@ -548,17 +533,16 @@ class PerThreadData : public PerThreadDa
      */
   private:
     friend class js::Activation;
     friend class js::ActivationIterator;
     friend class js::jit::JitActivation;
     friend class js::AsmJSActivation;
 #ifdef DEBUG
     friend bool js::CurrentThreadCanReadCompilationData();
-    friend void js::AssertCurrentThreadCanLock(RuntimeLock which);
 #endif
 
     /*
      * Points to the most recent activation running on the thread.
      * See Activation comment in vm/Stack.h.
      */
     js::Activation *activation_;
 
@@ -609,44 +593,27 @@ class PerThreadData : public PerThreadDa
 
     // Number of active bytecode compilation on this thread.
     unsigned activeCompilations;
 
     PerThreadData(JSRuntime *runtime);
     ~PerThreadData();
 
     bool init();
+    void addToThreadList();
+    void removeFromThreadList();
 
     bool associatedWith(const JSRuntime *rt) { return runtime_ == rt; }
     inline JSRuntime *runtimeFromMainThread();
     inline JSRuntime *runtimeIfOnOwnerThread();
 
     inline bool exclusiveThreadsPresent();
     inline void addActiveCompilation();
     inline void removeActiveCompilation();
 
-    // For threads which may be associated with different runtimes, depending
-    // on the work they are doing.
-    class AutoEnterRuntime
-    {
-        PerThreadData *pt;
-
-      public:
-        AutoEnterRuntime(PerThreadData *pt, JSRuntime *rt)
-          : pt(pt)
-        {
-            JS_ASSERT(!pt->runtime_);
-            pt->runtime_ = rt;
-        }
-
-        ~AutoEnterRuntime() {
-            pt->runtime_ = nullptr;
-        }
-    };
-
 #ifdef JS_ARM_SIMULATOR
     js::jit::Simulator *simulator() const;
     void setSimulator(js::jit::Simulator *sim);
     js::jit::SimulatorRuntime *simulatorRuntime() const;
     uintptr_t *addressOfSimulatorStackLimit();
 #endif
 };
 
@@ -675,35 +642,51 @@ struct JSRuntime : public JS::shadow::Ru
      *
      * NB: This field is statically asserted to be at offset
      * sizeof(js::shadow::Runtime). See
      * PerThreadDataFriendFields::getMainThread.
      */
     js::PerThreadData mainThread;
 
     /*
+     * List of per-thread data in the runtime, including mainThread. Currently
+     * this does not include instances of PerThreadData created for PJS.
+     */
+    mozilla::LinkedList<js::PerThreadData> threadList;
+
+    /*
      * If non-zero, we were been asked to call the operation callback as soon
      * as possible.
      */
 #ifdef JS_THREADSAFE
     mozilla::Atomic<int32_t, mozilla::Relaxed> interrupt;
 #else
     int32_t interrupt;
 #endif
 
     /* Set when handling a signal for a thread associated with this runtime. */
     bool handlingSignal;
 
     /* Branch callback */
     JSOperationCallback operationCallback;
 
+    // There are several per-runtime locks indicated by the enum below. When
+    // acquiring multiple of these locks, the acquisition must be done in the
+    // order below to avoid deadlocks.
+    enum RuntimeLock {
+        ExclusiveAccessLock,
+        WorkerThreadStateLock,
+        CompilationLock,
+        OperationCallbackLock,
+        GCLock
+    };
 #ifdef DEBUG
-    void assertCanLock(js::RuntimeLock which);
+    void assertCanLock(RuntimeLock which);
 #else
-    void assertCanLock(js::RuntimeLock which) {}
+    void assertCanLock(RuntimeLock which) {}
 #endif
 
   private:
     /*
      * Lock taken when triggering the operation callback from another thread.
      * Protects all data that is touched in this process.
      */
 #ifdef JS_THREADSAFE
@@ -714,17 +697,17 @@ struct JSRuntime : public JS::shadow::Ru
 #endif // JS_THREADSAFE
   public:
 
     class AutoLockForOperationCallback {
         JSRuntime *rt;
       public:
         AutoLockForOperationCallback(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : rt(rt) {
             MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-            rt->assertCanLock(js::OperationCallbackLock);
+            rt->assertCanLock(JSRuntime::OperationCallbackLock);
 #ifdef JS_THREADSAFE
             PR_Lock(rt->operationCallbackLock);
             rt->operationCallbackOwner = PR_GetCurrentThread();
 #else
             rt->operationCallbackLockTaken = true;
 #endif // JS_THREADSAFE
         }
         ~AutoLockForOperationCallback() {
@@ -745,16 +728,18 @@ struct JSRuntime : public JS::shadow::Ru
         return operationCallbackOwner == PR_GetCurrentThread();
 #else
         return operationCallbackLockTaken;
 #endif
     }
 
 #ifdef JS_THREADSAFE
 
+    js::WorkerThreadState *workerThreadState;
+
   private:
     /*
      * Lock taken when using per-runtime or per-zone data that could otherwise
      * be accessed simultaneously by both the main thread and another thread
      * with an ExclusiveContext.
      *
      * Locking this only occurs if there is actually a thread other than the
      * main thread with an ExclusiveContext which could access such data.
@@ -1394,17 +1379,17 @@ struct JSRuntime : public JS::shadow::Ru
     PRLock *gcLock;
     mozilla::DebugOnly<PRThread *> gcLockOwner;
 
     friend class js::GCHelperThread;
   public:
 
     void lockGC() {
 #ifdef JS_THREADSAFE
-        assertCanLock(js::GCLock);
+        assertCanLock(GCLock);
         PR_Lock(gcLock);
         JS_ASSERT(!gcLockOwner);
 #ifdef DEBUG
         gcLockOwner = PR_GetCurrentThread();
 #endif
 #endif
     }
 
@@ -1727,16 +1712,17 @@ struct JSRuntime : public JS::shadow::Ru
 
     void triggerOperationCallback(OperationCallbackTrigger trigger);
 
     void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes *runtime);
 
   private:
 
     JSUseHelperThreads useHelperThreads_;
+    unsigned cpuCount_;
 
     // Settings for how helper threads can be used.
     bool parallelIonCompilationEnabled_;
     bool parallelParsingEnabled_;
 
     // True iff this is a DOM Worker runtime.
     bool isWorkerRuntime_;
 
@@ -1748,24 +1734,49 @@ struct JSRuntime : public JS::shadow::Ru
     bool useHelperThreads() const {
 #ifdef JS_THREADSAFE
         return useHelperThreads_ == JS_USE_HELPER_THREADS;
 #else
         return false;
 #endif
     }
 
+    // This allows the JS shell to override GetCPUCount() when passed the
+    // --thread-count=N option.
+    void setFakeCPUCount(size_t count) {
+        cpuCount_ = count;
+    }
+
+    // Return a cached value of GetCPUCount() to avoid making the syscall all
+    // the time. Furthermore, this avoids pathological cases where the result of
+    // GetCPUCount() changes during execution.
+    unsigned cpuCount() const {
+        JS_ASSERT(cpuCount_ > 0);
+        return cpuCount_;
+    }
+
+    // The number of worker threads that will be available after
+    // EnsureWorkerThreadsInitialized has been called successfully.
+    unsigned workerThreadCount() const {
+        if (!useHelperThreads())
+            return 0;
+        return js::Max(2u, cpuCount());
+    }
+
     // Note: these values may be toggled dynamically (in response to about:config
     // prefs changing).
     void setParallelIonCompilationEnabled(bool value) {
         parallelIonCompilationEnabled_ = value;
     }
     bool canUseParallelIonCompilation() const {
+        // Require cpuCount_ > 1 so that Ion compilation jobs and main-thread
+        // execution are not competing for the same resources.
         return useHelperThreads() &&
-               parallelIonCompilationEnabled_;
+               parallelIonCompilationEnabled_ &&
+               cpuCount_ > 1;
     }
     void setParallelParsingEnabled(bool value) {
         parallelParsingEnabled_ = value;
     }
     bool canUseParallelParsing() const {
         return useHelperThreads() &&
                parallelParsingEnabled_;
     }
--- a/js/src/vm/ThreadPool.cpp
+++ b/js/src/vm/ThreadPool.cpp
@@ -386,22 +386,18 @@ ThreadPool::init()
 #else
     return true;
 #endif
 }
 
 uint32_t
 ThreadPool::numWorkers() const
 {
-#ifdef JS_THREADSAFE
     // Subtract one for the main thread, which always exists.
-    return WorkerThreadState().cpuCount - 1;
-#else
-    return 0;
-#endif
+    return runtime_->cpuCount() - 1;
 }
 
 bool
 ThreadPool::workStealing() const
 {
 #ifdef DEBUG
     if (char *stealEnv = getenv("JS_THREADPOOL_STEAL"))
         return !!strtol(stealEnv, nullptr, 10);