Bug 875125 - Allow scripts to be parsed/emitted off the main thread, r=billm.
authorBrian Hackett <bhackett1024@gmail.com>
Fri, 19 Jul 2013 08:06:02 -0600
changeset 151522 868ce514bba712fda6578a692505ad5cb938edb7
parent 151521 03d0dc9e1cb8b7e2c24c543dfdf412008a1bed23
child 151523 952442f77c132ff3b18ca6d35b4501658583b416
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm
bugs875125
milestone25.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 875125 - Allow scripts to be parsed/emitted off the main thread, r=billm.
js/src/builtin/Eval.cpp
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/BytecodeCompiler.h
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/ParseMaps-inl.h
js/src/frontend/Parser.cpp
js/src/gc/GCInternals.h
js/src/gc/Zone.cpp
js/src/gc/Zone.h
js/src/ion/AsmJS.cpp
js/src/ion/Ion.cpp
js/src/jsapi.cpp
js/src/jsatom.cpp
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jscntxtinlines.h
js/src/jscompartment.h
js/src/jscompartmentinlines.h
js/src/jsgc.cpp
js/src/jsobj.cpp
js/src/jspubtd.h
js/src/jsscript.cpp
js/src/jsscript.h
js/src/jsworkers.cpp
js/src/jsworkers.h
js/src/jswrapper.cpp
js/src/shell/js.cpp
js/src/vm/ArrayObject-inl.h
js/src/vm/ArrayObject.h
js/src/vm/Debugger.cpp
js/src/vm/Runtime-inl.h
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/vm/String.cpp
--- a/js/src/builtin/Eval.cpp
+++ b/js/src/builtin/Eval.cpp
@@ -311,17 +311,18 @@ EvalKernel(JSContext *cx, const CallArgs
 
         CompileOptions options(cx);
         options.setFileAndLine(filename, lineno)
                .setCompileAndGo(true)
                .setForEval(true)
                .setNoScriptRval(false)
                .setPrincipals(principals)
                .setOriginPrincipals(originPrincipals);
-        JSScript *compiled = frontend::CompileScript(cx, scopeobj, callerScript, options,
+        JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
+                                                     scopeobj, callerScript, options,
                                                      chars.get(), length, stableStr, staticLevel);
         if (!compiled)
             return false;
 
         MarkFunctionsWithinEvalScript(compiled);
 
         esg.setNewScript(compiled);
     }
@@ -375,17 +376,18 @@ js::DirectEvalFromIon(JSContext *cx,
 
         CompileOptions options(cx);
         options.setFileAndLine(filename, lineno)
                .setCompileAndGo(true)
                .setForEval(true)
                .setNoScriptRval(false)
                .setPrincipals(principals)
                .setOriginPrincipals(originPrincipals);
-        JSScript *compiled = frontend::CompileScript(cx, scopeobj, callerScript, options,
+        JSScript *compiled = frontend::CompileScript(cx, &cx->tempLifoAlloc(),
+                                                     scopeobj, callerScript, options,
                                                      chars.get(), length, stableStr, staticLevel);
         if (!compiled)
             return false;
 
         MarkFunctionsWithinEvalScript(compiled);
 
         esg.setNewScript(compiled);
     }
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -19,30 +19,31 @@
 
 #include "frontend/ParseMaps-inl.h"
 
 using namespace js;
 using namespace js::frontend;
 using mozilla::Maybe;
 
 static bool
-CheckLength(JSContext *cx, size_t length)
+CheckLength(ExclusiveContext *cx, size_t length)
 {
     // Note this limit is simply so we can store sourceStart and sourceEnd in
     // JSScript as 32-bits. It could be lifted fairly easily, since the compiler
     // is using size_t internally already.
     if (length > UINT32_MAX) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_SOURCE_TOO_LONG);
+        if (cx->isJSContext())
+            JS_ReportErrorNumber(cx->asJSContext(), js_GetErrorMessage, NULL, JSMSG_SOURCE_TOO_LONG);
         return false;
     }
     return true;
 }
 
 static bool
-SetSourceMap(JSContext *cx, TokenStream &tokenStream, ScriptSource *ss)
+SetSourceMap(ExclusiveContext *cx, TokenStream &tokenStream, ScriptSource *ss)
 {
     if (tokenStream.hasSourceMap()) {
         if (!ss->setSourceMap(cx, tokenStream.releaseSourceMap()))
             return false;
     }
     return true;
 }
 
@@ -69,23 +70,26 @@ CheckArgumentsWithinEval(JSContext *cx, 
         parser.report(ParseError, false, NULL, JSMSG_BAD_GENEXP_BODY, js_arguments_str);
         return false;
     }
 
     return true;
 }
 
 static bool
-MaybeCheckEvalFreeVariables(JSContext *cx, HandleScript evalCaller, HandleObject scopeChain,
+MaybeCheckEvalFreeVariables(ExclusiveContext *cxArg, HandleScript evalCaller, HandleObject scopeChain,
                             Parser<FullParseHandler> &parser,
                             ParseContext<FullParseHandler> &pc)
 {
     if (!evalCaller || !evalCaller->functionOrCallerFunction())
         return true;
 
+    // Eval scripts are only compiled on the main thread.
+    JSContext *cx = cxArg->asJSContext();
+
     // Watch for uses of 'arguments' within the evaluated script, both as
     // free variables and as variables redeclared with 'var'.
     RootedFunction fun(cx, evalCaller->functionOrCallerFunction());
     HandlePropertyName arguments = cx->names().arguments;
     for (AtomDefnRange r = pc.lexdeps->all(); !r.empty(); r.popFront()) {
         if (r.front().key() == arguments) {
             if (!CheckArgumentsWithinEval(cx, parser, fun))
                 return false;
@@ -115,51 +119,53 @@ MaybeCheckEvalFreeVariables(JSContext *c
             scope = scope->enclosingScope();
         }
     }
 
     return true;
 }
 
 inline bool
-CanLazilyParse(JSContext *cx, const CompileOptions &options)
+CanLazilyParse(ExclusiveContext *cx, const CompileOptions &options)
 {
     return options.canLazilyParse &&
         options.compileAndGo &&
         options.sourcePolicy == CompileOptions::SAVE_SOURCE &&
-        !cx->compartment()->debugMode();
+        cx->isJSContext() &&
+        !cx->asJSContext()->compartment()->debugMode();
 }
 
-inline void
-MaybeCallSourceHandler(JSContext *cx, const CompileOptions &options,
-                       const jschar *chars, size_t length)
+void
+frontend::MaybeCallSourceHandler(JSContext *cx, const CompileOptions &options,
+                                 const jschar *chars, size_t length)
 {
     JSSourceHandler listener = cx->runtime()->debugHooks.sourceHandler;
     void *listenerData = cx->runtime()->debugHooks.sourceHandlerData;
 
     if (listener) {
         void *listenerTSData;
         listener(options.filename, options.lineno, chars, length,
                  &listenerTSData, listenerData);
     }
 }
 
 JSScript *
-frontend::CompileScript(JSContext *cx, HandleObject scopeChain,
+frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject scopeChain,
                         HandleScript evalCaller,
                         const CompileOptions &options,
                         const jschar *chars, size_t length,
                         JSString *source_ /* = NULL */,
                         unsigned staticLevel /* = 0 */,
                         SourceCompressionToken *extraSct /* = NULL */)
 {
     RootedString source(cx, source_);
     SkipRoot skip(cx, &chars);
 
-    MaybeCallSourceHandler(cx, options, chars, length);
+    if (cx->isJSContext())
+        MaybeCallSourceHandler(cx->asJSContext(), options, chars, length);
 
     /*
      * The scripted callerFrame can only be given for compile-and-go scripts
      * and non-zero static level requires callerFrame.
      */
     JS_ASSERT_IF(evalCaller, options.compileAndGo);
     JS_ASSERT_IF(evalCaller, options.forEval);
     JS_ASSERT_IF(staticLevel != 0, evalCaller);
@@ -171,42 +177,49 @@ frontend::CompileScript(JSContext *cx, H
     if (!ss)
         return NULL;
     if (options.filename && !ss->setFilename(cx, options.filename))
         return NULL;
 
     JS::RootedScriptSource sourceObject(cx, ScriptSourceObject::create(cx, ss));
     if (!sourceObject)
         return NULL;
-    SourceCompressionToken mysct(cx);
-    SourceCompressionToken *sct = (extraSct) ? extraSct : &mysct;
+
+    // Saving source is not yet supported when parsing off thread.
+    JS_ASSERT_IF(!cx->isJSContext(), !extraSct && options.sourcePolicy == CompileOptions::NO_SOURCE);
+
+    SourceCompressionToken *sct = extraSct;
+    Maybe<SourceCompressionToken> mysct;
+    if (cx->isJSContext() && !sct) {
+        mysct.construct(cx->asJSContext());
+        sct = mysct.addr();
+    }
+
     switch (options.sourcePolicy) {
       case CompileOptions::SAVE_SOURCE:
-        if (!ss->setSourceCopy(cx, chars, length, false, sct))
+        if (!ss->setSourceCopy(cx->asJSContext(), chars, length, false, sct))
             return NULL;
         break;
       case CompileOptions::LAZY_SOURCE:
         ss->setSourceRetrievable();
         break;
       case CompileOptions::NO_SOURCE:
         break;
     }
 
     bool canLazilyParse = CanLazilyParse(cx, options);
 
     Maybe<Parser<SyntaxParseHandler> > syntaxParser;
     if (canLazilyParse) {
-        syntaxParser.construct(cx, &cx->tempLifoAlloc(),
-                               options, chars, length, /* foldConstants = */ false,
+        syntaxParser.construct(cx, alloc, options, chars, length, /* foldConstants = */ false,
                                (Parser<SyntaxParseHandler> *) NULL,
                                (LazyScript *) NULL);
     }
 
-    Parser<FullParseHandler> parser(cx, &cx->tempLifoAlloc(),
-                                    options, chars, length, /* foldConstants = */ true,
+    Parser<FullParseHandler> parser(cx, alloc, options, chars, length, /* foldConstants = */ true,
                                     canLazilyParse ? &syntaxParser.ref() : NULL, NULL);
     parser.sct = sct;
     parser.ss = ss;
 
     Directives directives(options.strictOption);
     GlobalSharedContext globalsc(cx, scopeChain, directives, options.extraWarningsOption);
 
     bool savedCallerFun =
@@ -322,17 +335,20 @@ frontend::CompileScript(JSContext *cx, H
 
         if (canHaveDirectives) {
             if (!parser.maybeParseDirective(/* stmtList = */ NULL, pn, &canHaveDirectives))
                 return NULL;
         }
 
         if (!FoldConstants(cx, &pn, &parser))
             return NULL;
-        if (!NameFunctions(cx, pn))
+
+        // Inferring names for functions in compiled scripts is currently only
+        // supported while on the main thread. See bug 895395.
+        if (cx->isJSContext() && !NameFunctions(cx->asJSContext(), pn))
             return NULL;
 
         if (!EmitTree(cx, &bce, pn))
             return NULL;
 
         parser.handler.freeTree(pn);
     }
 
@@ -349,17 +365,17 @@ frontend::CompileScript(JSContext *cx, H
     if (Emit1(cx, &bce, JSOP_STOP) < 0)
         return NULL;
 
     if (!JSScript::fullyInitFromEmitter(cx, script, &bce))
         return NULL;
 
     bce.tellDebuggerAboutCompiledScript(cx);
 
-    if (sct == &mysct && !sct->complete())
+    if (sct && !extraSct && !sct->complete())
         return NULL;
 
     return script;
 }
 
 bool
 frontend::CompileLazyFunction(JSContext *cx, LazyScript *lazy, const jschar *chars, size_t length)
 {
--- a/js/src/frontend/BytecodeCompiler.h
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -10,34 +10,44 @@
 #include "jsapi.h"
 
 class JSLinearString;
 
 namespace js {
 
 class AutoNameVector;
 class LazyScript;
+class LifoAlloc;
 struct SourceCompressionToken;
 
 namespace frontend {
 
 JSScript *
-CompileScript(JSContext *cx, HandleObject scopeChain, HandleScript evalCaller,
+CompileScript(ExclusiveContext *cx, LifoAlloc *alloc,
+              HandleObject scopeChain, HandleScript evalCaller,
               const CompileOptions &options, const jschar *chars, size_t length,
               JSString *source_ = NULL, unsigned staticLevel = 0,
               SourceCompressionToken *extraSct = NULL);
 
 bool
 CompileLazyFunction(JSContext *cx, LazyScript *lazy, const jschar *chars, size_t length);
 
 bool
 CompileFunctionBody(JSContext *cx, MutableHandleFunction fun, CompileOptions options,
                     const AutoNameVector &formals, const jschar *chars, size_t length);
 
 /*
+ * This should be called while still on the main thread if compilation will
+ * occur on a worker thread.
+ */
+void
+MaybeCallSourceHandler(JSContext *cx, const CompileOptions &options,
+                       const jschar *chars, size_t length);
+
+/*
  * True if str consists of an IdentifierStart character, followed by one or
  * more IdentifierPart characters, i.e. it matches the IdentifierName production
  * in the language spec.
  *
  * This returns true even if str is a keyword like "if".
  *
  * Defined in TokenStream.cpp.
  */
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -4456,16 +4456,18 @@ EmitFor(ExclusiveContext *cx, BytecodeEm
     return pn->pn_left->isKind(PNK_FORIN)
            ? EmitForIn(cx, bce, pn, top)
            : EmitNormalFor(cx, bce, pn, top);
 }
 
 static JS_NEVER_INLINE bool
 EmitFunc(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
+    cx->maybePause();
+
     FunctionBox *funbox = pn->pn_funbox;
     RootedFunction fun(cx, funbox->function());
     JS_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript());
 
     /*
      * Set the EMITTEDFUNCTION flag in function definitions once they have been
      * emitted. Function definitions that need hoisting to the top of the
      * function will be seen by EmitFunc in two places.
@@ -4518,19 +4520,16 @@ EmitFunc(ExclusiveContext *cx, BytecodeE
             options.setPrincipals(parent->principals())
                    .setOriginPrincipals(parent->originPrincipals)
                    .setCompileAndGo(parent->compileAndGo)
                    .setSelfHostingMode(parent->selfHosted)
                    .setNoScriptRval(false)
                    .setForEval(false)
                    .setVersion(parent->getVersion());
 
-            if (!cx->shouldBeJSContext())
-                return false;
-
             Rooted<JSObject*> enclosingScope(cx, EnclosingStaticScope(bce));
             Rooted<ScriptSourceObject *> sourceObject(cx, bce->script->sourceObject());
             Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingScope, false, options,
                                                           parent->staticLevel + 1,
                                                           sourceObject,
                                                           funbox->bufStart, funbox->bufEnd));
             if (!script)
                 return false;
--- a/js/src/frontend/ParseMaps-inl.h
+++ b/js/src/frontend/ParseMaps-inl.h
@@ -15,42 +15,49 @@ namespace js {
 namespace frontend {
 
 template <class Map>
 inline bool
 AtomThingMapPtr<Map>::ensureMap(ExclusiveContext *cx)
 {
     if (map_)
         return true;
+
+    AutoLockForExclusiveAccess lock(cx);
     map_ = cx->parseMapPool().acquire<Map>();
     return !!map_;
 }
 
 template <class Map>
 inline void
 AtomThingMapPtr<Map>::releaseMap(ExclusiveContext *cx)
 {
     if (!map_)
         return;
+
+    AutoLockForExclusiveAccess lock(cx);
     cx->parseMapPool().release(map_);
     map_ = NULL;
 }
 
 template <typename ParseHandler>
 inline bool
 AtomDecls<ParseHandler>::init()
 {
+    AutoLockForExclusiveAccess lock(cx);
     map = cx->parseMapPool().acquire<AtomDefnListMap>();
     return map;
 }
 
 template <typename ParseHandler>
 inline
 AtomDecls<ParseHandler>::~AtomDecls()
 {
-    if (map)
+    if (map) {
+        AutoLockForExclusiveAccess lock(cx);
         cx->parseMapPool().release(map);
+    }
 }
 
 } /* namespace frontend */
 } /* namespace js */
 
 #endif /* frontend_ParseMaps_inl_h */
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -2218,16 +2218,18 @@ bool
 Parser<ParseHandler>::functionArgsAndBodyGeneric(Node pn, HandleFunction fun, FunctionType type,
                                                  FunctionSyntaxKind kind,
                                                  Directives *newDirectives)
 {
     // Given a properly initialized parse context, try to parse an actual
     // function without concern for conversion to strict mode, use of lazy
     // parsing and such.
 
+    context->maybePause();
+
     Node prelude = null();
     bool hasRest;
     if (!functionArguments(kind, &prelude, pn, hasRest))
         return false;
 
     FunctionBox *funbox = pc->sc->asFunctionBox();
 
     fun->setArgCount(pc->numArgs());
--- a/js/src/gc/GCInternals.h
+++ b/js/src/gc/GCInternals.h
@@ -3,16 +3,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef gc_GCInternals_h
 #define gc_GCInternals_h
 
 #include "jsapi.h"
+#include "jsworkers.h"
 
 #include "vm/Runtime.h"
 
 namespace js {
 namespace gc {
 
 void
 MarkRuntime(JSTracer *trc, bool useSavedRoots = false);
@@ -45,16 +46,17 @@ class AutoTraceSession {
   protected:
     JSRuntime *runtime;
 
   private:
     AutoTraceSession(const AutoTraceSession&) MOZ_DELETE;
     void operator=(const AutoTraceSession&) MOZ_DELETE;
 
     js::HeapState prevState;
+    AutoPauseWorkersForGC pause;
 };
 
 struct AutoPrepareForTracing
 {
     AutoFinishGC finish;
     AutoTraceSession session;
     AutoCopyFreeListToArenas copy;
 
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -27,16 +27,17 @@ JS::Zone::Zone(JSRuntime *rt)
     active(false),
     gcScheduled(false),
     gcState(NoGC),
     gcPreserveCode(false),
     gcBytes(0),
     gcTriggerBytes(0),
     gcHeapGrowthFactor(3.0),
     isSystem(false),
+    usedByExclusiveThread(false),
     scheduledForDestruction(false),
     maybeAlive(true),
     gcMallocBytes(0),
     gcGrayRoots(),
     types(this)
 {
     /* Ensure that there are no vtables to mess us up here. */
     JS_ASSERT(reinterpret_cast<JS::shadow::Zone *>(this) ==
--- a/js/src/gc/Zone.h
+++ b/js/src/gc/Zone.h
@@ -181,17 +181,20 @@ struct Zone : private JS::shadow::Zone,
 
     void setGCState(CompartmentGCState state) {
         JS_ASSERT(rt->isHeapBusy());
         gcState = state;
     }
 
     void scheduleGC() {
         JS_ASSERT(!rt->isHeapBusy());
-        gcScheduled = true;
+
+        /* Note: zones cannot be collected while in use by other threads. */
+        if (!usedByExclusiveThread)
+            gcScheduled = true;
     }
 
     void unscheduleGC() {
         gcScheduled = false;
     }
 
     bool isGCScheduled() const {
         return gcScheduled;
@@ -230,16 +233,19 @@ struct Zone : private JS::shadow::Zone,
 
     volatile size_t              gcBytes;
     size_t                       gcTriggerBytes;
     size_t                       gcMaxMallocBytes;
     double                       gcHeapGrowthFactor;
 
     bool                         isSystem;
 
+    /* Whether this zone is being used by a thread with an ExclusiveContext. */
+    bool usedByExclusiveThread;
+
     /*
      * These flags help us to discover if a compartment that shouldn't be alive
      * manages to outlive a GC.
      */
     bool                         scheduledForDestruction;
     bool                         maybeAlive;
 
     /*
--- a/js/src/ion/AsmJS.cpp
+++ b/js/src/ion/AsmJS.cpp
@@ -4866,17 +4866,17 @@ CheckFunctionsSequential(ModuleCompiler 
     }
 
     if (!CheckAllFunctionsDefined(m))
         return false;
 
     return true;
 }
 
-#ifdef JS_PARALLEL_COMPILATION
+#ifdef JS_WORKER_THREADS
 // 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;
 
@@ -5058,17 +5058,17 @@ CheckFunctionsParallel(ModuleCompiler &m
             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;
 }
-#endif // JS_PARALLEL_COMPILATION
+#endif // JS_WORKER_THREADS
 
 static bool
 CheckFuncPtrTable(ModuleCompiler &m, ParseNode *var)
 {
     if (!IsDefinition(var))
         return m.fail(var, "function-pointer table name must be unique");
 
     ParseNode *arrayLiteral = MaybeDefinitionInitializer(var);
@@ -6306,17 +6306,17 @@ CheckModule(JSContext *cx, AsmJSParser &
         return false;
 
     if (!CheckPrecedingStatements(m, stmtList))
         return false;
 
     if (!CheckModuleGlobals(m))
         return false;
 
-#ifdef JS_PARALLEL_COMPILATION
+#ifdef JS_WORKER_THREADS
     if (OffThreadCompilationEnabled(cx)) {
         if (!CheckFunctionsParallel(m))
             return false;
     } else {
         if (!CheckFunctionsSequential(m))
             return false;
     }
 #else
@@ -6364,19 +6364,19 @@ js::CompileAsmJS(JSContext *cx, AsmJSPar
         return Warn(cx, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by javascript.options.asmjs in about:config");
 
     if (cx->compartment()->debugMode())
         return Warn(cx, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by debugger");
 
     if (!EnsureAsmJSSignalHandlersInstalled(cx->runtime()))
         return Warn(cx, JSMSG_USE_ASM_TYPE_FAIL, "Platform missing signal handler support");
 
-# ifdef JS_PARALLEL_COMPILATION
+# ifdef JS_WORKER_THREADS
     if (OffThreadCompilationEnabled(cx)) {
-        if (!EnsureParallelCompilationInitialized(cx->runtime()))
+        if (!EnsureWorkerThreadsInitialized(cx->runtime()))
             return Warn(cx, JSMSG_USE_ASM_TYPE_FAIL, "Failed compilation thread initialization");
     }
 # endif
 
     ScopedJSFreePtr<char> compilationTimeReport;
     ScopedJSDeletePtr<AsmJSModule> module;
     if (!CheckModule(cx, parser, stmtList, &module, &compilationTimeReport))
         return !cx->isExceptionPending();
--- a/js/src/ion/Ion.cpp
+++ b/js/src/ion/Ion.cpp
@@ -188,17 +188,18 @@ IonRuntime::~IonRuntime()
 {
     js_delete(functionWrappers_);
     freeOsrTempData();
 }
 
 bool
 IonRuntime::initialize(JSContext *cx)
 {
-    AutoCompartment ac(cx, cx->runtime()->atomsCompartment);
+    AutoLockForExclusiveAccess lock(cx);
+    AutoCompartment ac(cx, cx->atomsCompartment());
 
     IonContext ictx(cx, NULL);
     AutoFlushCache afc("IonRuntime::initialize");
 
     execAlloc_ = cx->runtime()->getExecAlloc(cx);
     if (!execAlloc_)
         return false;
 
@@ -272,16 +273,17 @@ IonRuntime::initialize(JSContext *cx)
 }
 
 IonCode *
 IonRuntime::debugTrapHandler(JSContext *cx)
 {
     if (!debugTrapHandler_) {
         // IonRuntime code stubs are shared across compartments and have to
         // be allocated in the atoms compartment.
+        AutoLockForExclusiveAccess lock(cx);
         AutoCompartment ac(cx, cx->runtime()->atomsCompartment);
         debugTrapHandler_ = generateDebugTrapHandler(cx);
     }
     return debugTrapHandler_;
 }
 
 uint8_t *
 IonRuntime::allocateOsrTempData(size_t size)
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -681,36 +681,59 @@ PerThreadData::PerThreadData(JSRuntime *
     gcKeepAtoms(0),
     activeCompilations(0)
 {}
 
 PerThreadData::~PerThreadData()
 {
     if (dtoaState)
         js_DestroyDtoaState(dtoaState);
+
+    if (isInList())
+        removeFromThreadList();
 }
 
 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.
+    runtime_->assertValidThread();
+    runtime_->threadList.insertBack(this);
+}
+
+void
+PerThreadData::removeFromThreadList()
+{
+    runtime_->assertValidThread();
+    removeFrom(runtime_->threadList);
+}
+
 JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
   : mainThread(this),
     interrupt(0),
 #ifdef JS_THREADSAFE
     operationCallbackLock(NULL),
 #ifdef DEBUG
     operationCallbackOwner(NULL),
 #endif
+    exclusiveAccessLock(NULL),
+    exclusiveAccessOwner(NULL),
+    mainThreadHasExclusiveAccess(false),
+    numExclusiveThreads(0),
 #endif
     atomsCompartment(NULL),
     systemZone(NULL),
     numCompartments(0),
     localeCallbacks(NULL),
     defaultLocale(NULL),
     defaultVersion_(JSVERSION_DEFAULT),
 #ifdef JS_THREADSAFE
@@ -886,22 +909,27 @@ bool
 JSRuntime::init(uint32_t maxbytes)
 {
 #ifdef JS_THREADSAFE
     ownerThread_ = PR_GetCurrentThread();
 
     operationCallbackLock = PR_NewLock();
     if (!operationCallbackLock)
         return false;
+
+    exclusiveAccessLock = PR_NewLock();
+    if (!exclusiveAccessLock)
+        return false;
 #endif
 
     if (!mainThread.init())
         return false;
 
     js::TlsPerThreadData.set(&mainThread);
+    mainThread.addToThreadList();
 
     if (!js_InitGC(this, maxbytes))
         return false;
 
     if (!gcMarker.init())
         return false;
 
     const char *size = getenv("JSGC_MARK_STACK_LIMIT");
@@ -952,38 +980,42 @@ JSRuntime::init(uint32_t maxbytes)
     nativeStackBase = GetNativeStackBase();
 
     jitSupportsFloatingPoint = JitSupportsFloatingPoint();
     return true;
 }
 
 JSRuntime::~JSRuntime()
 {
+    mainThread.removeFromThreadList();
+
 #ifdef JS_THREADSAFE
+# ifdef JS_ION
+    if (workerThreadState)
+        js_delete(workerThreadState);
+# endif
+    sourceCompressorThread.finish();
+
     clearOwnerThread();
 
     JS_ASSERT(!operationCallbackOwner);
     if (operationCallbackLock)
         PR_DestroyLock(operationCallbackLock);
+
+    JS_ASSERT(!exclusiveAccessOwner);
+    if (exclusiveAccessLock)
+        PR_DestroyLock(exclusiveAccessLock);
 #endif
 
     /*
      * Even though all objects in the compartment are dead, we may have keep
      * some filenames around because of gcKeepAtoms.
      */
     FreeScriptData(this);
 
-#ifdef JS_THREADSAFE
-# ifdef JS_ION
-    if (workerThreadState)
-        js_delete(workerThreadState);
-# endif
-    sourceCompressorThread.finish();
-#endif
-
 #ifdef DEBUG
     /* Don't hurt everyone in leaky ol' Mozilla with a fatal JS_ASSERT! */
     if (hasContexts()) {
         unsigned cxcount = 0;
         for (ContextIter acx(this); !acx.done(); acx.next()) {
             fprintf(stderr,
 "JS API usage error: found live context at %p\n",
                     (void *) acx.get());
@@ -5199,17 +5231,17 @@ JS::Compile(JSContext *cx, HandleObject 
 {
     JS_THREADSAFE_ASSERT(cx->compartment() != cx->runtime()->atomsCompartment);
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     assertSameCompartment(cx, obj);
     JS_ASSERT_IF(options.principals, cx->compartment()->principals == options.principals);
     AutoLastFrameCheck lfc(cx);
 
-    return frontend::CompileScript(cx, obj, NullPtr(), options, chars, length);
+    return frontend::CompileScript(cx, &cx->tempLifoAlloc(), obj, NullPtr(), options, chars, length);
 }
 
 JSScript *
 JS::Compile(JSContext *cx, HandleObject obj, CompileOptions options,
             const char *bytes, size_t length)
 {
     jschar *chars;
     if (options.utf8)
@@ -5540,17 +5572,18 @@ JS::Evaluate(JSContext *cx, HandleObject
     assertSameCompartment(cx, obj);
     JS_ASSERT_IF(options.principals, cx->compartment()->principals == options.principals);
 
     AutoLastFrameCheck lfc(cx);
 
     options.setCompileAndGo(true);
     options.setNoScriptRval(!rval);
     SourceCompressionToken sct(cx);
-    RootedScript script(cx, frontend::CompileScript(cx, obj, NullPtr(), options,
+    RootedScript script(cx, frontend::CompileScript(cx, &cx->tempLifoAlloc(),
+                                                    obj, NullPtr(), options,
                                                     chars, length, NULL, 0, &sct));
     if (!script)
         return false;
 
     JS_ASSERT(script->getVersion() == options.version);
 
     bool result = Execute(cx, script, *obj, rval);
     if (!sct.complete())
--- a/js/src/jsatom.cpp
+++ b/js/src/jsatom.cpp
@@ -237,33 +237,35 @@ AtomizeAndTakeOwnership(ExclusiveContext
 {
     JS_ASSERT(tbchars[length] == 0);
 
     if (JSAtom *s = cx->staticStrings().lookup(tbchars, length)) {
         js_free(tbchars);
         return s;
     }
 
+    AutoLockForExclusiveAccess lock(cx);
+
     /*
      * If a GC occurs at js_NewStringCopy then |p| will still have the correct
      * hash, allowing us to avoid rehashing it. Even though the hash is
      * unchanged, we need to re-lookup the table position because a last-ditch
      * GC will potentially free some table entries.
      */
     AtomSet& atoms = cx->atoms();
     AtomSet::AddPtr p = atoms.lookupForAdd(AtomHasher::Lookup(tbchars, length));
     SkipRoot skipHash(cx, &p); /* Prevent the hash from being poisoned. */
     if (p) {
         JSAtom *atom = p->asPtr();
         p->setTagged(bool(ib));
         js_free(tbchars);
         return atom;
     }
 
-    AutoCompartment ac(cx, cx->asJSContext()->runtime()->atomsCompartment);
+    AutoCompartment ac(cx, cx->atomsCompartment());
 
     JSFlatString *flat = js_NewString<CanGC>(cx, tbchars, length);
     if (!flat) {
         js_free(tbchars);
         return NULL;
     }
 
     JSAtom *atom = flat->morphAtomizedStringIntoAtom();
@@ -288,26 +290,28 @@ AtomizeAndCopyChars(ExclusiveContext *cx
 
     /*
      * If a GC occurs at js_NewStringCopy then |p| will still have the correct
      * hash, allowing us to avoid rehashing it. Even though the hash is
      * unchanged, we need to re-lookup the table position because a last-ditch
      * GC will potentially free some table entries.
      */
 
+    AutoLockForExclusiveAccess lock(cx);
+
     AtomSet& atoms = cx->atoms();
     AtomSet::AddPtr p = atoms.lookupForAdd(AtomHasher::Lookup(tbchars, length));
     SkipRoot skipHash(cx, &p); /* Prevent the hash from being poisoned. */
     if (p) {
         JSAtom *atom = p->asPtr();
         p->setTagged(bool(ib));
         return atom;
     }
 
-    AutoCompartment ac(cx, cx->asJSContext()->runtime()->atomsCompartment);
+    AutoCompartment ac(cx, cx->atomsCompartment());
 
     JSFlatString *flat = js_NewStringCopyN<allowGC>(cx, tbchars, length);
     if (!flat)
         return NULL;
 
     JSAtom *atom = flat->morphAtomizedStringIntoAtom();
 
     if (!atoms.relookupOrAdd(p, AtomHasher::Lookup(tbchars, length),
@@ -326,16 +330,18 @@ js::AtomizeString(ExclusiveContext *cx, 
                   js::InternBehavior ib /* = js::DoNotInternAtom */)
 {
     if (str->isAtom()) {
         JSAtom &atom = str->asAtom();
         /* N.B. static atoms are effectively always interned. */
         if (ib != InternAtom || js::StaticStrings::isStatic(&atom))
             return &atom;
 
+        AutoLockForExclusiveAccess lock(cx);
+
         AtomSet::Ptr p = cx->atoms().lookup(AtomHasher::Lookup(&atom));
         JS_ASSERT(p); /* Non-static atom must exist in atom state set. */
         JS_ASSERT(p->asPtr() == &atom);
         JS_ASSERT(ib == InternAtom);
         p->setTagged(bool(ib));
         return &atom;
     }
 
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -255,19 +255,20 @@ js::DestroyContext(JSContext *cx, Destro
 
         /*
          * Dump remaining type inference results first. This printing
          * depends on atoms still existing.
          */
         for (CompartmentsIter c(rt); !c.done(); c.next())
             c->types.print(cx, false);
 
-        /* Off thread ion compilations depend on atoms still existing. */
+        /* Off thread compilation and parsing depend on atoms still existing. */
         for (CompartmentsIter c(rt); !c.done(); c.next())
             CancelOffThreadIonCompile(c, NULL);
+        WaitForOffThreadParsingToFinish(rt);
 
         /* Unpin all common names before final GC. */
         FinishCommonNames(rt);
 
         /* Clear debugging state to remove GC roots. */
         for (CompartmentsIter c(rt); !c.done(); c.next())
             c->clearTraps(rt->defaultFreeOp());
         JS_ClearAllWatchPoints(cx);
@@ -1029,17 +1030,18 @@ js_HandleExecutionInterrupt(JSContext *c
     if (cx->runtime()->interrupt)
         result = js_InvokeOperationCallback(cx) && result;
     return result;
 }
 
 js::ThreadSafeContext::ThreadSafeContext(JSRuntime *rt, PerThreadData *pt, ContextKind kind)
   : ContextFriendFields(rt),
     contextKind_(kind),
-    perThreadData(pt)
+    perThreadData(pt),
+    allocator_(NULL)
 { }
 
 bool
 ThreadSafeContext::isForkJoinSlice() const
 {
     return contextKind_ == Context_ForkJoin;
 }
 
@@ -1053,17 +1055,16 @@ ThreadSafeContext::asForkJoinSlice()
 JSContext::JSContext(JSRuntime *rt)
   : ExclusiveContext(rt, &rt->mainThread, Context_JS),
     throwing(false),
     exception(UndefinedValue()),
     options_(0),
     reportGranularity(JS_DEFAULT_JITREPORT_GRANULARITY),
     resolvingList(NULL),
     generatingError(false),
-    enterCompartmentDepth_(0),
     savedFrameChains_(),
     defaultCompartmentObject_(NULL),
     cycleDetectorSet(MOZ_THIS_IN_INITIALIZER_LIST()),
     errorReporter(NULL),
     operationCallback(NULL),
     data(NULL),
     data2(NULL),
 #ifdef JS_THREADSAFE
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -118,24 +118,25 @@ class DtoaCache;
  * Several different structures may be used to provide a context for operations
  * on the VM. Each context is thread local, but varies in what data it can
  * access and what other threads may be running.
  *
  * - ThreadSafeContext is used by threads operating in one compartment which
  * may run in parallel with other threads operating on the same or other
  * compartments.
  *
- * - ExclusiveContext is used by threads operating in one compartment, where
- * other threads may operate in other compartments, but *not* the one which
- * the ExclusiveContext is in. A thread with an ExclusiveContext may enter the
- * atoms compartment and atomize strings, in which case a lock is used.
+ * - ExclusiveContext is used by threads operating in one compartment/zone,
+ * where other threads may operate in other compartments, but *not* the same
+ * compartment or zone which the ExclusiveContext is in. A thread with an
+ * ExclusiveContext may enter the atoms compartment and atomize strings, in
+ * which case a lock is used.
  *
  * - JSContext is used only by the runtime's main thread. The context may
- * operate in any compartment which is not locked by an ExclusiveContext or
- * ThreadSafeContext, and will only run in parallel with threads using such
+ * operate in any compartment or zone which is not used by an ExclusiveContext
+ * or ThreadSafeContext, and will only run in parallel with threads using such
  * contexts.
  *
  * An ExclusiveContext coerces to a ThreadSafeContext, and a JSContext coerces
  * to an ExclusiveContext or ThreadSafeContext.
  *
  * Contexts which are a ThreadSafeContext but not an ExclusiveContext are used
  * to represent a ForkJoinSlice, the per-thread parallel context used in PJS.
  */
@@ -274,33 +275,75 @@ struct ThreadSafeContext : ContextFriend
     bool isHeapBusy() { return runtime_->isHeapBusy(); }
 
     // Thread local data that may be accessed freely.
     DtoaState *dtoaState() {
         return perThreadData->dtoaState;
     }
 };
 
+struct WorkerThread;
+
 class ExclusiveContext : public ThreadSafeContext
 {
     friend class gc::ArenaLists;
     friend class CompartmentChecker;
-    friend class AutoEnterAtomsCompartment;
+    friend class AutoCompartment;
+    friend class AutoLockForExclusiveAccess;
     friend struct StackBaseShape;
     friend void JSScript::initCompartmentAndPrincipals(ExclusiveContext *cx,
                                                        const JS::CompileOptions &options);
 
-    inline void privateSetCompartment(JSCompartment *comp);
+    // The worker on which this context is running, if this is not a JSContext.
+    WorkerThread *workerThread;
 
   public:
 
     ExclusiveContext(JSRuntime *rt, PerThreadData *pt, ContextKind kind)
-      : ThreadSafeContext(rt, pt, kind)
+      : ThreadSafeContext(rt, pt, kind),
+        workerThread(NULL),
+        enterCompartmentDepth_(0)
     {}
 
+    /*
+     * "Entering" a compartment changes cx->compartment (which changes
+     * cx->global). Note that this does not push any StackFrame which means
+     * that it is possible for cx->fp()->compartment() != cx->compartment.
+     * This is not a problem since, in general, most places in the VM cannot
+     * know that they were called from script (e.g., they may have been called
+     * through the JSAPI via JS_CallFunction) and thus cannot expect fp.
+     *
+     * Compartments should be entered/left in a LIFO fasion. The depth of this
+     * enter/leave stack is maintained by enterCompartmentDepth_ and queried by
+     * hasEnteredCompartment.
+     *
+     * To enter a compartment, code should prefer using AutoCompartment over
+     * manually calling cx->enterCompartment/leaveCompartment.
+     */
+  protected:
+    unsigned            enterCompartmentDepth_;
+    inline void setCompartment(JSCompartment *comp);
+  public:
+    bool hasEnteredCompartment() const {
+        return enterCompartmentDepth_ > 0;
+    }
+#ifdef DEBUG
+    unsigned getEnterCompartmentDepth() const {
+        return enterCompartmentDepth_;
+    }
+#endif
+
+    inline void enterCompartment(JSCompartment *c);
+    inline void leaveCompartment(JSCompartment *oldCompartment);
+
+    void setWorkerThread(WorkerThread *workerThread);
+
+    // If required, pause this thread until notified to continue by the main thread.
+    inline void maybePause() const;
+
     inline bool typeInferenceEnabled() const;
 
     // Per compartment data that can be accessed freely from an ExclusiveContext.
     inline RegExpCompartment &regExps();
     inline RegExpStatics *regExpStatics();
     inline PropertyTree &propertyTree();
     inline BaseShapeSet &baseShapes();
     inline InitialShapeSet &initialShapes();
@@ -309,27 +352,32 @@ class ExclusiveContext : public ThreadSa
 
     // Current global. This is only safe to use within the scope of the
     // AutoCompartment from which it's called.
     inline js::Handle<js::GlobalObject*> global() const;
 
     // Methods to access runtime wide data that must be protected by locks.
 
     frontend::ParseMapPool &parseMapPool() {
-        runtime_->assertValidThread();
+        JS_ASSERT(runtime_->currentThreadHasExclusiveAccess());
         return runtime_->parseMapPool;
     }
 
     AtomSet &atoms() {
-        runtime_->assertValidThread();
+        JS_ASSERT(runtime_->currentThreadHasExclusiveAccess());
         return runtime_->atoms;
     }
 
+    JSCompartment *atomsCompartment() {
+        JS_ASSERT(runtime_->currentThreadHasExclusiveAccess());
+        return runtime_->atomsCompartment;
+    }
+
     ScriptDataTable &scriptDataTable() {
-        runtime_->assertValidThread();
+        JS_ASSERT(runtime_->currentThreadHasExclusiveAccess());
         return runtime_->scriptDataTable;
     }
 };
 
 inline void
 MaybeCheckStackRoots(ExclusiveContext *cx)
 {
     MaybeCheckStackRoots(cx->maybeJSContext());
@@ -348,64 +396,34 @@ struct JSContext : public js::ExclusiveC
 
     inline JS::Zone *zone() const {
         JS_ASSERT_IF(!compartment(), !zone_);
         JS_ASSERT_IF(compartment(), js::GetCompartmentZone(compartment()) == zone_);
         return zone_;
     }
     js::PerThreadData &mainThread() const { return runtime()->mainThread; }
 
+    friend class js::ExclusiveContext;
+
   private:
     /* Exception state -- the exception member is a GC root by definition. */
     bool                throwing;            /* is there a pending exception? */
     js::Value           exception;           /* most-recently-thrown exception */
 
     /* Per-context options. */
     unsigned            options_;            /* see jsapi.h for JSOPTION_* */
 
   public:
     int32_t             reportGranularity;  /* see vm/Probes.h */
 
     js::AutoResolving   *resolvingList;
 
     /* True if generating an error, to prevent runaway recursion. */
     bool                generatingError;
 
-    inline void setCompartment(JSCompartment *comp);
-
-    /*
-     * "Entering" a compartment changes cx->compartment (which changes
-     * cx->global). Note that this does not push any StackFrame which means
-     * that it is possible for cx->fp()->compartment() != cx->compartment.
-     * This is not a problem since, in general, most places in the VM cannot
-     * know that they were called from script (e.g., they may have been called
-     * through the JSAPI via JS_CallFunction) and thus cannot expect fp.
-     *
-     * Compartments should be entered/left in a LIFO fasion. The depth of this
-     * enter/leave stack is maintained by enterCompartmentDepth_ and queried by
-     * hasEnteredCompartment.
-     *
-     * To enter a compartment, code should prefer using AutoCompartment over
-     * manually calling cx->enterCompartment/leaveCompartment.
-     */
-  private:
-    unsigned            enterCompartmentDepth_;
-  public:
-    bool hasEnteredCompartment() const {
-        return enterCompartmentDepth_ > 0;
-    }
-#ifdef DEBUG
-    unsigned getEnterCompartmentDepth() const {
-        return enterCompartmentDepth_;
-    }
-#endif
-
-    inline void enterCompartment(JSCompartment *c);
-    inline void leaveCompartment(JSCompartment *oldCompartment);
-
     /* See JS_SaveFrameChain/JS_RestoreFrameChain. */
   private:
     struct SavedFrameChain {
         SavedFrameChain(JSCompartment *comp, unsigned count)
           : compartment(comp), enterCompartmentCount(count) {}
         JSCompartment *compartment;
         unsigned enterCompartmentCount;
     };
--- a/js/src/jscntxtinlines.h
+++ b/js/src/jscntxtinlines.h
@@ -8,16 +8,17 @@
 #define jscntxtinlines_h
 
 #include "jscntxt.h"
 
 #include "jscompartment.h"
 #include "jsfriendapi.h"
 #include "jsgc.h"
 #include "jsiter.h"
+#include "jsworkers.h"
 
 #include "builtin/Object.h" // For js::obj_construct
 #include "frontend/ParseMaps.h"
 #include "ion/IonFrames.h" // For GetPcScript
 #include "vm/Interpreter.h"
 #include "vm/Probes.h"
 #include "vm/RegExpObject.h"
 
@@ -370,16 +371,79 @@ ExclusiveContext::initialShapes()
 }
 
 inline DtoaCache &
 ExclusiveContext::dtoaCache()
 {
     return compartment_->dtoaCache;
 }
 
+inline void
+ExclusiveContext::maybePause() const
+{
+#ifdef JS_WORKER_THREADS
+    if (workerThread && runtime_->workerThreadState->shouldPause) {
+        AutoLockWorkerThreadState lock(runtime_);
+        workerThread->pause();
+    }
+#endif
+}
+
+class AutoLockForExclusiveAccess
+{
+#ifdef JS_THREADSAFE
+    JSRuntime *runtime;
+
+    void init(JSRuntime *rt) {
+        runtime = rt;
+        if (runtime->numExclusiveThreads) {
+            PR_Lock(runtime->exclusiveAccessLock);
+#ifdef DEBUG
+            runtime->exclusiveAccessOwner = PR_GetCurrentThread();
+#endif
+        } else {
+            JS_ASSERT(!runtime->mainThreadHasExclusiveAccess);
+            runtime->mainThreadHasExclusiveAccess = true;
+        }
+    }
+
+  public:
+    AutoLockForExclusiveAccess(ExclusiveContext *cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
+        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+        init(cx->runtime_);
+    }
+    AutoLockForExclusiveAccess(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
+        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+        init(rt);
+    }
+    ~AutoLockForExclusiveAccess() {
+        if (runtime->numExclusiveThreads) {
+            JS_ASSERT(runtime->exclusiveAccessOwner == PR_GetCurrentThread());
+#ifdef DEBUG
+            runtime->exclusiveAccessOwner = NULL;
+#endif
+            PR_Unlock(runtime->exclusiveAccessLock);
+        } else {
+            JS_ASSERT(runtime->mainThreadHasExclusiveAccess);
+            runtime->mainThreadHasExclusiveAccess = false;
+        }
+    }
+#else // JS_THREADSAFE
+  public:
+    AutoLockForExclusiveAccess(ExclusiveContext *cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
+        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+    }
+    AutoLockForExclusiveAccess(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
+        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+    }
+#endif // JS_THREADSAFE
+
+    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
 }  /* namespace js */
 
 inline js::LifoAlloc &
 JSContext::analysisLifoAlloc()
 {
     return compartment()->analysisLifoAlloc;
 }
 
@@ -406,65 +470,75 @@ JSContext::setDefaultCompartmentObject(J
 inline void
 JSContext::setDefaultCompartmentObjectIfUnset(JSObject *obj)
 {
     if (!defaultCompartmentObject_)
         setDefaultCompartmentObject(obj);
 }
 
 inline void
-JSContext::enterCompartment(JSCompartment *c)
+js::ExclusiveContext::enterCompartment(JSCompartment *c)
 {
     enterCompartmentDepth_++;
     c->enter();
     setCompartment(c);
-    if (throwing)
-        wrapPendingException();
+
+    if (JSContext *cx = maybeJSContext()) {
+        if (cx->throwing)
+            cx->wrapPendingException();
+    }
 }
 
 inline void
-JSContext::leaveCompartment(JSCompartment *oldCompartment)
+js::ExclusiveContext::leaveCompartment(JSCompartment *oldCompartment)
 {
     JS_ASSERT(hasEnteredCompartment());
     enterCompartmentDepth_--;
 
     // Only call leave() after we've setCompartment()-ed away from the current
     // compartment.
-    JSCompartment *startingCompartment = compartment();
+    JSCompartment *startingCompartment = compartment_;
     setCompartment(oldCompartment);
     startingCompartment->leave();
 
-    if (throwing && oldCompartment)
-        wrapPendingException();
+    if (JSContext *cx = maybeJSContext()) {
+        if (cx->throwing && oldCompartment)
+            cx->wrapPendingException();
+    }
 }
 
 inline void
-JSContext::setCompartment(JSCompartment *comp)
+js::ExclusiveContext::setCompartment(JSCompartment *comp)
 {
+    // ExclusiveContexts can only be in the atoms zone or in exclusive zones.
+    JS_ASSERT_IF(!isJSContext() && comp != runtime_->atomsCompartment,
+                 comp->zone()->usedByExclusiveThread);
+
+    // Normal JSContexts cannot enter exclusive zones.
+    JS_ASSERT_IF(isJSContext() && comp,
+                 !comp->zone()->usedByExclusiveThread);
+
+    // Only one thread can be in the atoms compartment at a time.
+    JS_ASSERT_IF(comp == runtime_->atomsCompartment,
+                 runtime_->currentThreadHasExclusiveAccess());
+
+    // Make sure that the atoms compartment has its own zone.
+    JS_ASSERT_IF(comp && comp != runtime_->atomsCompartment,
+                 comp->zone() != runtime_->atomsCompartment->zone());
+
     // Both the current and the new compartment should be properly marked as
     // entered at this point.
     JS_ASSERT_IF(compartment_, compartment_->hasBeenEntered());
     JS_ASSERT_IF(comp, comp->hasBeenEntered());
+
     compartment_ = comp;
     zone_ = comp ? comp->zone() : NULL;
     allocator_ = zone_ ? &zone_->allocator : NULL;
 }
 
-inline void
-js::ExclusiveContext::privateSetCompartment(JSCompartment *comp)
-{
-    if (isJSContext()) {
-        asJSContext()->setCompartment(comp);
-    } else {
-        compartment_ = comp;
-        if (zone_ != comp->zone())
-            MOZ_CRASH();
-    }
-}
-
 inline JSScript *
 JSContext::currentScript(jsbytecode **ppc,
                          MaybeAllowCrossCompartment allowCrossCompartment) const
 {
     if (ppc)
         *ppc = NULL;
 
     js::Activation *act = mainThread().activation();
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -451,25 +451,25 @@ class AssertCompartmentUnchanged
   protected:
     JSContext * const cx;
     JSCompartment * const oldCompartment;
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 class AutoCompartment
 {
-    JSContext * const cx_;
+    ExclusiveContext * const cx_;
     JSCompartment * const origin_;
 
   public:
-    inline AutoCompartment(JSContext *cx, JSObject *target);
+    inline AutoCompartment(ExclusiveContext *cx, JSObject *target);
     inline AutoCompartment(ExclusiveContext *cx, JSCompartment *target);
     inline ~AutoCompartment();
 
-    JSContext *context() const { return cx_; }
+    ExclusiveContext *context() const { return cx_; }
     JSCompartment *origin() const { return origin_; }
 
   private:
     AutoCompartment(const AutoCompartment &) MOZ_DELETE;
     AutoCompartment & operator=(const AutoCompartment &) MOZ_DELETE;
 };
 
 /*
--- a/js/src/jscompartmentinlines.h
+++ b/js/src/jscompartmentinlines.h
@@ -21,26 +21,26 @@ JSCompartment::initGlobal(js::GlobalObje
 
 js::GlobalObject *
 JSCompartment::maybeGlobal() const
 {
     JS_ASSERT_IF(global_, global_->compartment() == this);
     return global_;
 }
 
-js::AutoCompartment::AutoCompartment(JSContext *cx, JSObject *target)
+js::AutoCompartment::AutoCompartment(ExclusiveContext *cx, JSObject *target)
   : cx_(cx),
-    origin_(cx->compartment())
+    origin_(cx->compartment_)
 {
     cx_->enterCompartment(target->compartment());
 }
 
 js::AutoCompartment::AutoCompartment(ExclusiveContext *cx, JSCompartment *target)
-  : cx_(cx->asJSContext()),
-    origin_(cx_->compartment())
+  : cx_(cx),
+    origin_(cx_->compartment_)
 {
     cx_->enterCompartment(target);
 }
 
 js::AutoCompartment::~AutoCompartment()
 {
     cx_->leaveCompartment(origin_);
 }
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -73,16 +73,17 @@ using mozilla::Swap;
 #ifdef JS_ION
 # include "ion/BaselineJIT.h"
 #endif
 
 #include "jsgcinlines.h"
 #include "jsobjinlines.h"
 
 #include "vm/String-inl.h"
+#include "vm/Runtime-inl.h"
 #include "vm/Stack-inl.h"
 
 #ifdef XP_WIN
 # include "jswin.h"
 #else
 # include <unistd.h>
 #endif
 
@@ -2589,18 +2590,20 @@ PurgeRuntime(JSRuntime *rt)
     rt->interpreterStack().purge(rt);
 
     rt->gsnCache.purge();
     rt->newObjectCache.purge();
     rt->nativeIterCache.purge();
     rt->sourceDataCache.purge();
     rt->evalCache.clear();
 
-    // FIXME bug 875125 this should check all instances of PerThreadData.
-    if (!rt->mainThread.activeCompilations)
+    bool activeCompilations = false;
+    for (ThreadDataIter iter(rt); !iter.done(); iter.next())
+        activeCompilations |= iter->activeCompilations;
+    if (!activeCompilations)
         rt->parseMapPool.purgeAll();
 }
 
 static bool
 ShouldPreserveJITCode(JSCompartment *comp, int64_t currentTime)
 {
     if (comp->rt->gcShouldCleanUpEverything || !comp->zone()->types.inferenceEnabled)
         return false;
@@ -2763,18 +2766,21 @@ BeginMarkPhase(JSRuntime *rt)
     /*
      * Atoms are not in the cross-compartment map. So if there are any
      * zones that are not being collected, we are not allowed to collect
      * atoms. Otherwise, the non-collected zones could contain pointers
      * to atoms that we would miss.
      */
     Zone *atomsZone = rt->atomsCompartment->zone();
 
-    // FIXME bug 875125 this should check all instances of PerThreadData.
-    if (atomsZone->isGCScheduled() && rt->gcIsFull && !rt->mainThread.gcKeepAtoms) {
+    bool keepAtoms = false;
+    for (ThreadDataIter iter(rt); !iter.done(); iter.next())
+        keepAtoms |= iter->gcKeepAtoms;
+
+    if (atomsZone->isGCScheduled() && rt->gcIsFull && !keepAtoms) {
         JS_ASSERT(!atomsZone->isCollecting());
         atomsZone->setGCState(Zone::Mark);
     }
 
     /*
      * At the end of each incremental slice, we call prepareForIncrementalGC,
      * which marks objects in all arenas that we're currently allocating
      * into. This can cause leaks if unreachable objects are in these
@@ -4007,17 +4013,18 @@ class AutoGCSession : AutoTraceSession {
   public:
     explicit AutoGCSession(JSRuntime *rt);
     ~AutoGCSession();
 };
 
 /* Start a new heap session. */
 AutoTraceSession::AutoTraceSession(JSRuntime *rt, js::HeapState heapState)
   : runtime(rt),
-    prevState(rt->heapState)
+    prevState(rt->heapState),
+    pause(rt)
 {
     JS_ASSERT(!rt->noGCOrAllocationCheck);
     JS_ASSERT(!rt->isHeapBusy());
     JS_ASSERT(heapState != Idle);
     rt->heapState = heapState;
 }
 
 AutoTraceSession::~AutoTraceSession()
@@ -4338,18 +4345,21 @@ IncrementalCollectSlice(JSRuntime *rt,
     }
 }
 
 IncrementalSafety
 gc::IsIncrementalGCSafe(JSRuntime *rt)
 {
     JS_ASSERT(!rt->mainThread.suppressGC);
 
-    // FIXME bug 875125 this should check all instances of PerThreadData.
-    if (rt->mainThread.gcKeepAtoms)
+    bool keepAtoms = false;
+    for (ThreadDataIter iter(rt); !iter.done(); iter.next())
+        keepAtoms |= iter->gcKeepAtoms;
+
+    if (keepAtoms)
         return IncrementalSafety::Unsafe("gcKeepAtoms set");
 
     if (!rt->gcIncrementalEnabled)
         return IncrementalSafety::Unsafe("incremental permanently disabled");
 
     return IncrementalSafety::Safe();
 }
 
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3460,23 +3460,20 @@ CallAddPropertyHook(ExclusiveContext *cx
 }
 
 static inline bool
 CallAddPropertyHookDense(ExclusiveContext *cx, Class *clasp, HandleObject obj, uint32_t index,
                          HandleValue nominal)
 {
     /* Inline addProperty for array objects. */
     if (obj->is<ArrayObject>()) {
-        if (!cx->shouldBeJSContext())
-            return false;
-
         Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
         uint32_t length = arr->length();
         if (index >= length)
-            ArrayObject::setLength(cx->asJSContext(), arr, index + 1);
+            ArrayObject::setLength(cx, arr, index + 1);
         return true;
     }
 
     if (clasp->addProperty != JS_PropertyStub) {
         if (!cx->shouldBeJSContext())
             return false;
 
         /* Make a local copy of value so addProperty can mutate its inout parameter. */
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -300,17 +300,17 @@ struct ContextFriendFields
 
     /* The current zone. */
     JS::Zone            *zone_;
 
   public:
     explicit ContextFriendFields(JSRuntime *rt)
       : runtime_(rt), compartment_(NULL), zone_(NULL), autoGCRooters(NULL)
     {
-#if defined(JSGC_ROOT_ANALYSIS) || defined(JSGC_USE_EXACT_ROOTING)
+#ifdef JSGC_TRACK_EXACT_ROOTS
         mozilla::PodArrayZero(thingGCRooters);
 #endif
 #if defined(DEBUG) && defined(JS_GC_ZEAL) && defined(JSGC_ROOT_ANALYSIS) && !defined(JS_THREADSAFE)
         skipGCRooters = NULL;
 #endif
     }
 
     static const ContextFriendFields *get(const JSContext *cx) {
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -34,16 +34,17 @@
 #include "vm/Shape.h"
 #include "vm/Xdr.h"
 
 #include "jsfuninlines.h"
 #include "jsinferinlines.h"
 #include "jsscriptinlines.h"
 
 #include "vm/ScopeObject-inl.h"
+#include "vm/Runtime-inl.h"
 #include "vm/Stack-inl.h"
 
 using namespace js;
 using namespace js::gc;
 using namespace js::frontend;
 
 using mozilla::PodCopy;
 using mozilla::PodZero;
@@ -940,17 +941,17 @@ Class ScriptSourceObject::class_ = {
     JS_StrictPropertyStub,  /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     ScriptSourceObject::finalize
 };
 
 ScriptSourceObject *
-ScriptSourceObject::create(JSContext *cx, ScriptSource *source)
+ScriptSourceObject::create(ExclusiveContext *cx, ScriptSource *source)
 {
     RootedObject object(cx, NewObjectWithGivenProto(cx, &class_, NULL, cx->global()));
     if (!object)
         return NULL;
     JS::RootedScriptSource sourceObject(cx, &object->as<ScriptSourceObject>());
     sourceObject->setSlot(SOURCE_SLOT, PrivateValue(source));
     source->incref();
     return sourceObject;
@@ -1451,36 +1452,38 @@ ScriptSource::performXDR(XDRState<mode> 
 
     if (mode == XDR_DECODE)
         ready_ = true;
 
     return true;
 }
 
 bool
-ScriptSource::setFilename(JSContext *cx, const char *filename)
+ScriptSource::setFilename(ExclusiveContext *cx, const char *filename)
 {
     JS_ASSERT(!filename_);
     size_t len = strlen(filename) + 1;
     if (len == 1)
         return true;
     filename_ = cx->pod_malloc<char>(len);
     if (!filename_)
         return false;
     js_memcpy(filename_, filename, len);
     return true;
 }
 
 bool
-ScriptSource::setSourceMap(JSContext *cx, jschar *sourceMapURL)
+ScriptSource::setSourceMap(ExclusiveContext *cx, jschar *sourceMapURL)
 {
     JS_ASSERT(sourceMapURL);
     if (hasSourceMap()) {
-        if (!JS_ReportErrorFlagsAndNumber(cx, JSREPORT_WARNING, js_GetErrorMessage, NULL,
-                                          JSMSG_ALREADY_HAS_SOURCEMAP, filename_)) {
+        if (cx->isJSContext() &&
+            !JS_ReportErrorFlagsAndNumber(cx->asJSContext(), JSREPORT_WARNING, js_GetErrorMessage,
+                                          NULL, JSMSG_ALREADY_HAS_SOURCEMAP, filename_))
+        {
             js_free(sourceMapURL);
             return false;
         }
     }
     sourceMap_ = sourceMapURL;
     return true;
 }
 
@@ -1515,16 +1518,18 @@ js::SharedScriptData::new_(ExclusiveCont
 }
 
 bool
 js::SaveSharedScriptData(ExclusiveContext *cx, Handle<JSScript *> script, SharedScriptData *ssd)
 {
     ASSERT(script != NULL);
     ASSERT(ssd != NULL);
 
+    AutoLockForExclusiveAccess lock(cx);
+
     ScriptBytecodeHasher::Lookup l(ssd);
 
     ScriptDataTable::AddPtr p = cx->scriptDataTable().lookupForAdd(l);
     if (p) {
         js_free(ssd);
         ssd = *p;
     } else {
         if (!cx->scriptDataTable().add(p, ssd)) {
@@ -1553,22 +1558,26 @@ js::SaveSharedScriptData(ExclusiveContex
     return true;
 }
 
 void
 js::SweepScriptData(JSRuntime *rt)
 {
     JS_ASSERT(rt->gcIsFull);
     ScriptDataTable &table = rt->scriptDataTable;
+
+    bool keepAtoms = false;
+    for (ThreadDataIter iter(rt); !iter.done(); iter.next())
+        keepAtoms |= iter->gcKeepAtoms;
+
     for (ScriptDataTable::Enum e(table); !e.empty(); e.popFront()) {
         SharedScriptData *entry = e.front();
         if (entry->marked) {
             entry->marked = false;
-        } else if (!rt->mainThread.gcKeepAtoms) {
-            // FIXME bug 875125 this should check all instances of PerThreadData.
+        } else if (!keepAtoms) {
             js_free(entry);
             e.removeFront();
         }
     }
 }
 
 void
 js::FreeScriptData(JSRuntime *rt)
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -343,23 +343,23 @@ class ScriptSource
     const jschar *chars(JSContext *cx);
     JSStableString *substring(JSContext *cx, uint32_t start, uint32_t stop);
     size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
     // XDR handling
     template <XDRMode mode>
     bool performXDR(XDRState<mode> *xdr);
 
-    bool setFilename(JSContext *cx, const char *filename);
+    bool setFilename(ExclusiveContext *cx, const char *filename);
     const char *filename() const {
         return filename_;
     }
 
     // Source maps
-    bool setSourceMap(JSContext *cx, jschar *sourceMapURL);
+    bool setSourceMap(ExclusiveContext *cx, jschar *sourceMapURL);
     const jschar *sourceMap();
     bool hasSourceMap() const { return sourceMap_ != NULL; }
 
   private:
     void destroy();
     bool compressed() const { return compressedLength_ != 0; }
     size_t computedSizeOfData() const {
         return compressed() ? compressedLength_ : sizeof(jschar) * length_;
@@ -383,17 +383,17 @@ class ScriptSourceHolder
 };
 
 class ScriptSourceObject : public JSObject
 {
   public:
     static Class class_;
 
     static void finalize(FreeOp *fop, JSObject *obj);
-    static ScriptSourceObject *create(JSContext *cx, ScriptSource *source);
+    static ScriptSourceObject *create(ExclusiveContext *cx, ScriptSource *source);
 
     ScriptSource *source() {
         return static_cast<ScriptSource *>(getReservedSlot(SOURCE_SLOT).toPrivate());
     }
 
     void setSource(ScriptSource *source);
 
   private:
--- a/js/src/jsworkers.cpp
+++ b/js/src/jsworkers.cpp
@@ -5,30 +5,37 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jsworkers.h"
 
 #include "mozilla/DebugOnly.h"
 
 #include "prmjtime.h"
 
-#ifdef JS_PARALLEL_COMPILATION
+#include "frontend/BytecodeCompiler.h"
+
+#ifdef JS_WORKER_THREADS
 # include "ion/AsmJS.h"
 # include "ion/IonBuilder.h"
 # include "ion/ExecutionModeInlines.h"
 #endif
 
+#include "jscntxtinlines.h"
+#include "jscompartmentinlines.h"
+
+#include "vm/ObjectImpl-inl.h"
+
 using namespace js;
 
 using mozilla::DebugOnly;
 
-#ifdef JS_PARALLEL_COMPILATION
+#ifdef JS_WORKER_THREADS
 
 bool
-js::EnsureParallelCompilationInitialized(JSRuntime *rt)
+js::EnsureWorkerThreadsInitialized(JSRuntime *rt)
 {
     if (rt->workerThreadState)
         return true;
 
     rt->workerThreadState = rt->new_<WorkerThreadState>();
     if (!rt->workerThreadState)
         return false;
 
@@ -64,17 +71,17 @@ js::StartOffThreadAsmJSCompile(JSContext
     state.notify(WorkerThreadState::WORKER);
     return true;
 }
 
 bool
 js::StartOffThreadIonCompile(JSContext *cx, ion::IonBuilder *builder)
 {
     JSRuntime *rt = cx->runtime();
-    if (!EnsureParallelCompilationInitialized(rt))
+    if (!EnsureWorkerThreadsInitialized(rt))
         return false;
 
     WorkerThreadState &state = *cx->runtime()->workerThreadState;
     JS_ASSERT(state.numThreads);
 
     AutoLockWorkerThreadState lock(rt);
 
     if (!state.ionWorklist.append(builder))
@@ -150,16 +157,134 @@ js::CancelOffThreadIonCompile(JSCompartm
         if (CompiledScriptMatches(compartment, script, builder->script())) {
             ion::FinishOffThreadBuilder(builder);
             compilations[i--] = compilations.back();
             compilations.popBack();
         }
     }
 }
 
+static JSClass workerGlobalClass = {
+    "internal-worker-global", JSCLASS_GLOBAL_FLAGS,
+    JS_PropertyStub,  JS_DeletePropertyStub,
+    JS_PropertyStub,  JS_StrictPropertyStub,
+    JS_EnumerateStub, JS_ResolveStub,
+    JS_ConvertStub,   NULL
+};
+
+ParseTask::ParseTask(JSRuntime *rt, ExclusiveContext *cx, const CompileOptions &options,
+                     const jschar *chars, size_t length)
+  : runtime(rt), cx(cx), options(options), chars(chars), length(length),
+    alloc(JSRuntime::TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), script(NULL)
+{
+    if (options.principals)
+        JS_HoldPrincipals(options.principals);
+    if (options.originPrincipals)
+        JS_HoldPrincipals(options.originPrincipals);
+}
+
+ParseTask::~ParseTask()
+{
+    if (options.principals)
+        JS_DropPrincipals(runtime, options.principals);
+    if (options.originPrincipals)
+        JS_DropPrincipals(runtime, options.originPrincipals);
+
+    // ParseTask takes over ownership of its input exclusive context.
+    js_delete(cx);
+}
+
+bool
+js::StartOffThreadParseScript(JSContext *cx, const CompileOptions &options,
+                              const jschar *chars, size_t length)
+{
+    frontend::MaybeCallSourceHandler(cx, options, chars, length);
+
+    JSRuntime *rt = cx->runtime();
+    if (!EnsureWorkerThreadsInitialized(rt))
+        return false;
+
+    JS::CompartmentOptions compartmentOptions(cx->compartment()->options());
+    compartmentOptions.setZone(JS::FreshZone);
+
+    JSObject *global = JS_NewGlobalObject(cx, &workerGlobalClass, NULL, compartmentOptions);
+    if (!global)
+        return false;
+
+    // For now, type inference is always disabled in exclusive zones.
+    // This restriction would be fairly easy to lift.
+    global->zone()->types.inferenceEnabled = false;
+
+    // Initialize all classes needed for parsing while we are still on the main
+    // thread.
+    {
+        AutoCompartment ac(cx, global);
+
+        RootedObject obj(cx);
+        if (!js_GetClassObject(cx, global, JSProto_Function, &obj) ||
+            !js_GetClassObject(cx, global, JSProto_Array, &obj) ||
+            !js_GetClassObject(cx, global, JSProto_RegExp, &obj))
+        {
+            return false;
+        }
+    }
+
+    global->zone()->usedByExclusiveThread = true;
+
+    ScopedJSDeletePtr<ExclusiveContext> workercx(
+        cx->new_<ExclusiveContext>(cx->runtime(), (PerThreadData *) NULL,
+                                   ThreadSafeContext::Context_Exclusive));
+    if (!workercx)
+        return false;
+
+    workercx->enterCompartment(global->compartment());
+
+    ScopedJSDeletePtr<ParseTask> task(
+        cx->new_<ParseTask>(cx->runtime(), workercx.get(), options, chars, length));
+    if (!task)
+        return false;
+
+    workercx.forget();
+
+    WorkerThreadState &state = *cx->runtime()->workerThreadState;
+    JS_ASSERT(state.numThreads);
+
+    AutoLockWorkerThreadState lock(rt);
+
+    if (!state.parseWorklist.append(task.get()))
+        return false;
+
+    task.forget();
+
+    state.notify(WorkerThreadState::WORKER);
+    return true;
+}
+
+void
+js::WaitForOffThreadParsingToFinish(JSRuntime *rt)
+{
+    if (!rt->workerThreadState)
+        return;
+
+    WorkerThreadState &state = *rt->workerThreadState;
+
+    AutoLockWorkerThreadState lock(rt);
+
+    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::MAIN);
+    }
+}
+
 bool
 WorkerThreadState::init(JSRuntime *rt)
 {
     if (!rt->useHelperThreads()) {
         numThreads = 0;
         return true;
     }
 
@@ -192,16 +317,18 @@ WorkerThreadState::init(JSRuntime *rt)
         if (!helper.thread) {
             for (size_t j = 0; j < numThreads; j++)
                 threads[j].destroy();
             js_delete(threads);
             threads = NULL;
             numThreads = 0;
             return false;
         }
+        helper.threadData.construct(rt);
+        helper.threadData.ref().addToThreadList();
     }
 
     resetAsmJSFailureState();
     return true;
 }
 
 WorkerThreadState::~WorkerThreadState()
 {
@@ -319,32 +446,33 @@ WorkerThread::destroy()
         AutoLockWorkerThreadState lock(runtime);
         terminate = true;
 
         /* Notify all workers, to ensure that this thread wakes up. */
         state.notifyAll(WorkerThreadState::WORKER);
     }
 
     PR_JoinThread(thread);
+    threadData.ref().removeFromThreadList();
 }
 
 /* static */
 void
 WorkerThread::ThreadMain(void *arg)
 {
     PR_SetCurrentThreadName("Analysis Helper");
     static_cast<WorkerThread *>(arg)->threadLoop();
 }
 
 void
 WorkerThread::handleAsmJSWorkload(WorkerThreadState &state)
 {
     JS_ASSERT(state.isLocked());
     JS_ASSERT(state.canStartAsmJSCompile());
-    JS_ASSERT(!ionBuilder && !asmData);
+    JS_ASSERT(idle());
 
     asmData = state.asmJSWorklist.popCopy();
     bool success = false;
 
     state.unlock();
     do {
         ion::IonContext icx(asmData->mir->compartment, &asmData->mir->temp());
 
@@ -380,17 +508,17 @@ WorkerThread::handleAsmJSWorkload(Worker
     state.notify(WorkerThreadState::MAIN);
 }
 
 void
 WorkerThread::handleIonWorkload(WorkerThreadState &state)
 {
     JS_ASSERT(state.isLocked());
     JS_ASSERT(state.canStartIonCompile());
-    JS_ASSERT(!ionBuilder && !asmData);
+    JS_ASSERT(idle());
 
     ionBuilder = state.ionWorklist.popCopy();
 
     DebugOnly<ion::ExecutionMode> executionMode = ionBuilder->info().executionMode();
     JS_ASSERT(GetIonScript(ionBuilder->script(), executionMode) == ION_COMPILING_SCRIPT);
 
     state.unlock();
     {
@@ -406,45 +534,147 @@ WorkerThread::handleIonWorkload(WorkerTh
     state.notify(WorkerThreadState::MAIN);
 
     // Ping the main thread so that the compiled code can be incorporated
     // at the next operation callback.
     runtime->triggerOperationCallback();
 }
 
 void
+ExclusiveContext::setWorkerThread(WorkerThread *workerThread)
+{
+    this->workerThread = workerThread;
+    this->perThreadData = workerThread->threadData.addr();
+}
+
+void
+WorkerThread::handleParseWorkload(WorkerThreadState &state)
+{
+    JS_ASSERT(state.isLocked());
+    JS_ASSERT(!state.parseWorklist.empty());
+    JS_ASSERT(idle());
+
+    parseTask = state.parseWorklist.popCopy();
+    parseTask->cx->setWorkerThread(this);
+
+    {
+        AutoUnlockWorkerThreadState unlock(runtime);
+        parseTask->script = frontend::CompileScript(parseTask->cx, &parseTask->alloc,
+                                                    NullPtr(), NullPtr(),
+                                                    parseTask->options,
+                                                    parseTask->chars, parseTask->length);
+    }
+
+    state.parseFinishedList.append(parseTask);
+    parseTask = NULL;
+
+    // Notify the main thread in case it is waiting for the parse/emit to finish.
+    state.notify(WorkerThreadState::MAIN);
+}
+
+void
 WorkerThread::threadLoop()
 {
     WorkerThreadState &state = *runtime->workerThreadState;
-    state.lock();
+    AutoLockWorkerThreadState lock(runtime);
 
-    threadData.construct(runtime);
     js::TlsPerThreadData.set(threadData.addr());
 
     while (true) {
         JS_ASSERT(!ionBuilder && !asmData);
 
-        // Block until an Ion or AsmJS task is available.
-        while (!state.canStartIonCompile() && !state.canStartAsmJSCompile()) {
-            if (terminate) {
-                state.unlock();
+        // Block until a task is available.
+        while (!state.canStartIonCompile() &&
+               !state.canStartAsmJSCompile() &&
+               state.parseWorklist.empty())
+        {
+            if (state.shouldPause)
+                pause();
+            if (terminate)
                 return;
-            }
             state.wait(WorkerThreadState::WORKER);
         }
 
         // Dispatch tasks, prioritizing AsmJS work.
         if (state.canStartAsmJSCompile())
             handleAsmJSWorkload(state);
         else if (state.canStartIonCompile())
             handleIonWorkload(state);
+        else if (!state.parseWorklist.empty())
+            handleParseWorkload(state);
     }
 }
 
-#else /* JS_PARALLEL_COMPILATION */
+AutoPauseWorkersForGC::AutoPauseWorkersForGC(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
+  : runtime(rt), needsUnpause(false)
+{
+    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+
+    if (!runtime->workerThreadState)
+        return;
+
+    runtime->assertValidThread();
+
+    WorkerThreadState &state = *runtime->workerThreadState;
+    if (!state.numThreads)
+        return;
+
+    AutoLockWorkerThreadState lock(runtime);
+
+    // Tolerate reentrant use of AutoPauseWorkersForGC.
+    if (state.shouldPause) {
+        JS_ASSERT(state.numPaused == state.numThreads);
+        return;
+    }
+
+    needsUnpause = true;
+
+    state.shouldPause = 1;
+
+    while (state.numPaused != state.numThreads) {
+        state.notifyAll(WorkerThreadState::WORKER);
+        state.wait(WorkerThreadState::MAIN);
+    }
+}
+
+AutoPauseWorkersForGC::~AutoPauseWorkersForGC()
+{
+    if (!needsUnpause)
+        return;
+
+    WorkerThreadState &state = *runtime->workerThreadState;
+    AutoLockWorkerThreadState lock(runtime);
+
+    state.shouldPause = 0;
+
+    // Notify all workers, to ensure that each wakes up.
+    state.notifyAll(WorkerThreadState::WORKER);
+}
+
+void
+WorkerThread::pause()
+{
+    WorkerThreadState &state = *runtime->workerThreadState;
+    JS_ASSERT(state.isLocked());
+    JS_ASSERT(state.shouldPause);
+
+    JS_ASSERT(state.numPaused < state.numThreads);
+    state.numPaused++;
+
+    // Don't bother to notify the main thread until all workers have paused.
+    if (state.numPaused == state.numThreads)
+        state.notify(WorkerThreadState::MAIN);
+
+    while (state.shouldPause)
+        state.wait(WorkerThreadState::WORKER);
+
+    state.numPaused--;
+}
+
+#else /* JS_WORKER_THREADS */
 
 bool
 js::StartOffThreadAsmJSCompile(JSContext *cx, AsmJSParallelTask *asmData)
 {
     MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds");
 }
 
 bool
@@ -453,9 +683,30 @@ js::StartOffThreadIonCompile(JSContext *
     MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds");
 }
 
 void
 js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
 {
 }
 
-#endif /* JS_PARALLEL_COMPILATION */
+bool
+js::StartOffThreadParseScript(JSContext *cx, const CompileOptions &options,
+                              const jschar *chars, size_t length)
+{
+    MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds");
+}
+
+void
+js::WaitForOffThreadParsingToFinish(JSRuntime *rt)
+{
+}
+
+AutoPauseWorkersForGC::AutoPauseWorkersForGC(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
+{
+    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+}
+
+AutoPauseWorkersForGC::~AutoPauseWorkersForGC()
+{
+}
+
+#endif /* JS_WORKER_THREADS */
--- a/js/src/jsworkers.h
+++ b/js/src/jsworkers.h
@@ -25,46 +25,59 @@ namespace js {
 
 struct AsmJSParallelTask;
 
 namespace ion {
   class IonBuilder;
 }
 
 #if defined(JS_THREADSAFE) && defined(JS_ION)
-# define JS_PARALLEL_COMPILATION
+# define JS_WORKER_THREADS
 
 struct WorkerThread;
 struct AsmJSParallelTask;
+struct ParseTask;
 
 /* Per-runtime state for off thread work items. */
 class WorkerThreadState
 {
   public:
     /* Available threads. */
     WorkerThread *threads;
     size_t numThreads;
 
+    /*
+     * Whether all worker threads thread should pause their activity. This acts
+     * like the runtime's interrupt field and may be read without locking.
+     */
+    volatile size_t shouldPause;
+
+    /* After shouldPause is set, the number of threads which are paused. */
+    uint32_t numPaused;
+
     enum CondVar {
         MAIN,
         WORKER
     };
 
     /* Shared worklist for Ion worker threads. */
-    js::Vector<ion::IonBuilder*, 0, SystemAllocPolicy> ionWorklist;
+    Vector<ion::IonBuilder*, 0, SystemAllocPolicy> ionWorklist;
 
     /* Worklist for AsmJS worker threads. */
-    js::Vector<AsmJSParallelTask*, 0, SystemAllocPolicy> asmJSWorklist;
+    Vector<AsmJSParallelTask*, 0, SystemAllocPolicy> asmJSWorklist;
 
     /*
      * 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.
      */
-    js::Vector<AsmJSParallelTask*, 0, SystemAllocPolicy> asmJSFinishedList;
+    Vector<AsmJSParallelTask*, 0, SystemAllocPolicy> asmJSFinishedList;
+
+    /* Shared worklist for parsing/emitting scripts on worker threads. */
+    Vector<ParseTask*, 0, SystemAllocPolicy> parseWorklist, parseFinishedList;
 
     WorkerThreadState() { mozilla::PodZero(this); }
     ~WorkerThreadState();
 
     bool init(JSRuntime *rt);
 
     void lock();
     void unlock();
@@ -147,44 +160,53 @@ struct WorkerThread
     bool terminate;
 
     /* Any builder currently being compiled by Ion on this thread. */
     ion::IonBuilder *ionBuilder;
 
     /* Any AsmJS data currently being optimized by Ion on this thread. */
     AsmJSParallelTask *asmData;
 
+    /* Any source being parsed/emitted on this thread */
+    ParseTask *parseTask;
+
+    bool idle() const {
+        return !ionBuilder && !asmData && !parseTask;
+    }
+
+    void pause();
     void destroy();
 
     void handleAsmJSWorkload(WorkerThreadState &state);
     void handleIonWorkload(WorkerThreadState &state);
+    void handleParseWorkload(WorkerThreadState &state);
 
     static void ThreadMain(void *arg);
     void threadLoop();
 };
 
 #endif /* JS_THREADSAFE && JS_ION */
 
 inline bool
 OffThreadCompilationEnabled(JSContext *cx)
 {
-#ifdef JS_PARALLEL_COMPILATION
+#ifdef JS_WORKER_THREADSj
     return ion::js_IonOptions.parallelCompilation
         && cx->runtime()->useHelperThreads()
         && cx->runtime()->helperThreadCount() != 0;
 #else
     return false;
 #endif
 }
 
 /* Methods for interacting with worker threads. */
 
 /* Initialize worker threads unless already initialized. */
 bool
-EnsureParallelCompilationInitialized(JSRuntime *rt);
+EnsureWorkerThreadsInitialized(JSRuntime *rt);
 
 /* Perform MIR optimization and LIR generation on a single function. */
 bool
 StartOffThreadAsmJSCompile(JSContext *cx, AsmJSParallelTask *asmData);
 
 /*
  * Schedule an Ion compilation for a script, given a builder which has been
  * generated and read everything needed from the VM state.
@@ -194,39 +216,51 @@ StartOffThreadIonCompile(JSContext *cx, 
 
 /*
  * Cancel a scheduled or in progress Ion compilation for script. If script is
  * NULL, all compilations for the compartment are cancelled.
  */
 void
 CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script);
 
+/*
+ * Start a parse/emit cycle for a stream of source. The characters must stay
+ * alive until the compilation finishes.
+ */
+bool
+StartOffThreadParseScript(JSContext *cx, const CompileOptions &options,
+                          const jschar *chars, size_t length);
+
+/* Block until in progress and pending off thread parse jobs have finished. */
+void
+WaitForOffThreadParsingToFinish(JSRuntime *rt);
+
 class AutoLockWorkerThreadState
 {
     JSRuntime *rt;
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 
   public:
 
     AutoLockWorkerThreadState(JSRuntime *rt
                               MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : rt(rt)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-#ifdef JS_PARALLEL_COMPILATION
+#ifdef JS_WORKER_THREADS
         JS_ASSERT(rt->workerThreadState);
         rt->workerThreadState->lock();
 #else
         (void)this->rt;
 #endif
     }
 
     ~AutoLockWorkerThreadState()
     {
-#ifdef JS_PARALLEL_COMPILATION
+#ifdef JS_WORKER_THREADS
         rt->workerThreadState->unlock();
 #endif
     }
 };
 
 class AutoUnlockWorkerThreadState
 {
     JSRuntime *rt;
@@ -234,27 +268,63 @@ class AutoUnlockWorkerThreadState
 
   public:
 
     AutoUnlockWorkerThreadState(JSRuntime *rt
                                 MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : rt(rt)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-#ifdef JS_PARALLEL_COMPILATION
+#ifdef JS_WORKER_THREADS
         JS_ASSERT(rt->workerThreadState);
         rt->workerThreadState->unlock();
 #else
         (void)this->rt;
 #endif
     }
 
     ~AutoUnlockWorkerThreadState()
     {
-#ifdef JS_PARALLEL_COMPILATION
+#ifdef JS_WORKER_THREADS
         rt->workerThreadState->lock();
 #endif
     }
 };
 
+/* Pause any threads that are running jobs off thread during GC activity. */
+class AutoPauseWorkersForGC
+{
+    JSRuntime *runtime;
+    bool needsUnpause;
+    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+
+  public:
+    AutoPauseWorkersForGC(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
+    ~AutoPauseWorkersForGC();
+};
+
+/* Wait for any in progress off thread parses to halt. */
+void
+PauseOffThreadParsing();
+
+/* Resume any paused off thread parses. */
+void
+ResumeOffThreadParsing();
+
+struct ParseTask
+{
+    JSRuntime *runtime;
+    ExclusiveContext *cx;
+    CompileOptions options;
+    const jschar *chars;
+    size_t length;
+    LifoAlloc alloc;
+
+    JSScript *script;
+
+    ParseTask(JSRuntime *rt, ExclusiveContext *cx, const CompileOptions &options,
+              const jschar *chars, size_t length);
+    ~ParseTask();
+};
+
 } /* namespace js */
 
 #endif /* jsworkers_h */
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -131,17 +131,17 @@ js::TransparentObjectWrapper(JSContext *
 {
     // Allow wrapping outer window proxies.
     JS_ASSERT(!obj->isWrapper() || obj->getClass()->ext.innerObject);
     return Wrapper::New(cx, obj, wrappedProto, parent, &CrossCompartmentWrapper::singleton);
 }
 
 ErrorCopier::~ErrorCopier()
 {
-    JSContext *cx = ac.ref().context();
+    JSContext *cx = ac.ref().context()->asJSContext();
     if (ac.ref().origin() != cx->compartment() && cx->isExceptionPending()) {
         RootedValue exc(cx, cx->getPendingException());
         if (exc.isObject() && exc.toObject().is<ErrorObject>() &&
             exc.toObject().as<ErrorObject>().getExnPrivate())
         {
             cx->clearPendingException();
             ac.destroy();
             Rooted<JSObject*> errObj(cx, &exc.toObject());
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3224,16 +3224,63 @@ SyntaxParse(JSContext *cx, unsigned argc
         JS_ASSERT(cx->runtime()->hadOutOfMemory);
         return false;
     }
 
     args.rval().setBoolean(succeeded);
     return true;
 }
 
+#ifdef JS_THREADSAFE
+
+static JSBool
+OffThreadCompileScript(JSContext *cx, unsigned argc, jsval *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if (args.length() < 1) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED,
+                             "offThreadCompileScript", "0", "s");
+        return false;
+    }
+    if (!args[0].isString()) {
+        const char *typeName = JS_GetTypeName(cx, JS_TypeOfValue(cx, args[0]));
+        JS_ReportError(cx, "expected string to parse, got %s", typeName);
+        return false;
+    }
+
+    JSString *scriptContents = args[0].toString();
+    CompileOptions options(cx);
+    options.setFileAndLine("<string>", 1)
+           .setCompileAndGo(true)
+           .setSourcePolicy(CompileOptions::NO_SOURCE);
+
+    const jschar *chars = JS_GetStringCharsZ(cx, scriptContents);
+    if (!chars)
+        return false;
+    size_t length = JS_GetStringLength(scriptContents);
+
+    // Prevent the string contents from ever being GC'ed. This will leak memory
+    // but since the compiled script is never consumed there isn't much choice.
+    JSString **permanentRoot = cx->new_<JSString *>();
+    if (!permanentRoot)
+        return false;
+    *permanentRoot = scriptContents;
+    if (!JS_AddStringRoot(cx, permanentRoot))
+        return false;
+
+    if (!StartOffThreadParseScript(cx, options, chars, length))
+        return false;
+
+    args.rval().setUndefined();
+    return true;
+}
+
+#endif // JS_THREADSAFE
+
 struct FreeOnReturn
 {
     JSContext *cx;
     const char *ptr;
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 
     FreeOnReturn(JSContext *cx, const char *ptr = NULL
                  MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
@@ -3793,16 +3840,22 @@ static const JSFunctionSpecWithHelp shel
     JS_FN_HELP("parse", Parse, 1, 0,
 "parse(code)",
 "  Parses a string, potentially throwing."),
 
     JS_FN_HELP("syntaxParse", SyntaxParse, 1, 0,
 "syntaxParse(code)",
 "  Check the syntax of a string, returning success value"),
 
+#ifdef JS_THREADSAFE
+    JS_FN_HELP("offThreadCompileScript", OffThreadCompileScript, 1, 0,
+"offThreadCompileScript(code)",
+"  Trigger an off thread parse/emit for the input string"),
+#endif
+
     JS_FN_HELP("timeout", Timeout, 1, 0,
 "timeout([seconds], [func])",
 "  Get/Set the limit in seconds for the execution time for the current context.\n"
 "  A negative value (default) means that the execution time is unlimited.\n"
 "  If a second argument is provided, it will be invoked when the timer elapses.\n"),
 
     JS_FN_HELP("elapsed", Elapsed, 0, 0,
 "elapsed()",
--- a/js/src/vm/ArrayObject-inl.h
+++ b/js/src/vm/ArrayObject-inl.h
@@ -10,25 +10,25 @@
 #include "vm/ArrayObject.h"
 #include "vm/String.h"
 
 #include "jsinferinlines.h"
 
 namespace js {
 
 /* static */ inline void
-ArrayObject::setLength(JSContext *cx, Handle<ArrayObject*> arr, uint32_t length)
+ArrayObject::setLength(ExclusiveContext *cx, Handle<ArrayObject*> arr, uint32_t length)
 {
     JS_ASSERT(arr->lengthIsWritable());
 
     if (length > INT32_MAX) {
         /* Track objects with overflowing lengths in type information. */
-        js::types::MarkTypeObjectFlags(cx, arr, js::types::OBJECT_FLAG_LENGTH_OVERFLOW);
-        jsid lengthId = js::NameToId(cx->names().length);
-        js::types::AddTypePropertyId(cx, arr, lengthId, js::types::Type::DoubleType());
+        types::MarkTypeObjectFlags(cx, arr, types::OBJECT_FLAG_LENGTH_OVERFLOW);
+        jsid lengthId = NameToId(cx->names().length);
+        types::AddTypePropertyId(cx, arr, lengthId, types::Type::DoubleType());
     }
 
     arr->getElementsHeader()->length = length;
 }
 
 } // namespace js
 
 #endif // vm_ArrayObject_inl_h
--- a/js/src/vm/ArrayObject.h
+++ b/js/src/vm/ArrayObject.h
@@ -19,17 +19,17 @@ class ArrayObject : public JSObject
     bool lengthIsWritable() const {
         return !getElementsHeader()->hasNonwritableArrayLength();
     }
 
     uint32_t length() const {
         return getElementsHeader()->length;
     }
 
-    static inline void setLength(JSContext *cx, Handle<ArrayObject*> arr, uint32_t length);
+    static inline void setLength(ExclusiveContext *cx, Handle<ArrayObject*> arr, uint32_t length);
 
     // Variant of setLength for use on arrays where the length cannot overflow int32_t.
     void setLengthInt32(uint32_t length) {
         JS_ASSERT(lengthIsWritable());
         JS_ASSERT(length <= INT32_MAX);
         getElementsHeader()->length = length;
     }
 };
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -745,17 +745,17 @@ Debugger::unwrapDebuggeeValue(JSContext 
     }
     return true;
 }
 
 JSTrapStatus
 Debugger::handleUncaughtExceptionHelper(Maybe<AutoCompartment> &ac,
                                         MutableHandleValue *vp, bool callHook)
 {
-    JSContext *cx = ac.ref().context();
+    JSContext *cx = ac.ref().context()->asJSContext();
     if (cx->isExceptionPending()) {
         if (callHook && uncaughtExceptionHook) {
             Value fval = ObjectValue(*uncaughtExceptionHook);
             Value exc = cx->getPendingException();
             RootedValue rv(cx);
             cx->clearPendingException();
             if (Invoke(cx, ObjectValue(*object), fval, 1, &exc, &rv))
                 return vp ? parseResumptionValue(ac, true, rv, *vp, false) : JSTRAP_CONTINUE;
@@ -844,17 +844,17 @@ Debugger::newCompletionValue(JSContext *
     result.setObject(*obj);
     return true;
 }
 
 bool
 Debugger::receiveCompletionValue(Maybe<AutoCompartment> &ac, bool ok, Value val,
                                  MutableHandleValue vp)
 {
-    JSContext *cx = ac.ref().context();
+    JSContext *cx = ac.ref().context()->asJSContext();
 
     JSTrapStatus status;
     RootedValue value(cx);
     resultToCompletion(cx, ok, val, &status, &value);
     ac.destroy();
     return newCompletionValue(cx, status, value, vp);
 }
 
@@ -870,17 +870,17 @@ Debugger::parseResumptionValue(Maybe<Aut
         return JSTRAP_CONTINUE;
     }
     if (rv.isNull()) {
         ac.destroy();
         return JSTRAP_ERROR;
     }
 
     /* Check that rv is {return: val} or {throw: val}. */
-    JSContext *cx = ac.ref().context();
+    JSContext *cx = ac.ref().context()->asJSContext();
     Rooted<JSObject*> obj(cx);
     RootedShape shape(cx);
     RootedId returnId(cx, NameToId(cx->names().return_));
     RootedId throwId(cx, NameToId(cx->names().throw_));
     bool okResumption = rv.isObject();
     if (okResumption) {
         obj = &rv.toObject();
         okResumption = obj->is<JSObject>();
@@ -4121,17 +4121,17 @@ js::EvaluateInEnv(JSContext *cx, Handle<
     CompileOptions options(cx);
     options.setPrincipals(env->compartment()->principals)
            .setCompileAndGo(true)
            .setForEval(true)
            .setNoScriptRval(false)
            .setFileAndLine(filename, lineno)
            .setCanLazilyParse(false);
     RootedScript callerScript(cx, frame ? frame.script() : NULL);
-    RootedScript script(cx, frontend::CompileScript(cx, env, callerScript,
+    RootedScript script(cx, frontend::CompileScript(cx, &cx->tempLifoAlloc(), env, callerScript,
                                                     options, chars.get(), length,
                                                     /* source = */ NULL,
                                                     /* staticLevel = */ frame ? 1 : 0));
     if (!script)
         return false;
 
     script->isActiveEval = true;
     ExecuteType type = !frame && env->is<GlobalObject>() ? EXECUTE_DEBUG_GLOBAL : EXECUTE_DEBUG;
--- a/js/src/vm/Runtime-inl.h
+++ b/js/src/vm/Runtime-inl.h
@@ -8,16 +8,17 @@
 #define vm_Runtime_inl_h
 
 #include "vm/Runtime.h"
 
 #include "jscompartment.h"
 #include "jsfriendapi.h"
 #include "jsgc.h"
 #include "jsiter.h"
+#include "jsworkers.h"
 
 #include "builtin/Object.h" // For js::obj_construct
 #include "frontend/ParseMaps.h"
 #include "ion/IonFrames.h" // For GetPcScript
 #include "vm/Interpreter.h"
 #include "vm/Probes.h"
 #include "vm/RegExpObject.h"
 
@@ -71,11 +72,22 @@ NewObjectCache::newObjectFromHit(JSConte
         copyCachedToObject(obj, reinterpret_cast<JSObject *>(&entry->templateObject), entry->kind);
         Probes::createObject(cx, obj);
         return obj;
     }
 
     return NULL;
 }
 
+inline
+ThreadDataIter::ThreadDataIter(JSRuntime *rt)
+{
+#ifdef JS_WORKER_THREADS
+    // Only allow iteration over a runtime's threads when those threads are
+    // paused, to avoid racing when reading data from the PerThreadData.
+    JS_ASSERT_IF(rt->workerThreadState, rt->workerThreadState->shouldPause);
+#endif
+    iter = rt->threadList.getFirst();
+}
+
 }  /* namespace js */
 
 #endif /* vm_Runtime_inl_h */
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -16,16 +16,17 @@
 #include "jsgc.h"
 #include "jsmath.h"
 #include "jsobj.h"
 #include "jsscript.h"
 
 #include "js/MemoryMetrics.h"
 #include "yarr/BumpPointerAllocator.h"
 
+#include "jscntxtinlines.h"
 #include "jsgcinlines.h"
 
 using namespace js;
 using namespace js::gc;
 
 using mozilla::PodZero;
 
 void
@@ -41,16 +42,19 @@ NewObjectCache::clearNurseryObjects(JSRu
             PodZero(&e);
         }
     }
 }
 
 void
 JSRuntime::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes *rtSizes)
 {
+    // Several tables in the runtime enumerated below can be used off thread.
+    AutoLockForExclusiveAccess lock(this);
+
     rtSizes->object = mallocSizeOf(this);
 
     rtSizes->atomsTable = atoms.sizeOfExcludingThis(mallocSizeOf);
 
     rtSizes->contexts = 0;
     for (ContextIter acx(this); !acx.done(); acx.next())
         rtSizes->contexts += acx->sizeOfIncludingThis(mallocSizeOf);
 
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -442,17 +442,18 @@ namespace js {
 
 /*
  * Encapsulates portions of the runtime/context that are tied to a
  * 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 js::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 |associatedWith()| below.
      */
@@ -551,16 +552,18 @@ class PerThreadData : public js::PerThre
      * the runtime's parseMapPool will not be purged.
      */
     unsigned activeCompilations;
 
     PerThreadData(JSRuntime *runtime);
     ~PerThreadData();
 
     bool init();
+    void addToThreadList();
+    void removeFromThreadList();
 
     bool associatedWith(const JSRuntime *rt) { return runtime_ == rt; }
 };
 
 template<class Client>
 struct MallocProvider
 {
     void *malloc_(size_t bytes) {
@@ -635,32 +638,40 @@ struct MallocProvider
 };
 
 namespace gc {
 class MarkingValidator;
 } // namespace gc
 
 typedef Vector<JS::Zone *, 1, SystemAllocPolicy> ZoneVector;
 
+class AutoLockForExclusiveAccess;
+
 } // namespace js
 
 struct JSRuntime : public JS::shadow::Runtime,
                    public js::MallocProvider<JSRuntime>
 {
     /*
      * Per-thread data for the main thread that is associated with
      * this JSRuntime, as opposed to any worker threads used in
      * parallel sections.  See definition of |PerThreadData| struct
      * above for more details.
      *
      * NB: This field is statically asserted to be at offset
      * sizeof(js::shadow::Runtime). See
      * PerThreadDataFriendFields::getMainThread.
      */
-    js::PerThreadData   mainThread;
+    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.
      */
     volatile int32_t    interrupt;
 
 #ifdef JS_THREADSAFE
@@ -707,16 +718,47 @@ struct JSRuntime : public JS::shadow::Ru
     bool currentThreadOwnsOperationCallbackLock() {
 #if defined(JS_THREADSAFE) && defined(DEBUG)
         return operationCallbackOwner == PR_GetCurrentThread();
 #else
         return true;
 #endif
     }
 
+#ifdef JS_THREADSAFE
+  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.
+     */
+    PRLock *exclusiveAccessLock;
+    mozilla::DebugOnly<PRThread *> exclusiveAccessOwner;
+    mozilla::DebugOnly<bool> mainThreadHasExclusiveAccess;
+
+    /* Number of non-main threads with an ExclusiveContext. */
+    size_t numExclusiveThreads;
+
+    friend class js::AutoLockForExclusiveAccess;
+
+  public:
+#endif // JS_THREADSAFE
+
+    bool currentThreadHasExclusiveAccess() {
+#if defined(JS_THREADSAFE) && defined(DEBUG)
+        return (!numExclusiveThreads && mainThreadHasExclusiveAccess) ||
+            exclusiveAccessOwner == PR_GetCurrentThread();
+#else
+        return true;
+#endif
+    }
+
     /* Default compartment. */
     JSCompartment       *atomsCompartment;
 
     /* Embedders can use this zone however they wish. */
     JS::Zone            *systemZone;
 
     /* List of compartments and zones (protected by the GC lock). */
     js::ZoneVector      zones;
@@ -1709,16 +1751,48 @@ class RuntimeAllocPolicy
     RuntimeAllocPolicy(JSRuntime *rt) : runtime(rt) {}
     void *malloc_(size_t bytes) { return runtime->malloc_(bytes); }
     void *calloc_(size_t bytes) { return runtime->calloc_(bytes); }
     void *realloc_(void *p, size_t bytes) { return runtime->realloc_(p, bytes); }
     void free_(void *p) { js_free(p); }
     void reportAllocOverflow() const {}
 };
 
+/*
+ * Enumerate all the per thread data in a runtime.
+ */
+class ThreadDataIter {
+    PerThreadData *iter;
+
+public:
+    explicit inline ThreadDataIter(JSRuntime *rt);
+
+    bool done() const {
+        return !iter;
+    }
+
+    void next() {
+        JS_ASSERT(!done());
+        iter = iter->getNext();
+    }
+
+    PerThreadData *get() const {
+        JS_ASSERT(!done());
+        return iter;
+    }
+
+    operator PerThreadData *() const {
+        return get();
+    }
+
+    PerThreadData *operator ->() const {
+        return get();
+    }
+};
+
 } /* namespace js */
 
 #ifdef _MSC_VER
 #pragma warning(pop)
 #pragma warning(pop)
 #endif
 
 #endif /* vm_Runtime_h */
--- a/js/src/vm/String.cpp
+++ b/js/src/vm/String.cpp
@@ -655,16 +655,17 @@ ScopedThreadSafeStringInspector::ensureC
 
 #define R TO_SMALL_CHAR
 const StaticStrings::SmallChar StaticStrings::toSmallChar[] = { R7(0) };
 #undef R
 
 bool
 StaticStrings::init(JSContext *cx)
 {
+    AutoLockForExclusiveAccess lock(cx);
     AutoCompartment ac(cx, cx->runtime()->atomsCompartment);
 
     for (uint32_t i = 0; i < UNIT_STATIC_LIMIT; i++) {
         jschar buffer[] = { jschar(i), '\0' };
         JSFlatString *s = js_NewStringCopyN<CanGC>(cx, buffer, 1);
         if (!s)
             return false;
         unitStaticTable[i] = s->morphAtomizedStringIntoAtom();