Allow compiling scripts off thread with Ion, bug 774253. r=dvander
☠☠ backed out by 2a3e2f6288b7 ☠ ☠
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 22 Aug 2012 19:00:33 -0600
changeset 104994 4225ee7e35a0dcc37c4db156e878740ec88d33d5
parent 104993 ecf3af6113dd0e389e0717d01a03779a72104b1f
child 104995 c1b7927df5463f9983688f8574584bbe2bc4cb76
push id50
push usershu@rfrn.org
push dateTue, 28 Aug 2012 03:10:20 +0000
reviewersdvander
bugs774253
milestone17.0a1
Allow compiling scripts off thread with Ion, bug 774253. r=dvander
js/src/Makefile.in
js/src/ion/CodeGenerator.cpp
js/src/ion/CompilerRoot.h
js/src/ion/Ion.cpp
js/src/ion/Ion.h
js/src/ion/IonAllocPolicy.h
js/src/ion/IonBuilder.cpp
js/src/ion/IonBuilder.h
js/src/ion/IonCompartment.h
js/src/ion/IonFrames.cpp
js/src/ion/IonSpewer.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jsgc.cpp
js/src/jsinfer.cpp
js/src/jsinferinlines.h
js/src/jslock.h
js/src/jsscript.h
js/src/jsworkers.cpp
js/src/jsworkers.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/Compiler.h
js/src/methodjit/MethodJIT.cpp
js/src/methodjit/MethodJIT.h
js/src/methodjit/MonoIC.cpp
js/src/methodjit/StubCalls.cpp
js/src/methodjit/StubCalls.h
js/src/shell/js.cpp
--- a/js/src/Makefile.in
+++ b/js/src/Makefile.in
@@ -101,16 +101,17 @@ CPPSRCS		= \
 		jsreflect.cpp \
 		jsscope.cpp \
 		jsscript.cpp \
 		jsstr.cpp \
 		jstypedarray.cpp \
 		jsutil.cpp \
 		jswatchpoint.cpp \
 		jsweakmap.cpp \
+		jsworkers.cpp \
 		jswrapper.cpp \
 		jsxml.cpp \
 		prmjtime.cpp \
 		sharkctl.cpp \
 		ArgumentsObject.cpp \
 		ScopeObject.cpp \
 		Debugger.cpp \
 		GlobalObject.cpp \
--- a/js/src/ion/CodeGenerator.cpp
+++ b/js/src/ion/CodeGenerator.cpp
@@ -2798,17 +2798,17 @@ CodeGenerator::generate()
     IonCode *code = linker.newCode(cx);
     if (!code)
         return false;
 
     // We encode safepoints after the OSI-point offsets have been determined.
     encodeSafepoints();
 
     JSScript *script = gen->info().script();
-    JS_ASSERT(!script->ion);
+    JS_ASSERT(!script->hasIonScript());
 
     uint32 scriptFrameSize = frameClass_ == FrameSizeClass::None()
                            ? frameDepth_
                            : FrameSizeClass::FromDepth(frameDepth_).frameSize();
 
     script->ion = IonScript::New(cx, slots, scriptFrameSize, snapshots_.size(),
                                  bailouts_.length(), graph.numConstants(),
                                  safepointIndices_.length(), osiIndices_.length(),
--- a/js/src/ion/CompilerRoot.h
+++ b/js/src/ion/CompilerRoot.h
@@ -30,49 +30,31 @@ class CompilerRoot : public CompilerRoot
     {
         if (ptr)
             setRoot(ptr);
     }
 
   public:
     // Sets the pointer and inserts into root list. The pointer becomes read-only.
     void setRoot(T root) {
-        JSRuntime *rt = root->compartment()->rt;
+        JS::CompilerRootNode *&rootList = GetIonContext()->temp->rootList();
 
         JS_ASSERT(!ptr);
         ptr = root;
-        next = rt->ionCompilerRootList;
-        rt->ionCompilerRootList = this;
+        next = rootList;
+        rootList = this;
     }
 
   public:
     operator T () const { return static_cast<T>(ptr); }
     T operator ->() const { return static_cast<T>(ptr); }
 };
 
 typedef CompilerRoot<JSObject*>   CompilerRootObject;
 typedef CompilerRoot<JSFunction*> CompilerRootFunction;
 typedef CompilerRoot<PropertyName*> CompilerRootPropertyName;
 typedef CompilerRoot<Value> CompilerRootValue;
 
-// Automatically clears the compiler root list when compilation finishes.
-class AutoCompilerRoots
-{
-    JSRuntime *rt_;
-
-  public:
-    AutoCompilerRoots(JSRuntime *rt)
-      : rt_(rt)
-    {
-        JS_ASSERT(rt_->ionCompilerRootList == NULL);
-    }
-
-    ~AutoCompilerRoots()
-    {
-        rt_->ionCompilerRootList = NULL;
-    }
-};
-
 } // namespace ion
 } // namespace js
 
 #endif // jsion_ion_gc_h__
 
--- a/js/src/ion/Ion.cpp
+++ b/js/src/ion/Ion.cpp
@@ -13,16 +13,17 @@
 #include "LIR.h"
 #include "AliasAnalysis.h"
 #include "LICM.h"
 #include "ValueNumbering.h"
 #include "EdgeCaseAnalysis.h"
 #include "RangeAnalysis.h"
 #include "LinearScan.h"
 #include "jscompartment.h"
+#include "jsworkers.h"
 #include "IonCompartment.h"
 #include "CodeGenerator.h"
 
 #if defined(JS_CPU_X86)
 # include "x86/Lowering-x86.h"
 #elif defined(JS_CPU_X64)
 # include "x64/Lowering-x64.h"
 #elif defined(JS_CPU_ARM)
@@ -142,16 +143,39 @@ IonCompartment::initialize(JSContext *cx
     functionWrappers_ = cx->new_<VMWrapperMap>(cx);
     if (!functionWrappers_ || !functionWrappers_->init())
         return false;
 
     return true;
 }
 
 void
+ion::FinishOffThreadBuilder(IonBuilder *builder)
+{
+    if (builder->script->isIonCompilingOffThread()) {
+        types::TypeCompartment &types = builder->script->compartment()->types;
+        builder->recompileInfo.compilerOutput(types)->invalidate();
+        builder->script->ion = NULL;
+    }
+    Foreground::delete_(builder->temp().lifoAlloc());
+}
+
+static inline void
+FinishAllOffThreadCompilations(IonCompartment *ion)
+{
+    OffThreadCompilationVector &compilations = ion->finishedOffThreadCompilations();
+
+    for (size_t i = 0; i < compilations.length(); i++) {
+        IonBuilder *builder = compilations[i];
+        FinishOffThreadBuilder(builder);
+    }
+    compilations.clear();
+}
+
+void
 IonCompartment::mark(JSTracer *trc, JSCompartment *compartment)
 {
     // This function marks Ion code objects that must be kept alive if there is
     // any Ion code currently running. These pointers are marked at the start
     // of incremental GC. Entering Ion code in the middle of an incremental GC
     // triggers a read barrier on both these pointers, so they will still be
     // marked in that case.
 
@@ -174,16 +198,20 @@ IonCompartment::mark(JSTracer *trc, JSCo
 
     // These must be available if we could be running JIT code; they are not
     // traced as normal through IonCode or IonScript objects
     if (mustMarkEnterJIT)
         MarkIonCodeRoot(trc, enterJIT_.unsafeGet(), "enterJIT");
 
     // functionWrappers_ are not marked because this is a WeakCache of VM
     // function implementations.
+
+    // Cancel any active or pending off thread compilations.
+    CancelOffThreadIonCompile(compartment, NULL);
+    FinishAllOffThreadCompilations(this);
 }
 
 void
 IonCompartment::sweep(FreeOp *fop)
 {
     if (enterJIT_ && !IsIonCodeMarked(enterJIT_.unsafeGet()))
         enterJIT_ = NULL;
     if (bailoutHandler_ && !IsIonCodeMarked(bailoutHandler_.unsafeGet()))
@@ -695,26 +723,26 @@ ion::ToggleBarriers(JSCompartment *comp,
         if (script->hasIonScript())
             script->ion->toggleBarriers(needs);
     }
 }
 
 namespace js {
 namespace ion {
 
-static bool
-BuildMIR(IonBuilder &builder, MIRGraph &graph)
+bool
+CompileBackEnd(IonBuilder *builder)
 {
-    if (!builder.build())
-        return false;
     IonSpewPass("BuildSSA");
     // Note: don't call AssertGraphCoherency before SplitCriticalEdges,
     // the graph is not in RPO at this point.
 
-    if (!SplitCriticalEdges(&builder, graph))
+    MIRGraph &graph = builder->graph();
+
+    if (!SplitCriticalEdges(builder, graph))
         return false;
     IonSpewPass("Split Critical Edges");
     AssertGraphCoherency(graph);
 
     if (!RenumberBlocks(graph))
         return false;
     IonSpewPass("Renumber Blocks");
     AssertGraphCoherency(graph);
@@ -808,102 +836,214 @@ BuildMIR(IonBuilder &builder, MIRGraph &
     // move instructions. Since bounds check uses are replaced with the actual
     // index, code motion after this pass could incorrectly move a load or
     // store before its bounds check.
     if (!EliminateRedundantBoundsChecks(graph))
         return false;
     IonSpewPass("Bounds Check Elimination");
     AssertGraphCoherency(graph);
 
-    return true;
-}
+    LIRGraph *lir = builder->temp().lifoAlloc()->new_<LIRGraph>(graph);
+    if (!lir)
+        return false;
 
-static bool
-GenerateCode(IonBuilder &builder, MIRGraph &graph)
-{
-    LIRGraph lir(graph);
-    LIRGenerator lirgen(&builder, graph, lir);
+    LIRGenerator lirgen(builder, graph, *lir);
     if (!lirgen.generate())
         return false;
     IonSpewPass("Generate LIR");
 
     if (js_IonOptions.lsra) {
-        LinearScanAllocator regalloc(&lirgen, lir);
+        LinearScanAllocator regalloc(&lirgen, *lir);
         if (!regalloc.go())
             return false;
         IonSpewPass("Allocate Registers", &regalloc);
     }
 
-    CodeGenerator codegen(&builder, lir);
-    if (!codegen.generate())
-        return false;
-    // No spew: graph not changed.
-
+    builder->lir = lir;
     return true;
 }
 
+class AutoDestroyAllocator
+{
+    LifoAlloc *alloc;
+
+  public:
+    AutoDestroyAllocator(LifoAlloc *alloc) : alloc(alloc) {}
+
+    void cancel()
+    {
+        alloc = NULL;
+    }
+
+    ~AutoDestroyAllocator()
+    {
+        if (alloc)
+            Foreground::delete_(alloc);
+    }
+};
+
 /* static */ bool
-TestCompiler(IonBuilder &builder, MIRGraph &graph)
+TestCompiler(IonBuilder *builder, MIRGraph *graph, AutoDestroyAllocator &autoDestroy)
 {
-    IonSpewNewFunction(&graph, builder.script);
+    JS_ASSERT(!builder->script->ion);
+    JSContext *cx = GetIonContext()->cx;
+
+    IonSpewNewFunction(graph, builder->script);
+
+    if (!builder->build())
+        return false;
+    builder->clearForBackEnd();
+
+    if (js_IonOptions.parallelCompilation) {
+        builder->script->ion = ION_COMPILING_SCRIPT;
 
-    if (!BuildMIR(builder, graph))
+        if (!StartOffThreadIonCompile(cx, builder))
+            return false;
+
+        // The allocator and associated data will be destroyed after being
+        // processed in the finishedOffThreadCompilations list.
+        autoDestroy.cancel();
+
+        return true;
+    }
+
+    if (!CompileBackEnd(builder))
         return false;
 
-    if (!GenerateCode(builder, graph))
+    CodeGenerator codegen(builder, *builder->lir);
+    if (!codegen.generate())
         return false;
 
+    if (builder->script->hasIonScript()) {
+        // After Ion has finished compiling a script, remove any JITScripts it
+        // has to force continued execution in Ion code.
+        mjit::ReleaseScriptCodeFromVM(cx, builder->script);
+    }
+
     IonSpewEndFunction();
 
     return true;
 }
 
-template <bool Compiler(IonBuilder &, MIRGraph &)>
+void
+AttachFinishedCompilations(JSContext *cx)
+{
+    IonCompartment *ion = cx->compartment->ionCompartment();
+    if (!ion)
+        return;
+
+    AutoLockWorkerThreadState lock(cx->runtime);
+
+    OffThreadCompilationVector &compilations = ion->finishedOffThreadCompilations();
+
+    // Incorporate any off thread compilations which have finished, failed or
+    // have been cancelled, and destroy JM jitcode for any compilations which
+    // succeeded, to allow entering the Ion code from the interpreter.
+    while (!compilations.empty()) {
+        IonBuilder *builder = compilations.popCopy();
+
+        if (builder->lir) {
+            JSScript *script = builder->script;
+            IonContext ictx(cx, cx->compartment, &builder->temp());
+
+            CodeGenerator codegen(builder, *builder->lir);
+
+            types::AutoEnterCompilation enterCompiler(cx, types::AutoEnterCompilation::Ion);
+            enterCompiler.initExisting(builder->recompileInfo);
+
+            bool success;
+            {
+                // Release the worker thread lock and root the compiler for GC.
+                AutoTempAllocatorRooter root(cx, &builder->temp());
+                AutoUnlockWorkerThreadState unlock(cx->runtime);
+                success = codegen.generate();
+            }
+
+            if (success) {
+                if (script->hasIonScript())
+                    mjit::ReleaseScriptCodeFromVM(cx, script);
+            } else {
+                // Silently ignore OOM during code generation, we're at an
+                // operation callback and can't propagate failures.
+                cx->clearPendingException();
+            }
+        }
+
+        FinishOffThreadBuilder(builder);
+    }
+
+    compilations.clear();
+}
+
+static const size_t BUILDER_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 1 << 12;
+
+template <bool Compiler(IonBuilder *, MIRGraph *, AutoDestroyAllocator &)>
 static bool
 IonCompile(JSContext *cx, JSScript *script, JSFunction *fun, jsbytecode *osrPc, bool constructing)
 {
 #if JS_TRACE_LOGGING
     AutoTraceLog logger(TraceLogging::defaultLogger(),
                         TraceLogging::ION_COMPILE_START,
                         TraceLogging::ION_COMPILE_STOP,
                         script);
 #endif
 
-    TempAllocator temp(&cx->tempLifoAlloc());
-    IonContext ictx(cx, cx->compartment, &temp);
+    LifoAlloc *alloc = cx->new_<LifoAlloc>(BUILDER_LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+    if (!alloc)
+        return false;
+
+    AutoDestroyAllocator autoDestroy(alloc);
+
+    TempAllocator *temp = alloc->new_<TempAllocator>(alloc);
+    if (!temp)
+        return false;
+
+    IonContext ictx(cx, cx->compartment, temp);
 
     if (!cx->compartment->ensureIonCompartmentExists(cx))
         return false;
 
-    MIRGraph graph(&temp);
-    CompileInfo *info = cx->tempLifoAlloc().new_<CompileInfo>(script, fun, osrPc, constructing);
+    MIRGraph *graph = alloc->new_<MIRGraph>(temp);
+    CompileInfo *info = alloc->new_<CompileInfo>(script, fun, osrPc, constructing);
     if (!info)
         return false;
 
     types::AutoEnterTypeInference enter(cx, true);
     TypeInferenceOracle oracle;
 
     if (!oracle.init(cx, script))
         return false;
 
     AutoFlushCache afc("IonCompile");
 
     types::AutoEnterCompilation enterCompiler(cx, types::AutoEnterCompilation::Ion);
     enterCompiler.init(script, false, 0);
-    AutoCompilerRoots roots(script->compartment()->rt);
+
+    AutoTempAllocatorRooter root(cx, temp);
 
-    IonBuilder builder(cx, &temp, &graph, &oracle, info);
-    if (!Compiler(builder, graph)) {
+    IonBuilder *builder = alloc->new_<IonBuilder>(cx, temp, graph, &oracle, info);
+    if (!Compiler(builder, graph, autoDestroy)) {
         IonSpew(IonSpew_Abort, "IM Compilation failed.");
         return false;
     }
 
     return true;
 }
 
+bool
+TestIonCompile(JSContext *cx, JSScript *script, JSFunction *fun, jsbytecode *osrPc, bool constructing)
+{
+    if (!IonCompile<TestCompiler>(cx, script, fun, osrPc, constructing)) {
+        if (!cx->isExceptionPending())
+            ForbidCompilation(script);
+        return false;
+    }
+    return true;
+}
+
 static bool
 CheckFrame(StackFrame *fp)
 {
     if (fp->isEvalFrame()) {
         // Eval frames are not yet supported. Supporting this will require new
         // logic in pushBailoutFrame to deal with linking prev.
         // Additionally, JSOP_DEFVAR support will require baking in isEvalFrame().
         IonSpew(IonSpew_Abort, "eval frame");
@@ -974,17 +1114,17 @@ CheckScriptSize(JSScript *script)
     if (numLocalsAndArgs > MAX_LOCALS_AND_ARGS) {
         IonSpew(IonSpew_Abort, "Too many locals and arguments (%u)", numLocalsAndArgs);
         return false;
     }
 
     return true;
 }
 
-template <bool Compiler(IonBuilder &, MIRGraph &)>
+template <bool Compiler(IonBuilder *, MIRGraph *, AutoDestroyAllocator &)>
 static MethodStatus
 Compile(JSContext *cx, JSScript *script, JSFunction *fun, jsbytecode *osrPc, bool constructing)
 {
     JS_ASSERT(ion::IsEnabled(cx));
     JS_ASSERT_IF(osrPc != NULL, (JSOp)*osrPc == JSOP_LOOPENTRY);
 
     if (cx->compartment->debugMode()) {
         IonSpew(IonSpew_Abort, "debugging");
@@ -1029,16 +1169,20 @@ ion::CanEnterAtBranch(JSContext *cx, JSS
 {
     JS_ASSERT(ion::IsEnabled(cx));
     JS_ASSERT((JSOp)*pc == JSOP_LOOPENTRY);
 
     // Skip if the script has been disabled.
     if (script->ion == ION_DISABLED_SCRIPT)
         return Method_Skipped;
 
+    // Skip if the script is being compiled off thread.
+    if (script->ion == ION_COMPILING_SCRIPT)
+        return Method_Skipped;
+
     // Skip if the code is expected to result in a bailout.
     if (script->ion && script->ion->bailoutExpected())
         return Method_Skipped;
 
     // Optionally ignore on user request.
     if (!js_IonOptions.osr)
         return Method_Skipped;
 
@@ -1074,16 +1218,20 @@ MethodStatus
 ion::CanEnter(JSContext *cx, JSScript *script, StackFrame *fp, bool newType)
 {
     JS_ASSERT(ion::IsEnabled(cx));
 
     // Skip if the script has been disabled.
     if (script->ion == ION_DISABLED_SCRIPT)
         return Method_Skipped;
 
+    // Skip if the script is being compiled off thread.
+    if (script->ion == ION_COMPILING_SCRIPT)
+        return Method_Skipped;
+
     // Skip if the code is expected to result in a bailout.
     if (script->ion && script->ion->bailoutExpected())
         return Method_Skipped;
 
     // If constructing, allocate a new |this| object before building Ion.
     // Creating |this| is done before building Ion because it may change the
     // type information and invalidate compilation results.
     if (fp->isConstructing() && fp->functionThis().isPrimitive()) {
@@ -1374,16 +1522,23 @@ InvalidateActivation(FreeOp *fop, uint8 
     }
 
     IonSpew(IonSpew_Invalidate, "END invalidating activation");
 }
 
 void
 ion::InvalidateAll(FreeOp *fop, JSCompartment *c)
 {
+    if (!c->ionCompartment())
+        return;
+
+    CancelOffThreadIonCompile(c, NULL);
+
+    FinishAllOffThreadCompilations(c->ionCompartment());
+
     for (IonActivationIterator iter(fop->runtime()); iter.more(); ++iter) {
         if (iter.activation()->compartment() == c) {
             AutoFlushCache afc ("InvalidateAll", c->ionCompartment());
             IonSpew(IonSpew_Invalidate, "Invalidating all frames for GC");
             InvalidateActivation(fop, iter.top(), true);
         }
     }
 }
--- a/js/src/ion/Ion.h
+++ b/js/src/ion/Ion.h
@@ -62,16 +62,21 @@ struct IonOptions
     // Default: true
     bool edgeCaseAnalysis;
 
     // Toggles whether Range Analysis is used.
     //
     // Default: false
     bool rangeAnalysis;
 
+    // Toggles whether compilation occurs off the main thread.
+    //
+    // Default: true iff there are at least two CPUs available
+    bool parallelCompilation;
+
     // How many invocations or loop iterations are needed before functions
     // are compiled.
     //
     // Default: 10,240
     uint32 usesBeforeCompile;
 
     // How many invocations or loop iterations are needed before functions
     // are compiled when JM is disabled.
@@ -135,40 +140,44 @@ struct IonOptions
 
     void setEagerCompilation() {
         eagerCompilation = true;
         usesBeforeCompile = usesBeforeCompileNoJaeger = 0;
 
         // Eagerly inline calls to improve test coverage.
         usesBeforeInlining = 0;
         smallFunctionUsesBeforeInlining = 0;
+
+        parallelCompilation = false;
     }
 
     IonOptions()
       : gvn(true),
         gvnIsOptimistic(true),
         licm(true),
         osr(true),
         limitScriptSize(true),
         lsra(true),
         inlining(true),
         edgeCaseAnalysis(true),
         rangeAnalysis(false),
+        parallelCompilation(false),
         usesBeforeCompile(10240),
         usesBeforeCompileNoJaeger(40),
         usesBeforeInlining(usesBeforeCompile),
         maxStackArgs(4096),
         maxInlineDepth(3),
         smallFunctionMaxBytecodeLength(100),
         smallFunctionUsesBeforeInlining(usesBeforeInlining / 4),
         polyInlineMax(4),
         inlineMaxTotalBytecodeLength(800),
         eagerCompilation(false),
         slowCallLimit(512)
-    { }
+    {
+    }
 };
 
 enum MethodStatus
 {
     Method_Error,
     Method_CantCompile,
     Method_Skipped,
     Method_Compiled
@@ -225,16 +234,23 @@ void Invalidate(types::TypeCompartment &
                 const Vector<types::RecompileInfo> &invalid, bool resetUses = true);
 void Invalidate(JSContext *cx, const Vector<types::RecompileInfo> &invalid, bool resetUses = true);
 bool Invalidate(JSContext *cx, JSScript *script, bool resetUses = true);
 
 void MarkFromIon(JSCompartment *comp, Value *vp);
 
 void ToggleBarriers(JSCompartment *comp, bool needs);
 
+class IonBuilder;
+
+bool CompileBackEnd(IonBuilder *builder);
+void AttachFinishedCompilations(JSContext *cx);
+void FinishOffThreadBuilder(IonBuilder *builder);
+bool TestIonCompile(JSContext *cx, JSScript *script, JSFunction *fun, jsbytecode *osrPc, bool constructing);
+
 static inline bool IsEnabled(JSContext *cx)
 {
     return cx->hasRunOption(JSOPTION_ION) && cx->typeInferenceEnabled();
 }
 
 void ForbidCompilation(JSScript *script);
 uint32_t UsesBeforeIonRecompile(JSScript *script, jsbytecode *pc);
 
--- a/js/src/ion/IonAllocPolicy.h
+++ b/js/src/ion/IonAllocPolicy.h
@@ -17,20 +17,24 @@
 namespace js {
 namespace ion {
 
 class TempAllocator
 {
     LifoAlloc *lifoAlloc_;
     void *mark_;
 
+    // Linked list of GCThings rooted by this allocator.
+    JS::CompilerRootNode *rootList_;
+
   public:
     TempAllocator(LifoAlloc *lifoAlloc)
       : lifoAlloc_(lifoAlloc),
-        mark_(lifoAlloc->mark())
+        mark_(lifoAlloc->mark()),
+        rootList_(NULL)
     { }
 
     ~TempAllocator()
     {
         lifoAlloc_->release(mark_);
     }
 
     void *allocateInfallible(size_t bytes)
@@ -48,23 +52,47 @@ class TempAllocator
         return p;
     }
 
     LifoAlloc *lifoAlloc()
     {
         return lifoAlloc_;
     }
 
+    JS::CompilerRootNode *&rootList()
+    {
+        return rootList_;
+    }
+
     bool ensureBallast() {
         // Most infallible Ion allocations are small, so we use a ballast of
         // ~16K for now.
         return lifoAlloc_->ensureUnusedApproximate(16 * 1024);
     }
 };
 
+// Stack allocated rooter for all roots associated with a TempAllocator
+class AutoTempAllocatorRooter : private AutoGCRooter
+{
+  public:
+    explicit AutoTempAllocatorRooter(JSContext *cx, TempAllocator *temp
+                                     JS_GUARD_OBJECT_NOTIFIER_PARAM)
+      : AutoGCRooter(cx, IONALLOC), temp(temp)
+    {
+        JS_GUARD_OBJECT_NOTIFIER_INIT;
+    }
+
+    friend void AutoGCRooter::trace(JSTracer *trc);
+    void trace(JSTracer *trc);
+
+  private:
+    TempAllocator *temp;
+    JS_DECL_USE_GUARD_OBJECT_NOTIFIER
+};
+
 class IonAllocPolicy
 {
   public:
     void *malloc_(size_t bytes) {
         return GetIonContext()->temp->allocate(bytes);
     }
     void *realloc_(void *p, size_t oldBytes, size_t bytes) {
         void *n = malloc_(bytes);
--- a/js/src/ion/IonBuilder.cpp
+++ b/js/src/ion/IonBuilder.cpp
@@ -22,16 +22,18 @@
 
 using namespace js;
 using namespace js::ion;
 
 IonBuilder::IonBuilder(JSContext *cx, TempAllocator *temp, MIRGraph *graph,
                        TypeOracle *oracle, CompileInfo *info, size_t inliningDepth, uint32 loopDepth)
   : MIRGenerator(cx->compartment, temp, graph, info),
     script(info->script()),
+    recompileInfo(cx->compartment->types.compiledInfo),
+    lir(NULL),
     cx(cx),
     loopDepth_(loopDepth),
     callerResumePoint_(NULL),
     callerBuilder_(NULL),
     oracle(oracle),
     inliningDepth(inliningDepth),
     failedBoundsCheck_(script->failedBoundsCheck),
     lazyArguments_(NULL)
--- a/js/src/ion/IonBuilder.h
+++ b/js/src/ion/IonBuilder.h
@@ -12,16 +12,18 @@
 // JSScript.
 
 #include "MIR.h"
 #include "MIRGraph.h"
 
 namespace js {
 namespace ion {
 
+class LIRGraph;
+
 class IonBuilder : public MIRGenerator
 {
     enum ControlStatus {
         ControlStatus_Error,
         ControlStatus_Ended,        // There is no continuation/join point.
         ControlStatus_Joined,       // Created a join node.
         ControlStatus_Jumped,       // Parsing another branch at the same level.
         ControlStatus_None          // No control flow.
@@ -420,16 +422,22 @@ class IonBuilder : public MIRGenerator
                            types::TypeSet *types, types::TypeSet *barrier,
                            MBasicBlock *bottom,
                            Vector<MDefinition *, 8, IonAllocPolicy> &retvalDefns);
 
   public:
     // A builder is inextricably tied to a particular script.
     JSScript * const script;
 
+    // Compilation index for this attempt.
+    types::RecompileInfo const recompileInfo;
+
+    // If off thread compilation is successful, final LIR is attached here.
+    LIRGraph *lir;
+
     void clearForBackEnd();
 
   private:
     JSContext *cx;
 
     jsbytecode *pc;
     MBasicBlock *current;
     uint32 loopDepth_;
--- a/js/src/ion/IonCompartment.h
+++ b/js/src/ion/IonCompartment.h
@@ -18,31 +18,34 @@ namespace js {
 namespace ion {
 
 class FrameSizeClass;
 
 typedef void (*EnterIonCode)(void *code, int argc, Value *argv, StackFrame *fp,
                              CalleeToken calleeToken, Value *vp);
 
 class IonActivation;
+class IonBuilder;
+
+typedef Vector<IonBuilder*, 0, SystemAllocPolicy> OffThreadCompilationVector;
 
 class IonCompartment
 {
     typedef WeakCache<const VMFunction *, ReadBarriered<IonCode> > VMWrapperMap;
 
     friend class IonActivation;
 
     // Executable allocator (owned by the runtime).
     JSC::ExecutableAllocator *execAlloc_;
 
     // Trampoline for entering JIT code. Contains OSR prologue.
     ReadBarriered<IonCode> enterJIT_;
 
     // Vector mapping frame class sizes to bailout tables.
-    js::Vector<ReadBarriered<IonCode>, 4, SystemAllocPolicy> bailoutTables_;
+    Vector<ReadBarriered<IonCode>, 4, SystemAllocPolicy> bailoutTables_;
 
     // Generic bailout table; used if the bailout table overflows.
     ReadBarriered<IonCode> bailoutHandler_;
 
     // Argument-rectifying thunk, in the case of insufficient arguments passed
     // to a function call site. Pads with |undefined|.
     ReadBarriered<IonCode> argumentsRectifier_;
 
@@ -50,31 +53,41 @@ class IonCompartment
     ReadBarriered<IonCode> invalidator_;
 
     // Thunk that calls the GC pre barrier.
     ReadBarriered<IonCode> preBarrier_;
 
     // Map VMFunction addresses to the IonCode of the wrapper.
     VMWrapperMap *functionWrappers_;
 
+    // Any scripts for which off thread compilation has successfully finished,
+    // failed, or been cancelled. All off thread compilations which are started
+    // will eventually appear in this list asynchronously. Protected by the
+    // runtime's analysis lock.
+    OffThreadCompilationVector finishedOffThreadCompilations_;
+
     // Keep track of memoryregions that are going to be flushed.
-    js::ion::AutoFlushCache *flusher_;
+    AutoFlushCache *flusher_;
 
   private:
     IonCode *generateEnterJIT(JSContext *cx);
     IonCode *generateReturnError(JSContext *cx);
     IonCode *generateArgumentsRectifier(JSContext *cx);
     IonCode *generateBailoutTable(JSContext *cx, uint32 frameClass);
     IonCode *generateBailoutHandler(JSContext *cx);
     IonCode *generateInvalidator(JSContext *cx);
     IonCode *generatePreBarrier(JSContext *cx);
 
   public:
     IonCode *generateVMWrapper(JSContext *cx, const VMFunction &f);
 
+    OffThreadCompilationVector &finishedOffThreadCompilations() {
+        return finishedOffThreadCompilations_;
+    }
+
   public:
     bool initialize(JSContext *cx);
     IonCompartment();
     ~IonCompartment();
 
     void mark(JSTracer *trc, JSCompartment *compartment);
     void sweep(FreeOp *fop);
 
--- a/js/src/ion/IonFrames.cpp
+++ b/js/src/ion/IonFrames.cpp
@@ -616,20 +616,19 @@ MarkIonActivation(JSTracer *trc, const I
 void
 ion::MarkIonActivations(JSRuntime *rt, JSTracer *trc)
 {
     for (IonActivationIterator activations(rt); activations.more(); ++activations)
         MarkIonActivation(trc, activations);
 }
 
 void
-ion::MarkIonCompilerRoots(JSTracer *trc)
+ion::AutoTempAllocatorRooter::trace(JSTracer *trc)
 {
-    JSRuntime *rt = trc->runtime;
-    for (CompilerRootNode *root = rt->ionCompilerRootList; root != NULL; root = root->next)
+    for (CompilerRootNode *root = temp->rootList(); root != NULL; root = root->next)
         gc::MarkGCThingRoot(trc, root->address(), "ion-compiler-root");
 }
 
 void
 ion::GetPcScript(JSContext *cx, JSScript **scriptRes, jsbytecode **pcRes)
 {
     JS_ASSERT(cx->fp()->beginsIonActivation());
     IonSpew(IonSpew_Snapshots, "Recover PC & Script from the last frame.");
--- a/js/src/ion/IonSpewer.cpp
+++ b/js/src/ion/IonSpewer.cpp
@@ -2,16 +2,17 @@
  * vim: set ts=4 sw=4 et tw=99:
  *
  * 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/. */
 
 #ifdef DEBUG
 
+#include "Ion.h"
 #include "IonSpewer.h"
 
 #ifndef ION_SPEW_DIR
 # if defined(_WIN32)
 #  define ION_SPEW_DIR ""
 # else
 #  define ION_SPEW_DIR "/tmp/"
 # endif
@@ -37,35 +38,39 @@ void
 ion::EnableIonDebugLogging()
 {
     ionspewer.init();
 }
 
 void
 ion::IonSpewNewFunction(MIRGraph *graph, JSScript *function)
 {
-    ionspewer.beginFunction(graph, function);
+    if (!js_IonOptions.parallelCompilation)
+        ionspewer.beginFunction(graph, function);
 }
 
 void
 ion::IonSpewPass(const char *pass)
 {
-    ionspewer.spewPass(pass);
+    if (!js_IonOptions.parallelCompilation)
+        ionspewer.spewPass(pass);
 }
 
 void
 ion::IonSpewPass(const char *pass, LinearScanAllocator *ra)
 {
-    ionspewer.spewPass(pass, ra);
+    if (!js_IonOptions.parallelCompilation)
+        ionspewer.spewPass(pass, ra);
 }
 
 void
 ion::IonSpewEndFunction()
 {
-    ionspewer.endFunction();
+    if (!js_IonOptions.parallelCompilation)
+        ionspewer.endFunction();
 }
 
 
 IonSpewer::~IonSpewer()
 {
     if (!inited_)
         return;
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -43,16 +43,17 @@
 #include "jsopcode.h"
 #include "jsprobes.h"
 #include "jsproxy.h"
 #include "jsscope.h"
 #include "jsscript.h"
 #include "jsstr.h"
 #include "prmjtime.h"
 #include "jsweakmap.h"
+#include "jsworkers.h"
 #include "jswrapper.h"
 #include "jstypedarray.h"
 #include "jsxml.h"
 
 #include "builtin/Eval.h"
 #include "builtin/MapObject.h"
 #include "builtin/RegExp.h"
 #include "builtin/ParallelArray.h"
@@ -859,17 +860,16 @@ JSRuntime::JSRuntime()
     noGCOrAllocationCheck(0),
 #endif
     inOOMReport(0),
     jitHardening(false),
     ionTop(NULL),
     ionJSContext(NULL),
     ionStackLimit(0),
     ionActivation(NULL),
-    ionCompilerRootList(NULL),
     ionReturnOverride_(MagicValue(JS_ARG_POISON))
 {
     /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
     JS_INIT_CLIST(&contextList);
     JS_INIT_CLIST(&debuggerList);
 
     PodZero(&debugHooks);
     PodZero(&atomState);
@@ -923,16 +923,20 @@ JSRuntime::init(uint32_t maxbytes)
 
     if (!stackSpace.init())
         return false;
 
     if (!scriptFilenameTable.init())
         return false;
 
 #ifdef JS_THREADSAFE
+    workerThreadState = this->new_<WorkerThreadState>();
+    if (!workerThreadState || !workerThreadState->init(this))
+        return false;
+
     if (!sourceCompressorThread.init())
         return false;
 #endif
 
     if (!evalCache.init())
         return false;
 
     debugScopes = this->new_<DebugScopes>(this);
@@ -953,16 +957,17 @@ JSRuntime::~JSRuntime()
 
     /*
      * Even though all objects in the compartment are dead, we may have keep
      * some filenames around because of gcKeepAtoms.
      */
     FreeScriptFilenames(this);
 
 #ifdef JS_THREADSAFE
+    delete_(workerThreadState);
     sourceCompressorThread.finish();
 #endif
 
 #ifdef DEBUG
     /* Don't hurt everyone in leaky ol' Mozilla with a fatal JS_ASSERT! */
     if (!JS_CLIST_IS_EMPTY(&contextList)) {
         unsigned cxcount = 0;
         for (ContextIter acx(this); !acx.done(); acx.next()) {
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1059,17 +1059,18 @@ class JS_PUBLIC_API(AutoGCRooter) {
         SHAPERANGE =  -20, /* js::Shape::Range::AutoRooter */
         STACKSHAPE =  -21, /* js::StackShape::AutoRooter */
         STACKBASESHAPE=-22,/* js::StackBaseShape::AutoRooter */
         BINDINGS =    -23, /* js::Bindings::AutoRooter */
         GETTERSETTER =-24, /* js::AutoRooterGetterSetter */
         REGEXPSTATICS=-25, /* js::RegExpStatics::AutoRooter */
         NAMEVECTOR =  -26, /* js::AutoNameVector */
         HASHABLEVALUE=-27,
-        IONMASM =     -28  /* js::ion::MacroAssembler */
+        IONMASM =     -28, /* js::ion::MacroAssembler */
+        IONALLOC =    -29  /* js::ion::AutoTempAllocatorRooter */
     };
 
   private:
     AutoGCRooter ** const stackTop;
 
     /* No copy or assignment semantics. */
     AutoGCRooter(AutoGCRooter &ida) MOZ_DELETE;
     void operator=(AutoGCRooter &ida) MOZ_DELETE;
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -37,16 +37,18 @@
 #include "jsmath.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jsopcode.h"
 #include "jspubtd.h"
 #include "jsscope.h"
 #include "jsscript.h"
 #include "jsstr.h"
+#include "jsworkers.h"
+#include "ion/Ion.h"
 #include "ion/IonFrames.h"
 
 #ifdef JS_METHODJIT
 # include "assembler/assembler/MacroAssembler.h"
 # include "methodjit/MethodJIT.h"
 #endif
 #include "gc/Marking.h"
 #include "js/MemoryMetrics.h"
@@ -384,16 +386,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. */
+        for (CompartmentsIter c(rt); !c.done(); c.next())
+            CancelOffThreadIonCompile(c, NULL);
+
         /* Unpin all common atoms before final GC. */
         FinishCommonAtoms(rt);
 
         /* Clear debugging state to remove GC roots. */
         for (CompartmentsIter c(rt); !c.done(); c.next())
             c->clearTraps(rt->defaultFreeOp());
         JS_ClearAllWatchPoints(cx);
 
@@ -1006,16 +1012,22 @@ js_InvokeOperationCallback(JSContext *cx
 
     /* Reset Ion's stack limit. */
     cx->runtime->ionStackLimit = cx->runtime->nativeStackLimit;
 
     if (rt->gcIsNeeded)
         GCSlice(rt, GC_NORMAL, rt->gcTriggerReason);
 
     /*
+     * A worker thread may have set the callback after finishing an Ion
+     * compilation.
+     */
+    ion::AttachFinishedCompilations(cx);
+
+    /*
      * Important: Additional callbacks can occur inside the callback handler
      * if it re-enters the JS engine. The embedding must ensure that the
      * callback is disconnected before attempting such re-entry.
      */
     JSOperationCallback cb = cx->operationCallback;
     return !cb || cb(cx);
 }
 
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -72,16 +72,17 @@ class MathCache;
 
 namespace ion {
 class IonActivation;
 }
 
 class WeakMapBase;
 class InterpreterFrames;
 class DebugScopes;
+class WorkerThreadState;
 
 /*
  * GetSrcNote cache to avoid O(n^2) growth in finding a source note for a
  * given pc in a script. We use the script->code pointer to tag the cache,
  * instead of the script address itself, so that source notes are always found
  * by offset from the bytecode with which they were generated.
  */
 struct GSNCache {
@@ -784,16 +785,18 @@ struct JSRuntime : js::RuntimeFriendFiel
     void                *data;
 
     /* These combine to interlock the GC and new requests. */
     PRLock              *gcLock;
 
     js::GCHelperThread  gcHelperThread;
 
 #ifdef JS_THREADSAFE
+    js::WorkerThreadState *workerThreadState;
+
     js::SourceCompressorThread sourceCompressorThread;
 #endif
 
   private:
     js::FreeOp          defaultFreeOp_;
 
   public:
     js::FreeOp *defaultFreeOp() {
@@ -889,19 +892,16 @@ struct JSRuntime : js::RuntimeFriendFiel
     // aligned to an Ion exit frame.
     uint8_t             *ionTop;
     JSContext           *ionJSContext;
     uintptr_t            ionStackLimit;
 
     // This points to the most recent Ion activation running on the thread.
     js::ion::IonActivation  *ionActivation;
 
-    // Linked list of GCThings rooted for the current compilation.
-    JS::CompilerRootNode *ionCompilerRootList;
-
   private:
     // In certain cases, we want to optimize certain opcodes to typed instructions,
     // to avoid carrying an extra register to feed into an unbox. Unfortunately,
     // that's not always possible. For example, a GetPropertyCacheT could return a
     // typed double, but if it takes its out-of-line path, it could return an
     // object, and trigger invalidation. The invalidation bailout will consider the
     // return value to be a double, and create a garbage Value.
     //
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2489,16 +2489,23 @@ AutoGCRooter::trace(JSTracer *trc)
       }
 
       case IONMASM: {
 #ifdef JS_ION
         static_cast<js::ion::MacroAssembler::AutoRooter *>(this)->masm()->trace(trc);
 #endif
         return;
       }
+
+      case IONALLOC: {
+#ifdef JS_ION
+        static_cast<js::ion::AutoTempAllocatorRooter *>(this)->trace(trc);
+#endif
+        return;
+      }
     }
 
     JS_ASSERT(tag >= 0);
     MarkValueRootRange(trc, tag, static_cast<AutoArrayRooter *>(this)->array,
                        "JS::AutoArrayRooter.array");
 }
 
 /* static */ void
@@ -2618,17 +2625,16 @@ MarkRuntime(JSTracer *trc, bool useSaved
         mjit::ExpandInlineFrames(c);
 #endif
 
     rt->stackSpace.markAndClobber(trc);
     rt->debugScopes->mark(trc);
 	
 #ifdef JS_ION
     ion::MarkIonActivations(rt, trc);
-    ion::MarkIonCompilerRoots(trc);
 #endif
 
     for (CompartmentsIter c(rt); !c.done(); c.next())
         c->mark(trc);
 
     /* The embedding can register additional roots here. */
     if (JSTraceDataOp op = rt->gcBlackRootsTraceOp)
         (*op)(trc, rt->gcBlackRootsData);
@@ -2913,17 +2919,17 @@ AssertBackgroundSweepingFinshed(JSRuntim
             JS_ASSERT(!c->arenas.arenaListsToSweep[i]);
             JS_ASSERT(c->arenas.doneBackgroundFinalize(AllocKind(i)));
         }
     }
 }
 #endif
 
 #ifdef JS_THREADSAFE
-static unsigned
+unsigned
 GetCPUCount()
 {
     static unsigned ncpus = 0;
     if (ncpus == 0) {
 # ifdef XP_WIN
         SYSTEM_INFO sysinfo;
         GetSystemInfo(&sysinfo);
         ncpus = unsigned(sysinfo.dwNumberOfProcessors);
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -15,16 +15,17 @@
 #include "jsmath.h"
 #include "jsnum.h"
 #include "jsobj.h"
 #include "jsscript.h"
 #include "jscntxt.h"
 #include "jsscope.h"
 #include "jsstr.h"
 #include "jsiter.h"
+#include "jsworkers.h"
 
 #include "ion/Ion.h"
 #include "ion/IonCompartment.h"
 #include "frontend/TokenStream.h"
 #include "gc/Marking.h"
 #include "js/MemoryMetrics.h"
 #include "methodjit/MethodJIT.h"
 #include "methodjit/Retcon.h"
@@ -2248,16 +2249,19 @@ TypeCompartment::nukeTypes(FreeOp *fop)
 void
 TypeCompartment::addPendingRecompile(JSContext *cx, const RecompileInfo &info)
 {
     CompilerOutput *co = info.compilerOutput(cx);
 
     if (co->pendingRecompilation)
         return;
 
+    if (co->isValid())
+        CancelOffThreadIonCompile(cx->compartment, co->script);
+
     if (!co->isValid()) {
         JS_ASSERT(co->script == NULL);
         return;
     }
 
 #ifdef JS_METHODJIT
     mjit::JITScript *jit = co->script->getJIT(co->constructing, co->barriers);
     bool hasJITCode = jit && jit->chunkDescriptor(co->chunkIndex).chunk;
@@ -2314,16 +2318,18 @@ TypeCompartment::addPendingRecompile(JSC
             if (!chunk)
                 continue;
 
             addPendingRecompile(cx, chunk->recompileInfo);
         }
     }
 
 # ifdef JS_ION
+    CancelOffThreadIonCompile(cx->compartment, script);
+
     if (script->hasIonScript())
         addPendingRecompile(cx, script->ionScript()->recompileInfo());
 # endif
 #endif
 }
 
 void
 TypeCompartment::monitorBytecode(JSContext *cx, JSScript *script, uint32_t offset,
@@ -5844,43 +5850,41 @@ TypeCompartment::sweep(FreeOp *fop)
     pendingCapacity = 0;
 
     sweepCompilerOutputs(fop);
 }
 
 void
 TypeCompartment::sweepCompilerOutputs(FreeOp *fop)
 {
-
     if (constrainedOutputs) {
         bool isCompiling = compiledInfo.outputIndex != RecompileInfo::NoCompilerRunning;
         if (isCompiling && !compartment()->activeAnalysis)
         {
 #if DEBUG
             for (unsigned i = 0; i < constrainedOutputs->length(); i++) {
                 CompilerOutput &co = (*constrainedOutputs)[i];
                 JS_ASSERT(!co.isValid());
             }
 #endif
+
             fop->delete_(constrainedOutputs);
             constrainedOutputs = NULL;
         } else {
             // A Compilation is running and the AutoEnterCompilation class has
             // captured an index into the constrained outputs vector and
             // potentially created multiple types with this index.  Instead, we
             // invalidate all compilations except the one running now.
             size_t len = constrainedOutputs->length();
-            if (isCompiling) {
-                len--;
-                JS_ASSERT(compiledInfo.outputIndex == len);
-            }
             for (unsigned i = 0; i < len; i++) {
-                CompilerOutput &co = (*constrainedOutputs)[i];
-                JS_ASSERT(!co.isValid());
-                co.invalidate();
+                if (i != compiledInfo.outputIndex) {
+                    CompilerOutput &co = (*constrainedOutputs)[i];
+                    JS_ASSERT(!co.isValid());
+                    co.invalidate();
+                }
             }
         }
     }
 
     if (pendingRecompiles) {
         fop->delete_(pendingRecompiles);
         pendingRecompiles = NULL;
     }
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -75,20 +75,23 @@ CompilerOutput::isValid() const
         if (!chunk)
             return false;
         JS_ASSERT(this == chunk->recompileInfo.compilerOutput(types));
         return true;
     }
 #endif
 
     if (isIon()) {
-        if (!script->hasIonScript())
-            return false;
-        JS_ASSERT(this == script->ion->recompileInfo().compilerOutput(types));
-        return true;
+        if (script->hasIonScript()) {
+            JS_ASSERT(this == script->ion->recompileInfo().compilerOutput(types));
+            return true;
+        }
+        if (script->isIonCompilingOffThread())
+            return true;
+        return false;
     }
     return false;
 }
 
 inline CompilerOutput*
 RecompileInfo::compilerOutput(TypeCompartment &types) const
 {
     return &(*types.constrainedOutputs)[outputIndex];
@@ -367,16 +370,23 @@ struct AutoEnterCompilation
         if (info.outputIndex >= RecompileInfo::NoCompilerRunning)
             return false;
 
         if (!cx->compartment->types.constrainedOutputs->append(co))
             return false;
         return true;
     }
 
+    void initExisting(RecompileInfo oldInfo)
+    {
+        // Initialize the active compilation index from that produced during a
+        // previous compilation, for finishing an off thread compilation.
+        info = oldInfo;
+    }
+
     ~AutoEnterCompilation()
     {
         CompilerOutput *co = info.compilerOutput(cx);
         co->pendingRecompilation = false;
         if (!co->isValid())
             co->invalidate();
 
         info.outputIndex = RecompileInfo::NoCompilerRunning;
--- a/js/src/jslock.h
+++ b/js/src/jslock.h
@@ -16,16 +16,20 @@
 # include "prthread.h"
 # include "prinit.h"
 
 # define JS_ATOMIC_INCREMENT(p)      PR_ATOMIC_INCREMENT((int32_t *)(p))
 # define JS_ATOMIC_DECREMENT(p)      PR_ATOMIC_DECREMENT((int32_t *)(p))
 # define JS_ATOMIC_ADD(p,v)          PR_ATOMIC_ADD((int32_t *)(p), (int32_t)(v))
 # define JS_ATOMIC_SET(p,v)          PR_ATOMIC_SET((int32_t *)(p), (int32_t)(v))
 
+namespace js {
+    unsigned GetCPUCount();
+}
+
 #else  /* JS_THREADSAFE */
 
 typedef struct PRThread PRThread;
 typedef struct PRCondVar PRCondVar;
 typedef struct PRLock PRLock;
 
 # define JS_ATOMIC_INCREMENT(p)      (++*(p))
 # define JS_ATOMIC_DECREMENT(p)      (--*(p))
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -546,21 +546,24 @@ struct JSScript : public js::gc::Cell
 
     js::ion::IonScript *ion;          /* Information attached by Ion */
 
 #if defined(JS_METHODJIT) && JS_BITS_PER_WORD == 32
     void *padding_;
 #endif
 
     bool hasIonScript() const {
-        return ion && ion != ION_DISABLED_SCRIPT;
+        return ion && ion != ION_DISABLED_SCRIPT && ion != ION_COMPILING_SCRIPT;
     }
     bool canIonCompile() const {
         return ion != ION_DISABLED_SCRIPT;
     }
+    bool isIonCompilingOffThread() const {
+        return ion == ION_COMPILING_SCRIPT;
+    }
     js::ion::IonScript *ionScript() const {
         JS_ASSERT(hasIonScript());
         return ion;
     }
 
     /*
      * Original compiled function for the script, if it has a function.
      * NULL for global and eval scripts.
new file mode 100644
--- /dev/null
+++ b/js/src/jsworkers.cpp
@@ -0,0 +1,294 @@
+/* -*- Mode: c++; c-basic-offset: 4; tab-width: 40; indent-tabs-mode: nil -*- */
+/* vim: set ts=40 sw=4 et tw=99: */
+/* 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/. */
+
+#include "jsworkers.h"
+
+#include "ion/IonBuilder.h"
+
+using namespace js;
+
+#ifdef JS_THREADSAFE
+
+bool
+js::StartOffThreadIonCompile(JSContext *cx, ion::IonBuilder *builder)
+{
+    WorkerThreadState &state = *cx->runtime->workerThreadState;
+
+    JS_ASSERT(state.numThreads);
+
+    AutoLockWorkerThreadState lock(cx->runtime);
+
+    if (!state.ionWorklist.append(builder))
+        return false;
+
+    state.notify(WorkerThreadState::WORKER);
+
+    return true;
+}
+
+/*
+ * Move an IonBuilder for which compilation has either finished, failed, or
+ * been cancelled into the Ion compartment's finished compilations list.
+ * All off thread compilations which are started must eventually be finished.
+ */
+static void
+FinishOffThreadIonCompile(ion::IonBuilder *builder)
+{
+    JSCompartment *compartment = builder->script->compartment();
+    JS_ASSERT(compartment->rt->workerThreadState->isLocked());
+
+    compartment->ionCompartment()->finishedOffThreadCompilations().append(builder);
+}
+
+static inline bool
+CompiledScriptMatches(JSCompartment *compartment, JSScript *script, JSScript *target)
+{
+    if (script)
+        return target == script;
+    return target->compartment() == compartment;
+}
+
+void
+js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
+{
+    WorkerThreadState &state = *compartment->rt->workerThreadState;
+
+    ion::IonCompartment *ion = compartment->ionCompartment();
+    if (!ion)
+        return;
+
+    AutoLockWorkerThreadState lock(compartment->rt);
+
+    /* Cancel any pending entries for which processing hasn't started. */
+    for (size_t i = 0; i < state.ionWorklist.length(); i++) {
+        ion::IonBuilder *builder = state.ionWorklist[i];
+        if (CompiledScriptMatches(compartment, script, builder->script)) {
+            FinishOffThreadIonCompile(builder);
+            state.ionWorklist[i--] = state.ionWorklist.back();
+            state.ionWorklist.popBack();
+        }
+    }
+
+    /* Wait for in progress entries to finish up. */
+    for (size_t i = 0; i < state.numThreads; i++) {
+        const WorkerThread &helper = state.threads[i];
+        while (helper.ionScript && CompiledScriptMatches(compartment, script, helper.ionScript))
+            state.wait(WorkerThreadState::MAIN);
+    }
+
+    ion::OffThreadCompilationVector &compilations = ion->finishedOffThreadCompilations();
+
+    /* Cancel code generation for any completed entries. */
+    for (size_t i = 0; i < compilations.length(); i++) {
+        ion::IonBuilder *builder = compilations[i];
+        if (CompiledScriptMatches(compartment, script, builder->script)) {
+            ion::FinishOffThreadBuilder(builder);
+            compilations[i--] = compilations.back();
+            compilations.popBack();
+        }
+    }
+}
+
+bool
+WorkerThreadState::init(JSRuntime *rt)
+{
+    workerLock = PR_NewLock();
+    if (!workerLock)
+        return false;
+
+    mainWakeup = PR_NewCondVar(workerLock);
+    if (!mainWakeup)
+        return false;
+
+    helperWakeup = PR_NewCondVar(workerLock);
+    if (!helperWakeup)
+        return false;
+
+    numThreads = GetCPUCount() - 1;
+
+    threads = (WorkerThread*) rt->calloc_(sizeof(WorkerThread) * numThreads);
+    if (!threads) {
+        numThreads = 0;
+        return false;
+    }
+
+    for (size_t i = 0; i < numThreads; i++) {
+        WorkerThread &helper = threads[i];
+        helper.runtime = rt;
+        helper.thread = PR_CreateThread(PR_USER_THREAD,
+                                        WorkerThread::ThreadMain, &helper,
+                                        PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_JOINABLE_THREAD, 0);
+        if (!helper.thread) {
+            for (size_t j = 0; j < numThreads; j++)
+                threads[j].destroy();
+            Foreground::free_(threads);
+            threads = NULL;
+            numThreads = 0;
+            return false;
+        }
+    }
+
+    return true;
+}
+
+WorkerThreadState::~WorkerThreadState()
+{
+    /*
+     * Join created threads first, which needs locks and condition variables
+     * to be intact.
+     */
+    if (threads) {
+        for (size_t i = 0; i < numThreads; i++)
+            threads[i].destroy();
+        Foreground::free_(threads);
+    }
+
+    if (workerLock)
+        PR_DestroyLock(workerLock);
+
+    if (mainWakeup)
+        PR_DestroyCondVar(mainWakeup);
+
+    if (helperWakeup)
+        PR_DestroyCondVar(helperWakeup);
+}
+
+void
+WorkerThreadState::lock()
+{
+    JS_ASSERT(!isLocked());
+    PR_Lock(workerLock);
+#ifdef DEBUG
+    lockOwner = PR_GetCurrentThread();
+#endif
+}
+
+void
+WorkerThreadState::unlock()
+{
+    JS_ASSERT(isLocked());
+#ifdef DEBUG
+    lockOwner = NULL;
+#endif
+    PR_Unlock(workerLock);
+}
+
+#ifdef DEBUG
+bool
+WorkerThreadState::isLocked()
+{
+    return lockOwner == PR_GetCurrentThread();
+}
+#endif
+
+void
+WorkerThreadState::wait(CondVar which, uint32_t millis)
+{
+    JS_ASSERT(isLocked());
+#ifdef DEBUG
+    lockOwner = NULL;
+#endif
+    PR_WaitCondVar((which == MAIN) ? mainWakeup : helperWakeup,
+                   millis ? PR_MillisecondsToInterval(millis) : PR_INTERVAL_NO_TIMEOUT);
+#ifdef DEBUG
+    lockOwner = PR_GetCurrentThread();
+#endif
+}
+
+void
+WorkerThreadState::notify(CondVar which)
+{
+    JS_ASSERT(isLocked());
+    PR_NotifyAllCondVar((which == MAIN) ? mainWakeup : helperWakeup);
+}
+
+void
+WorkerThread::destroy()
+{
+    WorkerThreadState &state = *runtime->workerThreadState;
+
+    if (!thread)
+        return;
+
+    terminate = true;
+    {
+        AutoLockWorkerThreadState lock(runtime);
+        state.notify(WorkerThreadState::WORKER);
+    }
+    PR_JoinThread(thread);
+}
+
+/* static */
+void
+WorkerThread::ThreadMain(void *arg)
+{
+    PR_SetCurrentThreadName("Analysis Helper");
+    static_cast<WorkerThread *>(arg)->threadLoop();
+}
+
+void
+WorkerThread::threadLoop()
+{
+    WorkerThreadState &state = *runtime->workerThreadState;
+    state.lock();
+
+    while (true) {
+        JS_ASSERT(!ionScript);
+
+        while (state.ionWorklist.empty()) {
+            state.wait(WorkerThreadState::WORKER);
+            if (terminate) {
+                state.unlock();
+                return;
+            }
+        }
+
+        ion::IonBuilder *builder = state.ionWorklist.popCopy();
+        ionScript = builder->script;
+
+        JS_ASSERT(ionScript->ion == ION_COMPILING_SCRIPT);
+
+        state.unlock();
+
+        {
+            ion::IonContext ictx(NULL, ionScript->compartment(), &builder->temp());
+            ion::CompileBackEnd(builder);
+        }
+
+        state.lock();
+
+        ionScript = NULL;
+        FinishOffThreadIonCompile(builder);
+
+        /*
+         * Notify the main thread in case it is waiting for the compilation to
+         * finish.
+         */
+        state.notify(WorkerThreadState::MAIN);
+
+        /*
+         * Ping the main thread so that the compiled code can be incorporated
+         * at the next operation callback.
+         */
+        runtime->triggerOperationCallback();
+    }
+}
+
+#else /* JS_THREADSAFE */
+
+bool
+js::StartOffThreadIonCompile(JSContext *cx, ion::IonBuilder *builder)
+{
+    JS_NOT_REACHED("Off thread compilation not available in non-THREADSAFE builds");
+    return false;
+}
+
+void
+js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
+{
+}
+
+#endif /* JS_THREADSAFE */
new file mode 100644
--- /dev/null
+++ b/js/src/jsworkers.h
@@ -0,0 +1,162 @@
+//* -*- Mode: c++; c-basic-offset: 4; tab-width: 40; indent-tabs-mode: nil -*- */
+/* vim: set ts=40 sw=4 et tw=99: */
+/* 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/. */
+
+/*
+ * Definitions for managing off-main-thread work using a shared, per runtime
+ * worklist. Worklist items are engine internal, and are distinct from e.g.
+ * web workers.
+ */
+
+#ifndef jsworkers_h___
+#define jsworkers_h___
+
+#include "jscntxt.h"
+#include "jslock.h"
+
+namespace js {
+
+namespace ion {
+  class IonBuilder;
+}
+
+#ifdef JS_THREADSAFE
+
+struct WorkerThread;
+
+/* Per-runtime state for off thread work items. */
+struct WorkerThreadState
+{
+    /* Available threads. */
+    WorkerThread *threads;
+    size_t numThreads;
+
+    enum CondVar {
+        MAIN,
+        WORKER
+    };
+
+    /* Shared worklist for helper threads. */
+    js::Vector<ion::IonBuilder*, 0, SystemAllocPolicy> ionWorklist;
+
+    WorkerThreadState() { PodZero(this); }
+    ~WorkerThreadState();
+
+    bool init(JSRuntime *rt);
+
+    void lock();
+    void unlock();
+
+#ifdef DEBUG
+    bool isLocked();
+#endif
+
+    void wait(CondVar which, uint32_t timeoutMillis = 0);
+    void notify(CondVar which);
+
+  private:
+
+    /*
+     * Lock protecting all mutable shared state accessed by helper threads, and
+     * used by all condition variables.
+     */
+    PRLock *workerLock;
+
+#ifdef DEBUG
+    PRThread *lockOwner;
+#endif
+
+    /* Condvar to notify the main thread that work has been completed. */
+    PRCondVar *mainWakeup;
+
+    /* Condvar to notify helper threads that they may be able to make progress. */
+    PRCondVar *helperWakeup;
+};
+
+/* Individual helper thread, one allocated per core. */
+struct WorkerThread
+{
+    JSRuntime *runtime;
+    PRThread *thread;
+
+    /* Indicate to an idle thread that it should finish executing. */
+    bool terminate;
+
+    /* Any script currently being compiled for Ion on this thread. */
+    JSScript *ionScript;
+
+    void destroy();
+
+    static void ThreadMain(void *arg);
+    void threadLoop();
+};
+
+#endif /* JS_THREADSAFE */
+
+/* Methods for interacting with worker threads. */
+
+/*
+ * Schedule an Ion compilation for a script, given a builder which has been
+ * generated and read everything needed from the VM state.
+ */
+bool StartOffThreadIonCompile(JSContext *cx, ion::IonBuilder *builder);
+
+/*
+ * 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);
+
+class AutoLockWorkerThreadState
+{
+    JSRuntime *rt;
+    JS_DECL_USE_GUARD_OBJECT_NOTIFIER
+
+  public:
+
+    AutoLockWorkerThreadState(JSRuntime *rt JS_GUARD_OBJECT_NOTIFIER_PARAM)
+      : rt(rt)
+    {
+#ifdef JS_THREADSAFE
+        rt->workerThreadState->lock();
+#endif
+        JS_GUARD_OBJECT_NOTIFIER_INIT;
+    }
+
+    ~AutoLockWorkerThreadState()
+    {
+#ifdef JS_THREADSAFE
+        rt->workerThreadState->unlock();
+#endif
+    }
+};
+
+class AutoUnlockWorkerThreadState
+{
+    JSRuntime *rt;
+    JS_DECL_USE_GUARD_OBJECT_NOTIFIER
+
+  public:
+
+    AutoUnlockWorkerThreadState(JSRuntime *rt JS_GUARD_OBJECT_NOTIFIER_PARAM)
+      : rt(rt)
+    {
+#ifdef JS_THREADSAFE
+        rt->workerThreadState->unlock();
+#endif
+        JS_GUARD_OBJECT_NOTIFIER_INIT;
+    }
+
+    ~AutoUnlockWorkerThreadState()
+    {
+#ifdef JS_THREADSAFE
+        rt->workerThreadState->lock();
+#endif
+    }
+};
+
+} /* namespace js */
+
+#endif // jsworkers_h___
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -46,22 +46,16 @@ using namespace js::mjit::ic;
 using namespace js::analyze;
 
 #define RETURN_IF_OOM(retval)                                   \
     JS_BEGIN_MACRO                                              \
         if (oomInVector || masm.oom() || stubcc.masm.oom())     \
             return retval;                                      \
     JS_END_MACRO
 
-/*
- * Number of times a script must be called or had a backedge before we try to
- * inline its calls. This number should match IonMonkey's usesBeforeCompile.
- */
-static const size_t USES_BEFORE_INLINING = 10240;
-
 mjit::Compiler::Compiler(JSContext *cx, JSScript *outerScript,
                          unsigned chunkIndex, bool isConstructing)
   : BaseCompiler(cx),
     outerScript(cx, outerScript),
     chunkIndex(chunkIndex),
     isConstructing(isConstructing),
     outerChunk(outerJIT()->chunkDescriptor(chunkIndex)),
     ssa(cx, outerScript),
@@ -99,22 +93,16 @@ mjit::Compiler::Compiler(JSContext *cx, 
     debugMode_(cx->compartment->debugMode()),
     inlining_(false),
     hasGlobalReallocation(false),
     oomInVector(false),
     overflowICSpace(false),
     gcNumber(cx->runtime->gcNumber),
     pcLengths(NULL)
 {
-    /* Once a script starts getting really hot we will inline calls in it. */
-    if (!debugMode() && cx->typeInferenceEnabled() && globalObj &&
-        (outerScript->getUseCount() >= USES_BEFORE_INLINING ||
-         cx->hasRunOption(JSOPTION_METHODJIT_ALWAYS))) {
-        inlining_ = true;
-    }
 }
 
 CompileStatus
 mjit::Compiler::compile()
 {
     JS_ASSERT(!outerChunkRef().chunk);
 
 #if JS_TRACE_LOGGING
@@ -956,16 +944,21 @@ IonGetsFirstChance(JSContext *cx, JSScri
     // If there's no way this script is going to be Ion compiled, let JM take over.
     if (!script->canIonCompile())
         return false;
 
     // If we cannot enter Ion because bailouts are expected, let JM take over.
     if (script->hasIonScript() && script->ion->bailoutExpected())
         return false;
 
+    // If ion compilation is pending or in progress on another thread, continue
+    // using JM until that compilation finishes.
+    if (script->ion == ION_COMPILING_SCRIPT)
+        return false;
+
     return true;
 #endif
     return false;
 }
 
 CompileStatus
 mjit::CanMethodJIT(JSContext *cx, JSScript *script, jsbytecode *pc,
                    bool construct, CompileRequest request, StackFrame *frame)
@@ -1031,29 +1024,16 @@ mjit::CanMethodJIT(JSContext *cx, JSScri
         jith->setValid(jit);
     } else {
         jit = jith->getValid();
     }
 
     unsigned chunkIndex = jit->chunkIndex(pc);
     ChunkDescriptor &desc = jit->chunkDescriptor(chunkIndex);
 
-    if (jit->mustDestroyEntryChunk) {
-        // We kept this chunk around so that Ion can get info from its caches.
-        // If we end up here, we decided not to use Ion so we can destroy the
-        // chunk now.
-        JS_ASSERT(jit->nchunks == 1);
-        jit->mustDestroyEntryChunk = false;
-
-        if (desc.chunk) {
-            jit->destroyChunk(cx->runtime->defaultFreeOp(), chunkIndex, /* resetUses = */ false);
-            return Compile_Skipped;
-        }
-    }
-
     if (desc.chunk)
         return Compile_Okay;
 
     if (!cx->hasRunOption(JSOPTION_METHODJIT_ALWAYS) &&
         ++desc.counter <= INFER_USES_BEFORE_COMPILE)
     {
         return Compile_Skipped;
     }
@@ -1225,17 +1205,17 @@ mjit::Compiler::generatePrologue()
         }
 
         if (isConstructing && !constructThis())
             return Compile_Error;
     }
 
     CompileStatus status = methodEntryHelper();
     if (status == Compile_Okay)
-        recompileCheckHelper();
+        ionCompileHelper();
 
     return status;
 }
 
 void
 mjit::Compiler::ensureDoubleArguments()
 {
     /* Convert integer arguments which were inferred as (int|double) to doubles. */
@@ -3264,17 +3244,17 @@ mjit::Compiler::generateMethod()
                 interruptCheckHelper();
           END_CASE(JSOP_LOOPHEAD)
 
           BEGIN_CASE(JSOP_LOOPENTRY)
             // Unlike JM, IonMonkey OSR enters loops at the LOOPENTRY op.
             // Insert the recompile check here so that we can immediately
             // enter Ion.
             if (loop)
-                recompileCheckHelper();
+                ionCompileHelper();
           END_CASE(JSOP_LOOPENTRY)
 
           BEGIN_CASE(JSOP_DEBUGGER)
           {
             prepareStubCall(Uses(0));
             masm.move(ImmPtr(PC), Registers::ArgReg1);
             INLINE_STUBCALL(stubs::DebuggerStatement, REJOIN_FALLTHROUGH);
           }
@@ -3966,66 +3946,71 @@ MaybeIonCompileable(JSContext *cx, JSScr
     if (script->isShortRunning())
         *recompileCheckForIon = false;
 
     return true;
 #endif
     return false;
 }
 
+#ifdef JS_ION
 void
-mjit::Compiler::recompileCheckHelper()
+mjit::Compiler::ionCompileHelper()
 {
-    if (inlining() || debugMode() || !globalObj || !cx->typeInferenceEnabled())
+    if (debugMode() || !globalObj || !cx->typeInferenceEnabled())
         return;
 
-    bool recompileCheckForIon = true;
-    bool maybeIonCompileable = MaybeIonCompileable(cx, outerScript, &recompileCheckForIon);
-    bool hasFunctionCalls = analysis->hasFunctionCalls();
-
-    // Insert a recompile check if either:
-    // 1) IonMonkey is enabled, to optimize the function when it becomes hot.
-    // 2) The script contains function calls JM can inline.
-    if (!maybeIonCompileable && !hasFunctionCalls)
+    bool recompileCheckForIon = false;
+    if (!MaybeIonCompileable(cx, outerScript, &recompileCheckForIon))
         return;
 
-    uint32_t minUses = USES_BEFORE_INLINING;
-
-#ifdef JS_ION
-    if (recompileCheckForIon)
-        minUses = ion::UsesBeforeIonRecompile(outerScript, PC);
-#endif
-
-    uint32_t *addr = script->addressOfUseCount();
-    masm.add32(Imm32(1), AbsoluteAddress(addr));
-
-    // If there are no function calls, and we don't want to do a recompileCheck for
-    // Ion, then this just needs to increment the useCount so that we know when to
-    // recompile this function from an Ion call.  No need to call out to recompiler
-    // stub.
-    if (!hasFunctionCalls && !recompileCheckForIon)
+    uint32_t minUses = ion::UsesBeforeIonRecompile(outerScript, PC);
+
+    uint32_t *useCountAddress = script->addressOfUseCount();
+    masm.add32(Imm32(1), AbsoluteAddress(useCountAddress));
+
+    // If we don't want to do a recompileCheck for Ion, then this just needs to
+    // increment the useCount so that we know when to recompile this function
+    // from an Ion call.  No need to call out to recompiler stub.
+    if (!recompileCheckForIon)
         return;
 
+    void *ionScriptAddress = &script->ion;
+
+    // Trigger ion compilation if (a) the script has been used enough times for
+    // this opcode, and (b) the script does not already have ion information
+    // (whether successful, failed, or in progress off thread compilation).
 #if defined(JS_CPU_X86) || defined(JS_CPU_ARM)
-    Jump jump = masm.branch32(Assembler::GreaterThanOrEqual, AbsoluteAddress(addr),
-                              Imm32(minUses));
+    Jump first = masm.branch32(Assembler::LessThan, AbsoluteAddress(useCountAddress),
+                               Imm32(minUses));
+    Jump second = masm.branch32(Assembler::Equal, AbsoluteAddress(ionScriptAddress),
+                                Imm32(0));
 #else
     /* Handle processors that can't load from absolute addresses. */
     RegisterID reg = frame.allocReg();
-    masm.move(ImmPtr(addr), reg);
-    Jump jump = masm.branch32(Assembler::GreaterThanOrEqual, Address(reg, 0),
-                              Imm32(minUses));
+    masm.move(ImmPtr(useCountAddress), reg);
+    Jump first = masm.branch32(Assembler::LessThan, Address(reg), Imm32(minUses));
+    masm.move(ImmPtr(ionScriptAddress), reg);
+    Jump second = masm.branchPtr(Assembler::Equal, Address(reg), ImmPtr(NULL));
     frame.freeReg(reg);
 #endif
-    stubcc.linkExit(jump, Uses(0));
+    first.linkTo(masm.label(), &masm);
+
+    stubcc.linkExit(second, Uses(0));
     stubcc.leave();
 
-    OOL_STUBCALL(stubs::RecompileForInline, REJOIN_RESUME);
+    OOL_STUBCALL(stubs::TriggerIonCompile, REJOIN_RESUME);
     stubcc.rejoin(Changes(0));
 }
+#else /* JS_ION */
+void
+mjit::Compiler::ionCompileHelper()
+{
+}
+#endif /* JS_ION */
 
 CompileStatus
 mjit::Compiler::methodEntryHelper()
 {
     if (debugMode()) {
         prepareStubCall(Uses(0));
         INLINE_STUBCALL(stubs::ScriptDebugPrologue, REJOIN_RESUME);
 
--- a/js/src/methodjit/Compiler.h
+++ b/js/src/methodjit/Compiler.h
@@ -626,17 +626,17 @@ private:
     void jsop_this();
     void emitReturn(FrameEntry *fe);
     void emitFinalReturn(Assembler &masm);
     void loadReturnValue(Assembler *masm, FrameEntry *fe);
     void emitReturnValue(Assembler *masm, FrameEntry *fe);
     void emitInlineReturnValue(FrameEntry *fe);
     void dispatchCall(VoidPtrStubUInt32 stub, uint32_t argc);
     void interruptCheckHelper();
-    void recompileCheckHelper();
+    void ionCompileHelper();
     CompileStatus methodEntryHelper();
     CompileStatus profilingPushHelper();
     void profilingPopHelper();
     void emitUncachedCall(uint32_t argc, bool callingNew);
     void checkCallApplySpeculation(uint32_t argc, FrameEntry *origCallee, FrameEntry *origThis,
                                    MaybeRegisterID origCalleeType, RegisterID origCalleeData,
                                    MaybeRegisterID origThisType, RegisterID origThisData,
                                    Jump *uncachedCallSlowRejoin, CallPatchInfo *uncachedCallPatch);
--- a/js/src/methodjit/MethodJIT.cpp
+++ b/js/src/methodjit/MethodJIT.cpp
@@ -16,16 +16,17 @@
 #include "MonoIC.h"
 #include "PolyIC.h"
 #include "TrampolineCompiler.h"
 #include "jscntxtinlines.h"
 #include "jscompartment.h"
 #include "jsscope.h"
 #include "ion/Ion.h"
 #include "ion/IonCompartment.h"
+#include "methodjit/Retcon.h"
 
 #include "jsgcinlines.h"
 #include "jsinterpinlines.h"
 
 #if JS_TRACE_LOGGING
 #include "TraceLogging.h"
 #endif
 
@@ -1505,16 +1506,26 @@ JSScript::ReleaseCode(FreeOp *fop, JITSc
     if (jith->isValid()) {
         JITScript *jit = jith->getValid();
         jit->destroy(fop);
         fop->free_(jit);
         jith->setEmpty();
     }
 }
 
+void
+mjit::ReleaseScriptCodeFromVM(JSContext *cx, JSScript *script)
+{
+    if (script->hasMJITInfo()) {
+        ExpandInlineFrames(cx->compartment);
+        Recompiler::clearStackReferences(cx->runtime->defaultFreeOp(), script);
+        ReleaseScriptCode(cx->runtime->defaultFreeOp(), script);
+    }
+}
+
 #ifdef JS_METHODJIT_PROFILE_STUBS
 void JS_FASTCALL
 mjit::ProfileStubCall(VMFrame &f)
 {
     JSOp op = JSOp(*f.regs.pc);
     StubCallsForOp[op]++;
 }
 #endif
--- a/js/src/methodjit/MethodJIT.h
+++ b/js/src/methodjit/MethodJIT.h
@@ -790,23 +790,16 @@ struct JITScript
     JSC::ExecutablePool *shimPool;
 
     /*
      * Number of calls made to IonMonkey functions, used to avoid slow
      * JM -> Ion calls.
      */
     uint32_t        ionCalls;
 
-    /*
-     * If set, we decided to keep the JITChunk so that Ion can access its caches.
-     * The chunk has to be destroyed the next time the script runs in JM.
-     * Note that this flag implies nchunks == 1.
-     */
-    bool mustDestroyEntryChunk;
-
 #ifdef JS_MONOIC
     /* Inline cache at function entry for checking this/argument types. */
     JSC::CodeLocationLabel argsCheckStub;
     JSC::CodeLocationLabel argsCheckFallthrough;
     JSC::CodeLocationJump  argsCheckJump;
     JSC::ExecutablePool *argsCheckPool;
     void resetArgsCheck();
 #endif
@@ -904,16 +897,20 @@ ReleaseScriptCode(FreeOp *fop, JSScript 
             if (jith && jith->isValid())
                 JSScript::ReleaseCode(fop, jith);
         }
     }
 
     script->destroyMJITInfo(fop);
 }
 
+/* Can be called at any time. */
+void
+ReleaseScriptCodeFromVM(JSContext *cx, JSScript *script);
+
 // Expand all stack frames inlined by the JIT within a compartment.
 void
 ExpandInlineFrames(JSCompartment *compartment);
 
 // Return all VMFrames in a compartment to the interpreter. This must be
 // followed by destroying all JIT code in the compartment.
 void
 ClearAllFrames(JSCompartment *compartment);
--- a/js/src/methodjit/MonoIC.cpp
+++ b/js/src/methodjit/MonoIC.cpp
@@ -586,17 +586,17 @@ class CallCompiler : public BaseCompiler
         /* Load fun->u.i.script->ion */
         RegisterID ionScript = regs.takeAnyReg().reg();
         Address scriptAddr(funObjReg, JSFunction::offsetOfNativeOrScript());
         masm.loadPtr(scriptAddr, ionScript);
         masm.loadPtr(Address(ionScript, offsetof(JSScript, ion)), ionScript);
 
         /* Guard that the ion pointer is valid. */
         Jump noIonCode = masm.branchPtr(Assembler::BelowOrEqual, ionScript,
-                                        ImmPtr(ION_DISABLED_SCRIPT));
+                                        ImmPtr(ION_COMPILING_SCRIPT));
 
         RegisterID t0 = regs.takeAnyReg().reg();
         RegisterID t1 = Registers::ClobberInCall;
 
         masm.move(ImmPtr(f.regs.pc), t1);
 
         /* Set the CALLING_INTO_ION flag on the existing frame. */
         masm.load32(Address(JSFrameReg, StackFrame::offsetOfFlags()), t0);
--- a/js/src/methodjit/StubCalls.cpp
+++ b/js/src/methodjit/StubCalls.cpp
@@ -766,38 +766,29 @@ stubs::Interrupt(VMFrame &f, jsbytecode 
 {
     gc::MaybeVerifyBarriers(f.cx);
 
     if (!js_HandleExecutionInterrupt(f.cx))
         THROW();
 }
 
 void JS_FASTCALL
-stubs::RecompileForInline(VMFrame &f)
+stubs::TriggerIonCompile(VMFrame &f)
 {
     JSScript *script = f.script();
-
-    ExpandInlineFrames(f.cx->compartment);
-    Recompiler::clearStackReferences(f.cx->runtime->defaultFreeOp(), script);
+    JS_ASSERT(!script->ion);
 
-#ifdef JS_ION
-    if (ion::IsEnabled(f.cx) && f.jit()->nchunks == 1 &&
-        script->canIonCompile() && !script->hasIonScript())
-    {
-        // After returning to the interpreter, IonMonkey will try to compile
-        // this script. Don't destroy the JITChunk immediately so that Ion
-        // still has access to its ICs.
-        JS_ASSERT(!f.jit()->mustDestroyEntryChunk);
-        f.jit()->mustDestroyEntryChunk = true;
-        f.jit()->disableScriptEntry();
-        return;
+    jsbytecode *osrPC = f.regs.pc;
+    if (*osrPC != JSOP_LOOPENTRY)
+        osrPC = NULL;
+
+    if (!ion::TestIonCompile(f.cx, script, script->function(), osrPC, f.fp()->isConstructing())) {
+        if (f.cx->isExceptionPending())
+            THROW();
     }
-#endif
-
-    f.jit()->destroyChunk(f.cx->runtime->defaultFreeOp(), f.chunkIndex(), /* resetUses = */ false);
 }
 
 void JS_FASTCALL
 stubs::Trap(VMFrame &f, uint32_t trapTypes)
 {
     Value rval;
 
     /*
--- a/js/src/methodjit/StubCalls.h
+++ b/js/src/methodjit/StubCalls.h
@@ -21,17 +21,17 @@ typedef enum JSTrapType {
 } JSTrapType;
 
 void JS_FASTCALL This(VMFrame &f);
 void JS_FASTCALL NewInitArray(VMFrame &f, uint32_t count);
 void JS_FASTCALL NewInitObject(VMFrame &f, JSObject *base);
 void JS_FASTCALL Trap(VMFrame &f, uint32_t trapTypes);
 void JS_FASTCALL DebuggerStatement(VMFrame &f, jsbytecode *pc);
 void JS_FASTCALL Interrupt(VMFrame &f, jsbytecode *pc);
-void JS_FASTCALL RecompileForInline(VMFrame &f);
+void JS_FASTCALL TriggerIonCompile(VMFrame &f);
 void JS_FASTCALL InitElem(VMFrame &f, uint32_t last);
 void JS_FASTCALL InitProp(VMFrame &f, PropertyName *name);
 
 void JS_FASTCALL HitStackQuota(VMFrame &f);
 void * JS_FASTCALL FixupArity(VMFrame &f, uint32_t argc);
 void * JS_FASTCALL CompileFunction(VMFrame &f, uint32_t argc);
 void JS_FASTCALL SlowNew(VMFrame &f, uint32_t argc);
 void JS_FASTCALL SlowCall(VMFrame &f, uint32_t argc);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -4873,17 +4873,34 @@ ProcessArgs(JSContext *cx, JSObject *obj
         if (strcmp(str, "lsra") == 0)
             ion::js_IonOptions.lsra = true;
         else
             return OptionFailure("ion-regalloc", str);
     }
 
     if (op->getBoolOption("ion-eager"))
         ion::js_IonOptions.setEagerCompilation();
-#endif
+
+#ifdef JS_THREADSAFE
+    if (const char *str = op->getStringOption("ion-parallel-compile")) {
+        if (strcmp(str, "on") == 0) {
+            if (GetCPUCount() <= 1) {
+                fprintf(stderr, "Parallel compilation not available on single core machines");
+                return EXIT_FAILURE;
+            }
+            ion::js_IonOptions.parallelCompilation = true;
+        } else if (strcmp(str, "off") == 0) {
+            ion::js_IonOptions.parallelCompilation = false;
+        } else {
+            return OptionFailure("ion-parallel-compile", str);
+        }
+    }
+#endif /* JS_THREADSAFE */
+
+#endif /* JS_ION */
 
     /* |scriptArgs| gets bound on the global before any code is run. */
     if (!BindScriptArgs(cx, obj, op))
         return EXIT_FAILURE;
 
     MultiStringRange filePaths = op->getMultiStringOption('f');
     MultiStringRange codeChunks = op->getMultiStringOption('e');
 
@@ -5079,16 +5096,20 @@ main(int argc, char **argv, char **envp)
         || !op.addStringOption('\0', "ion-osr", "on/off",
                                "On-Stack Replacement (default: on, off to disable)")
         || !op.addStringOption('\0', "ion-limit-script-size", "on/off",
                                "Don't compile very large scripts (default: on, off to disable)")
         || !op.addStringOption('\0', "ion-regalloc", "[mode]",
                                "Specify Ion register allocation:\n"
                                "  lsra: Linear Scan register allocation (default)")
         || !op.addBoolOption('\0', "ion-eager", "Always ion-compile methods")
+#ifdef JS_THREADSAFE
+        || !op.addStringOption('\0', "ion-parallel-compile", "on/off",
+                               "Compile scripts off thread (default: off)")
+#endif
     )
     {
         return EXIT_FAILURE;
     }
 
     op.setArgTerminatesOptions("script", true);
 
     switch (op.parseArgs(argc, argv)) {