Bug 807853 - Add (but do not yet use) parallel compilation mode to ion r=dvander,terrence
authorNicholas D. Matsakis <nmatsakis@mozilla.com>
Thu, 07 Feb 2013 11:34:43 -0800
changeset 131085 80a21124ddbd311dca2f3220193b37e58e2466d8
parent 131084 17710a36638ac337c7cc45e0d5c42cea5f0d3f9d
child 131086 d8e39c6d798ca7c8d12c2c7087ab9595b18daa46
push id2323
push userbbajaj@mozilla.com
push dateMon, 01 Apr 2013 19:47:02 +0000
treeherdermozilla-beta@7712be144d91 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdvander, terrence
bugs807853
milestone21.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 807853 - Add (but do not yet use) parallel compilation mode to ion r=dvander,terrence
js/src/Makefile.in
js/src/builtin/TestingFunctions.cpp
js/src/builtin/TestingFunctions.h
js/src/gc/Root.h
js/src/gc/RootMarking.cpp
js/src/gc/Verifier.cpp
js/src/ion/Bailouts.cpp
js/src/ion/CodeGenerator.cpp
js/src/ion/CodeGenerator.h
js/src/ion/Ion.cpp
js/src/ion/Ion.h
js/src/ion/IonBuilder.cpp
js/src/ion/IonBuilder.h
js/src/ion/IonCode.h
js/src/ion/IonMacroAssembler.cpp
js/src/ion/IonMacroAssembler.h
js/src/ion/IonSpewer.cpp
js/src/ion/IonSpewer.h
js/src/ion/IonTypes.h
js/src/ion/LIR-Common.h
js/src/ion/LIR.h
js/src/ion/LOpcodes.h
js/src/ion/LinearScan.cpp
js/src/ion/Lowering.cpp
js/src/ion/Lowering.h
js/src/ion/MCallOptimize.cpp
js/src/ion/MIR.cpp
js/src/ion/MIR.h
js/src/ion/MIRGraph.cpp
js/src/ion/MIRGraph.h
js/src/ion/MOpcodes.h
js/src/ion/ParallelArrayAnalysis.cpp
js/src/ion/ParallelArrayAnalysis.h
js/src/ion/ParallelFunctions.cpp
js/src/ion/ParallelFunctions.h
js/src/ion/TypeOracle.cpp
js/src/ion/TypeOracle.h
js/src/ion/UnreachableCodeElimination.cpp
js/src/ion/UnreachableCodeElimination.h
js/src/ion/VMFunctions.cpp
js/src/ion/arm/MacroAssembler-arm.cpp
js/src/ion/arm/MacroAssembler-arm.h
js/src/ion/shared/CodeGenerator-shared.cpp
js/src/ion/shared/CodeGenerator-shared.h
js/src/ion/shared/CodeGenerator-x86-shared.cpp
js/src/ion/shared/Lowering-shared.h
js/src/ion/shared/MacroAssembler-x86-shared.h
js/src/ion/x86/CodeGenerator-x86.cpp
js/src/jsapi.cpp
js/src/jscntxt.h
js/src/jsgc.cpp
js/src/jsgc.h
js/src/jspubtd.h
js/src/vm/ForkJoin.cpp
js/src/vm/ForkJoin.h
js/src/vm/ParallelDo.cpp
js/src/vm/ParallelDo.h
js/src/vm/SelfHosting.cpp
--- a/js/src/Makefile.in
+++ b/js/src/Makefile.in
@@ -133,16 +133,17 @@ CPPSRCS		= \
 		Stack.cpp \
 		String.cpp \
 		BytecodeCompiler.cpp \
 		BytecodeEmitter.cpp \
 		CharacterEncoding.cpp \
 		FoldConstants.cpp \
 		Intl.cpp \
 		NameFunctions.cpp \
+		ParallelDo.cpp \
 		ParallelArray.cpp \
 		ParseMaps.cpp \
 		ParseNode.cpp \
 		Parser.cpp \
 		SPSProfiler.cpp \
 		SelfHosting.cpp \
 		TokenStream.cpp \
 		TestingFunctions.cpp \
@@ -312,17 +313,19 @@ CPPSRCS +=	MIR.cpp \
 		Snapshots.cpp \
 		Safepoints.cpp \
 		StupidAllocator.cpp \
 		TypeOracle.cpp \
 		TypePolicy.cpp \
 		ValueNumbering.cpp \
 		RangeAnalysis.cpp \
 		VMFunctions.cpp \
+		ParallelFunctions.cpp \
 		AliasAnalysis.cpp \
+		ParallelArrayAnalysis.cpp \
 		UnreachableCodeElimination.cpp \
 		$(NULL)
 endif #ENABLE_ION
 ifeq (86, $(findstring 86,$(TARGET_CPU)))
 ifdef ENABLE_ION
 CPPSRCS +=	CodeGenerator-x86-shared.cpp
 CPPSRCS +=	IonFrames-x86-shared.cpp
 CPPSRCS +=	MoveEmitter-x86-shared.cpp
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -11,16 +11,17 @@
 #include "jsgc.h"
 #include "jsobj.h"
 #include "jsobjinlines.h"
 #include "jsprf.h"
 #include "jswrapper.h"
 
 #include "builtin/TestingFunctions.h"
 #include "methodjit/MethodJIT.h"
+#include "vm/ForkJoin.h"
 
 #include "vm/Stack-inl.h"
 
 using namespace js;
 using namespace JS;
 
 using mozilla::ArrayLength;
 
@@ -873,16 +874,24 @@ DisplayName(JSContext *cx, unsigned argc
     }
 
     JSFunction *fun = args[0].toObject().toFunction();
     JSString *str = fun->displayAtom();
     vp->setString(str == NULL ? cx->runtime->emptyString : str);
     return true;
 }
 
+JSBool
+js::testingFunc_inParallelSection(JSContext *cx, unsigned argc, jsval *vp)
+{
+    JS_ASSERT(!ForkJoinSlice::InParallelSection());
+    JS_SET_RVAL(cx, vp, JSVAL_FALSE);
+    return true;
+}
+
 static JSFunctionSpecWithHelp TestingFunctions[] = {
     JS_FN_HELP("gc", ::GC, 0, 0,
 "gc([obj] | 'compartment')",
 "  Run the garbage collector. When obj is given, GC only its compartment.\n"
 "  If 'compartment' is given, GC any compartments that were scheduled for\n"
 "  GC via schedulegc."),
 
     JS_FN_HELP("gcparam", GCParameter, 2, 0,
@@ -1004,16 +1013,20 @@ static JSFunctionSpecWithHelp TestingFun
 "  assertions are disabled."),
 
     JS_FN_HELP("displayName", DisplayName, 1, 0,
 "displayName(fn)",
 "  Gets the display name for a function, which can possibly be a guessed or\n"
 "  inferred name based on where the function was defined. This can be\n"
 "  different from the 'name' property on the function."),
 
+    JS_FN_HELP("inParallelSection", testingFunc_inParallelSection, 0, 0,
+"inParallelSection()",
+"  True if this code is executing within a parallel section."),
+
     JS_FS_HELP_END
 };
 
 bool
 js::DefineTestingFunctions(JSContext *cx, HandleObject obj)
 {
     return JS_DefineFunctionsWithHelp(cx, obj, TestingFunctions);
 }
--- a/js/src/builtin/TestingFunctions.h
+++ b/js/src/builtin/TestingFunctions.h
@@ -6,11 +6,14 @@
 #ifndef TestingFunctions_h__
 #define TestingFunctions_h__
 
 namespace js {
 
 bool
 DefineTestingFunctions(JSContext *cx, JSHandleObject obj);
 
+JSBool
+testingFunc_inParallelSection(JSContext *cx, unsigned argc, jsval *vp);
+
 } /* namespace js */
 
 #endif /* TestingFunctions_h__ */
--- a/js/src/gc/Root.h
+++ b/js/src/gc/Root.h
@@ -605,16 +605,23 @@ class Rooted : public RootedBase<T>
 {
     void init(JSContext *cxArg) {
 #if defined(JSGC_ROOT_ANALYSIS) || defined(JSGC_USE_EXACT_ROOTING)
         ContextFriendFields *cx = ContextFriendFields::get(cxArg);
         commonInit(cx->thingGCRooters);
 #endif
     }
 
+    void init(PerThreadData *ptArg) {
+#if defined(JSGC_ROOT_ANALYSIS) || defined(JSGC_USE_EXACT_ROOTING)
+        PerThreadDataFriendFields *pt = PerThreadDataFriendFields::get(ptArg);
+        commonInit(pt->thingGCRooters);
+#endif
+    }
+
   public:
     Rooted(JSContext *cx
            MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : ptr(RootMethods<T>::initial())
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
         init(cx);
     }
@@ -631,16 +638,41 @@ class Rooted : public RootedBase<T>
     Rooted(JSContext *cx, const Unrooted<S> &initial
            MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : ptr(static_cast<S>(initial))
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
         init(cx);
     }
 
+    Rooted(PerThreadData *pt
+           MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+      : ptr(RootMethods<T>::initial())
+    {
+        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+        init(pt);
+    }
+
+    Rooted(PerThreadData *pt, T initial
+           MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+      : ptr(initial)
+    {
+        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+        init(pt);
+    }
+
+    template <typename S>
+    Rooted(PerThreadData *pt, const Unrooted<S> &initial
+           MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+      : ptr(static_cast<S>(initial))
+    {
+        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+        init(pt);
+    }
+
     ~Rooted() {
 #if defined(JSGC_ROOT_ANALYSIS) || defined(JSGC_USE_EXACT_ROOTING)
         JS_ASSERT(*stack == this);
         *stack = prev;
 #endif
     }
 
 #if defined(JSGC_ROOT_ANALYSIS) || defined(JSGC_USE_EXACT_ROOTING)
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -75,16 +75,18 @@ MarkExactStackRootList(JSTracer *trc, Ro
 }
 
 static void
 MarkExactStackRoots(JSTracer *trc)
 {
     for (unsigned i = 0; i < THING_ROOT_LIMIT; i++) {
         for (ContextIter cx(trc->runtime); !cx.done(); cx.next())
             MarkExactStackRootList(trc, cx->thingGCRooters[i], ThingRootKind(i));
+
+        MarkExactStackRootList(trc, trc->runtime->mainThread->thingGCRooters[i], ThingRootKind(i));
     }
 }
 #endif /* JSGC_USE_EXACT_ROOTING */
 
 enum ConservativeGCTest
 {
     CGCT_VALID,
     CGCT_LOWBITSET, /* excluded because one of the low bits was set */
--- a/js/src/gc/Verifier.cpp
+++ b/js/src/gc/Verifier.cpp
@@ -191,16 +191,29 @@ SuppressCheckRoots(Vector<Rooter, 0, Sys
     stacks[oldestMemory] = hash;
     oldestMemory = (oldestMemory + 1) % NumStackMemories;
     if (numMemories < NumStackMemories)
         numMemories++;
 
     return false;
 }
 
+static void
+GatherRooters(Vector<Rooter, 0, SystemAllocPolicy> &rooters,
+              Rooted<void*> **thingGCRooters,
+              unsigned thingRootKind)
+{
+    Rooted<void*> *rooter = thingGCRooters[thingRootKind];
+    while (rooter) {
+        Rooter r = { rooter, ThingRootKind(thingRootKind) };
+        JS_ALWAYS_TRUE(rooters.append(r));
+        rooter = rooter->previous();
+    }
+}
+
 void
 JS::CheckStackRoots(JSContext *cx)
 {
     JSRuntime *rt = cx->runtime;
 
     if (rt->gcZeal_ != ZealStackRootingValue)
         return;
 
@@ -238,26 +251,23 @@ JS::CheckStackRoots(JSContext *cx)
     stackMin = rt->nativeStackBase;
     stackEnd = cgcd->nativeStackTop;
 #else
     stackMin = cgcd->nativeStackTop + 1;
     stackEnd = reinterpret_cast<uintptr_t *>(rt->nativeStackBase);
 #endif
 
     // Gather up all of the rooters
-    Vector< Rooter, 0, SystemAllocPolicy> rooters;
+    Vector<Rooter, 0, SystemAllocPolicy> rooters;
     for (unsigned i = 0; i < THING_ROOT_LIMIT; i++) {
         for (ContextIter cx(rt); !cx.done(); cx.next()) {
-            Rooted<void*> *rooter = cx->thingGCRooters[i];
-            while (rooter) {
-                Rooter r = { rooter, ThingRootKind(i) };
-                JS_ALWAYS_TRUE(rooters.append(r));
-                rooter = rooter->previous();
-            }
+            GatherRooters(rooters, cx->thingGCRooters, i);
         }
+
+        GatherRooters(rooters, rt->mainThread.thingGCRooters, i);
     }
 
     if (SuppressCheckRoots(rooters))
         return;
 
     // Truncate stackEnd to just after the address of the youngest
     // already-scanned rooter on the stack, to avoid re-scanning the rest of
     // the stack.
--- a/js/src/ion/Bailouts.cpp
+++ b/js/src/ion/Bailouts.cpp
@@ -234,16 +234,18 @@ ConvertFrames(JSContext *cx, IonActivati
     AutoAssertNoGC nogc;
     IonSpew(IonSpew_Bailouts, "Bailing out %s:%u, IonScript %p",
             it.script()->filename, it.script()->lineno, (void *) it.ionScript());
     IonSpew(IonSpew_Bailouts, " reading from snapshot offset %u size %u",
             it.snapshotOffset(), it.ionScript()->snapshotsSize());
 #ifdef DEBUG
     // Use count is reset after invalidation. Log use count on bailouts to
     // determine if we have a critical sequence of bailout.
+    //
+    // Note: frame conversion only occurs in sequential mode
     if (it.script()->ion == it.ionScript()) {
         IonSpew(IonSpew_Bailouts, " Current script use count is %u",
                 it.script()->getUseCount());
     }
 #endif
 
     SnapshotIterator iter(it);
 
--- a/js/src/ion/CodeGenerator.cpp
+++ b/js/src/ion/CodeGenerator.cpp
@@ -13,17 +13,19 @@
 #include "CodeGenerator.h"
 #include "IonLinker.h"
 #include "IonSpewer.h"
 #include "MIRGenerator.h"
 #include "shared/CodeGenerator-shared-inl.h"
 #include "jsnum.h"
 #include "jsmath.h"
 #include "jsinterpinlines.h"
+#include "ParallelFunctions.h"
 #include "ExecutionModeInlines.h"
+#include "vm/ForkJoin.h"
 
 #include "vm/StringObject-inl.h"
 
 using namespace js;
 using namespace js::ion;
 
 using mozilla::DebugOnly;
 using mozilla::Maybe;
@@ -471,16 +473,27 @@ CodeGenerator::visitLambda(LLambda *lir)
         return false;
 
     JS_ASSERT(gen->compartment == fun->compartment());
     JS_ASSERT(!fun->hasSingletonType());
 
     masm.newGCThing(output, fun, ool->entry());
     masm.initGCThing(output, fun);
 
+    emitLambdaInit(output, scopeChain, fun);
+
+    masm.bind(ool->rejoin());
+    return true;
+}
+
+void
+CodeGenerator::emitLambdaInit(const Register &output,
+                              const Register &scopeChain,
+                              JSFunction *fun)
+{
     // Initialize nargs and flags. We do this with a single uint32 to avoid
     // 16-bit writes.
     union {
         struct S {
             uint16_t nargs;
             uint16_t flags;
         } s;
         uint32_t word;
@@ -489,18 +502,32 @@ CodeGenerator::visitLambda(LLambda *lir)
     u.s.flags = fun->flags & ~JSFunction::EXTENDED;
 
     JS_STATIC_ASSERT(offsetof(JSFunction, flags) == offsetof(JSFunction, nargs) + 2);
     masm.store32(Imm32(u.word), Address(output, offsetof(JSFunction, nargs)));
     masm.storePtr(ImmGCPtr(fun->nonLazyScript()),
                   Address(output, JSFunction::offsetOfNativeOrScript()));
     masm.storePtr(scopeChain, Address(output, JSFunction::offsetOfEnvironment()));
     masm.storePtr(ImmGCPtr(fun->displayAtom()), Address(output, JSFunction::offsetOfAtom()));
-
-    masm.bind(ool->rejoin());
+}
+
+bool
+CodeGenerator::visitParLambda(LParLambda *lir)
+{
+    Register resultReg = ToRegister(lir->output());
+    Register parSliceReg = ToRegister(lir->parSlice());
+    Register scopeChainReg    = ToRegister(lir->scopeChain());
+    Register tempReg1 = ToRegister(lir->getTemp0());
+    Register tempReg2 = ToRegister(lir->getTemp1());
+    JSFunction *fun = lir->mir()->fun();
+
+    JS_ASSERT(scopeChainReg != resultReg);
+
+    emitParAllocateGCThing(resultReg, parSliceReg, tempReg1, tempReg2, fun);
+    emitLambdaInit(resultReg, scopeChainReg, fun);
     return true;
 }
 
 bool
 CodeGenerator::visitLabel(LLabel *lir)
 {
     masm.bind(lir->label());
     return true;
@@ -759,16 +786,62 @@ bool
 CodeGenerator::visitFunctionEnvironment(LFunctionEnvironment *lir)
 {
     Address environment(ToRegister(lir->function()), JSFunction::offsetOfEnvironment());
     masm.loadPtr(environment, ToRegister(lir->output()));
     return true;
 }
 
 bool
+CodeGenerator::visitParSlice(LParSlice *lir)
+{
+    const Register tempReg = ToRegister(lir->getTempReg());
+
+    masm.setupUnalignedABICall(0, tempReg);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParForkJoinSlice));
+    JS_ASSERT(ToRegister(lir->output()) == ReturnReg);
+    return true;
+}
+
+bool
+CodeGenerator::visitParWriteGuard(LParWriteGuard *lir)
+{
+    JS_ASSERT(gen->info().executionMode() == ParallelExecution);
+
+    const Register tempReg = ToRegister(lir->getTempReg());
+    masm.setupUnalignedABICall(2, tempReg);
+    masm.passABIArg(ToRegister(lir->parSlice()));
+    masm.passABIArg(ToRegister(lir->object()));
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParWriteGuard));
+
+    Label *bail;
+    if (!ensureOutOfLineParallelAbort(&bail))
+        return false;
+
+    // branch to the OOL failure code if false is returned
+    masm.branchTestBool(Assembler::Zero, ReturnReg, ReturnReg, bail);
+
+    return true;
+}
+
+bool
+CodeGenerator::visitParDump(LParDump *lir)
+{
+    ValueOperand value = ToValue(lir, 0);
+    masm.reserveStack(sizeof(Value));
+    masm.storeValue(value, Address(StackPointer, 0));
+    masm.movePtr(StackPointer, CallTempReg0);
+    masm.setupUnalignedABICall(1, CallTempReg1);
+    masm.passABIArg(CallTempReg0);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParDumpValue));
+    masm.freeStack(sizeof(Value));
+    return true;
+}
+
+bool
 CodeGenerator::visitTypeBarrier(LTypeBarrier *lir)
 {
     ValueOperand operand = ToValue(lir, LTypeBarrier::Input);
     Register scratch = ToRegister(lir->temp());
 
     Label mismatched;
     masm.guardTypeSet(operand, lir->mir()->typeSet(), scratch, &mismatched);
     if (!bailoutFrom(&mismatched, lir->snapshot()))
@@ -981,18 +1054,36 @@ CodeGenerator::visitCallDOMNative(LCallD
 
 typedef bool (*GetIntrinsicValueFn)(JSContext *cx, HandlePropertyName, MutableHandleValue);
 static const VMFunction GetIntrinsicValueInfo =
     FunctionInfo<GetIntrinsicValueFn>(GetIntrinsicValue);
 
 bool
 CodeGenerator::visitCallGetIntrinsicValue(LCallGetIntrinsicValue *lir)
 {
-    pushArg(ImmGCPtr(lir->mir()->name()));
-    return callVM(GetIntrinsicValueInfo, lir);
+    // When compiling parallel kernels, always bail.
+    switch (gen->info().executionMode()) {
+      case SequentialExecution: {
+        pushArg(ImmGCPtr(lir->mir()->name()));
+        return callVM(GetIntrinsicValueInfo, lir);
+      }
+
+      case ParallelExecution: {
+        Label *bail;
+        if (!ensureOutOfLineParallelAbort(&bail))
+            return false;
+
+        masm.jump(bail);
+        return true;
+      }
+
+      default:
+        JS_NOT_REACHED("Bad execution mode");
+        return false;
+    }
 }
 
 typedef bool (*InvokeFunctionFn)(JSContext *, HandleFunction, uint32_t, Value *, Value *);
 static const VMFunction InvokeFunctionInfo = FunctionInfo<InvokeFunctionFn>(InvokeFunction);
 
 bool
 CodeGenerator::emitCallInvokeFunction(LInstruction *call, Register calleereg,
                                       uint32_t argc, uint32_t unusedStack)
@@ -1026,17 +1117,18 @@ static inline int32_t ionOffset(Executio
 
 bool
 CodeGenerator::visitCallGeneric(LCallGeneric *call)
 {
     Register calleereg = ToRegister(call->getFunction());
     Register objreg    = ToRegister(call->getTempObject());
     Register nargsreg  = ToRegister(call->getNargsReg());
     uint32_t unusedStack = StackOffsetOfPassedArg(call->argslot());
-    Label invoke, thunk, makeCall, end;
+    ExecutionMode executionMode = gen->info().executionMode();
+    Label uncompiled, thunk, makeCall, end;
 
     // Known-target case is handled by LCallKnown.
     JS_ASSERT(!call->hasSingleTarget());
 
     // Generate an ArgumentsRectifier.
     IonCompartment *ion = gen->ionCompartment();
     IonCode *argumentsRectifier = ion->getArgumentsRectifier();
 
@@ -1044,25 +1136,24 @@ CodeGenerator::visitCallGeneric(LCallGen
 
     // Guard that calleereg is actually a function object.
     masm.loadObjClass(calleereg, nargsreg);
     masm.cmpPtr(nargsreg, ImmWord(&js::FunctionClass));
     if (!bailoutIf(Assembler::NotEqual, call->snapshot()))
         return false;
 
     // Guard that calleereg is an interpreted function with a JSScript:
-    masm.branchIfFunctionHasNoScript(calleereg, &invoke);
+    masm.branchIfFunctionHasNoScript(calleereg, &uncompiled);
 
     // Knowing that calleereg is a non-native function, load the JSScript.
     masm.loadPtr(Address(calleereg, offsetof(JSFunction, u.i.script_)), objreg);
-    ExecutionMode executionMode = gen->info().executionMode();
     masm.loadPtr(Address(objreg, ionOffset(executionMode)), objreg);
 
     // Guard that the IonScript has been compiled.
-    masm.branchPtr(Assembler::BelowOrEqual, objreg, ImmWord(ION_COMPILING_SCRIPT), &invoke);
+    masm.branchPtr(Assembler::BelowOrEqual, objreg, ImmWord(ION_COMPILING_SCRIPT), &uncompiled);
 
     // Nestle the StackPointer up to the argument vector.
     masm.freeStack(unusedStack);
 
     // Construct the IonFramePrefix.
     uint32_t descriptor = MakeFrameDescriptor(masm.framePushed(), IonFrame_OptimizedJS);
     masm.Push(Imm32(call->numActualArgs()));
     masm.Push(calleereg);
@@ -1095,60 +1186,93 @@ CodeGenerator::visitCallGeneric(LCallGen
 
     // Increment to remove IonFramePrefix; decrement to fill FrameSizeClass.
     // The return address has already been removed from the Ion frame.
     int prefixGarbage = sizeof(IonJSFrameLayout) - sizeof(void *);
     masm.adjustStack(prefixGarbage - unusedStack);
     masm.jump(&end);
 
     // Handle uncompiled or native functions.
-    masm.bind(&invoke);
-    if (!emitCallInvokeFunction(call, calleereg, call->numActualArgs(), unusedStack))
-        return false;
+    masm.bind(&uncompiled);
+    switch (executionMode) {
+      case SequentialExecution:
+        if (!emitCallInvokeFunction(call, calleereg, call->numActualArgs(), unusedStack))
+            return false;
+        break;
+
+      case ParallelExecution:
+        if (!emitParCallToUncompiledScript(calleereg))
+            return false;
+        break;
+    }
 
     masm.bind(&end);
 
     // If the return value of the constructing function is Primitive,
     // replace the return value with the Object from CreateThis.
     if (call->mir()->isConstructing()) {
         Label notPrimitive;
         masm.branchTestPrimitive(Assembler::NotEqual, JSReturnOperand, &notPrimitive);
         masm.loadValue(Address(StackPointer, unusedStack), JSReturnOperand);
         masm.bind(&notPrimitive);
     }
 
+    if (!checkForParallelBailout())
+        return false;
+
     dropArguments(call->numStackArgs() + 1);
     return true;
 }
 
+// Generates a call to ParCallToUncompiledScript() and then bails out.
+// |calleeReg| should contain the JSFunction*.
+bool
+CodeGenerator::emitParCallToUncompiledScript(Register calleeReg)
+{
+    Label *bail;
+    if (!ensureOutOfLineParallelAbort(&bail))
+        return false;
+
+    masm.movePtr(calleeReg, CallTempReg0);
+    masm.setupUnalignedABICall(1, CallTempReg1);
+    masm.passABIArg(CallTempReg0);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParCallToUncompiledScript));
+    masm.jump(bail);
+    return true;
+}
+
 bool
 CodeGenerator::visitCallKnown(LCallKnown *call)
 {
     JSContext *cx = GetIonContext()->cx;
     Register calleereg = ToRegister(call->getFunction());
     Register objreg    = ToRegister(call->getTempObject());
     uint32_t unusedStack = StackOffsetOfPassedArg(call->argslot());
     RootedFunction target(cx, call->getSingleTarget());
-    Label end, invoke;
+    ExecutionMode executionMode = gen->info().executionMode();
+    Label end, uncompiled;
 
     // Native single targets are handled by LCallNative.
     JS_ASSERT(!target->isNative());
     // Missing arguments must have been explicitly appended by the IonBuilder.
     JS_ASSERT(target->nargs <= call->numStackArgs());
 
     masm.checkStackAlignment();
 
     // Make sure the function has a JSScript
     if (target->isInterpretedLazy() && !target->getOrCreateScript(cx))
         return false;
 
-    // If the function is known to be uncompilable, only emit the call to InvokeFunction.
-    ExecutionMode executionMode = gen->info().executionMode();
+    // If the function is known to be uncompilable, just emit the call to
+    // Invoke in sequential mode, else mark as cannot compile.
     RootedScript targetScript(cx, target->nonLazyScript());
     if (GetIonScript(targetScript, executionMode) == ION_DISABLED_SCRIPT) {
+        if (executionMode == ParallelExecution)
+            return false;
+
         if (!emitCallInvokeFunction(call, calleereg, call->numActualArgs(), unusedStack))
             return false;
 
         if (call->mir()->isConstructing()) {
             Label notPrimitive;
             masm.branchTestPrimitive(Assembler::NotEqual, JSReturnOperand, &notPrimitive);
             masm.loadValue(Address(StackPointer, unusedStack), JSReturnOperand);
             masm.bind(&notPrimitive);
@@ -1158,17 +1282,17 @@ CodeGenerator::visitCallKnown(LCallKnown
         return true;
     }
 
     // Knowing that calleereg is a non-native function, load the JSScript.
     masm.loadPtr(Address(calleereg, offsetof(JSFunction, u.i.script_)), objreg);
     masm.loadPtr(Address(objreg, ionOffset(executionMode)), objreg);
 
     // Guard that the IonScript has been compiled.
-    masm.branchPtr(Assembler::BelowOrEqual, objreg, ImmWord(ION_COMPILING_SCRIPT), &invoke);
+    masm.branchPtr(Assembler::BelowOrEqual, objreg, ImmWord(ION_COMPILING_SCRIPT), &uncompiled);
 
     // Load the start of the target IonCode.
     masm.loadPtr(Address(objreg, IonScript::offsetOfMethod()), objreg);
     masm.loadPtr(Address(objreg, IonCode::offsetOfCode()), objreg);
 
     // Nestle the StackPointer up to the argument vector.
     masm.freeStack(unusedStack);
 
@@ -1185,36 +1309,64 @@ CodeGenerator::visitCallKnown(LCallKnown
 
     // Increment to remove IonFramePrefix; decrement to fill FrameSizeClass.
     // The return address has already been removed from the Ion frame.
     int prefixGarbage = sizeof(IonJSFrameLayout) - sizeof(void *);
     masm.adjustStack(prefixGarbage - unusedStack);
     masm.jump(&end);
 
     // Handle uncompiled functions.
-    masm.bind(&invoke);
-    if (!emitCallInvokeFunction(call, calleereg, call->numActualArgs(), unusedStack))
-        return false;
+    masm.bind(&uncompiled);
+    switch (executionMode) {
+      case SequentialExecution:
+        if (!emitCallInvokeFunction(call, calleereg, call->numActualArgs(), unusedStack))
+            return false;
+        break;
+
+      case ParallelExecution:
+        if (!emitParCallToUncompiledScript(calleereg))
+            return false;
+        break;
+    }
 
     masm.bind(&end);
 
+    if (!checkForParallelBailout())
+        return false;
+
     // If the return value of the constructing function is Primitive,
     // replace the return value with the Object from CreateThis.
     if (call->mir()->isConstructing()) {
         Label notPrimitive;
         masm.branchTestPrimitive(Assembler::NotEqual, JSReturnOperand, &notPrimitive);
         masm.loadValue(Address(StackPointer, unusedStack), JSReturnOperand);
         masm.bind(&notPrimitive);
     }
 
     dropArguments(call->numStackArgs() + 1);
     return true;
 }
 
 bool
+CodeGenerator::checkForParallelBailout()
+{
+    // In parallel mode, if we call another ion-compiled function and
+    // it returns JS_ION_ERROR, that indicates a bailout that we have
+    // to propagate up the stack.
+    ExecutionMode executionMode = gen->info().executionMode();
+    if (executionMode == ParallelExecution) {
+        Label *bail;
+        if (!ensureOutOfLineParallelAbort(&bail))
+            return false;
+        masm.branchTestMagic(Assembler::Equal, JSReturnOperand, bail);
+    }
+    return true;
+}
+
+bool
 CodeGenerator::emitCallInvokeFunction(LApplyArgsGeneric *apply, Register extraStackSize)
 {
     Register objreg = ToRegister(apply->getTempObject());
     JS_ASSERT(objreg != extraStackSize);
 
     // Push the space used by the arguments.
     masm.movePtr(StackPointer, objreg);
     masm.Push(extraStackSize);
@@ -1574,16 +1726,152 @@ CodeGenerator::visitCheckOverRecursedFai
     if (!callVM(CheckOverRecursedInfo, ool->lir()))
         return false;
 
     restoreLive(ool->lir());
     masm.jump(ool->rejoin());
     return true;
 }
 
+// Out-of-line path to report over-recursed error and fail.
+class ParCheckOverRecursedFailure : public OutOfLineCodeBase<CodeGenerator>
+{
+    LParCheckOverRecursed *lir_;
+
+  public:
+    ParCheckOverRecursedFailure(LParCheckOverRecursed *lir)
+      : lir_(lir)
+    { }
+
+    bool accept(CodeGenerator *codegen) {
+        return codegen->visitParCheckOverRecursedFailure(this);
+    }
+
+    LParCheckOverRecursed *lir() const {
+        return lir_;
+    }
+};
+
+bool
+CodeGenerator::visitParCheckOverRecursed(LParCheckOverRecursed *lir)
+{
+    // See above: unlike visitCheckOverRecursed(), this code runs in
+    // parallel mode and hence uses the ionStackLimit from the current
+    // thread state.  Also, we must check the interrupt flags because
+    // on interrupt or abort, only the stack limit for the main thread
+    // is reset, not the worker threads.  See comment in vm/ForkJoin.h
+    // for more details.
+
+    Register parSliceReg = ToRegister(lir->parSlice());
+    Register tempReg = ToRegister(lir->getTempReg());
+
+    masm.loadPtr(Address(parSliceReg, offsetof(ForkJoinSlice, perThreadData)), tempReg);
+    masm.loadPtr(Address(tempReg, offsetof(PerThreadData, ionStackLimit)), tempReg);
+
+    // Conditional forward (unlikely) branch to failure.
+    ParCheckOverRecursedFailure *ool = new ParCheckOverRecursedFailure(lir);
+    if (!addOutOfLineCode(ool))
+        return false;
+    masm.branchPtr(Assembler::BelowOrEqual, StackPointer, tempReg, ool->entry());
+    masm.parCheckInterruptFlags(tempReg, ool->entry());
+    masm.bind(ool->rejoin());
+
+    return true;
+}
+
+bool
+CodeGenerator::visitParCheckOverRecursedFailure(ParCheckOverRecursedFailure *ool)
+{
+    Label *bail;
+    if (!ensureOutOfLineParallelAbort(&bail))
+        return false;
+
+    // Avoid saving/restoring the temp register since we will put the
+    // ReturnReg into it below and we don't want to clobber that
+    // during PopRegsInMask():
+    LParCheckOverRecursed *lir = ool->lir();
+    Register tempReg = ToRegister(lir->getTempReg());
+    RegisterSet saveSet(lir->safepoint()->liveRegs());
+    saveSet.maybeTake(tempReg);
+
+    masm.PushRegsInMask(saveSet);
+    masm.movePtr(ToRegister(lir->parSlice()), CallTempReg0);
+    masm.setupUnalignedABICall(1, CallTempReg1);
+    masm.passABIArg(CallTempReg0);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParCheckOverRecursed));
+    masm.movePtr(ReturnReg, tempReg);
+    masm.PopRegsInMask(saveSet);
+    masm.branchTestBool(Assembler::Zero, tempReg, tempReg, bail);
+    masm.jump(ool->rejoin());
+
+    return true;
+}
+
+// Out-of-line path to report over-recursed error and fail.
+class OutOfLineParCheckInterrupt : public OutOfLineCodeBase<CodeGenerator>
+{
+  public:
+    LParCheckInterrupt *const lir;
+
+    OutOfLineParCheckInterrupt(LParCheckInterrupt *lir)
+      : lir(lir)
+    { }
+
+    bool accept(CodeGenerator *codegen) {
+        return codegen->visitOutOfLineParCheckInterrupt(this);
+    }
+};
+
+bool
+CodeGenerator::visitParCheckInterrupt(LParCheckInterrupt *lir)
+{
+    // First check for slice->shared->interrupt_.
+    OutOfLineParCheckInterrupt *ool = new OutOfLineParCheckInterrupt(lir);
+    if (!addOutOfLineCode(ool))
+        return false;
+
+    // We must check two flags:
+    // - runtime->interrupt
+    // - runtime->parallelAbort
+    // See vm/ForkJoin.h for discussion on why we use this design.
+
+    Register tempReg = ToRegister(lir->getTempReg());
+    masm.parCheckInterruptFlags(tempReg, ool->entry());
+    masm.bind(ool->rejoin());
+    return true;
+}
+
+bool
+CodeGenerator::visitOutOfLineParCheckInterrupt(OutOfLineParCheckInterrupt *ool)
+{
+    Label *bail;
+    if (!ensureOutOfLineParallelAbort(&bail))
+        return false;
+
+    // Avoid saving/restoring the temp register since we will put the
+    // ReturnReg into it below and we don't want to clobber that
+    // during PopRegsInMask():
+    LParCheckInterrupt *lir = ool->lir;
+    Register tempReg = ToRegister(lir->getTempReg());
+    RegisterSet saveSet(lir->safepoint()->liveRegs());
+    saveSet.maybeTake(tempReg);
+
+    masm.PushRegsInMask(saveSet);
+    masm.movePtr(ToRegister(ool->lir->parSlice()), CallTempReg0);
+    masm.setupUnalignedABICall(1, CallTempReg1);
+    masm.passABIArg(CallTempReg0);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParCheckInterrupt));
+    masm.movePtr(ReturnReg, tempReg);
+    masm.PopRegsInMask(saveSet);
+    masm.branchTestBool(Assembler::Zero, tempReg, tempReg, bail);
+    masm.jump(ool->rejoin());
+
+    return true;
+}
+
 IonScriptCounts *
 CodeGenerator::maybeCreateScriptCounts()
 {
     // If scripts are being profiled, create a new IonScriptCounts and attach
     // it to the script. This must be done on the main thread.
     JSContext *cx = GetIonContext()->cx;
     if (!cx)
         return NULL;
@@ -1723,16 +2011,19 @@ CodeGenerator::generateBody()
             if (counts)
                 blockCounts.ref().visitInstruction(*iter);
 
             if (iter->safepoint() && pushedArgumentSlots_.length()) {
                 if (!markArgumentSlots(iter->safepoint()))
                     return false;
             }
 
+            if (!callTraceLIR(i, *iter))
+                return false;
+
             if (!iter->accept(this))
                 return false;
         }
         if (masm.oom())
             return false;
     }
 
     JS_ASSERT(pushedArgumentSlots_.empty());
@@ -1760,16 +2051,18 @@ class OutOfLineNewArray : public OutOfLi
 
 typedef JSObject *(*NewInitArrayFn)(JSContext *, uint32_t, types::TypeObject *);
 static const VMFunction NewInitArrayInfo =
     FunctionInfo<NewInitArrayFn>(NewInitArray);
 
 bool
 CodeGenerator::visitNewArrayCallVM(LNewArray *lir)
 {
+    JS_ASSERT(gen->info().executionMode() == SequentialExecution);
+
     Register objReg = ToRegister(lir->output());
 
     JS_ASSERT(!lir->isCall());
     saveLive(lir);
 
     JSObject *templateObject = lir->mir()->templateObject();
     types::TypeObject *type = templateObject->hasSingletonType() ? NULL : templateObject->type();
 
@@ -1808,31 +2101,24 @@ CodeGenerator::visitNewSlots(LNewSlots *
         return false;
 
     return true;
 }
 
 bool
 CodeGenerator::visitNewArray(LNewArray *lir)
 {
+    JS_ASSERT(gen->info().executionMode() == SequentialExecution);
     Register objReg = ToRegister(lir->output());
     JSObject *templateObject = lir->mir()->templateObject();
     uint32_t count = lir->mir()->count();
 
     JS_ASSERT(count < JSObject::NELEMENTS_LIMIT);
 
-    size_t maxArraySlots =
-        gc::GetGCKindSlots(gc::FINALIZE_OBJECT_LAST) - ObjectElements::VALUES_PER_HEADER;
-
-    // Allocate space using the VMCall
-    // when mir hints it needs to get allocated immediatly,
-    // but only when data doesn't fit the available array slots.
-    bool allocating = lir->mir()->isAllocating() && count > maxArraySlots;
-
-    if (templateObject->hasSingletonType() || allocating)
+    if (lir->mir()->shouldUseVM())
         return visitNewArrayCallVM(lir);
 
     OutOfLineNewArray *ool = new OutOfLineNewArray(lir);
     if (!addOutOfLineCode(ool))
         return false;
 
     masm.newGCThing(objReg, templateObject, ool->entry());
     masm.initGCThing(objReg, templateObject);
@@ -1870,16 +2156,18 @@ class OutOfLineNewObject : public OutOfL
 };
 
 typedef JSObject *(*NewInitObjectFn)(JSContext *, HandleObject);
 static const VMFunction NewInitObjectInfo = FunctionInfo<NewInitObjectFn>(NewInitObject);
 
 bool
 CodeGenerator::visitNewObjectVMCall(LNewObject *lir)
 {
+    JS_ASSERT(gen->info().executionMode() == SequentialExecution);
+
     Register objReg = ToRegister(lir->output());
 
     JS_ASSERT(!lir->isCall());
     saveLive(lir);
 
     pushArg(ImmGCPtr(lir->mir()->templateObject()));
     if (!callVM(NewInitObjectInfo, lir))
         return false;
@@ -1889,21 +2177,21 @@ CodeGenerator::visitNewObjectVMCall(LNew
 
     restoreLive(lir);
     return true;
 }
 
 bool
 CodeGenerator::visitNewObject(LNewObject *lir)
 {
+    JS_ASSERT(gen->info().executionMode() == SequentialExecution);
     Register objReg = ToRegister(lir->output());
-
     JSObject *templateObject = lir->mir()->templateObject();
 
-    if (templateObject->hasSingletonType() || templateObject->hasDynamicSlots())
+    if (lir->mir()->shouldUseVM())
         return visitNewObjectVMCall(lir);
 
     OutOfLineNewObject *ool = new OutOfLineNewObject(lir);
     if (!addOutOfLineCode(ool))
         return false;
 
     masm.newGCThing(objReg, templateObject, ool->entry());
     masm.initGCThing(objReg, templateObject);
@@ -1950,17 +2238,17 @@ typedef JSObject *(*NewCallObjectFn)(JSC
 static const VMFunction NewCallObjectInfo =
     FunctionInfo<NewCallObjectFn>(NewCallObject);
 
 bool
 CodeGenerator::visitNewCallObject(LNewCallObject *lir)
 {
     Register obj = ToRegister(lir->output());
 
-    JSObject *templateObj = lir->mir()->templateObj();
+    JSObject *templateObj = lir->mir()->templateObject();
 
     // If we have a template object, we can inline call object creation.
     OutOfLineCode *ool;
     if (lir->slots()->isRegister()) {
         ool = oolCallVM(NewCallObjectInfo, lir,
                         (ArgList(), ImmGCPtr(templateObj->lastProperty()),
                                     ImmGCPtr(templateObj->type()),
                                     ToRegister(lir->slots())),
@@ -1979,16 +2267,78 @@ CodeGenerator::visitNewCallObject(LNewCa
     masm.initGCThing(obj, templateObj);
 
     if (lir->slots()->isRegister())
         masm.storePtr(ToRegister(lir->slots()), Address(obj, JSObject::offsetOfSlots()));
     masm.bind(ool->rejoin());
     return true;
 }
 
+bool
+CodeGenerator::visitParNewCallObject(LParNewCallObject *lir)
+{
+    Register resultReg = ToRegister(lir->output());
+    Register parSliceReg = ToRegister(lir->parSlice());
+    Register tempReg1 = ToRegister(lir->getTemp0());
+    Register tempReg2 = ToRegister(lir->getTemp1());
+    JSObject *templateObj = lir->mir()->templateObj();
+
+    emitParAllocateGCThing(resultReg, parSliceReg, tempReg1, tempReg2, templateObj);
+
+    // NB: !lir->slots()->isRegister() implies that there is no slots
+    // array at all, and the memory is already zeroed when copying
+    // from the template object
+
+    if (lir->slots()->isRegister()) {
+        Register slotsReg = ToRegister(lir->slots());
+        JS_ASSERT(slotsReg != resultReg);
+        masm.storePtr(slotsReg, Address(resultReg, JSObject::offsetOfSlots()));
+    }
+
+    return true;
+}
+
+bool
+CodeGenerator::visitParNewDenseArray(LParNewDenseArray *lir)
+{
+    Register parSliceReg = ToRegister(lir->parSlice());
+    Register lengthReg = ToRegister(lir->length());
+    Register tempReg0 = ToRegister(lir->getTemp0());
+    Register tempReg1 = ToRegister(lir->getTemp1());
+    Register tempReg2 = ToRegister(lir->getTemp2());
+    JSObject *templateObj = lir->mir()->templateObject();
+
+    // Allocate the array into tempReg2.  Don't use resultReg because it
+    // may alias parSliceReg etc.
+    emitParAllocateGCThing(tempReg2, parSliceReg, tempReg0, tempReg1, templateObj);
+
+    // Invoke a C helper to allocate the elements.  For convenience,
+    // this helper also returns the array back to us, or NULL, which
+    // obviates the need to preserve the register across the call.  In
+    // reality, we should probably just have the C helper also
+    // *allocate* the array, but that would require that it initialize
+    // the various fields of the object, and I didn't want to
+    // duplicate the code in initGCThing() that already does such an
+    // admirable job.
+    masm.setupUnalignedABICall(3, CallTempReg3);
+    masm.passABIArg(parSliceReg);
+    masm.passABIArg(tempReg2);
+    masm.passABIArg(lengthReg);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParExtendArray));
+
+    Register resultReg = ToRegister(lir->output());
+    JS_ASSERT(resultReg == ReturnReg);
+    Label *bail;
+    if (!ensureOutOfLineParallelAbort(&bail))
+        return false;
+    masm.branchTestPtr(Assembler::Zero, resultReg, resultReg, bail);
+
+    return true;
+}
+
 typedef JSObject *(*NewStringObjectFn)(JSContext *, HandleString);
 static const VMFunction NewStringObjectInfo = FunctionInfo<NewStringObjectFn>(NewStringObject);
 
 bool
 CodeGenerator::visitNewStringObject(LNewStringObject *lir)
 {
     Register input = ToRegister(lir->input());
     Register output = ToRegister(lir->output());
@@ -2014,16 +2364,110 @@ CodeGenerator::visitNewStringObject(LNew
 }
 
 typedef bool(*InitPropFn)(JSContext *cx, HandleObject obj,
                           HandlePropertyName name, HandleValue value);
 static const VMFunction InitPropInfo =
     FunctionInfo<InitPropFn>(InitProp);
 
 bool
+CodeGenerator::visitParNew(LParNew *lir)
+{
+    Register objReg = ToRegister(lir->output());
+    Register parSliceReg = ToRegister(lir->parSlice());
+    Register tempReg1 = ToRegister(lir->getTemp0());
+    Register tempReg2 = ToRegister(lir->getTemp1());
+    JSObject *templateObject = lir->mir()->templateObject();
+    emitParAllocateGCThing(objReg, parSliceReg, tempReg1, tempReg2,
+                           templateObject);
+    return true;
+}
+
+class OutOfLineParNewGCThing : public OutOfLineCodeBase<CodeGenerator>
+{
+public:
+    gc::AllocKind allocKind;
+    Register objReg;
+
+    OutOfLineParNewGCThing(gc::AllocKind allocKind, Register objReg)
+        : allocKind(allocKind), objReg(objReg)
+    {}
+
+    bool accept(CodeGenerator *codegen) {
+        return codegen->visitOutOfLineParNewGCThing(this);
+    }
+};
+
+bool
+CodeGenerator::emitParAllocateGCThing(const Register &objReg,
+                                      const Register &parSliceReg,
+                                      const Register &tempReg1,
+                                      const Register &tempReg2,
+                                      JSObject *templateObj)
+{
+    gc::AllocKind allocKind = templateObj->getAllocKind();
+    OutOfLineParNewGCThing *ool = new OutOfLineParNewGCThing(allocKind, objReg);
+    if (!ool || !addOutOfLineCode(ool))
+        return false;
+
+    masm.parNewGCThing(objReg, parSliceReg, tempReg1, tempReg2,
+                       templateObj, ool->entry());
+    masm.bind(ool->rejoin());
+    masm.initGCThing(objReg, templateObj);
+    return true;
+}
+
+bool
+CodeGenerator::visitOutOfLineParNewGCThing(OutOfLineParNewGCThing *ool)
+{
+    // As a fallback for allocation in par. exec. mode, we invoke the
+    // C helper ParNewGCThing(), which calls into the GC code.  If it
+    // returns NULL, we bail.  If returns non-NULL, we rejoin the
+    // original instruction.
+
+    // This saves all caller-save registers, regardless of whether
+    // they are live.  This is wasteful but a simplification, given
+    // that for some of the LIR that this is used with
+    // (e.g., LParLambda) there are values in those registers
+    // that must not be clobbered but which are not technically
+    // considered live.
+    RegisterSet saveSet(RegisterSet::Volatile());
+
+    // Also preserve the temps we're about to overwrite,
+    // but don't bother to save the objReg.
+    saveSet.addUnchecked(CallTempReg0);
+    saveSet.addUnchecked(CallTempReg1);
+    saveSet.maybeTake(AnyRegister(ool->objReg));
+
+    masm.PushRegsInMask(saveSet);
+    masm.move32(Imm32(ool->allocKind), CallTempReg0);
+    masm.setupUnalignedABICall(1, CallTempReg1);
+    masm.passABIArg(CallTempReg0);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParNewGCThing));
+    masm.movePtr(ReturnReg, ool->objReg);
+    masm.PopRegsInMask(saveSet);
+    Label *bail;
+    if (!ensureOutOfLineParallelAbort(&bail))
+        return false;
+    masm.branchTestPtr(Assembler::Zero, ool->objReg, ool->objReg, bail);
+    masm.jump(ool->rejoin());
+    return true;
+}
+
+bool
+CodeGenerator::visitParBailout(LParBailout *lir)
+{
+    Label *bail;
+    if (!ensureOutOfLineParallelAbort(&bail))
+        return false;
+    masm.jump(bail);
+    return true;
+}
+
+bool
 CodeGenerator::visitInitProp(LInitProp *lir)
 {
     Register objReg = ToRegister(lir->getObject());
 
     pushArg(ToValue(lir, LInitProp::ValueIndex));
     pushArg(ImmGCPtr(lir->mir()->propertyName()));
     pushArg(objReg);
 
@@ -2366,16 +2810,44 @@ CodeGenerator::visitBinaryV(LBinaryV *li
         return callVM(UrshInfo, lir);
 
       default:
         JS_NOT_REACHED("Unexpected binary op");
         return false;
     }
 }
 
+bool
+CodeGenerator::visitParCompareS(LParCompareS *lir)
+{
+    JSOp op = lir->mir()->jsop();
+    Register left = ToRegister(lir->left());
+    Register right = ToRegister(lir->right());
+
+    JS_ASSERT((op == JSOP_EQ || op == JSOP_STRICTEQ) ||
+              (op == JSOP_NE || op == JSOP_STRICTNE));
+
+    masm.setupUnalignedABICall(2, CallTempReg2);
+    masm.passABIArg(left);
+    masm.passABIArg(right);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParCompareStrings));
+    masm.and32(Imm32(0xF), ReturnReg); // The C functions return an enum whose size is undef
+
+    // Check for cases that we do not currently handle in par exec
+    Label *bail;
+    if (!ensureOutOfLineParallelAbort(&bail))
+        return false;
+    masm.branch32(Assembler::Equal, ReturnReg, Imm32(ParCompareUnknown), bail);
+
+    if (op == JSOP_NE || op == JSOP_STRICTNE)
+        masm.xor32(Imm32(1), ReturnReg);
+
+    return true;
+}
+
 typedef bool (*StringCompareFn)(JSContext *, HandleString, HandleString, JSBool *);
 static const VMFunction stringsEqualInfo =
     FunctionInfo<StringCompareFn>(ion::StringsEqual<true>);
 static const VMFunction stringsNotEqualInfo =
     FunctionInfo<StringCompareFn>(ion::StringsEqual<false>);
 
 bool
 CodeGenerator::emitCompareS(LInstruction *lir, JSOp op, Register left, Register right,
@@ -3125,27 +3597,35 @@ CodeGenerator::visitOutOfLineStoreElemen
         index = store->index();
         valueType = store->mir()->value()->type();
         if (store->value()->isConstant())
             value = ConstantOrRegister(*store->value()->toConstant());
         else
             value = TypedOrValueRegister(valueType, ToAnyRegister(store->value()));
     }
 
+    // We can bump the initialized length inline if index ==
+    // initializedLength and index < capacity.  Otherwise, we have to
+    // consider fallback options.  In fallback cases, we branch to one
+    // of two labels because (at least in parallel mode) we can
+    // recover from index < capacity but not index !=
+    // initializedLength.
+    Label indexNotInitLen;
+    Label indexWouldExceedCapacity;
+
     // If index == initializedLength, try to bump the initialized length inline.
     // If index > initializedLength, call a stub. Note that this relies on the
     // condition flags sticking from the incoming branch.
-    Label callStub;
-    masm.j(Assembler::NotEqual, &callStub);
+    masm.j(Assembler::NotEqual, &indexNotInitLen);
 
     Int32Key key = ToInt32Key(index);
 
     // Check array capacity.
     masm.branchKey(Assembler::BelowOrEqual, Address(elements, ObjectElements::offsetOfCapacity()),
-                   key, &callStub);
+                   key, &indexWouldExceedCapacity);
 
     // Update initialized length. The capacity guard above ensures this won't overflow,
     // due to NELEMENTS_LIMIT.
     masm.bumpKey(&key, 1);
     masm.storeKey(key, Address(elements, ObjectElements::offsetOfInitializedLength()));
 
     // Update length if length < initializedLength.
     Label dontUpdate;
@@ -3163,32 +3643,92 @@ CodeGenerator::visitOutOfLineStoreElemen
         storeElementTyped(ins->toStoreElementHoleT()->value(), valueType, MIRType_None, elements,
                           index);
         masm.jump(ool->rejoin());
     } else {
         // Jump to the inline path where we will store the value.
         masm.jump(ool->rejoinStore());
     }
 
-    masm.bind(&callStub);
-    saveLive(ins);
-
-    pushArg(Imm32(current->mir()->strict()));
-    pushArg(value);
-    if (index->isConstant())
-        pushArg(*index->toConstant());
-    else
-        pushArg(TypedOrValueRegister(MIRType_Int32, ToAnyRegister(index)));
-    pushArg(object);
-    if (!callVM(SetObjectElementInfo, ins))
-        return false;
-
-    restoreLive(ins);
-    masm.jump(ool->rejoin());
-    return true;
+    switch (gen->info().executionMode()) {
+      case SequentialExecution:
+        masm.bind(&indexNotInitLen);
+        masm.bind(&indexWouldExceedCapacity);
+        saveLive(ins);
+
+        pushArg(Imm32(current->mir()->strict()));
+        pushArg(value);
+        if (index->isConstant())
+            pushArg(*index->toConstant());
+        else
+            pushArg(TypedOrValueRegister(MIRType_Int32, ToAnyRegister(index)));
+        pushArg(object);
+        if (!callVM(SetObjectElementInfo, ins))
+            return false;
+
+        restoreLive(ins);
+        masm.jump(ool->rejoin());
+        return true;
+
+      case ParallelExecution:
+        Label *bail;
+        if (!ensureOutOfLineParallelAbort(&bail))
+            return false;
+
+        //////////////////////////////////////////////////////////////
+        // If the problem is that we do not have sufficient capacity,
+        // try to reallocate the elements array and then branch back
+        // to perform the actual write.  Note that we do not want to
+        // force the reg alloc to assign any particular register, so
+        // we make space on the stack and pass the arguments that way.
+        // (Also, outside of the VM call mechanism, it's very hard to
+        // pass in a Value to a C function!).
+        masm.bind(&indexWouldExceedCapacity);
+
+        // The use of registers here is somewhat subtle.  We need to
+        // save and restore the volatile registers but we also need to
+        // preserve the ReturnReg. Normally we'd just add a constraint
+        // to the regalloc, but since this is the slow path of a hot
+        // instruction we don't want to do that.  So instead we push
+        // the volatile registers but we don't save the register
+        // `object`.  We will copy the ReturnReg into `object`.  The
+        // function we are calling (`ParPush`) agrees to either return
+        // `object` unchanged or NULL.  This way after we restore the
+        // registers, we can examine `object` to know whether an error
+        // occurred.
+        RegisterSet saveSet(ins->safepoint()->liveRegs());
+        saveSet.maybeTake(object);
+
+        masm.PushRegsInMask(saveSet);
+        masm.reserveStack(sizeof(ParPushArgs));
+        masm.storePtr(object, Address(StackPointer, offsetof(ParPushArgs, object)));
+        masm.storeConstantOrRegister(value, Address(StackPointer,
+                                                    offsetof(ParPushArgs, value)));
+        masm.movePtr(StackPointer, CallTempReg0);
+        masm.setupUnalignedABICall(1, CallTempReg1);
+        masm.passABIArg(CallTempReg0);
+        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParPush));
+        masm.freeStack(sizeof(ParPushArgs));
+        masm.movePtr(ReturnReg, object);
+        masm.PopRegsInMask(saveSet);
+        masm.branchTestPtr(Assembler::Zero, object, object, bail);
+        masm.jump(ool->rejoin());
+
+        //////////////////////////////////////////////////////////////
+        // If the problem is that we are trying to write an index that
+        // is not the initialized length, that would result in a
+        // sparse array, and since we don't want to think about that
+        // case right now, we just bail out.
+        masm.bind(&indexNotInitLen);
+        masm.jump(bail);
+        return true;
+    }
+
+    JS_ASSERT(false);
+    return false;
 }
 
 typedef bool (*ArrayPopShiftFn)(JSContext *, HandleObject, MutableHandleValue);
 static const VMFunction ArrayPopDenseInfo = FunctionInfo<ArrayPopShiftFn>(ion::ArrayPopDense);
 static const VMFunction ArrayShiftDenseInfo = FunctionInfo<ArrayPopShiftFn>(ion::ArrayShiftDense);
 
 bool
 CodeGenerator::emitArrayPopShift(LInstruction *lir, const MArrayPopShift *mir, Register obj,
@@ -3694,17 +4234,18 @@ CodeGenerator::link()
     if (cx->compartment->types.compiledInfo.compilerOutput(cx)->isInvalidated())
         return true;
 
     IonScript *ionScript =
       IonScript::New(cx, graph.totalSlotCount(), scriptFrameSize, snapshots_.size(),
                      bailouts_.length(), graph.numConstants(),
                      safepointIndices_.length(), osiIndices_.length(),
                      cacheList_.length(), safepoints_.size(),
-                     graph.mir().numScripts());
+                     graph.mir().numScripts(),
+                     executionMode == ParallelExecution ? ForkJoinSlices(cx) : 0);
     SetIonScript(script, executionMode, ionScript);
 
     if (!ionScript)
         return false;
     invalidateEpilogueData_.fixup(&masm);
     Assembler::patchDataWithValueCheck(CodeLocationLabel(code, invalidateEpilogueData_),
                                        ImmWord(uintptr_t(ionScript)),
                                        ImmWord(uintptr_t(-1)));
@@ -3733,16 +4274,19 @@ CodeGenerator::link()
     if (cacheList_.length())
         ionScript->copyCacheEntries(&cacheList_[0], masm);
     if (safepoints_.size())
         ionScript->copySafepoints(&safepoints_);
 
     JS_ASSERT(graph.mir().numScripts() > 0);
     ionScript->copyScriptEntries(graph.mir().scripts());
 
+    if (executionMode == ParallelExecution)
+        ionScript->zeroParallelInvalidatedScripts();
+
     linkAbsoluteLabels();
 
     // The correct state for prebarriers is unknown until the end of compilation,
     // since a GC can occur during code generation. All barriers are emitted
     // off-by-default, and are toggled on here if necessary.
     if (cx->zone()->needsBarrier())
         ionScript->toggleBarriers(true);
 
@@ -5085,11 +5629,24 @@ CodeGenerator::visitFunctionBoundary(LFu
             sps_.pop(masm, temp);
             return true;
 
         default:
             JS_NOT_REACHED("invalid LFunctionBoundary type");
     }
 }
 
+bool
+CodeGenerator::visitOutOfLineParallelAbort(OutOfLineParallelAbort *ool)
+{
+    masm.movePtr(ImmWord((void *) current->mir()->info().script()), CallTempReg0);
+    masm.setupUnalignedABICall(1, CallTempReg1);
+    masm.passABIArg(CallTempReg0);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParallelAbort));
+
+    masm.moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand);
+    masm.jump(returnLabel_);
+    return true;
+}
+
 } // namespace ion
 } // namespace js
 
--- a/js/src/ion/CodeGenerator.h
+++ b/js/src/ion/CodeGenerator.h
@@ -16,25 +16,29 @@
 # include "arm/CodeGenerator-arm.h"
 #else
 #error "CPU Not Supported"
 #endif
 
 namespace js {
 namespace ion {
 
+class OutOfLineNewParallelArray;
 class OutOfLineTestObject;
 class OutOfLineNewArray;
 class OutOfLineNewObject;
 class CheckOverRecursedFailure;
+class ParCheckOverRecursedFailure;
+class OutOfLineParCheckInterrupt;
 class OutOfLineUnboxDouble;
 class OutOfLineCache;
 class OutOfLineStoreElementHole;
 class OutOfLineTypeOfV;
 class OutOfLineLoadTypedArray;
+class OutOfLineParNewGCThing;
 
 class CodeGenerator : public CodeGeneratorSpecific
 {
     bool generateArgumentsChecks();
     bool generateBody();
 
   public:
     CodeGenerator(MIRGenerator *gen, LIRGraph *graph);
@@ -67,16 +71,17 @@ class CodeGenerator : public CodeGenerat
     bool visitTestVAndBranch(LTestVAndBranch *lir);
     bool visitPolyInlineDispatch(LPolyInlineDispatch *lir);
     bool visitIntToString(LIntToString *lir);
     bool visitInteger(LInteger *lir);
     bool visitRegExp(LRegExp *lir);
     bool visitRegExpTest(LRegExpTest *lir);
     bool visitLambda(LLambda *lir);
     bool visitLambdaForSingleton(LLambdaForSingleton *lir);
+    bool visitParLambda(LParLambda *lir);
     bool visitPointer(LPointer *lir);
     bool visitSlots(LSlots *lir);
     bool visitStoreSlotV(LStoreSlotV *store);
     bool visitElements(LElements *lir);
     bool visitConvertElementsToDoubles(LConvertElementsToDoubles *lir);
     bool visitTypeBarrier(LTypeBarrier *lir);
     bool visitMonitorTypes(LMonitorTypes *lir);
     bool visitCallNative(LCallNative *call);
@@ -85,25 +90,30 @@ class CodeGenerator : public CodeGenerat
     bool visitCallGeneric(LCallGeneric *call);
     bool visitCallKnown(LCallKnown *call);
     bool emitCallInvokeFunction(LApplyArgsGeneric *apply, Register extraStackSize);
     void emitPushArguments(LApplyArgsGeneric *apply, Register extraStackSpace);
     void emitPopArguments(LApplyArgsGeneric *apply, Register extraStackSize);
     bool visitApplyArgsGeneric(LApplyArgsGeneric *apply);
     bool visitDoubleToInt32(LDoubleToInt32 *lir);
     bool visitNewSlots(LNewSlots *lir);
+    bool visitOutOfLineNewParallelArray(OutOfLineNewParallelArray *ool);
     bool visitNewArrayCallVM(LNewArray *lir);
     bool visitNewArray(LNewArray *lir);
     bool visitOutOfLineNewArray(OutOfLineNewArray *ool);
     bool visitNewObjectVMCall(LNewObject *lir);
     bool visitNewObject(LNewObject *lir);
     bool visitOutOfLineNewObject(OutOfLineNewObject *ool);
     bool visitNewDeclEnvObject(LNewDeclEnvObject *lir);
     bool visitNewCallObject(LNewCallObject *lir);
+    bool visitParNewCallObject(LParNewCallObject *lir);
     bool visitNewStringObject(LNewStringObject *lir);
+    bool visitParNew(LParNew *lir);
+    bool visitParNewDenseArray(LParNewDenseArray *lir);
+    bool visitParBailout(LParBailout *lir);
     bool visitInitProp(LInitProp *lir);
     bool visitCreateThis(LCreateThis *lir);
     bool visitCreateThisWithProto(LCreateThisWithProto *lir);
     bool visitCreateThisWithTemplate(LCreateThisWithTemplate *lir);
     bool visitReturnFromCtor(LReturnFromCtor *lir);
     bool visitArrayLength(LArrayLength *lir);
     bool visitTypedArrayLength(LTypedArrayLength *lir);
     bool visitTypedArrayElements(LTypedArrayElements *lir);
@@ -127,25 +137,29 @@ class CodeGenerator : public CodeGenerat
     bool visitMathFunctionD(LMathFunctionD *ins);
     bool visitModD(LModD *ins);
     bool visitMinMaxI(LMinMaxI *lir);
     bool visitBinaryV(LBinaryV *lir);
     bool emitCompareS(LInstruction *lir, JSOp op, Register left, Register right,
                       Register output, Register temp);
     bool visitCompareS(LCompareS *lir);
     bool visitCompareStrictS(LCompareStrictS *lir);
+    bool visitParCompareS(LParCompareS *lir);
     bool visitCompareVM(LCompareVM *lir);
     bool visitIsNullOrLikeUndefined(LIsNullOrLikeUndefined *lir);
     bool visitIsNullOrLikeUndefinedAndBranch(LIsNullOrLikeUndefinedAndBranch *lir);
     bool visitEmulatesUndefined(LEmulatesUndefined *lir);
     bool visitEmulatesUndefinedAndBranch(LEmulatesUndefinedAndBranch *lir);
     bool visitConcat(LConcat *lir);
     bool visitCharCodeAt(LCharCodeAt *lir);
     bool visitFromCharCode(LFromCharCode *lir);
     bool visitFunctionEnvironment(LFunctionEnvironment *lir);
+    bool visitParSlice(LParSlice *lir);
+    bool visitParWriteGuard(LParWriteGuard *lir);
+    bool visitParDump(LParDump *lir);
     bool visitCallGetProperty(LCallGetProperty *lir);
     bool visitCallGetElement(LCallGetElement *lir);
     bool visitCallSetElement(LCallSetElement *lir);
     bool visitThrow(LThrow *lir);
     bool visitTypeOfV(LTypeOfV *lir);
     bool visitOutOfLineTypeOfV(OutOfLineTypeOfV *ool);
     bool visitToIdV(LToIdV *lir);
     bool visitLoadElementV(LLoadElementV *load);
@@ -191,27 +205,37 @@ class CodeGenerator : public CodeGenerat
     bool visitGetDOMProperty(LGetDOMProperty *lir);
     bool visitSetDOMProperty(LSetDOMProperty *lir);
     bool visitCallDOMNative(LCallDOMNative *lir);
     bool visitCallGetIntrinsicValue(LCallGetIntrinsicValue *lir);
 
     bool visitCheckOverRecursed(LCheckOverRecursed *lir);
     bool visitCheckOverRecursedFailure(CheckOverRecursedFailure *ool);
 
+    bool visitParCheckOverRecursed(LParCheckOverRecursed *lir);
+    bool visitParCheckOverRecursedFailure(ParCheckOverRecursedFailure *ool);
+
+    bool visitParCheckInterrupt(LParCheckInterrupt *lir);
+    bool visitOutOfLineParCheckInterrupt(OutOfLineParCheckInterrupt *ool);
+
     bool visitUnboxDouble(LUnboxDouble *lir);
     bool visitOutOfLineUnboxDouble(OutOfLineUnboxDouble *ool);
     bool visitOutOfLineStoreElementHole(OutOfLineStoreElementHole *ool);
 
     bool visitOutOfLineCacheGetProperty(OutOfLineCache *ool);
     bool visitOutOfLineGetElementCache(OutOfLineCache *ool);
     bool visitOutOfLineSetPropertyCache(OutOfLineCache *ool);
     bool visitOutOfLineBindNameCache(OutOfLineCache *ool);
     bool visitOutOfLineGetNameCache(OutOfLineCache *ool);
     bool visitOutOfLineCallsiteCloneCache(OutOfLineCache *ool);
 
+    bool visitOutOfLineParNewGCThing(OutOfLineParNewGCThing *ool);
+
+    bool visitOutOfLineParallelAbort(OutOfLineParallelAbort *ool);
+
     bool visitGetPropertyCacheV(LGetPropertyCacheV *ins) {
         return visitCache(ins);
     }
     bool visitGetPropertyCacheT(LGetPropertyCacheT *ins) {
         return visitCache(ins);
     }
     bool visitGetElementCacheV(LGetElementCacheV *ins) {
         return visitCache(ins);
@@ -231,19 +255,33 @@ class CodeGenerator : public CodeGenerat
     bool visitCallsiteCloneCache(LCallsiteCloneCache *ins) {
         return visitCache(ins);
     }
 
   private:
     bool visitCache(LInstruction *load);
     bool visitCallSetProperty(LInstruction *ins);
 
+    bool checkForParallelBailout();
+
     ConstantOrRegister getSetPropertyValue(LInstruction *ins);
     bool generateBranchV(const ValueOperand &value, Label *ifTrue, Label *ifFalse, FloatRegister fr);
 
+    bool emitParAllocateGCThing(const Register &objReg,
+                                const Register &threadContextReg,
+                                const Register &tempReg1,
+                                const Register &tempReg2,
+                                JSObject *templateObj);
+
+    bool emitParCallToUncompiledScript(Register calleeReg);
+
+    void emitLambdaInit(const Register &resultReg,
+                        const Register &scopeChainReg,
+                        JSFunction *fun);
+
     IonScriptCounts *maybeCreateScriptCounts();
 
     // Test whether value is truthy or not and jump to the corresponding label.
     // If the value can be an object that emulates |undefined|, |ool| must be
     // non-null; otherwise it may be null (and the scratch definitions should
     // be bogus), in which case an object encountered here will always be
     // truthy.
     void testValueTruthy(const ValueOperand &value,
--- a/js/src/ion/Ion.cpp
+++ b/js/src/ion/Ion.cpp
@@ -12,16 +12,18 @@
 #include "IonSpewer.h"
 #include "LIR.h"
 #include "AliasAnalysis.h"
 #include "LICM.h"
 #include "ValueNumbering.h"
 #include "EdgeCaseAnalysis.h"
 #include "RangeAnalysis.h"
 #include "LinearScan.h"
+#include "vm/ParallelDo.h"
+#include "ParallelArrayAnalysis.h"
 #include "jscompartment.h"
 #include "vm/ThreadPool.h"
 #include "vm/ForkJoin.h"
 #include "IonCompartment.h"
 #include "CodeGenerator.h"
 #include "jsworkers.h"
 #include "BacktrackingAllocator.h"
 #include "StupidAllocator.h"
@@ -465,29 +467,30 @@ IonScript::IonScript()
     osiIndexOffset_(0),
     osiIndexEntries_(0),
     cacheList_(0),
     cacheEntries_(0),
     safepointsStart_(0),
     safepointsSize_(0),
     scriptList_(0),
     scriptEntries_(0),
+    parallelInvalidatedScriptList_(0),
     refcount_(0),
     recompileInfo_(),
     slowCallCount(0)
 {
 }
 
 static const int DataAlignment = 4;
 
 IonScript *
 IonScript::New(JSContext *cx, uint32_t frameSlots, uint32_t frameSize, size_t snapshotsSize,
                size_t bailoutEntries, size_t constants, size_t safepointIndices,
                size_t osiIndices, size_t cacheEntries, size_t safepointsSize,
-               size_t scriptEntries)
+               size_t scriptEntries, size_t parallelInvalidatedScriptEntries)
 {
     if (snapshotsSize >= MAX_BUFFER_SIZE ||
         (bailoutEntries >= MAX_BUFFER_SIZE / sizeof(uint32_t)))
     {
         js_ReportOutOfMemory(cx);
         return NULL;
     }
 
@@ -497,24 +500,27 @@ IonScript::New(JSContext *cx, uint32_t f
     size_t paddedSnapshotsSize = AlignBytes(snapshotsSize, DataAlignment);
     size_t paddedBailoutSize = AlignBytes(bailoutEntries * sizeof(uint32_t), DataAlignment);
     size_t paddedConstantsSize = AlignBytes(constants * sizeof(Value), DataAlignment);
     size_t paddedSafepointIndicesSize = AlignBytes(safepointIndices * sizeof(SafepointIndex), DataAlignment);
     size_t paddedOsiIndicesSize = AlignBytes(osiIndices * sizeof(OsiIndex), DataAlignment);
     size_t paddedCacheEntriesSize = AlignBytes(cacheEntries * sizeof(IonCache), DataAlignment);
     size_t paddedSafepointSize = AlignBytes(safepointsSize, DataAlignment);
     size_t paddedScriptSize = AlignBytes(scriptEntries * sizeof(RawScript), DataAlignment);
+    size_t paddedParallelInvalidatedScriptSize =
+        AlignBytes(parallelInvalidatedScriptEntries * sizeof(RawScript), DataAlignment);
     size_t bytes = paddedSnapshotsSize +
                    paddedBailoutSize +
                    paddedConstantsSize +
                    paddedSafepointIndicesSize+
                    paddedOsiIndicesSize +
                    paddedCacheEntriesSize +
                    paddedSafepointSize +
-                   paddedScriptSize;
+                   paddedScriptSize +
+                   paddedParallelInvalidatedScriptSize;
     uint8_t *buffer = (uint8_t *)cx->malloc_(sizeof(IonScript) + bytes);
     if (!buffer)
         return NULL;
 
     IonScript *script = reinterpret_cast<IonScript *>(buffer);
     new (script) IonScript();
 
     uint32_t offsetCursor = sizeof(IonScript);
@@ -546,16 +552,20 @@ IonScript::New(JSContext *cx, uint32_t f
     script->safepointsStart_ = offsetCursor;
     script->safepointsSize_ = safepointsSize;
     offsetCursor += paddedSafepointSize;
 
     script->scriptList_ = offsetCursor;
     script->scriptEntries_ = scriptEntries;
     offsetCursor += paddedScriptSize;
 
+    script->parallelInvalidatedScriptList_ = offsetCursor;
+    script->parallelInvalidatedScriptEntries_ = parallelInvalidatedScriptEntries;
+    offsetCursor += parallelInvalidatedScriptEntries;
+
     script->frameSlots_ = frameSlots;
     script->frameSize_ = frameSize;
 
     script->recompileInfo_ = cx->compartment->types.compiledInfo;
 
     return script;
 }
 
@@ -602,16 +612,23 @@ IonScript::copyConstants(const HeapValue
 void
 IonScript::copyScriptEntries(JSScript **scripts)
 {
     for (size_t i = 0; i < scriptEntries_; i++)
         scriptList()[i] = scripts[i];
 }
 
 void
+IonScript::zeroParallelInvalidatedScripts()
+{
+    memset(parallelInvalidatedScriptList(), 0,
+           parallelInvalidatedScriptEntries_ * sizeof(JSScript *));
+}
+
+void
 IonScript::copySafepointIndices(const SafepointIndex *si, MacroAssembler &masm)
 {
     /*
      * Jumps in the caches reflect the offset of those jumps in the compiled
      * code, not the absolute positions of the jumps. Update according to the
      * final code address now.
      */
     SafepointIndex *table = safepointIndices();
@@ -767,192 +784,197 @@ ion::ToggleBarriers(JSCompartment *comp,
         if (script->hasIonScript())
             script->ion->toggleBarriers(needs);
     }
 }
 
 namespace js {
 namespace ion {
 
-CodeGenerator *
-CompileBackEnd(MIRGenerator *mir)
+bool
+OptimizeMIR(MIRGenerator *mir)
 {
     IonSpewPass("BuildSSA");
     // Note: don't call AssertGraphCoherency before SplitCriticalEdges,
     // the graph is not in RPO at this point.
 
     MIRGraph &graph = mir->graph();
 
     if (mir->shouldCancel("Start"))
-        return NULL;
+        return false;
 
     if (!SplitCriticalEdges(graph))
-        return NULL;
+        return false;
     IonSpewPass("Split Critical Edges");
     AssertGraphCoherency(graph);
 
     if (mir->shouldCancel("Split Critical Edges"))
-        return NULL;
+        return false;
 
     if (!RenumberBlocks(graph))
-        return NULL;
+        return false;
     IonSpewPass("Renumber Blocks");
     AssertGraphCoherency(graph);
 
     if (mir->shouldCancel("Renumber Blocks"))
-        return NULL;
+        return false;
 
     if (!BuildDominatorTree(graph))
-        return NULL;
+        return false;
     // No spew: graph not changed.
 
     if (mir->shouldCancel("Dominator Tree"))
-        return NULL;
+        return false;
 
     // This must occur before any code elimination.
     if (!EliminatePhis(mir, graph, AggressiveObservability))
-        return NULL;
+        return false;
     IonSpewPass("Eliminate phis");
     AssertGraphCoherency(graph);
 
     if (mir->shouldCancel("Eliminate phis"))
-        return NULL;
+        return false;
 
     if (!BuildPhiReverseMapping(graph))
-        return NULL;
+        return false;
     AssertExtendedGraphCoherency(graph);
     // No spew: graph not changed.
 
     if (mir->shouldCancel("Phi reverse mapping"))
-        return NULL;
+        return false;
 
     // This pass also removes copies.
     if (!ApplyTypeInformation(mir, graph))
-        return NULL;
+        return false;
     IonSpewPass("Apply types");
     AssertExtendedGraphCoherency(graph);
 
     if (mir->shouldCancel("Apply types"))
-        return NULL;
+        return false;
 
     // Alias analysis is required for LICM and GVN so that we don't move
     // loads across stores.
     if (js_IonOptions.licm || js_IonOptions.gvn) {
         AliasAnalysis analysis(mir, graph);
         if (!analysis.analyze())
-            return NULL;
+            return false;
         IonSpewPass("Alias analysis");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("Alias analysis"))
-            return NULL;
+            return false;
 
         // Eliminating dead resume point operands requires basic block
         // instructions to be numbered. Reuse the numbering computed during
         // alias analysis.
         if (!EliminateDeadResumePointOperands(mir, graph))
-            return NULL;
+            return false;
 
         if (mir->shouldCancel("Eliminate dead resume point operands"))
-            return NULL;
+            return false;
     }
 
     if (js_IonOptions.gvn) {
         ValueNumberer gvn(mir, graph, js_IonOptions.gvnIsOptimistic);
         if (!gvn.analyze())
-            return NULL;
+            return false;
         IonSpewPass("GVN");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("GVN"))
-            return NULL;
+            return false;
     }
 
     if (js_IonOptions.uce) {
         UnreachableCodeElimination uce(mir, graph);
         if (!uce.analyze())
-            return NULL;
+            return false;
         IonSpewPass("UCE");
         AssertExtendedGraphCoherency(graph);
     }
 
     if (mir->shouldCancel("UCE"))
-        return NULL;
+        return false;
 
     if (js_IonOptions.licm) {
         LICM licm(mir, graph);
         if (!licm.analyze())
-            return NULL;
+            return false;
         IonSpewPass("LICM");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("LICM"))
-            return NULL;
+            return false;
     }
 
     if (js_IonOptions.rangeAnalysis) {
         RangeAnalysis r(graph);
         if (!r.addBetaNobes())
-            return NULL;
+            return false;
         IonSpewPass("Beta");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("RA Beta"))
-            return NULL;
+            return false;
 
         if (!r.analyze())
-            return NULL;
+            return false;
         IonSpewPass("Range Analysis");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("Range Analysis"))
-            return NULL;
+            return false;
 
         if (!r.removeBetaNobes())
-            return NULL;
+            return false;
         IonSpewPass("De-Beta");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("RA De-Beta"))
-            return NULL;
+            return false;
     }
 
     if (!EliminateDeadCode(mir, graph))
-        return NULL;
+        return false;
     IonSpewPass("DCE");
     AssertExtendedGraphCoherency(graph);
 
     if (mir->shouldCancel("DCE"))
-        return NULL;
+        return false;
 
     // Passes after this point must not move instructions; these analyses
     // depend on knowing the final order in which instructions will execute.
 
     if (js_IonOptions.edgeCaseAnalysis) {
         EdgeCaseAnalysis edgeCaseAnalysis(mir, graph);
         if (!edgeCaseAnalysis.analyzeLate())
-            return NULL;
+            return false;
         IonSpewPass("Edge Case Analysis (Late)");
         AssertGraphCoherency(graph);
 
         if (mir->shouldCancel("Edge Case Analysis (Late)"))
-            return NULL;
+            return false;
     }
 
     // Note: check elimination has to run after all other passes that move
     // instructions. Since 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 (!EliminateRedundantChecks(graph))
-        return NULL;
+        return false;
     IonSpewPass("Bounds Check Elimination");
     AssertGraphCoherency(graph);
 
-    if (mir->shouldCancel("Bounds Check Elimination"))
-        return NULL;
+    return true;
+}
+
+CodeGenerator *
+GenerateLIR(MIRGenerator *mir)
+{
+    MIRGraph &graph = mir->graph();
 
     LIRGraph *lir = mir->temp().lifoAlloc()->new_<LIRGraph>(&graph);
     if (!lir)
         return NULL;
 
     LIRGenerator lirgen(mir, graph, *lir);
     if (!lirgen.generate())
         return NULL;
@@ -1023,22 +1045,31 @@ CompileBackEnd(MIRGenerator *mir)
     if (!codegen || !codegen->generate()) {
         js_delete(codegen);
         return NULL;
     }
 
     return codegen;
 }
 
+CodeGenerator *
+CompileBackEnd(MIRGenerator *mir)
+{
+    if (!OptimizeMIR(mir))
+        return NULL;
+    return GenerateLIR(mir);
+}
+
 class SequentialCompileContext {
 public:
     ExecutionMode executionMode() {
         return SequentialExecution;
     }
 
+    MethodStatus checkScriptSize(JSContext *cx, UnrootedScript script);
     AbortReason compile(IonBuilder *builder, MIRGraph *graph,
                         ScopedJSDeletePtr<LifoAlloc> &autoDelete);
 };
 
 void
 AttachFinishedCompilations(JSContext *cx)
 {
 #ifdef JS_THREADSAFE
@@ -1297,18 +1328,18 @@ CheckScript(UnrootedScript script)
     if (!script->compileAndGo) {
         IonSpew(IonSpew_Abort, "not compile-and-go");
         return false;
     }
 
     return true;
 }
 
-static MethodStatus
-CheckScriptSize(JSContext *cx, UnrootedScript script)
+MethodStatus
+SequentialCompileContext::checkScriptSize(JSContext *cx, UnrootedScript script)
 {
     if (!js_IonOptions.limitScriptSize)
         return Method_Compiled;
 
     // Longer scripts can only be compiled off thread, as these compilations
     // can be expensive and stall the main thread for too long.
     static const uint32_t MAX_MAIN_THREAD_SCRIPT_SIZE = 2000;
     static const uint32_t MAX_OFF_THREAD_SCRIPT_SIZE = 20000;
@@ -1340,62 +1371,67 @@ CheckScriptSize(JSContext *cx, UnrootedS
     if (numLocalsAndArgs > MAX_LOCALS_AND_ARGS) {
         IonSpew(IonSpew_Abort, "Too many locals and arguments (%u)", numLocalsAndArgs);
         return Method_CantCompile;
     }
 
     return Method_Compiled;
 }
 
+template <typename CompileContext>
 static MethodStatus
-Compile(JSContext *cx, JSScript *script, JSFunction *fun, jsbytecode *osrPc, bool constructing)
+Compile(JSContext *cx, HandleScript script, HandleFunction fun, jsbytecode *osrPc, bool constructing,
+        CompileContext &compileContext)
 {
     JS_ASSERT(ion::IsEnabled(cx));
     JS_ASSERT_IF(osrPc != NULL, (JSOp)*osrPc == JSOP_LOOPENTRY);
 
     if (cx->compartment->debugMode()) {
         IonSpew(IonSpew_Abort, "debugging");
         return Method_CantCompile;
     }
 
     if (!CheckScript(script)) {
         IonSpew(IonSpew_Abort, "Aborted compilation of %s:%d", script->filename, script->lineno);
         return Method_CantCompile;
     }
 
-    MethodStatus status = CheckScriptSize(cx, script);
+    MethodStatus status = compileContext.checkScriptSize(cx, script);
     if (status != Method_Compiled) {
         IonSpew(IonSpew_Abort, "Aborted compilation of %s:%d", script->filename, script->lineno);
         return status;
     }
 
-    if (script->ion) {
-        if (!script->ion->method())
+    ExecutionMode executionMode = compileContext.executionMode();
+    IonScript *scriptIon = GetIonScript(script, executionMode);
+    if (scriptIon) {
+        if (!scriptIon->method())
             return Method_CantCompile;
         return Method_Compiled;
     }
 
-    if (cx->methodJitEnabled) {
-        // If JM is enabled we use getUseCount instead of incUseCount to avoid
-        // bumping the use count twice.
-        if (script->getUseCount() < js_IonOptions.usesBeforeCompile)
-            return Method_Skipped;
-    } else {
-        if (script->incUseCount() < js_IonOptions.usesBeforeCompileNoJaeger)
-            return Method_Skipped;
+    if (executionMode == SequentialExecution) {
+        if (cx->methodJitEnabled) {
+            // If JM is enabled we use getUseCount instead of incUseCount to avoid
+            // bumping the use count twice.
+
+            if (script->getUseCount() < js_IonOptions.usesBeforeCompile)
+                return Method_Skipped;
+        } else {
+            if (script->incUseCount() < js_IonOptions.usesBeforeCompileNoJaeger)
+                return Method_Skipped;
+        }
     }
 
-    SequentialCompileContext compileContext;
-
     AbortReason reason = IonCompile(cx, script, fun, osrPc, constructing, compileContext);
     if (reason == AbortReason_Disable)
         return Method_CantCompile;
 
     // Compilation succeeded or we invalidated right away or an inlining/alloc abort
-    return script->hasIonScript() ? Method_Compiled : Method_Skipped;
+    return HasIonScript(script, executionMode) ? Method_Compiled : Method_Skipped;
 }
 
 } // namespace ion
 } // namespace js
 
 // Decide if a transition from interpreter execution to Ion code should occur.
 // May compile or recompile the target JSScript.
 MethodStatus
@@ -1423,25 +1459,27 @@ ion::CanEnterAtBranch(JSContext *cx, JSS
 
     // Mark as forbidden if frame can't be handled.
     if (!CheckFrame(fp)) {
         ForbidCompilation(cx, script);
         return Method_CantCompile;
     }
 
     // Attempt compilation. Returns Method_Compiled if already compiled.
-    JSFunction *fun = fp.isFunctionFrame() ? fp.fun() : NULL;
-    MethodStatus status = Compile(cx, script, fun, pc, isConstructing);
+    RootedFunction fun(cx, fp.isFunctionFrame() ? fp.fun() : NULL);
+    SequentialCompileContext compileContext;
+    RootedScript rscript(cx, script);
+    MethodStatus status = Compile(cx, rscript, fun, pc, isConstructing, compileContext);
     if (status != Method_Compiled) {
         if (status == Method_CantCompile)
             ForbidCompilation(cx, script);
         return status;
     }
 
-    if (script->ion->osrPc() != pc)
+    if (script->ion && script->ion->osrPc() != pc)
         return Method_Skipped;
 
     return Method_Compiled;
 }
 
 MethodStatus
 ion::CanEnter(JSContext *cx, JSScript *script, AbstractFramePtr fp,
               bool isConstructing, bool newType)
@@ -1475,28 +1513,159 @@ ion::CanEnter(JSContext *cx, JSScript *s
 
     // Mark as forbidden if frame can't be handled.
     if (!CheckFrame(fp)) {
         ForbidCompilation(cx, script);
         return Method_CantCompile;
     }
 
     // Attempt compilation. Returns Method_Compiled if already compiled.
-    JSFunction *fun = fp.isFunctionFrame() ? fp.fun() : NULL;
-    MethodStatus status = Compile(cx, script, fun, NULL, isConstructing);
+    RootedFunction fun(cx, fp.isFunctionFrame() ? fp.fun() : NULL);
+    SequentialCompileContext compileContext;
+    RootedScript rscript(cx, script);
+    MethodStatus status = Compile(cx, rscript, fun, NULL, isConstructing, compileContext);
     if (status != Method_Compiled) {
         if (status == Method_CantCompile)
             ForbidCompilation(cx, script);
         return status;
     }
 
     return Method_Compiled;
 }
 
 MethodStatus
+ParallelCompileContext::checkScriptSize(JSContext *cx, UnrootedScript script)
+{
+    if (!js_IonOptions.limitScriptSize)
+        return Method_Compiled;
+
+    // When compiling for parallel execution we don't have off-thread
+    // compilation. We also up the max script size of the kernels.
+    static const uint32_t MAX_SCRIPT_SIZE = 5000;
+    static const uint32_t MAX_LOCALS_AND_ARGS = 256;
+
+    if (script->length > MAX_SCRIPT_SIZE) {
+        IonSpew(IonSpew_Abort, "Script too large (%u bytes)", script->length);
+        return Method_CantCompile;
+    }
+
+    uint32_t numLocalsAndArgs = analyze::TotalSlots(script);
+    if (numLocalsAndArgs > MAX_LOCALS_AND_ARGS) {
+        IonSpew(IonSpew_Abort, "Too many locals and arguments (%u)", numLocalsAndArgs);
+        return Method_CantCompile;
+    }
+
+    return Method_Compiled;
+}
+
+MethodStatus
+ParallelCompileContext::compileTransitively()
+{
+    using parallel::SpewBeginCompile;
+    using parallel::SpewEndCompile;
+
+    if (worklist_.empty())
+        return Method_Skipped;
+
+    RootedFunction fun(cx_);
+    RootedScript script(cx_);
+    while (!worklist_.empty()) {
+        fun = worklist_.back()->toFunction();
+        script = fun->nonLazyScript();
+        worklist_.popBack();
+
+        SpewBeginCompile(fun);
+
+        // If we had invalidations last time the parallel script run, add the
+        // invalidated scripts to the worklist.
+        if (script->hasParallelIonScript()) {
+            IonScript *ion = script->parallelIonScript();
+            JS_ASSERT(ion->parallelInvalidatedScriptEntries() > 0);
+
+            RootedFunction invalidFun(cx_);
+            for (uint32_t i = 0; i < ion->parallelInvalidatedScriptEntries(); i++) {
+                if (JSScript *invalid = ion->getAndZeroParallelInvalidatedScript(i)) {
+                    invalidFun = invalid->function();
+                    parallel::Spew(parallel::SpewCompile,
+                                   "Adding previously invalidated function %p:%s:%u",
+                                   fun.get(), invalid->filename, invalid->lineno);
+                    appendToWorklist(invalidFun);
+                }
+            }
+        }
+
+        // Attempt compilation. Returns Method_Compiled if already compiled.
+        MethodStatus status = Compile(cx_, script, fun, NULL, false, *this);
+        if (status != Method_Compiled) {
+            if (status == Method_CantCompile)
+                ForbidCompilation(cx_, script, ParallelExecution);
+            return SpewEndCompile(status);
+        }
+
+        // This can GC, so afterward, script->parallelIon is not guaranteed to be valid.
+        if (!cx_->compartment->ionCompartment()->enterJIT())
+            return SpewEndCompile(Method_Error);
+
+        // Subtle: it is possible for GC to occur during compilation of
+        // one of the invoked functions, which would cause the earlier
+        // functions (such as the kernel itself) to be collected.  In this
+        // event, we give up and fallback to sequential for now.
+        if (!script->hasParallelIonScript()) {
+            parallel::Spew(parallel::SpewCompile,
+                           "Function %p:%s:%u was garbage-collected or invalidated",
+                           fun.get(), script->filename, script->lineno);
+            return SpewEndCompile(Method_Skipped);
+        }
+
+        SpewEndCompile(Method_Compiled);
+    }
+
+    return Method_Compiled;
+}
+
+AbortReason
+ParallelCompileContext::compile(IonBuilder *builder,
+                                MIRGraph *graph,
+                                ScopedJSDeletePtr<LifoAlloc> &autoDelete)
+{
+    JS_ASSERT(!builder->script()->parallelIon);
+
+    RootedScript builderScript(cx_, builder->script());
+    IonSpewNewFunction(graph, builderScript);
+
+    if (!builder->build())
+        return builder->abortReason();
+    builder->clearForBackEnd();
+
+    // For the time being, we do not enable parallel compilation.
+
+    if (!OptimizeMIR(builder)) {
+        IonSpew(IonSpew_Abort, "Failed during back-end compilation.");
+        return AbortReason_Disable;
+    }
+
+    if (!analyzeAndGrowWorklist(builder, *graph)) {
+        return AbortReason_Disable;
+    }
+
+    CodeGenerator *codegen = GenerateLIR(builder);
+    if (!codegen)  {
+        IonSpew(IonSpew_Abort, "Failed during back-end compilation.");
+        return AbortReason_Disable;
+    }
+
+    bool success = codegen->link();
+    js_delete(codegen);
+
+    IonSpewEndFunction();
+
+    return success ? AbortReason_NoAbort : AbortReason_Disable;
+}
+
+MethodStatus
 ion::CanEnterUsingFastInvoke(JSContext *cx, HandleScript script, uint32_t numActualArgs)
 {
     JS_ASSERT(ion::IsEnabled(cx));
 
     // Skip if the code is expected to result in a bailout.
     if (!script->hasIonScript() || script->ion->bailoutExpected())
         return Method_Skipped;
 
@@ -1948,48 +2117,70 @@ ion::Invalidate(types::TypeCompartment &
 void
 ion::Invalidate(JSContext *cx, const Vector<types::RecompileInfo> &invalid, bool resetUses)
 {
     AutoAssertNoGC nogc;
     ion::Invalidate(cx->compartment->types, cx->runtime->defaultFreeOp(), invalid, resetUses);
 }
 
 bool
-ion::Invalidate(JSContext *cx, UnrootedScript script, bool resetUses)
+ion::Invalidate(JSContext *cx, UnrootedScript script, ExecutionMode mode, bool resetUses)
 {
     AutoAssertNoGC nogc;
     JS_ASSERT(script->hasIonScript());
 
     Vector<types::RecompileInfo> scripts(cx);
-    if (!scripts.append(script->ionScript()->recompileInfo()))
-        return false;
+
+    switch (mode) {
+      case SequentialExecution:
+        JS_ASSERT(script->hasIonScript());
+        if (!scripts.append(script->ionScript()->recompileInfo()))
+            return false;
+        break;
+      case ParallelExecution:
+        JS_ASSERT(script->hasParallelIonScript());
+        if (!scripts.append(script->parallelIonScript()->recompileInfo()))
+            return false;
+        break;
+    }
 
     Invalidate(cx, scripts, resetUses);
     return true;
 }
 
+bool
+ion::Invalidate(JSContext *cx, UnrootedScript script, bool resetUses)
+{
+    return Invalidate(cx, script, SequentialExecution, resetUses);
+}
+
+static void
+FinishInvalidationOf(FreeOp *fop, UnrootedScript script, IonScript **ionField)
+{
+    // If this script has Ion code on the stack, invalidation() will return
+    // true. In this case we have to wait until destroying it.
+    if (!(*ionField)->invalidated()) {
+        types::TypeCompartment &types = script->compartment()->types;
+        (*ionField)->recompileInfo().compilerOutput(types)->invalidate();
+
+        ion::IonScript::Destroy(fop, *ionField);
+    }
+
+    // In all cases, NULL out script->ion to avoid re-entry.
+    *ionField = NULL;
+}
+
 void
 ion::FinishInvalidation(FreeOp *fop, UnrootedScript script)
 {
-    if (!script->hasIonScript())
-        return;
+    if (script->hasIonScript())
+        FinishInvalidationOf(fop, script, &script->ion);
 
-    /*
-     * If this script has Ion code on the stack, invalidation() will return
-     * true. In this case we have to wait until destroying it.
-     */
-    if (!script->ion->invalidated()) {
-        types::TypeCompartment &types = script->compartment()->types;
-        script->ion->recompileInfo().compilerOutput(types)->invalidate();
-
-        ion::IonScript::Destroy(fop, script->ion);
-    }
-
-    /* In all cases, NULL out script->ion to avoid re-entry. */
-    script->ion = NULL;
+    if (script->hasParallelIonScript())
+        FinishInvalidationOf(fop, script, &script->parallelIon);
 }
 
 void
 ion::MarkValueFromIon(JSRuntime *rt, Value *vp)
 {
     gc::MarkValueUnbarriered(&rt->gcMarker, vp, "write barrier");
 }
 
@@ -1997,32 +2188,53 @@ void
 ion::MarkShapeFromIon(JSRuntime *rt, Shape **shapep)
 {
     gc::MarkShapeUnbarriered(&rt->gcMarker, shapep, "write barrier");
 }
 
 void
 ion::ForbidCompilation(JSContext *cx, UnrootedScript script)
 {
-    IonSpew(IonSpew_Abort, "Disabling Ion compilation of script %s:%d",
-            script->filename, script->lineno);
+    ForbidCompilation(cx, script, SequentialExecution);
+}
+
+void
+ion::ForbidCompilation(JSContext *cx, UnrootedScript script, ExecutionMode mode)
+{
+    IonSpew(IonSpew_Abort, "Disabling Ion mode %d compilation of script %s:%d",
+            mode, script->filename, script->lineno);
 
     CancelOffThreadIonCompile(cx->compartment, script);
 
-    if (script->hasIonScript()) {
-        // It is only safe to modify script->ion if the script is not currently
-        // running, because IonFrameIterator needs to tell what ionScript to
-        // use (either the one on the JSScript, or the one hidden in the
-        // breadcrumbs Invalidation() leaves). Therefore, if invalidation
-        // fails, we cannot disable the script.
-        if (!Invalidate(cx, script, false))
-            return;
+    switch (mode) {
+      case SequentialExecution:
+        if (script->hasIonScript()) {
+            // It is only safe to modify script->ion if the script is not currently
+            // running, because IonFrameIterator needs to tell what ionScript to
+            // use (either the one on the JSScript, or the one hidden in the
+            // breadcrumbs Invalidation() leaves). Therefore, if invalidation
+            // fails, we cannot disable the script.
+            if (!Invalidate(cx, script, mode, false))
+                return;
+        }
+
+        script->ion = ION_DISABLED_SCRIPT;
+        return;
+
+      case ParallelExecution:
+        if (script->hasParallelIonScript()) {
+            if (!Invalidate(cx, script, mode, false))
+                return;
+        }
+
+        script->parallelIon = ION_DISABLED_SCRIPT;
+        return;
     }
 
-    script->ion = ION_DISABLED_SCRIPT;
+    JS_NOT_REACHED("No such execution mode");
 }
 
 uint32_t
 ion::UsesBeforeIonRecompile(UnrootedScript script, jsbytecode *pc)
 {
     JS_ASSERT(pc == script->code || JSOp(*pc) == JSOP_LOOPENTRY);
 
     uint32_t minUses = js_IonOptions.usesBeforeCompile;
@@ -2096,17 +2308,17 @@ AutoFlushInhibitor::~AutoFlushInhibitor(
 int js::ion::LabelBase::id_count = 0;
 
 void
 ion::PurgeCaches(UnrootedScript script, JSCompartment *c) {
     if (script->hasIonScript())
         script->ion->purgeCaches(c);
 
     if (script->hasParallelIonScript())
-        script->ion->purgeCaches(c);
+        script->parallelIon->purgeCaches(c);
 }
 
 size_t
 ion::MemoryUsed(UnrootedScript script, JSMallocSizeOfFun mallocSizeOf) {
     size_t result = 0;
 
     if (script->hasIonScript())
         result += script->ion->sizeOfIncludingThis(mallocSizeOf);
--- a/js/src/ion/Ion.h
+++ b/js/src/ion/Ion.h
@@ -6,23 +6,25 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #if !defined(jsion_ion_h__) && defined(JS_ION)
 #define jsion_ion_h__
 
 #include "jscntxt.h"
 #include "jscompartment.h"
 #include "IonCode.h"
+#include "CompileInfo.h"
 #include "jsinfer.h"
 #include "jsinterp.h"
 
 namespace js {
 namespace ion {
 
 class TempAllocator;
+class ParallelCompileContext; // in ParallelArrayAnalysis.h
 
 // Possible register allocators which may be used.
 enum IonRegisterAllocator {
     RegisterAllocator_LSRA,
     RegisterAllocator_Backtracking,
     RegisterAllocator_Stupid
 };
 
@@ -168,16 +170,21 @@ struct IonOptions
 
     // When caller runs in IM, but callee not, we take a slow path to the interpreter.
     // This has a significant overhead. In order to decrease the number of times this happens,
     // the useCount gets incremented faster to compile this function in IM and use the fastpath.
     //
     // Default: 5
     uint32_t slowCallIncUseCount;
 
+    // How many uses of a parallel kernel before we attempt compilation.
+    //
+    // Default: 1
+    uint32_t usesBeforeCompileParallel;
+
     void setEagerCompilation() {
         eagerCompilation = true;
         usesBeforeCompile = usesBeforeCompileNoJaeger = 0;
 
         // Eagerly inline calls to improve test coverage.
         usesBeforeInlining = 0;
         smallFunctionUsesBeforeInlining = 0;
 
@@ -204,17 +211,18 @@ struct IonOptions
         smallFunctionMaxInlineDepth(10),
         smallFunctionMaxBytecodeLength(100),
         smallFunctionUsesBeforeInlining(usesBeforeInlining / 4),
         polyInlineMax(4),
         inlineMaxTotalBytecodeLength(800),
         inlineUseCountRatio(128),
         eagerCompilation(false),
         slowCallLimit(512),
-        slowCallIncUseCount(5)
+        slowCallIncUseCount(5),
+        usesBeforeCompileParallel(1)
     {
     }
 };
 
 enum MethodStatus
 {
     Method_Error,
     Method_CantCompile,
@@ -296,16 +304,17 @@ IonExecStatus SideCannon(JSContext *cx, 
 
 // Used to enter Ion from C++ natives like Array.map. Called from FastInvokeGuard.
 IonExecStatus FastInvoke(JSContext *cx, HandleFunction fun, CallArgsList &args);
 
 // Walk the stack and invalidate active Ion frames for the invalid scripts.
 void Invalidate(types::TypeCompartment &types, FreeOp *fop,
                 const Vector<types::RecompileInfo> &invalid, bool resetUses = true);
 void Invalidate(JSContext *cx, const Vector<types::RecompileInfo> &invalid, bool resetUses = true);
+bool Invalidate(JSContext *cx, UnrootedScript script, ExecutionMode mode, bool resetUses = true);
 bool Invalidate(JSContext *cx, UnrootedScript script, bool resetUses = true);
 
 void MarkValueFromIon(JSRuntime *rt, Value *vp);
 void MarkShapeFromIon(JSRuntime *rt, Shape **shapep);
 
 void ToggleBarriers(JSCompartment *comp, bool needs);
 
 class IonBuilder;
@@ -318,16 +327,17 @@ void FinishOffThreadBuilder(IonBuilder *
 MethodStatus TestIonCompile(JSContext *cx, HandleScript script, HandleFunction fun, jsbytecode *osrPc, bool constructing);
 
 static inline bool IsEnabled(JSContext *cx)
 {
     return cx->hasRunOption(JSOPTION_ION) && cx->typeInferenceEnabled();
 }
 
 void ForbidCompilation(JSContext *cx, UnrootedScript script);
+void ForbidCompilation(JSContext *cx, UnrootedScript script, ExecutionMode mode);
 uint32_t UsesBeforeIonRecompile(UnrootedScript script, jsbytecode *pc);
 
 void PurgeCaches(UnrootedScript script, JSCompartment *c);
 size_t MemoryUsed(UnrootedScript script, JSMallocSizeOfFun mallocSizeOf);
 void DestroyIonScripts(FreeOp *fop, UnrootedScript script);
 void TraceIonScripts(JSTracer* trc, UnrootedScript script);
 
 } // namespace ion
--- a/js/src/ion/IonBuilder.cpp
+++ b/js/src/ion/IonBuilder.cpp
@@ -4502,16 +4502,26 @@ IonBuilder::jsop_initprop(HandleProperty
     TypeOracle::BinaryTypes b = oracle->binaryTypes(script(), pc);
     if (b.lhsTypes &&
         (id == types::IdToTypeId(id)) &&
         !b.lhsTypes->propertyNeedsBarrier(cx, id))
     {
         needsBarrier = false;
     }
 
+    // In parallel execution, we never require write barriers.  See
+    // forkjoin.cpp for more information.
+    switch (info().executionMode()) {
+      case SequentialExecution:
+        break;
+      case ParallelExecution:
+        needsBarrier = false;
+        break;
+    }
+
     if (templateObject->isFixedSlot(shape->slot())) {
         MStoreFixedSlot *store = MStoreFixedSlot::New(obj, shape->slot(), value);
         if (needsBarrier)
             store->setNeedsBarrier();
 
         current->add(store);
         return resumeAfter(store);
     }
@@ -5465,30 +5475,30 @@ IonBuilder::jsop_getelem_dense()
 
     if (knownType != JSVAL_TYPE_UNKNOWN)
         load->setResultType(MIRTypeFromValueType(knownType));
 
     current->push(load);
     return pushTypeBarrier(load, types, barrier);
 }
 
-static MInstruction *
-GetTypedArrayLength(MDefinition *obj)
+MInstruction *
+IonBuilder::getTypedArrayLength(MDefinition *obj)
 {
     if (obj->isConstant()) {
         JSObject *array = &obj->toConstant()->value().toObject();
         int32_t length = (int32_t) TypedArray::length(array);
         obj->setFoldedUnchecked();
         return MConstant::New(Int32Value(length));
     }
     return MTypedArrayLength::New(obj);
 }
 
-static MInstruction *
-GetTypedArrayElements(MDefinition *obj)
+MInstruction *
+IonBuilder::getTypedArrayElements(MDefinition *obj)
 {
     if (obj->isConstant()) {
         JSObject *array = &obj->toConstant()->value().toObject();
         void *data = TypedArray::viewData(array);
         obj->setFoldedUnchecked();
         return MConstantElements::New(data);
     }
     return MTypedArrayElements::New(obj);
@@ -5541,24 +5551,24 @@ IonBuilder::jsop_getelem_typed(int array
             knownType = MIRType_Double;
             break;
           default:
             JS_NOT_REACHED("Unknown typed array type");
             return false;
         }
 
         // Get the length.
-        MInstruction *length = GetTypedArrayLength(obj);
+        MInstruction *length = getTypedArrayLength(obj);
         current->add(length);
 
         // Bounds check.
         id = addBoundsCheck(id, length);
 
         // Get the elements vector.
-        MInstruction *elements = GetTypedArrayElements(obj);
+        MInstruction *elements = getTypedArrayElements(obj);
         current->add(elements);
 
         // Load the element.
         MLoadTypedArrayElement *load = MLoadTypedArrayElement::New(elements, id, arrayType);
         current->add(load);
         current->push(load);
 
         load->setResultType(knownType);
@@ -5718,24 +5728,24 @@ IonBuilder::jsop_setelem_typed(int array
     MDefinition *obj = current->pop();
 
     // Ensure id is an integer.
     MInstruction *idInt32 = MToInt32::New(id);
     current->add(idInt32);
     id = idInt32;
 
     // Get the length.
-    MInstruction *length = GetTypedArrayLength(obj);
+    MInstruction *length = getTypedArrayLength(obj);
     current->add(length);
 
     // Bounds check.
     id = addBoundsCheck(id, length);
 
     // Get the elements vector.
-    MInstruction *elements = GetTypedArrayElements(obj);
+    MInstruction *elements = getTypedArrayElements(obj);
     current->add(elements);
 
     // Clamp value to [0, 255] for Uint8ClampedArray.
     MDefinition *unclampedValue = value;
     if (arrayType == TypedArray::TYPE_UINT8_CLAMPED) {
         value = MClampToUint8::New(value);
         current->add(value->toInstruction());
     }
@@ -5789,17 +5799,17 @@ IonBuilder::jsop_length_fastPath()
             MArrayLength *length = new MArrayLength(elements);
             current->add(length);
             current->push(length);
             return true;
         }
 
         if (sig.inTypes->getTypedArrayType() != TypedArray::TYPE_MAX) {
             MDefinition *obj = current->pop();
-            MInstruction *length = GetTypedArrayLength(obj);
+            MInstruction *length = getTypedArrayLength(obj);
             current->add(length);
             current->push(length);
             return true;
         }
 
         return false;
       }
 
--- a/js/src/ion/IonBuilder.h
+++ b/js/src/ion/IonBuilder.h
@@ -305,16 +305,20 @@ class IonBuilder : public MIRGenerator
     bool getPropTryCommonGetter(bool *emitted, HandleId id, types::StackTypeSet *barrier,
                                 types::StackTypeSet *types, TypeOracle::UnaryTypes unaryTypes);
     bool getPropTryMonomorphic(bool *emitted, HandleId id, types::StackTypeSet *barrier,
                                TypeOracle::Unary unary, TypeOracle::UnaryTypes unaryTypes);
     bool getPropTryPolymorphic(bool *emitted, HandlePropertyName name, HandleId id,
                                types::StackTypeSet *barrier, types::StackTypeSet *types,
                                TypeOracle::Unary unary, TypeOracle::UnaryTypes unaryTypes);
 
+    // Typed array helpers.
+    MInstruction *getTypedArrayLength(MDefinition *obj);
+    MInstruction *getTypedArrayElements(MDefinition *obj);
+
     bool jsop_add(MDefinition *left, MDefinition *right);
     bool jsop_bitnot();
     bool jsop_bitop(JSOp op);
     bool jsop_binary(JSOp op);
     bool jsop_binary(JSOp op, MDefinition *left, MDefinition *right);
     bool jsop_pos();
     bool jsop_neg();
     bool jsop_defvar(uint32_t index);
@@ -410,16 +414,28 @@ class IonBuilder : public MIRGenerator
     InliningStatus inlineStringObject(CallInfo &callInfo);
     InliningStatus inlineStrCharCodeAt(CallInfo &callInfo);
     InliningStatus inlineStrFromCharCode(CallInfo &callInfo);
     InliningStatus inlineStrCharAt(CallInfo &callInfo);
 
     // RegExp natives.
     InliningStatus inlineRegExpTest(CallInfo &callInfo);
 
+    // Parallel Array.
+    InliningStatus inlineUnsafeSetElement(CallInfo &callInfo);
+    bool inlineUnsafeSetDenseArrayElement(CallInfo &callInfo, uint32_t base);
+    bool inlineUnsafeSetTypedArrayElement(CallInfo &callInfo, uint32_t base, int arrayType);
+    InliningStatus inlineForceSequentialOrInParallelSection(CallInfo &callInfo);
+    InliningStatus inlineNewDenseArray(CallInfo &callInfo);
+    InliningStatus inlineNewDenseArrayForSequentialExecution(CallInfo &callInfo);
+    InliningStatus inlineNewDenseArrayForParallelExecution(CallInfo &callInfo);
+
+    InliningStatus inlineThrowError(CallInfo &callInfo);
+    InliningStatus inlineDump(CallInfo &callInfo);
+
     InliningStatus inlineNativeCall(CallInfo &callInfo, JSNative native);
 
     // Call functions
     bool jsop_call_inline(HandleFunction callee, CallInfo &callInfo, MBasicBlock *bottom,
                           Vector<MDefinition *, 8, IonAllocPolicy> &retvalDefns);
     bool inlineScriptedCalls(AutoObjectVector &targets, AutoObjectVector &originals,
                              CallInfo &callInfo);
     bool inlineScriptedCall(HandleFunction target, CallInfo &callInfo);
--- a/js/src/ion/IonCode.h
+++ b/js/src/ion/IonCode.h
@@ -206,16 +206,28 @@ struct IonScript
     // Offset to and length of the safepoint table in bytes.
     uint32_t safepointsStart_;
     uint32_t safepointsSize_;
 
     // List of compiled/inlined JSScript's.
     uint32_t scriptList_;
     uint32_t scriptEntries_;
 
+    // In parallel mode, list of scripts that we call that were invalidated
+    // last time this script bailed out. These will be recompiled (or tried to
+    // be) upon next parallel entry of this script.
+    //
+    // For non-parallel IonScripts, this is NULL.
+    //
+    // For parallel IonScripts, there are as many entries as there are slices,
+    // since for any single parallel execution, we can only get a single
+    // invalidation per slice.
+    uint32_t parallelInvalidatedScriptList_;
+    uint32_t parallelInvalidatedScriptEntries_;
+
     // Number of references from invalidation records.
     size_t refcount_;
 
     types::RecompileInfo recompileInfo_;
 
   public:
     // Number of times this function has tried to call a non-IM compileable function
     uint32_t slowCallCount;
@@ -239,28 +251,33 @@ struct IonScript
         return (OsiIndex *)(reinterpret_cast<uint8_t *>(this) + osiIndexOffset_);
     }
     IonCache *cacheList() {
         return (IonCache *)(reinterpret_cast<uint8_t *>(this) + cacheList_);
     }
     JSScript **scriptList() const {
         return (JSScript **)(reinterpret_cast<const uint8_t *>(this) + scriptList_);
     }
+    JSScript **parallelInvalidatedScriptList() {
+        return (JSScript **)(reinterpret_cast<const uint8_t *>(this) +
+                             parallelInvalidatedScriptList_);
+    }
 
   private:
     void trace(JSTracer *trc);
 
   public:
     // Do not call directly, use IonScript::New. This is public for cx->new_.
     IonScript();
 
     static IonScript *New(JSContext *cx, uint32_t frameLocals, uint32_t frameSize,
                           size_t snapshotsSize, size_t snapshotEntries,
                           size_t constants, size_t safepointIndexEntries, size_t osiIndexEntries,
-                          size_t cacheEntries, size_t safepointsSize, size_t scriptEntries);
+                          size_t cacheEntries, size_t safepointsSize, size_t scriptEntries,
+                          size_t parallelInvalidatedScriptEntries);
     static void Trace(JSTracer *trc, IonScript *script);
     static void Destroy(FreeOp *fop, IonScript *script);
 
     static inline size_t offsetOfMethod() {
         return offsetof(IonScript, method_);
     }
     static inline size_t offsetOfOsrEntryOffset() {
         return offsetof(IonScript, osrEntryOffset_);
@@ -334,16 +351,25 @@ struct IonScript
     }
     UnrootedScript getScript(size_t i) const {
         JS_ASSERT(i < scriptEntries_);
         return scriptList()[i];
     }
     size_t scriptEntries() const {
         return scriptEntries_;
     }
+    size_t parallelInvalidatedScriptEntries() const {
+        return parallelInvalidatedScriptEntries_;
+    }
+    RawScript getAndZeroParallelInvalidatedScript(uint32_t i) {
+        JS_ASSERT(i < parallelInvalidatedScriptEntries_);
+        RawScript script = parallelInvalidatedScriptList()[i];
+        parallelInvalidatedScriptList()[i] = NULL;
+        return script;
+    }
     size_t sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf) const {
         return mallocSizeOf(this);
     }
     HeapValue &getConstant(size_t index) {
         JS_ASSERT(index < numConstants());
         return constants()[index];
     }
     size_t numConstants() const {
@@ -375,16 +401,17 @@ struct IonScript
     void copySnapshots(const SnapshotWriter *writer);
     void copyBailoutTable(const SnapshotOffset *table);
     void copyConstants(const HeapValue *vp);
     void copySafepointIndices(const SafepointIndex *firstSafepointIndex, MacroAssembler &masm);
     void copyOsiIndices(const OsiIndex *firstOsiIndex, MacroAssembler &masm);
     void copyCacheEntries(const IonCache *caches, MacroAssembler &masm);
     void copySafepoints(const SafepointWriter *writer);
     void copyScriptEntries(JSScript **scripts);
+    void zeroParallelInvalidatedScripts();
 
     bool invalidated() const {
         return refcount_ != 0;
     }
     size_t refcount() const {
         return refcount_;
     }
     void incref() {
--- a/js/src/ion/IonMacroAssembler.cpp
+++ b/js/src/ion/IonMacroAssembler.cpp
@@ -328,16 +328,70 @@ MacroAssembler::newGCThing(const Registe
     branchPtr(Assembler::BelowOrEqual, AbsoluteAddress(&list->last), result, fail);
 
     addPtr(Imm32(thingSize), result);
     storePtr(result, AbsoluteAddress(&list->first));
     subPtr(Imm32(thingSize), result);
 }
 
 void
+MacroAssembler::parNewGCThing(const Register &result,
+                              const Register &threadContextReg,
+                              const Register &tempReg1,
+                              const Register &tempReg2,
+                              JSObject *templateObject,
+                              Label *fail)
+{
+    // Similar to ::newGCThing(), except that it allocates from a
+    // custom Allocator in the ForkJoinSlice*, rather than being
+    // hardcoded to the compartment allocator.  This requires two
+    // temporary registers.
+    //
+    // Subtle: I wanted to reuse `result` for one of the temporaries,
+    // but the register allocator was assigning it to the same
+    // register as `threadContextReg`.  Then we overwrite that
+    // register which messed up the OOL code.
+
+    gc::AllocKind allocKind = templateObject->getAllocKind();
+    uint32_t thingSize = (uint32_t)gc::Arena::thingSize(allocKind);
+
+    // Load the allocator:
+    // tempReg1 = (Allocator*) forkJoinSlice->allocator
+    loadPtr(Address(threadContextReg, offsetof(js::ForkJoinSlice, allocator)),
+            tempReg1);
+
+    // Get a pointer to the relevant free list:
+    // tempReg1 = (FreeSpan*) &tempReg1->arenas.freeLists[(allocKind)]
+    uint32_t offset = (offsetof(Allocator, arenas) +
+                       js::gc::ArenaLists::getFreeListOffset(allocKind));
+    addPtr(Imm32(offset), tempReg1);
+
+    // Load first item on the list
+    // tempReg2 = tempReg1->first
+    loadPtr(Address(tempReg1, offsetof(gc::FreeSpan, first)), tempReg2);
+
+    // Check whether list is empty
+    // if tempReg1->last <= tempReg2, fail
+    branchPtr(Assembler::BelowOrEqual,
+              Address(tempReg1, offsetof(gc::FreeSpan, last)),
+              tempReg2,
+              fail);
+
+    // If not, take first and advance pointer by thingSize bytes.
+    // result = tempReg2;
+    // tempReg2 += thingSize;
+    movePtr(tempReg2, result);
+    addPtr(Imm32(thingSize), tempReg2);
+
+    // Update `first`
+    // tempReg1->first = tempReg2;
+    storePtr(tempReg2, Address(tempReg1, offsetof(gc::FreeSpan, first)));
+}
+
+void
 MacroAssembler::initGCThing(const Register &obj, JSObject *templateObject)
 {
     // Fast initialization of an empty object returned by NewGCThing().
 
     storePtr(ImmGCPtr(templateObject->lastProperty()), Address(obj, JSObject::offsetOfShape()));
     storePtr(ImmGCPtr(templateObject->type()), Address(obj, JSObject::offsetOfType()));
     storePtr(ImmWord((void *)NULL), Address(obj, JSObject::offsetOfSlots()));
 
@@ -374,16 +428,28 @@ MacroAssembler::initGCThing(const Regist
     if (templateObject->hasPrivate()) {
         uint32_t nfixed = templateObject->numFixedSlots();
         storePtr(ImmWord(templateObject->getPrivate()),
                  Address(obj, JSObject::getPrivateDataOffset(nfixed)));
     }
 }
 
 void
+MacroAssembler::parCheckInterruptFlags(const Register &tempReg,
+                                       Label *fail)
+{
+    JSCompartment *compartment = GetIonContext()->compartment;
+
+    void *interrupt = (void*)&compartment->rt->interrupt;
+    movePtr(ImmWord(interrupt), tempReg);
+    load32(Address(tempReg, 0), tempReg);
+    branchTest32(Assembler::NonZero, tempReg, tempReg, fail);
+}
+
+void
 MacroAssembler::maybeRemoveOsrFrame(Register scratch)
 {
     // Before we link an exit frame, check for an OSR frame, which is
     // indicative of working inside an existing bailout. In this case, remove
     // the OSR frame, so we don't explode the stack with repeated bailouts.
     Label osrRemoved;
     loadPtr(Address(StackPointer, IonCommonFrameLayout::offsetOfDescriptor()), scratch);
     and32(Imm32(FRAMETYPE_MASK), scratch);
--- a/js/src/ion/IonMacroAssembler.h
+++ b/js/src/ion/IonMacroAssembler.h
@@ -13,16 +13,19 @@
 #elif defined(JS_CPU_X64)
 # include "ion/x64/MacroAssembler-x64.h"
 #elif defined(JS_CPU_ARM)
 # include "ion/arm/MacroAssembler-arm.h"
 #endif
 #include "ion/IonCompartment.h"
 #include "ion/IonInstrumentation.h"
 #include "ion/TypeOracle.h"
+#include "ion/ParallelFunctions.h"
+
+#include "vm/ForkJoin.h"
 
 #include "jstypedarray.h"
 #include "jscompartment.h"
 
 #include "vm/Shape.h"
 
 namespace js {
 namespace ion {
@@ -484,18 +487,29 @@ class MacroAssembler : public MacroAssem
     }
 
     // Inline version of js_TypedArray_uint8_clamp_double.
     // This function clobbers the input register.
     void clampDoubleToUint8(FloatRegister input, Register output);
 
     // Inline allocation.
     void newGCThing(const Register &result, JSObject *templateObject, Label *fail);
+    void parNewGCThing(const Register &result,
+                       const Register &threadContextReg,
+                       const Register &tempReg1,
+                       const Register &tempReg2,
+                       JSObject *templateObject,
+                       Label *fail);
     void initGCThing(const Register &obj, JSObject *templateObject);
 
+    // Checks the flags that signal that parallel code may need to interrupt or
+    // abort.  Branches to fail in that case.
+    void parCheckInterruptFlags(const Register &tempReg,
+                                Label *fail);
+
     // If the IonCode that created this assembler needs to transition into the VM,
     // we want to store the IonCode on the stack in order to mark it during a GC.
     // This is a reference to a patch location where the IonCode* will be written.
   private:
     CodeOffsetLabel exitCodePatch_;
 
   public:
     void enterExitFrame(const VMFunction *f = NULL) {
--- a/js/src/ion/IonSpewer.cpp
+++ b/js/src/ion/IonSpewer.cpp
@@ -231,16 +231,17 @@ ion::CheckLogging()
             "  codegen    Native code generation\n"
             "  bailouts   Bailouts\n"
             "  caches     Inline caches\n"
             "  osi        Invalidation\n"
             "  safepoints Safepoints\n"
             "  pools      Literal Pools (ARM only for now)\n"
             "  cacheflush Instruction Cache flushes (ARM only for now)\n"
             "  logs       C1 and JSON visualization logging\n"
+            "  trace      Generate calls to js::ion::Trace() for effectful instructions\n"
             "  all        Everything\n"
             "\n"
         );
         exit(0);
         /*NOTREACHED*/
     }
     if (ContainsFlag(env, "aborts"))
         EnableChannel(IonSpew_Abort);
@@ -273,16 +274,18 @@ ion::CheckLogging()
     if (ContainsFlag(env, "safepoints"))
         EnableChannel(IonSpew_Safepoints);
     if (ContainsFlag(env, "pools"))
         EnableChannel(IonSpew_Pools);
     if (ContainsFlag(env, "cacheflush"))
         EnableChannel(IonSpew_CacheFlush);
     if (ContainsFlag(env, "logs"))
         EnableIonDebugLogging();
+    if (ContainsFlag(env, "trace"))
+        EnableChannel(IonSpew_Trace);
     if (ContainsFlag(env, "all"))
         LoggingBits = uint32_t(-1);
 
     if (LoggingBits != 0)
         EnableIonDebugLogging();
 
     IonSpewFile = stderr;
 }
--- a/js/src/ion/IonSpewer.h
+++ b/js/src/ion/IonSpewer.h
@@ -47,16 +47,18 @@ namespace ion {
     /* Debug info about snapshots */        \
     _(Snapshots)                            \
     /* Generated inline cache stubs */      \
     _(InlineCaches)                         \
     /* Debug info about safepoints */       \
     _(Safepoints)                           \
     /* Debug info about Pools*/             \
     _(Pools)                                \
+    /* Calls to js::ion::Trace() */         \
+    _(Trace)                                \
     /* Debug info about the I$ */           \
     _(CacheFlush)
 
 
 enum IonSpewChannel {
 #define IONSPEW_CHANNEL(name) IonSpew_##name,
     IONSPEW_CHANNEL_LIST(IONSPEW_CHANNEL)
 #undef IONSPEW_CHANNEL
--- a/js/src/ion/IonTypes.h
+++ b/js/src/ion/IonTypes.h
@@ -59,21 +59,22 @@ enum MIRType
     MIRType_Null,
     MIRType_Boolean,
     MIRType_Int32,
     MIRType_Double,
     MIRType_String,
     MIRType_Object,
     MIRType_Magic,
     MIRType_Value,
-    MIRType_None,       // Invalid, used as a placeholder.
-    MIRType_Slots,      // A slots vector
-    MIRType_Elements,   // An elements vector
-    MIRType_StackFrame, // StackFrame pointer for OSR.
-    MIRType_Shape       // A Shape pointer.
+    MIRType_None,         // Invalid, used as a placeholder.
+    MIRType_Slots,        // A slots vector
+    MIRType_Elements,     // An elements vector
+    MIRType_StackFrame,   // StackFrame pointer for OSR.
+    MIRType_Shape,        // A Shape pointer.
+    MIRType_ForkJoinSlice // js::ForkJoinSlice*
 };
 
 #ifdef DEBUG
 // Track the pipeline of opcodes which has produced a snapshot.
 #define TRACK_SNAPSHOTS 1
 #endif
 
 } // namespace ion
--- a/js/src/ion/LIR-Common.h
+++ b/js/src/ion/LIR-Common.h
@@ -244,16 +244,26 @@ class LNewSlots : public LCallInstructio
         return getTemp(2);
     }
 
     MNewSlots *mir() const {
         return mir_->toNewSlots();
     }
 };
 
+class LNewParallelArray : public LInstructionHelper<1, 0, 0>
+{
+  public:
+    LIR_HEADER(NewParallelArray);
+
+    MNewParallelArray *mir() const {
+        return mir_->toNewParallelArray();
+    }
+};
+
 class LNewArray : public LInstructionHelper<1, 0, 0>
 {
   public:
     LIR_HEADER(NewArray)
 
     MNewArray *mir() const {
         return mir_->toNewArray();
     }
@@ -264,16 +274,89 @@ class LNewObject : public LInstructionHe
   public:
     LIR_HEADER(NewObject)
 
     MNewObject *mir() const {
         return mir_->toNewObject();
     }
 };
 
+class LParNew : public LInstructionHelper<1, 1, 2>
+{
+  public:
+    LIR_HEADER(ParNew);
+
+    LParNew(const LAllocation &parSlice,
+            const LDefinition &temp1,
+            const LDefinition &temp2)
+    {
+        setOperand(0, parSlice);
+        setTemp(0, temp1);
+        setTemp(1, temp2);
+    }
+
+    MParNew *mir() const {
+        return mir_->toParNew();
+    }
+
+    const LAllocation *parSlice() {
+        return getOperand(0);
+    }
+
+    const LAllocation *getTemp0() {
+        return getTemp(0)->output();
+    }
+
+    const LAllocation *getTemp1() {
+        return getTemp(1)->output();
+    }
+};
+
+class LParNewDenseArray : public LCallInstructionHelper<1, 2, 3>
+{
+  public:
+    LIR_HEADER(ParNewDenseArray);
+
+    LParNewDenseArray(const LAllocation &parSlice,
+                      const LAllocation &length,
+                      const LDefinition &temp1,
+                      const LDefinition &temp2,
+                      const LDefinition &temp3) {
+        setOperand(0, parSlice);
+        setOperand(1, length);
+        setTemp(0, temp1);
+        setTemp(1, temp2);
+        setTemp(2, temp3);
+    }
+
+    MParNewDenseArray *mir() const {
+        return mir_->toParNewDenseArray();
+    }
+
+    const LAllocation *parSlice() {
+        return getOperand(0);
+    }
+
+    const LAllocation *length() {
+        return getOperand(1);
+    }
+
+    const LAllocation *getTemp0() {
+        return getTemp(0)->output();
+    }
+
+    const LAllocation *getTemp1() {
+        return getTemp(1)->output();
+    }
+
+    const LAllocation *getTemp2() {
+        return getTemp(2)->output();
+    }
+};
+
 // Allocates a new DeclEnvObject.
 //
 // This instruction generates two possible instruction sets:
 //   (1) An inline allocation of the call object is attempted.
 //   (2) Otherwise, a callVM create a new object.
 //
 class LNewDeclEnvObject : public LInstructionHelper<1, 0, 0>
 {
@@ -306,16 +389,74 @@ class LNewCallObject : public LInstructi
     const LAllocation *slots() {
         return getOperand(0);
     }
     MNewCallObject *mir() const {
         return mir_->toNewCallObject();
     }
 };
 
+class LParNewCallObject : public LInstructionHelper<1, 2, 2>
+{
+    LParNewCallObject(const LAllocation &parSlice,
+                      const LAllocation &slots,
+                      const LDefinition &temp1,
+                      const LDefinition &temp2) {
+        setOperand(0, parSlice);
+        setOperand(1, slots);
+        setTemp(0, temp1);
+        setTemp(1, temp2);
+    }
+
+public:
+    LIR_HEADER(ParNewCallObject);
+
+    static LParNewCallObject *NewWithSlots(const LAllocation &parSlice,
+                                           const LAllocation &slots,
+                                           const LDefinition &temp1,
+                                           const LDefinition &temp2) {
+        return new LParNewCallObject(parSlice, slots, temp1, temp2);
+    }
+
+    static LParNewCallObject *NewSansSlots(const LAllocation &parSlice,
+                                           const LDefinition &temp1,
+                                           const LDefinition &temp2) {
+        LAllocation slots = LConstantIndex::Bogus();
+        return new LParNewCallObject(parSlice, slots, temp1, temp2);
+    }
+
+    const LAllocation *parSlice() {
+        return getOperand(0);
+    }
+
+    const LAllocation *slots() {
+        return getOperand(1);
+    }
+
+    const bool hasDynamicSlots() {
+        // TO INVESTIGATE: Felix tried using isRegister() method here,
+        // but for useFixed(_, CallTempN), isRegister() is false (and
+        // isUse() is true).  So for now ignore that and try to match
+        // the LConstantIndex::Bogus() generated above instead.
+        return slots() && ! slots()->isConstant();
+    }
+
+    const MParNewCallObject *mir() const {
+        return mir_->toParNewCallObject();
+    }
+
+    const LAllocation *getTemp0() {
+        return getTemp(0)->output();
+    }
+
+    const LAllocation *getTemp1() {
+        return getTemp(1)->output();
+    }
+};
+
 class LNewStringObject : public LInstructionHelper<1, 1, 1>
 {
   public:
     LIR_HEADER(NewStringObject)
 
     LNewStringObject(const LAllocation &input, const LDefinition &temp) {
         setOperand(0, input);
         setTemp(0, temp);
@@ -327,16 +468,22 @@ class LNewStringObject : public LInstruc
     const LDefinition *temp() {
         return getTemp(0);
     }
     MNewStringObject *mir() const {
         return mir_->toNewStringObject();
     }
 };
 
+class LParBailout : public LInstructionHelper<0, 0, 0>
+{
+  public:
+    LIR_HEADER(ParBailout);
+};
+
 // Takes in an Object and a Value.
 class LInitProp : public LCallInstructionHelper<0, 1 + BOX_PIECES, 0>
 {
   public:
     LIR_HEADER(InitProp)
 
     LInitProp(const LAllocation &object) {
         setOperand(0, object);
@@ -366,16 +513,58 @@ class LCheckOverRecursed : public LInstr
         setTemp(0, limitreg);
     }
 
     const LAllocation *limitTemp() {
         return getTemp(0)->output();
     }
 };
 
+class LParCheckOverRecursed : public LInstructionHelper<0, 1, 1>
+{
+  public:
+    LIR_HEADER(ParCheckOverRecursed);
+
+    LParCheckOverRecursed(const LAllocation &parSlice,
+                          const LDefinition &tempReg)
+    {
+        setOperand(0, parSlice);
+        setTemp(0, tempReg);
+    }
+
+    const LAllocation *parSlice() {
+        return getOperand(0);
+    }
+
+    const LDefinition *getTempReg() {
+        return getTemp(0);
+    }
+};
+
+class LParCheckInterrupt : public LInstructionHelper<0, 1, 1>
+{
+  public:
+    LIR_HEADER(ParCheckInterrupt);
+
+    LParCheckInterrupt(const LAllocation &parSlice,
+                       const LDefinition &tempReg)
+    {
+        setOperand(0, parSlice);
+        setTemp(0, tempReg);
+    }
+
+    const LAllocation *parSlice() {
+        return getOperand(0);
+    }
+
+    const LDefinition *getTempReg() {
+        return getTemp(0);
+    }
+};
+
 class LDefVar : public LCallInstructionHelper<0, 1, 0>
 {
   public:
     LIR_HEADER(DefVar)
 
     LDefVar(const LAllocation &scopeChain)
     {
         setOperand(0, scopeChain);
@@ -1141,16 +1330,37 @@ class LCompareStrictS : public LInstruct
     const LDefinition *temp1() {
         return getTemp(1);
     }
     MCompare *mir() {
         return mir_->toCompare();
     }
 };
 
+class LParCompareS : public LCallInstructionHelper<1, 2, 0>
+{
+  public:
+    LIR_HEADER(ParCompareS);
+
+    LParCompareS(const LAllocation &left, const LAllocation &right) {
+        setOperand(0, left);
+        setOperand(1, right);
+    }
+
+    const LAllocation *left() {
+        return getOperand(0);
+    }
+    const LAllocation *right() {
+        return getOperand(1);
+    }
+    MCompare *mir() {
+        return mir_->toCompare();
+    }
+};
+
 // Used for strict-equality comparisons where one side is a boolean
 // and the other is a value. Note that CompareI is used to compare
 // two booleans.
 class LCompareB : public LInstructionHelper<1, BOX_PIECES + 1, 0>
 {
   public:
     LIR_HEADER(CompareB)
 
@@ -2093,16 +2303,47 @@ class LLambda : public LInstructionHelpe
     const LAllocation *scopeChain() {
         return getOperand(0);
     }
     const MLambda *mir() const {
         return mir_->toLambda();
     }
 };
 
+class LParLambda : public LInstructionHelper<1, 2, 2>
+{
+  public:
+    LIR_HEADER(ParLambda);
+
+    LParLambda(const LAllocation &parSlice,
+               const LAllocation &scopeChain,
+               const LDefinition &temp1,
+               const LDefinition &temp2) {
+        setOperand(0, parSlice);
+        setOperand(1, scopeChain);
+        setTemp(0, temp1);
+        setTemp(1, temp2);
+    }
+    const LAllocation *parSlice() {
+        return getOperand(0);
+    }
+    const LAllocation *scopeChain() {
+        return getOperand(1);
+    }
+    const MParLambda *mir() const {
+        return mir_->toParLambda();
+    }
+    const LAllocation *getTemp0() {
+        return getTemp(0)->output();
+    }
+    const LAllocation *getTemp1() {
+        return getTemp(1)->output();
+    }
+};
+
 // Determines the implicit |this| value for function calls.
 class LImplicitThis : public LInstructionHelper<BOX_PIECES, 1, 0>
 {
   public:
     LIR_HEADER(ImplicitThis)
     BOX_OUTPUT_ACCESSORS()
 
     LImplicitThis(const LAllocation &callee) {
@@ -3071,16 +3312,30 @@ class LFunctionEnvironment : public LIns
     LFunctionEnvironment(const LAllocation &function) {
         setOperand(0, function);
     }
     const LAllocation *function() {
         return getOperand(0);
     }
 };
 
+class LParSlice : public LCallInstructionHelper<1, 0, 1>
+{
+  public:
+    LIR_HEADER(ParSlice);
+
+    LParSlice(const LDefinition &temp1) {
+        setTemp(0, temp1);
+    }
+
+    const LAllocation *getTempReg() {
+        return getTemp(0)->output();
+    }
+};
+
 class LCallGetProperty : public LCallInstructionHelper<BOX_PIECES, BOX_PIECES, 0>
 {
   public:
     LIR_HEADER(CallGetProperty)
 
     static const size_t Value = 0;
 
     MCallGetProperty *mir() const {
@@ -3320,16 +3575,58 @@ class LGetArgument : public LInstruction
     LGetArgument(const LAllocation &index) {
         setOperand(0, index);
     }
     const LAllocation *index() {
         return getOperand(0);
     }
 };
 
+class LParWriteGuard : public LCallInstructionHelper<0, 2, 1>
+{
+  public:
+    LIR_HEADER(ParWriteGuard);
+
+    LParWriteGuard(const LAllocation &parSlice,
+                   const LAllocation &object,
+                   const LDefinition &temp1) {
+        setOperand(0, parSlice);
+        setOperand(1, object);
+        setTemp(0, temp1);
+    }
+
+    bool isCall() const {
+        return true;
+    }
+
+    const LAllocation *parSlice() {
+        return getOperand(0);
+    }
+
+    const LAllocation *object() {
+        return getOperand(1);
+    }
+
+    const LAllocation *getTempReg() {
+        return getTemp(0)->output();
+    }
+};
+
+class LParDump : public LCallInstructionHelper<0, BOX_PIECES, 0>
+{
+  public:
+    LIR_HEADER(ParDump);
+
+    static const size_t Value = 0;
+
+    const LAllocation *value() {
+        return getOperand(0);
+    }
+};
+
 // Guard that a value is in a TypeSet.
 class LTypeBarrier : public LInstructionHelper<BOX_PIECES, BOX_PIECES, 1>
 {
   public:
     LIR_HEADER(TypeBarrier)
     BOX_OUTPUT_ACCESSORS()
 
     LTypeBarrier(const LDefinition &temp) {
--- a/js/src/ion/LIR.h
+++ b/js/src/ion/LIR.h
@@ -541,16 +541,18 @@ class LDefinition
 #endif
           case MIRType_Slots:
           case MIRType_Elements:
             // When we begin allocating slots vectors from the GC, this will
             // need to change to ::OBJECT.
             return LDefinition::GENERAL;
           case MIRType_StackFrame:
             return LDefinition::GENERAL;
+          case MIRType_ForkJoinSlice:
+            return LDefinition::GENERAL;
           default:
             JS_NOT_REACHED("unexpected type");
             return LDefinition::GENERAL;
         }
     }
 };
 
 // Forward declarations of LIR types.
--- a/js/src/ion/LOpcodes.h
+++ b/js/src/ion/LOpcodes.h
@@ -17,24 +17,30 @@
     _(Pointer)                      \
     _(Double)                       \
     _(Value)                        \
     _(Parameter)                    \
     _(Callee)                       \
     _(TableSwitch)                  \
     _(TableSwitchV)                 \
     _(Goto)                         \
+    _(NewParallelArray)             \
     _(NewArray)                     \
     _(NewObject)                    \
     _(NewSlots)                     \
     _(NewDeclEnvObject)             \
     _(NewCallObject)                \
     _(NewStringObject)              \
+    _(ParNew)                       \
+    _(ParNewDenseArray)             \
+    _(ParNewCallObject)             \
+    _(ParBailout)                   \
     _(InitProp)                     \
     _(CheckOverRecursed)            \
+    _(ParCheckOverRecursed)         \
     _(RecompileCheck)               \
     _(DefVar)                       \
     _(DefFun)                       \
     _(CallKnown)                    \
     _(CallGeneric)                  \
     _(CallNative)                   \
     _(ApplyArgsGeneric)             \
     _(StackArgT)                    \
@@ -58,16 +64,17 @@
     _(TestOAndBranch)               \
     _(PolyInlineDispatch)           \
     _(Compare)                      \
     _(CompareAndBranch)             \
     _(CompareD)                     \
     _(CompareDAndBranch)            \
     _(CompareS)                     \
     _(CompareStrictS)               \
+    _(ParCompareS)                  \
     _(CompareB)                     \
     _(CompareBAndBranch)            \
     _(CompareV)                     \
     _(CompareVAndBranch)            \
     _(CompareVM)                    \
     _(IsNullOrLikeUndefined)        \
     _(IsNullOrLikeUndefinedAndBranch)\
     _(EmulatesUndefined)            \
@@ -104,26 +111,29 @@
     _(Start)                        \
     _(OsrEntry)                     \
     _(OsrValue)                     \
     _(OsrScopeChain)                \
     _(RegExp)                       \
     _(RegExpTest)                   \
     _(Lambda)                       \
     _(LambdaForSingleton)           \
+    _(ParLambda)                    \
     _(ImplicitThis)                 \
     _(Slots)                        \
     _(Elements)                     \
     _(ConvertElementsToDoubles)     \
     _(LoadSlotV)                    \
     _(LoadSlotT)                    \
     _(StoreSlotV)                   \
     _(StoreSlotT)                   \
     _(GuardShape)                   \
     _(GuardClass)                   \
+    _(ParWriteGuard)                \
+    _(ParDump)                      \
     _(TypeBarrier)                  \
     _(MonitorTypes)                 \
     _(InitializedLength)            \
     _(SetInitializedLength)         \
     _(BoundsCheck)                  \
     _(BoundsCheckRange)             \
     _(BoundsCheckLower)             \
     _(LoadElementV)                 \
@@ -144,16 +154,17 @@
     _(ClampIToUint8)                \
     _(ClampDToUint8)                \
     _(ClampVToUint8)                \
     _(LoadFixedSlotV)               \
     _(LoadFixedSlotT)               \
     _(StoreFixedSlotV)              \
     _(StoreFixedSlotT)              \
     _(FunctionEnvironment)          \
+    _(ParSlice)                     \
     _(GetPropertyCacheV)            \
     _(GetPropertyCacheT)            \
     _(GetElementCacheV)             \
     _(BindNameCache)                \
     _(CallGetProperty)              \
     _(GetNameCache)                 \
     _(CallGetIntrinsicValue)        \
     _(CallsiteCloneCache)           \
@@ -179,16 +190,17 @@
     _(Floor)                        \
     _(Round)                        \
     _(In)                           \
     _(InArray)                      \
     _(InstanceOfO)                  \
     _(InstanceOfV)                  \
     _(CallInstanceOf)               \
     _(InterruptCheck)               \
+    _(ParCheckInterrupt)            \
     _(FunctionBoundary)             \
     _(GetDOMProperty)               \
     _(SetDOMProperty)               \
     _(CallDOMNative)
 
 #if defined(JS_CPU_X86)
 # include "x86/LOpcodes-x86.h"
 #elif defined(JS_CPU_X64)
--- a/js/src/ion/LinearScan.cpp
+++ b/js/src/ion/LinearScan.cpp
@@ -624,16 +624,17 @@ LinearScanAllocator::splitBlockingInterv
 {
     JS_ASSERT(allocation.isRegister());
 
     // Split current before the next fixed use.
     LiveInterval *fixed = fixedIntervals[allocation.toRegister().code()];
     if (fixed->numRanges() > 0) {
         CodePosition fixedPos = current->intersect(fixed);
         if (fixedPos != CodePosition::MIN) {
+            JS_ASSERT(fixedPos > current->start());
             JS_ASSERT(fixedPos < current->end());
             if (!splitInterval(current, fixedPos))
                 return false;
         }
     }
 
     // Split the blocking interval if it exists.
     for (IntervalIterator i(active.begin()); i != active.end(); i++) {
--- a/js/src/ion/Lowering.cpp
+++ b/js/src/ion/Lowering.cpp
@@ -109,16 +109,29 @@ LIRGenerator::visitCheckOverRecursed(MCh
         return false;
     if (!assignSafepoint(lir, ins))
         return false;
 
     return true;
 }
 
 bool
+LIRGenerator::visitParCheckOverRecursed(MParCheckOverRecursed *ins)
+{
+    LParCheckOverRecursed *lir = new LParCheckOverRecursed(
+        useRegister(ins->parSlice()),
+        temp());
+    if (!add(lir))
+        return false;
+    if (!assignSafepoint(lir, ins))
+        return false;
+    return true;
+}
+
+bool
 LIRGenerator::visitDefVar(MDefVar *ins)
 {
     LDefVar *lir = new LDefVar(useRegisterAtStart(ins->scopeChain()));
     if (!add(lir, ins))
         return false;
     if (!assignSafepoint(lir, ins))
         return false;
 
@@ -139,16 +152,23 @@ LIRGenerator::visitNewSlots(MNewSlots *i
     LNewSlots *lir = new LNewSlots(tempFixed(CallTempReg0), tempFixed(CallTempReg1),
                                    tempFixed(CallTempReg2));
     if (!assignSnapshot(lir))
         return false;
     return defineReturn(lir, ins);
 }
 
 bool
+LIRGenerator::visitNewParallelArray(MNewParallelArray *ins)
+{
+    LNewParallelArray *lir = new LNewParallelArray();
+    return define(lir, ins) && assignSafepoint(lir, ins);
+}
+
+bool
 LIRGenerator::visitNewArray(MNewArray *ins)
 {
     LNewArray *lir = new LNewArray();
     return define(lir, ins) && assignSafepoint(lir, ins);
 }
 
 bool
 LIRGenerator::visitNewObject(MNewObject *ins)
@@ -179,25 +199,51 @@ LIRGenerator::visitNewCallObject(MNewCal
 
     if (!assignSafepoint(lir, ins))
         return false;
 
     return true;
 }
 
 bool
+LIRGenerator::visitParNewCallObject(MParNewCallObject *ins)
+{
+    const LAllocation &parThreadContext = useRegister(ins->parSlice());
+    const LDefinition &temp1 = temp();
+    const LDefinition &temp2 = temp();
+
+    LParNewCallObject *lir;
+    if (ins->slots()->type() == MIRType_Slots) {
+        const LAllocation &slots = useRegister(ins->slots());
+        lir = LParNewCallObject::NewWithSlots(parThreadContext, slots,
+                                              temp1, temp2);
+    } else {
+        lir = LParNewCallObject::NewSansSlots(parThreadContext, temp1, temp2);
+    }
+
+    return define(lir, ins);
+}
+
+bool
 LIRGenerator::visitNewStringObject(MNewStringObject *ins)
 {
     JS_ASSERT(ins->input()->type() == MIRType_String);
 
     LNewStringObject *lir = new LNewStringObject(useRegister(ins->input()), temp());
     return define(lir, ins) && assignSafepoint(lir, ins);
 }
 
 bool
+LIRGenerator::visitParBailout(MParBailout *ins)
+{
+    LParBailout *lir = new LParBailout();
+    return add(lir, ins);
+}
+
+bool
 LIRGenerator::visitInitProp(MInitProp *ins)
 {
     LInitProp *lir = new LInitProp(useRegisterAtStart(ins->getObject()));
     if (!useBoxAtStart(lir, LInitProp::ValueIndex, ins->getValue()))
         return false;
 
     return add(lir, ins) && assignSafepoint(lir, ins);
 }
@@ -575,20 +621,34 @@ LIRGenerator::visitCompare(MCompare *com
     bool result;
     if (comp->tryFold(&result))
         return define(new LInteger(result), comp);
 
     // Move below the emitAtUses call if we ever implement
     // LCompareSAndBranch. Doing this now wouldn't be wrong, but doesn't
     // make sense and avoids confusion.
     if (comp->compareType() == MCompare::Compare_String) {
-        LCompareS *lir = new LCompareS(useRegister(left), useRegister(right), temp());
-        if (!define(lir, comp))
-            return false;
-        return assignSafepoint(lir, comp);
+        switch (comp->block()->info().executionMode()) {
+          case SequentialExecution:
+          {
+              LCompareS *lir = new LCompareS(useRegister(left), useRegister(right), temp());
+              if (!define(lir, comp))
+                  return false;
+              return assignSafepoint(lir, comp);
+          }
+
+          case ParallelExecution:
+          {
+              LParCompareS *lir = new LParCompareS(useFixed(left, CallTempReg0),
+                                                   useFixed(right, CallTempReg1));
+              return defineReturn(lir, comp);
+          }
+        }
+
+        JS_NOT_REACHED("Unexpected execution mode");
     }
 
     // Strict compare between value and string
     if (comp->compareType() == MCompare::Compare_StrictString) {
         JS_ASSERT(left->type() == MIRType_Value);
         JS_ASSERT(right->type() == MIRType_String);
 
         LCompareStrictS *lir = new LCompareStrictS(useRegister(right), temp(), temp());
@@ -1378,16 +1438,27 @@ LIRGenerator::visitLambda(MLambda *ins)
         return defineReturn(lir, ins) && assignSafepoint(lir, ins);
     }
 
     LLambda *lir = new LLambda(useRegister(ins->scopeChain()));
     return define(lir, ins) && assignSafepoint(lir, ins);
 }
 
 bool
+LIRGenerator::visitParLambda(MParLambda *ins)
+{
+    JS_ASSERT(!ins->fun()->hasSingletonType());
+    JS_ASSERT(!types::UseNewTypeForClone(ins->fun()));
+    LParLambda *lir = new LParLambda(useRegister(ins->parSlice()),
+                                     useRegister(ins->scopeChain()),
+                                     temp(), temp());
+    return define(lir, ins);
+}
+
+bool
 LIRGenerator::visitImplicitThis(MImplicitThis *ins)
 {
     JS_ASSERT(ins->callee()->type() == MIRType_Object);
 
     LImplicitThis *lir = new LImplicitThis(useRegister(ins->callee()));
     return assignSnapshot(lir) && defineBox(lir, ins);
 }
 
@@ -1435,16 +1506,72 @@ LIRGenerator::visitLoadSlot(MLoadSlot *i
 
 bool
 LIRGenerator::visitFunctionEnvironment(MFunctionEnvironment *ins)
 {
     return define(new LFunctionEnvironment(useRegisterAtStart(ins->function())), ins);
 }
 
 bool
+LIRGenerator::visitParSlice(MParSlice *ins)
+{
+    LParSlice *lir = new LParSlice(tempFixed(CallTempReg0));
+    return defineReturn(lir, ins);
+}
+
+bool
+LIRGenerator::visitParWriteGuard(MParWriteGuard *ins)
+{
+    return add(new LParWriteGuard(useFixed(ins->parSlice(), CallTempReg0),
+                                  useFixed(ins->object(), CallTempReg1),
+                                  tempFixed(CallTempReg2)));
+}
+
+bool
+LIRGenerator::visitParCheckInterrupt(MParCheckInterrupt *ins)
+{
+    LParCheckInterrupt *lir = new LParCheckInterrupt(
+        useRegister(ins->parSlice()),
+        temp());
+    if (!add(lir))
+        return false;
+    if (!assignSafepoint(lir, ins))
+        return false;
+    return true;
+}
+
+bool
+LIRGenerator::visitParDump(MParDump *ins)
+{
+    LParDump *lir = new LParDump();
+    useBoxFixed(lir, LParDump::Value, ins->value(), CallTempReg0, CallTempReg1);
+    return add(lir);
+}
+
+bool
+LIRGenerator::visitParNew(MParNew *ins)
+{
+    LParNew *lir = new LParNew(useRegister(ins->parSlice()),
+                               temp(), temp());
+    return define(lir, ins);
+}
+
+bool
+LIRGenerator::visitParNewDenseArray(MParNewDenseArray *ins)
+{
+    LParNewDenseArray *lir = new LParNewDenseArray(
+        useFixed(ins->parSlice(), CallTempReg0),
+        useFixed(ins->length(), CallTempReg1),
+        tempFixed(CallTempReg2),
+        tempFixed(CallTempReg3),
+        tempFixed(CallTempReg4));
+    return defineReturn(lir, ins);
+}
+
+bool
 LIRGenerator::visitStoreSlot(MStoreSlot *ins)
 {
     LInstruction *lir;
 
     switch (ins->value()->type()) {
       case MIRType_Value:
         lir = new LStoreSlotV(useRegister(ins->slots()));
         if (!useBox(lir, LStoreSlotV::Value, ins->value()))
--- a/js/src/ion/Lowering.h
+++ b/js/src/ion/Lowering.h
@@ -74,23 +74,29 @@ class LIRGenerator : public LIRGenerator
 
     // Visitor hooks are explicit, to give CPU-specific versions a chance to
     // intercept without a bunch of explicit gunk in the .cpp.
     bool visitParameter(MParameter *param);
     bool visitCallee(MCallee *callee);
     bool visitGoto(MGoto *ins);
     bool visitTableSwitch(MTableSwitch *tableswitch);
     bool visitNewSlots(MNewSlots *ins);
+    bool visitNewParallelArray(MNewParallelArray *ins);
     bool visitNewArray(MNewArray *ins);
     bool visitNewObject(MNewObject *ins);
     bool visitNewDeclEnvObject(MNewDeclEnvObject *ins);
     bool visitNewCallObject(MNewCallObject *ins);
     bool visitNewStringObject(MNewStringObject *ins);
+    bool visitParNew(MParNew *ins);
+    bool visitParNewCallObject(MParNewCallObject *ins);
+    bool visitParNewDenseArray(MParNewDenseArray *ins);
+    bool visitParBailout(MParBailout *ins);
     bool visitInitProp(MInitProp *ins);
     bool visitCheckOverRecursed(MCheckOverRecursed *ins);
+    bool visitParCheckOverRecursed(MParCheckOverRecursed *ins);
     bool visitDefVar(MDefVar *ins);
     bool visitDefFun(MDefFun *ins);
     bool visitPrepareCall(MPrepareCall *ins);
     bool visitPassArg(MPassArg *arg);
     bool visitCreateThisWithTemplate(MCreateThisWithTemplate *ins);
     bool visitCreateThisWithProto(MCreateThisWithProto *ins);
     bool visitCreateThis(MCreateThis *ins);
     bool visitReturnFromCtor(MReturnFromCtor *ins);
@@ -131,23 +137,28 @@ class LIRGenerator : public LIRGenerator
     bool visitOsrScopeChain(MOsrScopeChain *object);
     bool visitToDouble(MToDouble *convert);
     bool visitToInt32(MToInt32 *convert);
     bool visitTruncateToInt32(MTruncateToInt32 *truncate);
     bool visitToString(MToString *convert);
     bool visitRegExp(MRegExp *ins);
     bool visitRegExpTest(MRegExpTest *ins);
     bool visitLambda(MLambda *ins);
+    bool visitParLambda(MParLambda *ins);
     bool visitImplicitThis(MImplicitThis *ins);
     bool visitSlots(MSlots *ins);
     bool visitElements(MElements *ins);
     bool visitConstantElements(MConstantElements *ins);
     bool visitConvertElementsToDoubles(MConvertElementsToDoubles *ins);
     bool visitLoadSlot(MLoadSlot *ins);
     bool visitFunctionEnvironment(MFunctionEnvironment *ins);
+    bool visitParSlice(MParSlice *ins);
+    bool visitParWriteGuard(MParWriteGuard *ins);
+    bool visitParCheckInterrupt(MParCheckInterrupt *ins);
+    bool visitParDump(MParDump *ins);
     bool visitStoreSlot(MStoreSlot *ins);
     bool visitTypeBarrier(MTypeBarrier *ins);
     bool visitMonitorTypes(MMonitorTypes *ins);
     bool visitArrayLength(MArrayLength *ins);
     bool visitTypedArrayLength(MTypedArrayLength *ins);
     bool visitTypedArrayElements(MTypedArrayElements *ins);
     bool visitInitializedLength(MInitializedLength *ins);
     bool visitSetInitializedLength(MSetInitializedLength *ins);
--- a/js/src/ion/MCallOptimize.cpp
+++ b/js/src/ion/MCallOptimize.cpp
@@ -2,16 +2,18 @@
  * 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/. */
 
 #include "jslibmath.h"
 #include "jsmath.h"
+#include "builtin/ParallelArray.h"
+#include "builtin/TestingFunctions.h"
 
 #include "MIR.h"
 #include "MIRGraph.h"
 #include "IonBuilder.h"
 
 #include "vm/StringObject-inl.h"
 
 namespace js {
@@ -71,16 +73,32 @@ IonBuilder::inlineNativeCall(CallInfo &c
         return inlineStrCharAt(callInfo);
 
     // RegExp natives.
     if (native == regexp_exec && !CallResultEscapes(pc))
         return inlineRegExpTest(callInfo);
     if (native == regexp_test)
         return inlineRegExpTest(callInfo);
 
+    // Parallel Array
+    if (native == intrinsic_UnsafeSetElement)
+        return inlineUnsafeSetElement(callInfo);
+    if (native == testingFunc_inParallelSection)
+        return inlineForceSequentialOrInParallelSection(callInfo);
+    if (native == intrinsic_NewDenseArray)
+        return inlineNewDenseArray(callInfo);
+
+    // Self-hosting
+    if (native == intrinsic_ThrowError)
+        return inlineThrowError(callInfo);
+#ifdef DEBUG
+    if (native == intrinsic_Dump)
+        return inlineDump(callInfo);
+#endif
+
     return InliningStatus_NotInlined;
 }
 
 types::StackTypeSet *
 IonBuilder::getInlineReturnTypeSet()
 {
     types::StackTypeSet *barrier;
     types::StackTypeSet *returnTypes = oracle->returnTypeSet(script(), pc, &barrier);
@@ -841,10 +859,291 @@ IonBuilder::inlineRegExpTest(CallInfo &c
     current->add(match);
     current->push(match);
     if (!resumeAfter(match))
         return InliningStatus_Error;
 
     return InliningStatus_Inlined;
 }
 
+IonBuilder::InliningStatus
+IonBuilder::inlineUnsafeSetElement(CallInfo &callInfo)
+{
+    uint32_t argc = callInfo.argc();
+    if (argc < 3 || (argc % 3) != 0 || callInfo.constructing())
+        return InliningStatus_NotInlined;
+
+    /* Important:
+     *
+     * Here we inline each of the stores resulting from a call to
+     * %UnsafeSetElement().  It is essential that these stores occur
+     * atomically and cannot be interrupted by a stack or recursion
+     * check.  If this is not true, race conditions can occur.
+     */
+
+    for (uint32_t base = 0; base < argc; base += 3) {
+        uint32_t arri = base + 1;
+        uint32_t idxi = base + 2;
+
+        types::StackTypeSet *obj = getInlineArgTypeSet(callInfo, arri);
+        types::StackTypeSet *id = getInlineArgTypeSet(callInfo, idxi);
+
+        int arrayType;
+        if (!oracle->elementAccessIsDenseNative(obj, id) &&
+            !oracle->elementAccessIsTypedArray(obj, id, &arrayType))
+        {
+            return InliningStatus_NotInlined;
+        }
+    }
+
+    callInfo.unwrapArgs();
+
+    // Push the result first so that the stack depth matches up for
+    // the potential bailouts that will occur in the stores below.
+    MConstant *udef = MConstant::New(UndefinedValue());
+    current->add(udef);
+    current->push(udef);
+
+    for (uint32_t base = 0; base < argc; base += 3) {
+        uint32_t arri = base + 1;
+        uint32_t idxi = base + 2;
+
+        types::StackTypeSet *obj = getInlineArgTypeSet(callInfo, arri);
+        types::StackTypeSet *id = getInlineArgTypeSet(callInfo, idxi);
+
+        if (oracle->elementAccessIsDenseNative(obj, id)) {
+            if (!inlineUnsafeSetDenseArrayElement(callInfo, base))
+                return InliningStatus_Error;
+            continue;
+        }
+
+        int arrayType;
+        if (oracle->elementAccessIsTypedArray(obj, id, &arrayType)) {
+            if (!inlineUnsafeSetTypedArrayElement(callInfo, base, arrayType))
+                return InliningStatus_Error;
+            continue;
+        }
+
+        JS_NOT_REACHED("Element access not dense array nor typed array");
+    }
+
+    return InliningStatus_Inlined;
+}
+
+bool
+IonBuilder::inlineUnsafeSetDenseArrayElement(CallInfo &callInfo, uint32_t base)
+{
+    // Note: we do not check the conditions that are asserted as true
+    // in intrinsic_UnsafeSetElement():
+    // - arr is a dense array
+    // - idx < initialized length
+    // Furthermore, note that inference should be propagating
+    // the type of the value to the JSID_VOID property of the array.
+
+    uint32_t arri = base + 1;
+    uint32_t idxi = base + 2;
+    uint32_t elemi = base + 3;
+
+    MElements *elements = MElements::New(callInfo.getArg(arri));
+    current->add(elements);
+
+    MToInt32 *id = MToInt32::New(callInfo.getArg(idxi));
+    current->add(id);
+
+    // We disable the hole check for this store.  This implies that if
+    // there were setters on the prototype, they would not be invoked.
+    // But this is actually the desired behavior.
+
+    MStoreElement *store = MStoreElement::New(elements, id,
+                                              callInfo.getArg(elemi),
+                                              /* needsHoleCheck = */ false);
+    store->setRacy();
+
+    current->add(store);
+
+    if (!resumeAfter(store))
+        return false;
+
+    return true;
+}
+
+bool
+IonBuilder::inlineUnsafeSetTypedArrayElement(CallInfo &callInfo,
+                                             uint32_t base,
+                                             int arrayType)
+{
+    // Note: we do not check the conditions that are asserted as true
+    // in intrinsic_UnsafeSetElement():
+    // - arr is a typed array
+    // - idx < length
+
+    uint32_t arri = base + 1;
+    uint32_t idxi = base + 2;
+    uint32_t elemi = base + 3;
+
+    MInstruction *elements = getTypedArrayElements(callInfo.getArg(arri));
+    current->add(elements);
+
+    MToInt32 *id = MToInt32::New(callInfo.getArg(idxi));
+    current->add(id);
+
+    MDefinition *value = callInfo.getArg(elemi);
+    if (arrayType == TypedArray::TYPE_UINT8_CLAMPED) {
+        value = MClampToUint8::New(value);
+        current->add(value->toInstruction());
+    }
+
+    MStoreTypedArrayElement *store = MStoreTypedArrayElement::New(elements, id, value, arrayType);
+    store->setRacy();
+
+    current->add(store);
+
+    if (!resumeAfter(store))
+        return false;
+
+    return true;
+}
+
+IonBuilder::InliningStatus
+IonBuilder::inlineForceSequentialOrInParallelSection(CallInfo &callInfo)
+{
+    if (callInfo.constructing())
+        return InliningStatus_NotInlined;
+
+    ExecutionMode executionMode = info().executionMode();
+    switch (executionMode) {
+      case SequentialExecution:
+        // In sequential mode, leave as is, because we'd have to
+        // access the "in warmup" flag of the runtime.
+        return InliningStatus_NotInlined;
+
+      case ParallelExecution:
+        // During Parallel Exec, we always force sequential, so
+        // replace with true.  This permits UCE to eliminate the
+        // entire path as dead, which is important.
+        callInfo.unwrapArgs();
+        MConstant *ins = MConstant::New(BooleanValue(true));
+        current->add(ins);
+        current->push(ins);
+        return InliningStatus_Inlined;
+    }
+
+    JS_NOT_REACHED("Invalid execution mode");
+}
+
+IonBuilder::InliningStatus
+IonBuilder::inlineNewDenseArray(CallInfo &callInfo)
+{
+    if (callInfo.constructing() || callInfo.argc() != 1)
+        return InliningStatus_NotInlined;
+
+    // For now, in seq. mode we just call the C function.  In
+    // par. mode we use inlined MIR.
+    ExecutionMode executionMode = info().executionMode();
+    switch (executionMode) {
+      case SequentialExecution:
+        return inlineNewDenseArrayForSequentialExecution(callInfo);
+      case ParallelExecution:
+        return inlineNewDenseArrayForParallelExecution(callInfo);
+    }
+
+    JS_NOT_REACHED("unknown ExecutionMode");
+}
+
+IonBuilder::InliningStatus
+IonBuilder::inlineNewDenseArrayForSequentialExecution(CallInfo &callInfo)
+{
+    // not yet implemented; in seq. mode the C function is not so bad
+    return InliningStatus_NotInlined;
+}
+
+IonBuilder::InliningStatus
+IonBuilder::inlineNewDenseArrayForParallelExecution(CallInfo &callInfo)
+{
+    // Create the new parallel array object.  Parallel arrays have specially
+    // constructed type objects, so we can only perform the inlining if we
+    // already have one of these type objects.
+    types::StackTypeSet *returnTypes = getInlineReturnTypeSet();
+    if (returnTypes->getKnownTypeTag() != JSVAL_TYPE_OBJECT)
+        return InliningStatus_NotInlined;
+    if (returnTypes->getObjectCount() != 1)
+        return InliningStatus_NotInlined;
+    types::TypeObject *typeObject = returnTypes->getTypeObject(0);
+
+    RootedObject templateObject(cx, NewDenseAllocatedArray(cx, 0));
+    if (!templateObject)
+        return InliningStatus_Error;
+    templateObject->setType(typeObject);
+
+    MParNewDenseArray *newObject = new MParNewDenseArray(graph().parSlice(),
+                                                         callInfo.getArg(1),
+                                                         templateObject);
+    current->add(newObject);
+    current->push(newObject);
+
+    return InliningStatus_Inlined;
+}
+
+IonBuilder::InliningStatus
+IonBuilder::inlineThrowError(CallInfo &callInfo)
+{
+    // In Parallel Execution, convert %ThrowError() into a bailout.
+
+    if (callInfo.constructing())
+        return InliningStatus_NotInlined;
+
+    ExecutionMode executionMode = info().executionMode();
+    switch (executionMode) {
+      case SequentialExecution:
+        return InliningStatus_NotInlined;
+      case ParallelExecution:
+        break;
+    }
+
+    callInfo.unwrapArgs();
+
+    MParBailout *bailout = new MParBailout();
+    if (!bailout)
+        return InliningStatus_Error;
+    current->end(bailout);
+
+    current = newBlock(pc);
+    if (!current)
+        return InliningStatus_Error;
+
+    MConstant *udef = MConstant::New(UndefinedValue());
+    current->add(udef);
+    current->push(udef);
+
+    return InliningStatus_Inlined;
+}
+
+IonBuilder::InliningStatus
+IonBuilder::inlineDump(CallInfo &callInfo)
+{
+    // In Parallel Execution, call ParDump.  We just need a debugging
+    // aid!
+
+    if (callInfo.constructing())
+        return InliningStatus_NotInlined;
+
+    ExecutionMode executionMode = info().executionMode();
+    switch (executionMode) {
+      case SequentialExecution:
+        return InliningStatus_NotInlined;
+      case ParallelExecution:
+        break;
+    }
+
+    callInfo.unwrapArgs();
+
+    MParDump *dump = new MParDump(callInfo.getArg(1));
+    current->add(dump);
+
+    MConstant *udef = MConstant::New(UndefinedValue());
+    current->add(udef);
+    current->push(udef);
+
+    return InliningStatus_Inlined;
+}
+
 } // namespace ion
 } // namespace js
--- a/js/src/ion/MIR.cpp
+++ b/js/src/ion/MIR.cpp
@@ -1875,16 +1875,39 @@ MBeta::computeRange()
         IonSpew(IonSpew_Range, "Marking block for inst %d unexitable", id());
         block()->setEarlyAbort();
     } else {
         setRange(range);
     }
 }
 
 bool
+MNewObject::shouldUseVM() const
+{
+    return templateObject()->hasSingletonType() ||
+           templateObject()->hasDynamicSlots();
+}
+
+bool
+MNewArray::shouldUseVM() const
+{
+    JS_ASSERT(count() < JSObject::NELEMENTS_LIMIT);
+
+    size_t maxArraySlots =
+        gc::GetGCKindSlots(gc::FINALIZE_OBJECT_LAST) - ObjectElements::VALUES_PER_HEADER;
+
+    // Allocate space using the VMCall
+    // when mir hints it needs to get allocated immediatly,
+    // but only when data doesn't fit the available array slots.
+    bool allocating = isAllocating() && count() > maxArraySlots;
+
+    return templateObject()->hasSingletonType() || allocating;
+}
+
+bool
 MLoadFixedSlot::mightAlias(MDefinition *store)
 {
     if (store->isStoreFixedSlot() && store->toStoreFixedSlot()->slot() != slot())
         return false;
     return true;
 }
 
 bool
--- a/js/src/ion/MIR.h
+++ b/js/src/ion/MIR.h
@@ -305,16 +305,17 @@ class MDefinition : public MNode
         range_(),
         resultType_(MIRType_None),
         flags_(0),
         dependency_(NULL),
         trackedPc_(NULL)
     { }
 
     virtual Opcode op() const = 0;
+    virtual const char *opName() const = 0;
     void printName(FILE *fp);
     static void PrintOpcodeName(FILE *fp, Opcode op);
     virtual void printOpcode(FILE *fp);
 
     void setTrackedPc(jsbytecode *pc) {
         trackedPc_ = pc;
     }
 
@@ -574,16 +575,19 @@ class MInstruction
         return resumePoint_;
     }
 };
 
 #define INSTRUCTION_HEADER(opcode)                                          \
     Opcode op() const {                                                     \
         return MDefinition::Op_##opcode;                                    \
     }                                                                       \
+    const char *opName() const {                                            \
+        return #opcode;                                                     \
+    }                                                                       \
     bool accept(MInstructionVisitor *visitor) {                             \
         return visitor->visit##opcode(this);                                \
     }
 
 template <size_t Arity>
 class MAryInstruction : public MInstruction
 {
   protected:
@@ -605,16 +609,25 @@ class MAryInstruction : public MInstruct
     size_t numOperands() const {
         return Arity;
     }
 };
 
 class MNullaryInstruction : public MAryInstruction<0>
 { };
 
+class MUnaryInstruction : public MAryInstruction<1>
+{
+  protected:
+    MUnaryInstruction(MDefinition *ins)
+    {
+        setOperand(0, ins);
+    }
+};
+
 // Generates an LSnapshot without further effect.
 class MStart : public MNullaryInstruction
 {
   public:
     enum StartType {
         StartType_Default,
         StartType_Osr
     };
@@ -1063,16 +1076,38 @@ class MThrow
     TypePolicy *typePolicy() {
         return this;
     }
     virtual AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 };
 
+class MNewParallelArray : public MNullaryInstruction
+{
+    CompilerRootObject templateObject_;
+
+    MNewParallelArray(JSObject *templateObject)
+      : templateObject_(templateObject)
+    {
+        setResultType(MIRType_Object);
+    }
+
+  public:
+    INSTRUCTION_HEADER(NewParallelArray);
+
+    static MNewParallelArray *New(JSObject *templateObject) {
+        return new MNewParallelArray(templateObject);
+    }
+
+    JSObject *templateObject() const {
+        return templateObject_;
+    }
+};
+
 class MNewArray : public MNullaryInstruction
 {
   public:
     enum AllocatingBehaviour {
         NewArray_Allocating,
         NewArray_Unallocating
     };
 
@@ -1102,16 +1137,20 @@ class MNewArray : public MNullaryInstruc
     JSObject *templateObject() const {
         return templateObject_;
     }
 
     bool isAllocating() const {
         return allocating_ == NewArray_Allocating;
     }
 
+    // Returns true if the code generator should call through to the
+    // VM rather than the fast path.
+    bool shouldUseVM() const;
+
     // NewArray is marked as non-effectful because all our allocations are
     // either lazy when we are using "new Array(length)" or bounded by the
     // script or the stack size when we are using "new Array(...)" or "[...]"
     // notations.  So we might have to allocate the array twice if we bail
     // during the computation of the first element of the square braket
     // notation.
     virtual AliasSet getAliasSet() const {
         return AliasSet::None();
@@ -1130,21 +1169,64 @@ class MNewObject : public MNullaryInstru
 
   public:
     INSTRUCTION_HEADER(NewObject)
 
     static MNewObject *New(JSObject *templateObject) {
         return new MNewObject(templateObject);
     }
 
+    // Returns true if the code generator should call through to the
+    // VM rather than the fast path.
+    bool shouldUseVM() const;
+
     JSObject *templateObject() const {
         return templateObject_;
     }
 };
 
+// Could be allocating either a new array or a new object.
+class MParNew : public MUnaryInstruction
+{
+    CompilerRootObject templateObject_;
+
+  public:
+    INSTRUCTION_HEADER(ParNew);
+
+    MParNew(MDefinition *parSlice,
+            JSObject *templateObject)
+      : MUnaryInstruction(parSlice),
+        templateObject_(templateObject)
+    {
+        setResultType(MIRType_Object);
+    }
+
+    MDefinition *parSlice() const {
+        return getOperand(0);
+    }
+
+    JSObject *templateObject() const {
+        return templateObject_;
+    }
+};
+
+// Could be allocating either a new array or a new object.
+class MParBailout : public MAryControlInstruction<0, 0>
+{
+  public:
+    INSTRUCTION_HEADER(ParBailout);
+
+    MParBailout()
+      : MAryControlInstruction()
+    {
+        setResultType(MIRType_Undefined);
+        setGuard();
+    }
+};
+
 // Slow path for adding a property to an object without a known base.
 class MInitProp
   : public MAryInstruction<2>,
     public MixPolicy<ObjectPolicy<0>, BoxPolicy<1> >
 {
   public:
     CompilerRootPropertyName name_;
 
@@ -1355,25 +1437,16 @@ class MApplyArgs
         return getOperand(2);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
 };
 
-class MUnaryInstruction : public MAryInstruction<1>
-{
-  protected:
-    MUnaryInstruction(MDefinition *ins)
-    {
-        setOperand(0, ins);
-    }
-};
-
 class MBinaryInstruction : public MAryInstruction<2>
 {
   protected:
     MBinaryInstruction(MDefinition *left, MDefinition *right)
     {
         setOperand(0, left);
         setOperand(1, right);
     }
@@ -3183,16 +3256,55 @@ class MOsrScopeChain : public MUnaryInst
 
 // Check the current frame for over-recursion past the global stack limit.
 class MCheckOverRecursed : public MNullaryInstruction
 {
   public:
     INSTRUCTION_HEADER(CheckOverRecursed)
 };
 
+// Check the current frame for over-recursion past the global stack limit.
+// Uses the per-thread recursion limit.
+class MParCheckOverRecursed : public MUnaryInstruction
+{
+  public:
+    INSTRUCTION_HEADER(ParCheckOverRecursed);
+
+    MParCheckOverRecursed(MDefinition *parForkJoinSlice)
+      : MUnaryInstruction(parForkJoinSlice)
+    {
+        setResultType(MIRType_None);
+        setGuard();
+        setMovable();
+    }
+
+    MDefinition *parSlice() const {
+        return getOperand(0);
+    }
+};
+
+// Check for an interrupt (or rendezvous) in parallel mode.
+class MParCheckInterrupt : public MUnaryInstruction
+{
+  public:
+    INSTRUCTION_HEADER(ParCheckInterrupt);
+
+    MParCheckInterrupt(MDefinition *parForkJoinSlice)
+      : MUnaryInstruction(parForkJoinSlice)
+    {
+        setResultType(MIRType_None);
+        setGuard();
+        setMovable();
+    }
+
+    MDefinition *parSlice() const {
+        return getOperand(0);
+    }
+};
+
 // Check the script's use count and trigger recompilation to inline
 // calls when the script becomes hot.
 class MRecompileCheck : public MNullaryInstruction
 {
     uint32_t minUses_;
 
     MRecompileCheck(uint32_t minUses)
       : minUses_(minUses)
@@ -3396,16 +3508,57 @@ class MLambda
     JSFunction *fun() const {
         return fun_;
     }
     TypePolicy *typePolicy() {
         return this;
     }
 };
 
+class MParLambda
+  : public MBinaryInstruction,
+    public SingleObjectPolicy
+{
+    CompilerRootFunction fun_;
+
+    MParLambda(MDefinition *parSlice,
+               MDefinition *scopeChain, JSFunction *fun)
+      : MBinaryInstruction(parSlice, scopeChain), fun_(fun)
+    {
+        setResultType(MIRType_Object);
+    }
+
+  public:
+    INSTRUCTION_HEADER(ParLambda);
+
+    static MParLambda *New(MDefinition *parSlice,
+                           MDefinition *scopeChain, JSFunction *fun) {
+        return new MParLambda(parSlice, scopeChain, fun);
+    }
+
+    static MParLambda *New(MDefinition *parSlice,
+                           MLambda *originalInstruction) {
+        return New(parSlice,
+                   originalInstruction->scopeChain(),
+                   originalInstruction->fun());
+    }
+
+    MDefinition *parSlice() const {
+        return getOperand(0);
+    }
+
+    MDefinition *scopeChain() const {
+        return getOperand(1);
+    }
+
+    JSFunction *fun() const {
+        return fun_;
+    }
+};
+
 // Determines the implicit |this| value for function calls.
 class MImplicitThis
   : public MUnaryInstruction,
     public SingleObjectPolicy
 {
     MImplicitThis(MDefinition *callee)
       : MUnaryInstruction(callee)
     {
@@ -3955,37 +4108,45 @@ class MLoadElementHole
         return AliasSet::Load(AliasSet::Element);
     }
 };
 
 class MStoreElementCommon
 {
     bool needsBarrier_;
     MIRType elementType_;
+    bool racy_; // if true, exempted from normal data race req. during par. exec.
 
   protected:
     MStoreElementCommon()
       : needsBarrier_(false),
-        elementType_(MIRType_Value)
+        elementType_(MIRType_Value),
+        racy_(false)
     { }
 
   public:
     MIRType elementType() const {
         return elementType_;
     }
     void setElementType(MIRType elementType) {
         JS_ASSERT(elementType != MIRType_None);
         elementType_ = elementType;
     }
     bool needsBarrier() const {
         return needsBarrier_;
     }
     void setNeedsBarrier() {
         needsBarrier_ = true;
     }
+    bool racy() const {
+        return racy_;
+    }
+    void setRacy() {
+        racy_ = true;
+    }
 };
 
 // Store a value to a dense array slots vector.
 class MStoreElement
   : public MAryInstruction<3>,
     public MStoreElementCommon,
     public SingleObjectPolicy
 {
@@ -4283,19 +4444,22 @@ class MLoadTypedArrayElementHole
 };
 
 class MStoreTypedArrayElement
   : public MTernaryInstruction,
     public StoreTypedArrayPolicy
 {
     int arrayType_;
 
+    // See note in MStoreElementCommon.
+    bool racy_;
+
     MStoreTypedArrayElement(MDefinition *elements, MDefinition *index, MDefinition *value,
                             int arrayType)
-      : MTernaryInstruction(elements, index, value), arrayType_(arrayType)
+      : MTernaryInstruction(elements, index, value), arrayType_(arrayType), racy_(false)
     {
         setResultType(MIRType_Value);
         setMovable();
         JS_ASSERT(elements->type() == MIRType_Elements);
         JS_ASSERT(index->type() == MIRType_Int32);
         JS_ASSERT(arrayType >= 0 && arrayType < TypedArray::TYPE_MAX);
     }
 
@@ -4329,16 +4493,22 @@ class MStoreTypedArrayElement
         return getOperand(1);
     }
     MDefinition *value() const {
         return getOperand(2);
     }
     AliasSet getAliasSet() const {
         return AliasSet::Store(AliasSet::TypedArrayElement);
     }
+    bool racy() const {
+        return racy_;
+    }
+    void setRacy() {
+        racy_ = true;
+    }
 };
 
 // Clamp input to range [0, 255] for Uint8ClampedArray.
 class MClampToUint8
   : public MUnaryInstruction,
     public ClampPolicy
 {
     MClampToUint8(MDefinition *input)
@@ -5049,16 +5219,37 @@ class MFunctionEnvironment
         return new MFunctionEnvironment(function);
     }
 
     MDefinition *function() const {
         return getOperand(0);
     }
 };
 
+// Loads the current js::ForkJoinSlice*.
+// Only applicable in ParallelExecution.
+class MParSlice
+  : public MNullaryInstruction
+{
+  public:
+    MParSlice()
+        : MNullaryInstruction()
+    {
+        setResultType(MIRType_ForkJoinSlice);
+    }
+
+    INSTRUCTION_HEADER(ParSlice);
+
+    AliasSet getAliasSet() const {
+        // Indicate that this instruction reads nothing, stores nothing.
+        // (For all intents and purposes)
+        return AliasSet::None();
+    }
+};
+
 // Store to vp[slot] (slots that are not inline in an object).
 class MStoreSlot
   : public MBinaryInstruction,
     public SingleObjectPolicy
 {
     uint32_t slot_;
     MIRType slotType_;
     bool needsBarrier_;
@@ -5890,16 +6081,72 @@ class MGetArgument
     bool congruentTo(MDefinition *const &ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
    }
 };
 
+class MParWriteGuard
+  : public MBinaryInstruction,
+    public ObjectPolicy<1>
+{
+    MParWriteGuard(MDefinition *parThreadContext,
+                   MDefinition *obj)
+      : MBinaryInstruction(parThreadContext, obj)
+    {
+        setResultType(MIRType_None);
+        setGuard();
+        setMovable();
+    }
+
+  public:
+    INSTRUCTION_HEADER(ParWriteGuard);
+
+    static MParWriteGuard *New(MDefinition *parThreadContext, MDefinition *obj) {
+        return new MParWriteGuard(parThreadContext, obj);
+    }
+    MDefinition *parSlice() const {
+        return getOperand(0);
+    }
+    MDefinition *object() const {
+        return getOperand(1);
+    }
+    BailoutKind bailoutKind() const {
+        return Bailout_Normal;
+    }
+    AliasSet getAliasSet() const {
+        return AliasSet::None();
+    }
+};
+
+class MParDump
+  : public MUnaryInstruction,
+    public BoxPolicy<0>
+{
+  public:
+    INSTRUCTION_HEADER(ParDump);
+
+    MParDump(MDefinition *v)
+      : MUnaryInstruction(v)
+    {
+        setResultType(MIRType_None);
+    }
+
+    MDefinition *value() const {
+        return getOperand(0);
+    }
+
+    TypePolicy *typePolicy() {
+        return this;
+    }
+};
+
+
 // Given a value, guard that the value is in a particular TypeSet, then returns
 // that value.
 class MTypeBarrier : public MUnaryInstruction
 {
     BailoutKind bailoutKind_;
     const types::StackTypeSet *typeSet_;
 
     MTypeBarrier(MDefinition *def, const types::StackTypeSet *types)
@@ -6040,24 +6287,69 @@ class MNewCallObject : public MUnaryInst
 
     static MNewCallObject *New(HandleObject templateObj, MDefinition *slots) {
         return new MNewCallObject(templateObj, slots);
     }
 
     MDefinition *slots() {
         return getOperand(0);
     }
-    JSObject *templateObj() {
+    JSObject *templateObject() {
         return templateObj_;
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 };
 
+class MParNewCallObject : public MBinaryInstruction
+{
+    CompilerRootObject templateObj_;
+
+    MParNewCallObject(MDefinition *parSlice,
+                      JSObject *templateObj, MDefinition *slots)
+        : MBinaryInstruction(parSlice, slots),
+          templateObj_(templateObj)
+    {
+        setResultType(MIRType_Object);
+    }
+
+  public:
+    INSTRUCTION_HEADER(ParNewCallObject);
+
+    static MParNewCallObject *New(MDefinition *parSlice,
+                                  JSObject *templateObj,
+                                  MDefinition *slots) {
+        return new MParNewCallObject(parSlice, templateObj, slots);
+    }
+
+    static MParNewCallObject *New(MDefinition *parSlice,
+                                  MNewCallObject *originalInstruction) {
+        return New(parSlice,
+                   originalInstruction->templateObject(),
+                   originalInstruction->slots());
+    }
+
+    MDefinition *parSlice() const {
+        return getOperand(0);
+    }
+
+    MDefinition *slots() const {
+        return getOperand(1);
+    }
+
+    JSObject *templateObj() const {
+        return templateObj_;
+    }
+
+    AliasSet getAliasSet() const {
+        return AliasSet::None();
+    }
+};
+
 class MNewStringObject :
   public MUnaryInstruction,
   public StringPolicy
 {
     CompilerRootObject templateObj_;
 
     MNewStringObject(MDefinition *input, HandleObject templateObj)
       : MUnaryInstruction(input),
@@ -6153,16 +6445,48 @@ class MEnclosingScope : public MLoadFixe
     }
 
     AliasSet getAliasSet() const {
         // ScopeObject reserved slots are immutable.
         return AliasSet::None();
     }
 };
 
+// Creates a dense array of the given length.
+//
+// Note: the template object should be an *empty* dense array!
+class MParNewDenseArray : public MBinaryInstruction
+{
+    CompilerRootObject templateObject_;
+
+  public:
+    INSTRUCTION_HEADER(ParNewDenseArray);
+
+    MParNewDenseArray(MDefinition *parSlice,
+                      MDefinition *length,
+                      JSObject *templateObject)
+      : MBinaryInstruction(parSlice, length),
+        templateObject_(templateObject)
+    {
+        setResultType(MIRType_Object);
+    }
+
+    MDefinition *parSlice() const {
+        return getOperand(0);
+    }
+
+    MDefinition *length() const {
+        return getOperand(1);
+    }
+
+    JSObject *templateObject() const {
+        return templateObject_;
+    }
+};
+
 // A resume point contains the information needed to reconstruct the interpreter
 // state from a position in the JIT. See the big comment near resumeAfter() in
 // IonBuilder.cpp.
 class MResumePoint : public MNode
 {
   public:
     enum Mode {
         ResumeAt,    // Resume until before the current instruction
--- a/js/src/ion/MIRGraph.cpp
+++ b/js/src/ion/MIRGraph.cpp
@@ -62,16 +62,47 @@ MIRGraph::insertBlockAfter(MBasicBlock *
 }
 
 void
 MIRGraph::unmarkBlocks() {
     for (MBasicBlockIterator i(blocks_.begin()); i != blocks_.end(); i++)
         i->unmark();
 }
 
+MDefinition *
+MIRGraph::parSlice() {
+    // Search the entry block to find a par slice instruction.  If we do not
+    // find one, add one after the Start instruction.
+    //
+    // Note: the original design used a field in MIRGraph to cache the
+    // parSlice rather than searching for it again.  However, this
+    // could become out of date due to DCE.  Given that we do not
+    // generally have to search very far to find the par slice
+    // instruction if it exists, and that we don't look for it that
+    // often, I opted to simply eliminate the cache and search anew
+    // each time, so that it is that much easier to keep the IR
+    // coherent. - nmatsakis
+
+    MBasicBlock *entry = entryBlock();
+    JS_ASSERT(entry->info().executionMode() == ParallelExecution);
+
+    MInstruction *start = NULL;
+    for (MInstructionIterator ins(entry->begin()); ins != entry->end(); ins++) {
+        if (ins->isParSlice())
+            return *ins;
+        else if (ins->isStart())
+            start = *ins;
+    }
+    JS_ASSERT(start);
+
+    MParSlice *parSlice = new MParSlice();
+    entry->insertAfter(start, parSlice);
+    return parSlice;
+}
+
 MBasicBlock *
 MBasicBlock::New(MIRGraph &graph, CompileInfo &info,
                  MBasicBlock *pred, jsbytecode *entryPc, Kind kind)
 {
     MBasicBlock *block = new MBasicBlock(graph, info, entryPc, kind);
     if (!block->init())
         return NULL;
 
@@ -122,16 +153,32 @@ MBasicBlock::NewPendingLoopHeader(MIRGra
 }
 
 MBasicBlock *
 MBasicBlock::NewSplitEdge(MIRGraph &graph, CompileInfo &info, MBasicBlock *pred)
 {
     return MBasicBlock::New(graph, info, pred, pred->pc(), SPLIT_EDGE);
 }
 
+MBasicBlock *
+MBasicBlock::NewParBailout(MIRGraph &graph, CompileInfo &info,
+                           MBasicBlock *pred, jsbytecode *entryPc)
+{
+    MBasicBlock *block = MBasicBlock::New(graph, info, pred, entryPc, NORMAL);
+    if (!block)
+        return NULL;
+
+    MParBailout *bailout = new MParBailout();
+    if (!bailout)
+        return NULL;
+
+    block->end(bailout);
+    return block;
+}
+
 MBasicBlock::MBasicBlock(MIRGraph &graph, CompileInfo &info, jsbytecode *pc, Kind kind)
     : earlyAbort_(false),
     graph_(graph),
     info_(info),
     stackPosition_(info_.firstStackSlot()),
     lastIns_(NULL),
     pc_(pc),
     lir_(NULL),
@@ -725,24 +772,37 @@ MBasicBlock::numSuccessors() const
 
 MBasicBlock *
 MBasicBlock::getSuccessor(size_t index) const
 {
     JS_ASSERT(lastIns());
     return lastIns()->getSuccessor(index);
 }
 
+size_t
+MBasicBlock::getSuccessorIndex(MBasicBlock *block) const
+{
+    JS_ASSERT(lastIns());
+    for (size_t i = 0; i < numSuccessors(); i++) {
+        if (getSuccessor(i) == block)
+            return i;
+    }
+    JS_NOT_REACHED("Invalid successor");
+}
+
 void
 MBasicBlock::replaceSuccessor(size_t pos, MBasicBlock *split)
 {
     JS_ASSERT(lastIns());
-    lastIns()->replaceSuccessor(pos, split);
 
-    // Note, successors-with-phis is not yet set.
-    JS_ASSERT(!successorWithPhis_);
+    // Note, during split-critical-edges, successors-with-phis is not yet set.
+    // During PAA, this case is handled before we enter.
+    JS_ASSERT_IF(successorWithPhis_, successorWithPhis_ != getSuccessor(pos));
+
+    lastIns()->replaceSuccessor(pos, split);
 }
 
 void
 MBasicBlock::replacePredecessor(MBasicBlock *old, MBasicBlock *split)
 {
     for (size_t i = 0; i < numPredecessors(); i++) {
         if (getPredecessor(i) == old) {
             predecessors_[i] = split;
@@ -788,16 +848,17 @@ MBasicBlock::removePredecessor(MBasicBlo
                 getPredecessor(j)->setSuccessorWithPhis(this, j - 1);
         }
 
         // Remove from pred list.
         MBasicBlock **ptr = predecessors_.begin() + i;
         predecessors_.erase(ptr);
         return;
     }
+
     JS_NOT_REACHED("predecessor was not found");
 }
 
 void
 MBasicBlock::inheritPhis(MBasicBlock *header)
 {
     for (MPhiIterator iter = header->phisBegin(); iter != header->phisEnd(); iter++) {
         MPhi *phi = *iter;
--- a/js/src/ion/MIRGraph.h
+++ b/js/src/ion/MIRGraph.h
@@ -74,16 +74,18 @@ class MBasicBlock : public TempObject, p
     static MBasicBlock *NewPopN(MIRGraph &graph, CompileInfo &info,
                                 MBasicBlock *pred, jsbytecode *entryPc, Kind kind, uint32_t popn);
     static MBasicBlock *NewWithResumePoint(MIRGraph &graph, CompileInfo &info,
                                            MBasicBlock *pred, jsbytecode *entryPc,
                                            MResumePoint *resumePoint);
     static MBasicBlock *NewPendingLoopHeader(MIRGraph &graph, CompileInfo &info,
                                              MBasicBlock *pred, jsbytecode *entryPc);
     static MBasicBlock *NewSplitEdge(MIRGraph &graph, CompileInfo &info, MBasicBlock *pred);
+    static MBasicBlock *NewParBailout(MIRGraph &graph, CompileInfo &info,
+                                      MBasicBlock *pred, jsbytecode *entryPc);
 
     bool dominates(MBasicBlock *other);
 
     void setId(uint32_t id) {
         id_ = id;
     }
     void setEarlyAbort() {
         earlyAbort_ = true;
@@ -160,18 +162,21 @@ class MBasicBlock : public TempObject, p
     bool addPredecessor(MBasicBlock *pred);
     bool addPredecessorPopN(MBasicBlock *pred, uint32_t popped);
 
     // Stranger utilities used for inlining.
     bool addPredecessorWithoutPhis(MBasicBlock *pred);
     void inheritSlots(MBasicBlock *parent);
     bool initEntrySlots();
 
-    // Replaces an edge for a given block with a new block. This is used for
-    // critical edge splitting.
+    // Replaces an edge for a given block with a new block. This is
+    // used for critical edge splitting and also for inserting
+    // bailouts during ParallelArrayAnalysis.
+    //
+    // Note: If successorWithPhis is set, you must not be replacing it.
     void replacePredecessor(MBasicBlock *old, MBasicBlock *split);
     void replaceSuccessor(size_t pos, MBasicBlock *split);
 
     // Removes `pred` from the predecessor list.  `pred` should not be
     // the final predecessor. If this block defines phis, removes the
     // entry for `pred` and updates the indices of later entries.
     // This may introduce redundant phis if the new block has fewer
     // than two predecessors.
@@ -389,16 +394,17 @@ class MBasicBlock : public TempObject, p
         return positionInPhiSuccessor_;
     }
     void setSuccessorWithPhis(MBasicBlock *successor, uint32_t id) {
         successorWithPhis_ = successor;
         positionInPhiSuccessor_ = id;
     }
     size_t numSuccessors() const;
     MBasicBlock *getSuccessor(size_t index) const;
+    size_t getSuccessorIndex(MBasicBlock *) const;
 
     // Specifies the closest loop header dominating this block.
     void setLoopHeader(MBasicBlock *loop) {
         JS_ASSERT(loop->isLoopHeader());
         loopHeader_ = loop;
     }
     MBasicBlock *loopHeader() const {
         return loopHeader_;
@@ -603,16 +609,24 @@ class MIRGraph
         return scripts_.append(script);
     }
     size_t numScripts() const {
         return scripts_.length();
     }
     JSScript **scripts() {
         return scripts_.begin();
     }
+
+    // The ParSlice is an instance of ForkJoinSlice*, it carries
+    // "per-helper-thread" information.  So as not to modify the
+    // calling convention for parallel code, we obtain the current
+    // slice from thread-local storage.  This helper method will
+    // lazilly insert an MParSlice instruction in the entry block and
+    // return the definition.
+    MDefinition *parSlice();
 };
 
 class MDefinitionIterator
 {
 
   friend class MBasicBlock;
 
   private:
--- a/js/src/ion/MOpcodes.h
+++ b/js/src/ion/MOpcodes.h
@@ -67,16 +67,17 @@ namespace ion {
     _(Unbox)                                                                \
     _(GuardObject)                                                          \
     _(GuardString)                                                          \
     _(ToDouble)                                                             \
     _(ToInt32)                                                              \
     _(TruncateToInt32)                                                      \
     _(ToString)                                                             \
     _(NewSlots)                                                             \
+    _(NewParallelArray)                                                     \
     _(NewArray)                                                             \
     _(NewObject)                                                            \
     _(NewDeclEnvObject)                                                     \
     _(NewCallObject)                                                        \
     _(NewStringObject)                                                      \
     _(InitProp)                                                             \
     _(Start)                                                                \
     _(OsrEntry)                                                             \
@@ -140,24 +141,42 @@ namespace ion {
     _(Floor)                                                                \
     _(Round)                                                                \
     _(In)                                                                   \
     _(InstanceOf)                                                           \
     _(CallInstanceOf)                                                       \
     _(InterruptCheck)                                                       \
     _(FunctionBoundary)                                                     \
     _(GetDOMProperty)                                                       \
-    _(SetDOMProperty)
+    _(SetDOMProperty)                                                       \
+    _(ParCheckOverRecursed)                                                 \
+    _(ParNewCallObject)                                                     \
+    _(ParNew)                                                               \
+    _(ParNewDenseArray)                                                     \
+    _(ParBailout)                                                           \
+    _(ParLambda)                                                            \
+    _(ParSlice)                                                             \
+    _(ParWriteGuard)                                                        \
+    _(ParDump)                                                              \
+    _(ParCheckInterrupt)
 
 // Forward declarations of MIR types.
 #define FORWARD_DECLARE(op) class M##op;
  MIR_OPCODE_LIST(FORWARD_DECLARE)
 #undef FORWARD_DECLARE
 
-class MInstructionVisitor
+class MInstructionVisitor // interface i.e. pure abstract class
+{
+  public:
+#define VISIT_INS(op) virtual bool visit##op(M##op *) = 0;
+    MIR_OPCODE_LIST(VISIT_INS)
+#undef VISIT_INS
+};
+
+class MInstructionVisitorWithDefaults : public MInstructionVisitor
 {
   public:
 #define VISIT_INS(op) virtual bool visit##op(M##op *) { JS_NOT_REACHED("NYI: " #op); return false; }
     MIR_OPCODE_LIST(VISIT_INS)
 #undef VISIT_INS
 };
 
 } // namespace ion
new file mode 100644
--- /dev/null
+++ b/js/src/ion/ParallelArrayAnalysis.cpp
@@ -0,0 +1,848 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+#include <stdio.h>
+
+#include "Ion.h"
+#include "MIR.h"
+#include "MIRGraph.h"
+#include "ParallelArrayAnalysis.h"
+#include "IonSpewer.h"
+#include "UnreachableCodeElimination.h"
+#include "IonAnalysis.h"
+
+#include "vm/ParallelDo.h"
+
+#include "vm/Stack.h"
+
+namespace js {
+namespace ion {
+
+using parallel::Spew;
+using parallel::SpewMIR;
+using parallel::SpewCompile;
+
+#define SAFE_OP(op)                             \
+    virtual bool visit##op(M##op *prop) { return true; }
+
+#define CUSTOM_OP(op)                        \
+    virtual bool visit##op(M##op *prop);
+
+#define DROP_OP(op)                             \
+    virtual bool visit##op(M##op *ins) {        \
+        MBasicBlock *block = ins->block();      \
+        block->discard(ins);                    \
+        return true;                            \
+    }
+
+#define PERMIT(T) (1 << T)
+
+#define PERMIT_NUMERIC (PERMIT(MIRType_Int32) | PERMIT(MIRType_Double))
+
+#define SPECIALIZED_OP(op, flags)                                               \
+    virtual bool visit##op(M##op *ins) {                                        \
+        return visitSpecializedInstruction(ins, ins->specialization(), flags);  \
+    }
+
+#define UNSAFE_OP(op)                                               \
+    virtual bool visit##op(M##op *ins) {                            \
+        SpewMIR(ins, "Unsafe");                                     \
+        return markUnsafe();                                        \
+    }
+
+#define WRITE_GUARDED_OP(op, obj)                   \
+    virtual bool visit##op(M##op *prop) {           \
+        return insertWriteGuard(prop, prop->obj()); \
+    }
+
+#define MAYBE_WRITE_GUARDED_OP(op, obj)                                       \
+    virtual bool visit##op(M##op *prop) {                                     \
+        if (prop->racy())                                                     \
+            return true;                                                      \
+        return insertWriteGuard(prop, prop->obj());                           \
+    }
+
+class ParallelArrayVisitor : public MInstructionVisitor
+{
+    JSContext *cx_;
+    ParallelCompileContext &compileContext_;
+    MIRGraph &graph_;
+    bool unsafe_;
+    MDefinition *parSlice_;
+
+    bool insertWriteGuard(MInstruction *writeInstruction,
+                          MDefinition *valueBeingWritten);
+
+    bool replaceWithParNew(MInstruction *newInstruction,
+                           JSObject *templateObject);
+
+    bool replace(MInstruction *oldInstruction,
+                 MInstruction *replacementInstruction);
+
+    bool visitSpecializedInstruction(MInstruction *ins, MIRType spec, uint32_t flags);
+
+    // Intended for use in a visitXyz() instruction like "return
+    // markUnsafe()".  Sets the unsafe flag and returns true (since
+    // this does not indicate an unrecoverable compilation failure).
+    bool markUnsafe() {
+        JS_ASSERT(!unsafe_);
+        unsafe_ = true;
+        return true;
+    }
+
+  public:
+    AutoObjectVector callTargets;
+
+    ParallelArrayVisitor(JSContext *cx, ParallelCompileContext &compileContext,
+                         MIRGraph &graph)
+      : cx_(cx),
+        compileContext_(compileContext),
+        graph_(graph),
+        unsafe_(false),
+        parSlice_(NULL),
+        callTargets(cx)
+    { }
+
+    void clearUnsafe() { unsafe_ = false; }
+    bool unsafe() { return unsafe_; }
+    MDefinition *parSlice() {
+        if (!parSlice_)
+            parSlice_ = graph_.parSlice();
+        return parSlice_;
+    }
+
+    bool convertToBailout(MBasicBlock *block, MInstruction *ins);
+
+    // I am taking the policy of blacklisting everything that's not
+    // obviously safe for now.  We can loosen as we need.
+
+    SAFE_OP(Constant)
+    SAFE_OP(Parameter)
+    SAFE_OP(Callee)
+    SAFE_OP(TableSwitch)
+    SAFE_OP(Goto)
+    CUSTOM_OP(Test)
+    CUSTOM_OP(Compare)
+    SAFE_OP(Phi)
+    SAFE_OP(Beta)
+    UNSAFE_OP(OsrValue)
+    UNSAFE_OP(OsrScopeChain)
+    UNSAFE_OP(ReturnFromCtor)
+    CUSTOM_OP(CheckOverRecursed)
+    DROP_OP(RecompileCheck)
+    UNSAFE_OP(DefVar)
+    UNSAFE_OP(DefFun)
+    UNSAFE_OP(CreateThis)
+    UNSAFE_OP(CreateThisWithTemplate)
+    UNSAFE_OP(CreateThisWithProto)
+    SAFE_OP(PrepareCall)
+    SAFE_OP(PassArg)
+    CUSTOM_OP(Call)
+    UNSAFE_OP(ApplyArgs)
+    SAFE_OP(BitNot)
+    UNSAFE_OP(TypeOf)
+    SAFE_OP(ToId)
+    SAFE_OP(BitAnd)
+    SAFE_OP(BitOr)
+    SAFE_OP(BitXor)
+    SAFE_OP(Lsh)
+    SAFE_OP(Rsh)
+    SPECIALIZED_OP(Ursh, PERMIT_NUMERIC)
+    SPECIALIZED_OP(MinMax, PERMIT_NUMERIC)
+    SAFE_OP(Abs)
+    SAFE_OP(Sqrt)
+    SAFE_OP(MathFunction)
+    SPECIALIZED_OP(Add, PERMIT_NUMERIC)
+    SPECIALIZED_OP(Sub, PERMIT_NUMERIC)
+    SPECIALIZED_OP(Mul, PERMIT_NUMERIC)
+    SPECIALIZED_OP(Div, PERMIT_NUMERIC)
+    SPECIALIZED_OP(Mod, PERMIT_NUMERIC)
+    UNSAFE_OP(Concat)
+    UNSAFE_OP(CharCodeAt)
+    UNSAFE_OP(FromCharCode)
+    SAFE_OP(Return)
+    CUSTOM_OP(Throw)
+    SAFE_OP(Box)     // Boxing just creates a JSVal, doesn't alloc.
+    SAFE_OP(Unbox)
+    SAFE_OP(GuardObject)
+    SAFE_OP(ToDouble)
+    SAFE_OP(ToInt32)
+    SAFE_OP(TruncateToInt32)
+    UNSAFE_OP(ToString)
+    SAFE_OP(NewSlots)
+    CUSTOM_OP(NewArray)
+    CUSTOM_OP(NewObject)
+    CUSTOM_OP(NewCallObject)
+    CUSTOM_OP(NewParallelArray)
+    UNSAFE_OP(InitProp)
+    SAFE_OP(Start)
+    UNSAFE_OP(OsrEntry)
+    SAFE_OP(Nop)
+    UNSAFE_OP(RegExp)
+    CUSTOM_OP(Lambda)
+    UNSAFE_OP(ImplicitThis)
+    SAFE_OP(Slots)
+    SAFE_OP(Elements)
+    SAFE_OP(ConstantElements)
+    SAFE_OP(LoadSlot)
+    WRITE_GUARDED_OP(StoreSlot, slots)
+    SAFE_OP(FunctionEnvironment) // just a load of func env ptr
+    SAFE_OP(TypeBarrier) // causes a bailout if the type is not found: a-ok with us
+    SAFE_OP(MonitorTypes) // causes a bailout if the type is not found: a-ok with us
+    UNSAFE_OP(GetPropertyCache)
+    UNSAFE_OP(GetElementCache)
+    UNSAFE_OP(BindNameCache)
+    SAFE_OP(GuardShape)
+    SAFE_OP(GuardClass)
+    SAFE_OP(ArrayLength)
+    SAFE_OP(TypedArrayLength)
+    SAFE_OP(TypedArrayElements)
+    SAFE_OP(InitializedLength)
+    WRITE_GUARDED_OP(SetInitializedLength, elements)
+    SAFE_OP(Not)
+    SAFE_OP(BoundsCheck)
+    SAFE_OP(BoundsCheckLower)
+    SAFE_OP(LoadElement)
+    SAFE_OP(LoadElementHole)
+    MAYBE_WRITE_GUARDED_OP(StoreElement, elements)
+    WRITE_GUARDED_OP(StoreElementHole, elements)
+    UNSAFE_OP(ArrayPopShift)
+    UNSAFE_OP(ArrayPush)
+    SAFE_OP(LoadTypedArrayElement)
+    SAFE_OP(LoadTypedArrayElementHole)
+    MAYBE_WRITE_GUARDED_OP(StoreTypedArrayElement, elements)
+    UNSAFE_OP(ClampToUint8)
+    SAFE_OP(LoadFixedSlot)
+    WRITE_GUARDED_OP(StoreFixedSlot, object)
+    UNSAFE_OP(CallGetProperty)
+    UNSAFE_OP(GetNameCache)
+    SAFE_OP(CallGetIntrinsicValue) // Bails in parallel mode
+    UNSAFE_OP(CallsiteCloneCache)
+    UNSAFE_OP(CallGetElement)
+    UNSAFE_OP(CallSetElement)
+    UNSAFE_OP(CallSetProperty)
+    UNSAFE_OP(DeleteProperty)
+    UNSAFE_OP(SetPropertyCache)
+    UNSAFE_OP(IteratorStart)
+    UNSAFE_OP(IteratorNext)
+    UNSAFE_OP(IteratorMore)
+    UNSAFE_OP(IteratorEnd)
+    SAFE_OP(StringLength)
+    UNSAFE_OP(ArgumentsLength)
+    UNSAFE_OP(GetArgument)
+    SAFE_OP(Floor)
+    SAFE_OP(Round)
+    UNSAFE_OP(InstanceOf)
+    CUSTOM_OP(InterruptCheck)
+    SAFE_OP(ParSlice)
+    SAFE_OP(ParNew)
+    SAFE_OP(ParNewDenseArray)
+    SAFE_OP(ParNewCallObject)
+    SAFE_OP(ParLambda)
+    SAFE_OP(ParDump)
+    SAFE_OP(ParBailout)
+    UNSAFE_OP(ArrayConcat)
+    UNSAFE_OP(GetDOMProperty)
+    UNSAFE_OP(SetDOMProperty)
+    UNSAFE_OP(NewStringObject)
+    UNSAFE_OP(Random)
+    UNSAFE_OP(Pow)
+    UNSAFE_OP(PowHalf)
+    UNSAFE_OP(RegExpTest)
+    UNSAFE_OP(CallInstanceOf)
+    UNSAFE_OP(FunctionBoundary)
+    UNSAFE_OP(GuardString)
+    UNSAFE_OP(NewDeclEnvObject)
+    UNSAFE_OP(In)
+    UNSAFE_OP(InArray)
+    SAFE_OP(ParWriteGuard)
+    SAFE_OP(ParCheckInterrupt)
+    SAFE_OP(ParCheckOverRecursed)
+    SAFE_OP(PolyInlineDispatch)
+
+    // It looks like this could easily be made safe:
+    UNSAFE_OP(ConvertElementsToDoubles)
+};
+
+bool
+ParallelCompileContext::appendToWorklist(HandleFunction fun)
+{
+    JS_ASSERT(fun);
+
+    if (!fun->isInterpreted())
+        return true;
+
+    RootedScript script(cx_, fun->nonLazyScript());
+
+    // Skip if we're disabled.
+    if (!script->canParallelIonCompile()) {
+        Spew(SpewCompile, "Skipping %p:%s:%u, canParallelIonCompile() is false",
+             fun.get(), script->filename, script->lineno);
+        return true;
+    }
+
+    // Skip if we're compiling off thread.
+    if (script->parallelIon == ION_COMPILING_SCRIPT) {
+        Spew(SpewCompile, "Skipping %p:%s:%u, off-main-thread compilation in progress",
+             fun.get(), script->filename, script->lineno);
+        return true;
+    }
+
+    // Skip if the code is expected to result in a bailout.
+    if (script->parallelIon && script->parallelIon->bailoutExpected()) {
+        Spew(SpewCompile, "Skipping %p:%s:%u, bailout expected",
+             fun.get(), script->filename, script->lineno);
+        return true;
+    }
+
+    // Skip if we haven't warmed up to get some type info. We're betting
+    // that the parallel kernel will be non-branchy for the most part, so
+    // this threshold is usually very low (1).
+    if (script->getUseCount() < js_IonOptions.usesBeforeCompileParallel) {
+        Spew(SpewCompile, "Skipping %p:%s:%u, use count %u < %u",
+             fun.get(), script->filename, script->lineno,
+             script->getUseCount(), js_IonOptions.usesBeforeCompileParallel);
+        return true;
+    }
+
+    for (uint32_t i = 0; i < worklist_.length(); i++) {
+        if (worklist_[i]->toFunction() == fun)
+            return true;
+    }
+
+    // Note that we add all possibly compilable functions to the worklist,
+    // even if they're already compiled. This is so that we can return
+    // Method_Compiled and not Method_Skipped if we have a worklist full of
+    // already-compiled functions.
+    return worklist_.append(fun);
+}
+
+bool
+ParallelCompileContext::analyzeAndGrowWorklist(MIRGenerator *mir, MIRGraph &graph)
+{
+    // Walk the basic blocks in a DFS.  When we encounter a block with an
+    // unsafe instruction, then we know that this block will bailout when
+    // executed.  Therefore, we replace the block.
+    //
+    // We don't need a worklist, though, because the graph is sorted
+    // in RPO.  Therefore, we just use the marked flags to tell us
+    // when we visited some predecessor of the current block.
+    ParallelArrayVisitor visitor(cx_, *this, graph);
+    graph.entryBlock()->mark();  // Note: in par. exec., we never enter from OSR.
+    uint32_t marked = 0;
+    for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); block++) {
+        if (mir->shouldCancel("ParallelArrayAnalysis"))
+            return false;
+
+        if (block->isMarked()) {
+            // Iterate through and transform the instructions.  Stop
+            // if we encounter an inherently unsafe operation, in
+            // which case we will transform this block into a bailout
+            // block.
+            MInstruction *instr = NULL;
+            for (MInstructionIterator ins(block->begin());
+                 ins != block->end() && !visitor.unsafe();)
+            {
+                if (mir->shouldCancel("ParallelArrayAnalysis"))
+                    return false;
+
+                // We may be removing or replacing the current
+                // instruction, so advance `ins` now.  Remember the
+                // last instr. we looked at for use later if it should
+                // prove unsafe.
+                instr = *ins++;
+
+                if (!instr->accept(&visitor))
+                    return false;
+            }
+
+            if (!visitor.unsafe()) {
+                // Count the number of reachable blocks.
+                marked++;
+
+                // Block consists of only safe instructions.  Visit its successors.
+                for (uint32_t i = 0; i < block->numSuccessors(); i++)
+                    block->getSuccessor(i)->mark();
+            } else {
+                // Block contains an unsafe instruction.  That means that once
+                // we enter this block, we are guaranteed to bailout.
+
+                // If this is the entry block, then there is no point
+                // in even trying to execute this function as it will
+                // always bailout.
+                if (*block == graph.entryBlock()) {
+                    Spew(SpewCompile, "Entry block contains unsafe MIR");
+                    return false;
+                }
+
+                // Otherwise, create a replacement that will.
+                if (!visitor.convertToBailout(*block, instr))
+                    return false;
+
+                JS_ASSERT(!block->isMarked());
+            }
+        }
+    }
+
+    // Append newly discovered outgoing callgraph edges to the worklist.
+    RootedFunction target(cx_);
+    for (uint32_t i = 0; i < visitor.callTargets.length(); i++) {
+        target = visitor.callTargets[i]->toFunction();
+        appendToWorklist(target);
+    }
+
+    Spew(SpewCompile, "Safe");
+    IonSpewPass("ParallelArrayAnalysis");
+
+    UnreachableCodeElimination uce(mir, graph);
+    if (!uce.removeUnmarkedBlocks(marked))
+        return false;
+    IonSpewPass("UCEAfterParallelArrayAnalysis");
+    AssertExtendedGraphCoherency(graph);
+
+    if (!removeResumePointOperands(mir, graph))
+        return false;
+    IonSpewPass("RemoveResumePointOperands");
+    AssertExtendedGraphCoherency(graph);
+
+    if (!EliminateDeadCode(mir, graph))
+        return false;
+    IonSpewPass("DCEAfterParallelArrayAnalysis");
+    AssertExtendedGraphCoherency(graph);
+
+    return true;
+}
+
+bool
+ParallelCompileContext::removeResumePointOperands(MIRGenerator *mir, MIRGraph &graph)
+{
+    // In parallel exec mode, nothing is effectful, therefore we do
+    // not need to reconstruct interpreter state and can simply
+    // bailout by returning a special code.  Ideally we'd either
+    // remove the unused resume points or else never generate them in
+    // the first place, but I encountered various assertions and
+    // crashes attempting to do that, so for the time being I simply
+    // replace their operands with undefined.  This prevents them from
+    // interfering with DCE and other optimizations.  It is also *necessary*
+    // to handle cases like this:
+    //
+    //     foo(a, b, c.bar())
+    //
+    // where `foo` was deemed to be an unsafe function to call.  This
+    // is because without neutering the ResumePoints, they would still
+    // refer to the MPassArg nodes generated for the call to foo().
+    // But the call to foo() is dead and has been removed, leading to
+    // an inconsistent IR and assertions at codegen time.
+
+    MConstant *udef = NULL;
+    for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); block++) {
+        if (udef)
+            replaceOperandsOnResumePoint(block->entryResumePoint(), udef);
+
+        for (MInstructionIterator ins(block->begin()); ins != block->end(); ins++) {
+            if (ins->isStart()) {
+                JS_ASSERT(udef == NULL);
+                udef = MConstant::New(UndefinedValue());
+                block->insertAfter(*ins, udef);
+            } else if (udef) {
+                if (MResumePoint *resumePoint = ins->resumePoint())
+                    replaceOperandsOnResumePoint(resumePoint, udef);
+            }
+        }
+    }
+    return true;
+}
+
+void
+ParallelCompileContext::replaceOperandsOnResumePoint(MResumePoint *resumePoint,
+                                                     MDefinition *withDef)
+{
+    for (size_t i = 0; i < resumePoint->numOperands(); i++)
+        resumePoint->replaceOperand(i, withDef);
+}
+
+bool
+ParallelArrayVisitor::visitTest(MTest *)
+{
+    return true;
+}
+
+bool
+ParallelArrayVisitor::visitCompare(MCompare *compare)
+{
+    MCompare::CompareType type = compare->compareType();
+    return type == MCompare::Compare_Int32 ||
+           type == MCompare::Compare_Double ||
+           type == MCompare::Compare_String;
+}
+
+bool
+ParallelArrayVisitor::convertToBailout(MBasicBlock *block, MInstruction *ins)
+{
+    JS_ASSERT(unsafe()); // `block` must have contained unsafe items
+    JS_ASSERT(block->isMarked()); // `block` must have been reachable to get here
+
+    // Clear the unsafe flag for subsequent blocks.
+    clearUnsafe();
+
+    // This block is no longer reachable.
+    block->unmark();
+
+    // Determine the best PC to use for the bailouts we'll be creating.
+    jsbytecode *pc = block->pc();
+    if (!pc)
+        pc = block->pc();
+
+    // Create a bailout block for each predecessor.  In principle, we
+    // only need one bailout block--in fact, only one per graph! But I
+    // found this approach easier to implement given the design of the
+    // MIR Graph construction routines.  Besides, most often `block`
+    // has only one predecessor.  Also, using multiple blocks helps to
+    // keep the PC information more accurate (though replacing `block`
+    // with exactly one bailout would be just as good).
+    for (size_t i = 0; i < block->numPredecessors(); i++) {
+        MBasicBlock *pred = block->getPredecessor(i);
+
+        // We only care about incoming edges from reachable predecessors.
+        if (!pred->isMarked())
+            continue;
+
+        // create bailout block to insert on this edge
+        MBasicBlock *bailBlock = MBasicBlock::NewParBailout(graph_, block->info(), pred, pc);
+        if (!bailBlock)
+            return false;
+
+        // if `block` had phis, we are replacing it with `bailBlock` which does not
+        if (pred->successorWithPhis() == block)
+            pred->setSuccessorWithPhis(NULL, 0);
+
+        // redirect the predecessor to the bailout block
+        uint32_t succIdx = pred->getSuccessorIndex(block);
+        pred->replaceSuccessor(succIdx, bailBlock);
+
+        // Insert the bailout block after `block` in the execution
+        // order.  This should satisfy the RPO requirements and
+        // moreover ensures that we will visit this block in our outer
+        // walk, thus allowing us to keep the count of marked blocks
+        // accurate.
+        graph_.insertBlockAfter(block, bailBlock);
+        bailBlock->mark();
+    }
+
+    return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Memory allocation
+//
+// Simple memory allocation opcodes---those which ultimately compile
+// down to a (possibly inlined) invocation of NewGCThing()---are
+// replaced with MParNew, which is supplied with the thread context.
+// These allocations will take place using per-helper-thread arenas.
+
+bool
+ParallelArrayVisitor::visitNewParallelArray(MNewParallelArray *ins)
+{
+    MParNew *parNew = new MParNew(parSlice(), ins->templateObject());
+    replace(ins, parNew);
+    return true;
+}
+
+bool
+ParallelArrayVisitor::visitNewCallObject(MNewCallObject *ins)
+{
+    // fast path: replace with ParNewCallObject op
+    MParNewCallObject *parNewCallObjectInstruction =
+        MParNewCallObject::New(parSlice(), ins);
+    replace(ins, parNewCallObjectInstruction);
+    return true;
+}
+
+bool
+ParallelArrayVisitor::visitLambda(MLambda *ins)
+{
+    if (ins->fun()->hasSingletonType() ||
+        types::UseNewTypeForClone(ins->fun()))
+    {
+        // slow path: bail on parallel execution.
+        return markUnsafe();
+    }
+
+    // fast path: replace with ParLambda op
+    MParLambda *parLambdaInstruction = MParLambda::New(parSlice(), ins);
+    replace(ins, parLambdaInstruction);
+    return true;
+}
+
+bool
+ParallelArrayVisitor::visitNewObject(MNewObject *newInstruction)
+{
+    if (newInstruction->shouldUseVM()) {
+        SpewMIR(newInstruction, "should use VM");
+        return markUnsafe();
+    }
+
+    return replaceWithParNew(newInstruction,
+                             newInstruction->templateObject());
+}
+
+bool
+ParallelArrayVisitor::visitNewArray(MNewArray *newInstruction)
+{
+    if (newInstruction->shouldUseVM()) {
+        SpewMIR(newInstruction, "should use VM");
+        return markUnsafe();
+    }
+
+    return replaceWithParNew(newInstruction,
+                             newInstruction->templateObject());
+}
+
+bool
+ParallelArrayVisitor::replaceWithParNew(MInstruction *newInstruction,
+                                        JSObject *templateObject)
+{
+    MParNew *parNewInstruction = new MParNew(parSlice(), templateObject);
+    replace(newInstruction, parNewInstruction);
+    return true;
+}
+
+bool
+ParallelArrayVisitor::replace(MInstruction *oldInstruction,
+                              MInstruction *replacementInstruction)
+{
+    MBasicBlock *block = oldInstruction->block();
+    block->insertBefore(oldInstruction, replacementInstruction);
+    oldInstruction->replaceAllUsesWith(replacementInstruction);
+    block->discard(oldInstruction);
+    return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Write Guards
+//
+// We only want to permit writes to locally guarded objects.
+// Furthermore, we want to avoid PICs and other non-thread-safe things
+// (though perhaps we should support PICs at some point).  If we
+// cannot determine the origin of an object, we can insert a write
+// guard which will check whether the object was allocated from the
+// per-thread-arena or not.
+
+bool
+ParallelArrayVisitor::insertWriteGuard(MInstruction *writeInstruction,
+                                       MDefinition *valueBeingWritten)
+{
+    // Many of the write operations do not take the JS object
+    // but rather something derived from it, such as the elements.
+    // So we need to identify the JS object:
+    MDefinition *object;
+    switch (valueBeingWritten->type()) {
+      case MIRType_Object:
+        object = valueBeingWritten;
+        break;
+
+      case MIRType_Slots:
+        switch (valueBeingWritten->op()) {
+          case MDefinition::Op_Slots:
+            object = valueBeingWritten->toSlots()->object();
+            break;
+
+          case MDefinition::Op_NewSlots:
+            // Values produced by new slots will ALWAYS be
+            // thread-local.
+            return true;
+
+          default:
+            SpewMIR(writeInstruction, "cannot insert write guard for %s",
+                    valueBeingWritten->opName());
+            return markUnsafe();
+        }
+        break;
+
+      case MIRType_Elements:
+        switch (valueBeingWritten->op()) {
+          case MDefinition::Op_Elements:
+            object = valueBeingWritten->toElements()->object();
+            break;
+
+          case MDefinition::Op_TypedArrayElements:
+            object = valueBeingWritten->toTypedArrayElements()->object();
+            break;
+
+          default:
+            SpewMIR(writeInstruction, "cannot insert write guard for %s",
+                    valueBeingWritten->opName());
+            return markUnsafe();
+        }
+        break;
+
+      default:
+        SpewMIR(writeInstruction, "cannot insert write guard for MIR Type %d",
+                valueBeingWritten->type());
+        return markUnsafe();
+    }
+
+    if (object->isUnbox())
+        object = object->toUnbox()->input();
+
+    switch (object->op()) {
+      case MDefinition::Op_ParNew:
+        // MParNew will always be creating something thread-local, omit the guard
+        SpewMIR(writeInstruction, "write to ParNew prop does not require guard");
+        return true;
+      default:
+        break;
+    }
+
+    MBasicBlock *block = writeInstruction->block();
+    MParWriteGuard *writeGuard = MParWriteGuard::New(parSlice(), object);
+    block->insertBefore(writeInstruction, writeGuard);
+    writeGuard->adjustInputs(writeGuard);
+    return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Calls
+//
+// We only support calls to interpreted functions that that have already been
+// Ion compiled. If a function has no IonScript, we bail out. The compilation
+// is done during warmup of the parallel kernel, see js::RunScript.
+
+static bool
+GetPossibleCallees(JSContext *cx, HandleScript script, jsbytecode *pc,
+                   types::StackTypeSet *calleeTypes, AutoObjectVector &targets)
+{
+    JS_ASSERT(calleeTypes);
+
+    if (calleeTypes->baseFlags() != 0)
+        return true;
+
+    unsigned objCount = calleeTypes->getObjectCount();
+
+    if (objCount == 0)
+        return true;
+
+    RootedFunction fun(cx);
+    for (unsigned i = 0; i < objCount; i++) {
+        RawObject obj = calleeTypes->getSingleObject(i);
+        if (obj && obj->isFunction()) {
+            fun = obj->toFunction();
+        } else {
+            types::TypeObject *typeObj = calleeTypes->getTypeObject(i);
+            if (!typeObj)
+                continue;
+            fun = typeObj->interpretedFunction;
+            if (!fun)
+                continue;
+        }
+
+        if (fun->isCloneAtCallsite()) {
+            fun = CloneFunctionAtCallsite(cx, fun, script, pc);
+            if (!fun)
+                return false;
+        }
+
+        if (!targets.append(fun))
+            return false;
+    }
+
+    return true;
+}
+
+bool
+ParallelArrayVisitor::visitCall(MCall *ins)
+{
+    JS_ASSERT(ins->getSingleTarget() || ins->calleeTypes());
+
+    // DOM? Scary.
+    if (ins->isDOMFunction()) {
+        SpewMIR(ins, "call to dom function");
+        return markUnsafe();
+    }
+
+    RootedFunction target(cx_, ins->getSingleTarget());
+    if (target) {
+        // Native? Scary.
+        if (target->isNative()) {
+            SpewMIR(ins, "call to native function");
+            return markUnsafe();
+        }
+        return callTargets.append(target);
+    }
+
+    if (ins->isConstructing()) {
+        SpewMIR(ins, "call to unknown constructor");
+        return markUnsafe();
+    }
+
+    RootedScript script(cx_, ins->block()->info().script());
+    return GetPossibleCallees(cx_, script, ins->resumePoint()->pc(),
+                              ins->calleeTypes(), callTargets);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Stack limit, interrupts
+//
+// In sequential Ion code, the stack limit is stored in the JSRuntime.
+// We store it in the thread context.  We therefore need a separate
+// instruction to access it, one parameterized by the thread context.
+// Similar considerations apply to checking for interrupts.
+
+bool
+ParallelArrayVisitor::visitCheckOverRecursed(MCheckOverRecursed *ins)
+{
+    MParCheckOverRecursed *replacement = new MParCheckOverRecursed(parSlice());
+    return replace(ins, replacement);
+}
+
+bool
+ParallelArrayVisitor::visitInterruptCheck(MInterruptCheck *ins)
+{
+    MParCheckInterrupt *replacement = new MParCheckInterrupt(parSlice());
+    return replace(ins, replacement);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Specialized ops
+//
+// Some ops, like +, can be specialized to ints/doubles.  Anything
+// else is terrifying.
+//
+// TODO---Eventually, we should probably permit arbitrary + but bail
+// if the operands are not both integers/floats.
+
+bool
+ParallelArrayVisitor::visitSpecializedInstruction(MInstruction *ins, MIRType spec,
+                                                  uint32_t flags)
+{
+    uint32_t flag = 1 << spec;
+    if (flags & flag)
+        return true;
+
+    SpewMIR(ins, "specialized to unacceptable type %d", spec);
+    return markUnsafe();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Throw
+
+bool
+ParallelArrayVisitor::visitThrow(MThrow *thr)
+{
+    MBasicBlock *block = thr->block();
+    JS_ASSERT(block->lastIns() == thr);
+    block->discardLastIns();
+    MParBailout *bailout = new MParBailout();
+    if (!bailout)
+        return false;
+    block->end(bailout);
+    return true;
+}
+
+}
+}
+
new file mode 100644
--- /dev/null
+++ b/js/src/ion/ParallelArrayAnalysis.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+#ifndef jsion_parallel_array_analysis_h__
+#define jsion_parallel_array_analysis_h__
+
+#include "MIR.h"
+#include "CompileInfo.h"
+
+namespace js {
+
+class StackFrame;
+
+namespace ion {
+
+class MIRGraph;
+class AutoDestroyAllocator;
+
+class ParallelCompileContext
+{
+  private:
+    JSContext *cx_;
+
+    // Compilation is transitive from some set of root(s).
+    AutoObjectVector worklist_;
+
+    // Is a function compilable for parallel execution?
+    bool analyzeAndGrowWorklist(MIRGenerator *mir, MIRGraph &graph);
+
+    bool removeResumePointOperands(MIRGenerator *mir, MIRGraph &graph);
+    void replaceOperandsOnResumePoint(MResumePoint *resumePoint, MDefinition *withDef);
+
+  public:
+    ParallelCompileContext(JSContext *cx)
+      : cx_(cx),
+        worklist_(cx)
+    { }
+
+    // Should we append a function to the worklist?
+    bool appendToWorklist(HandleFunction fun);
+
+    ExecutionMode executionMode() {
+        return ParallelExecution;
+    }
+
+    // Defined in Ion.cpp, so that they can make use of static fns defined there
+    MethodStatus checkScriptSize(JSContext *cx, UnrootedScript script);
+    MethodStatus compileTransitively();
+    AbortReason compile(IonBuilder *builder, MIRGraph *graph,
+                        ScopedJSDeletePtr<LifoAlloc> &autoDelete);
+};
+
+
+} // namespace ion
+} // namespace js
+
+#endif // jsion_parallel_array_analysis_h
new file mode 100644
--- /dev/null
+++ b/js/src/ion/ParallelFunctions.cpp
@@ -0,0 +1,225 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+#include "jsinterp.h"
+#include "ParallelFunctions.h"
+#include "IonSpewer.h"
+
+#include "jsinterpinlines.h"
+#include "jscompartmentinlines.h"
+
+#include "vm/ParallelDo.h"
+
+using namespace js;
+using namespace ion;
+
+using parallel::Spew;
+using parallel::SpewBailouts;
+using parallel::SpewBailoutIR;
+
+// Load the current thread context.
+ForkJoinSlice *
+ion::ParForkJoinSlice()
+{
+    return ForkJoinSlice::Current();
+}
+
+// ParNewGCThing() is called in place of NewGCThing() when executing
+// parallel code.  It uses the ArenaLists for the current thread and
+// allocates from there.
+JSObject *
+ion::ParNewGCThing(gc::AllocKind allocKind)
+{
+    ForkJoinSlice *slice = ForkJoinSlice::Current();
+    uint32_t thingSize = (uint32_t)gc::Arena::thingSize(allocKind);
+    void *t = slice->allocator->parallelNewGCThing(allocKind, thingSize);
+    return static_cast<JSObject *>(t);
+}
+
+// Check that the object was created by the current thread
+// (and hence is writable).
+bool
+ion::ParWriteGuard(ForkJoinSlice *slice, JSObject *object)
+{
+    JS_ASSERT(ForkJoinSlice::Current() == slice);
+    return slice->allocator->arenas.containsArena(slice->runtime(),
+                                                  object->arenaHeader());
+}
+
+#ifdef DEBUG
+static void
+printTrace(const char *prefix, struct IonLIRTraceData *cached)
+{
+    fprintf(stderr, "%s / Block %3u / LIR %3u / Mode %u / LIR %s\n",
+            prefix,
+            cached->bblock, cached->lir, cached->execModeInt, cached->lirOpName);
+}
+
+struct IonLIRTraceData seqTraceData;
+#endif
+
+void
+ion::TraceLIR(uint32_t bblock, uint32_t lir, uint32_t execModeInt,
+              const char *lirOpName, const char *mirOpName,
+              JSScript *script, jsbytecode *pc)
+{
+#ifdef DEBUG
+    static enum { NotSet, All, Bailouts } traceMode;
+
+    // If you set IONFLAGS=trace, this function will be invoked before every LIR.
+    //
+    // You can either modify it to do whatever you like, or use gdb scripting.
+    // For example:
+    //
+    // break ParTrace
+    // commands
+    // continue
+    // exit
+
+    if (traceMode == NotSet) {
+        // Racy, but that's ok.
+        const char *env = getenv("IONFLAGS");
+        if (strstr(env, "trace-all"))
+            traceMode = All;
+        else
+            traceMode = Bailouts;
+    }
+
+    IonLIRTraceData *cached;
+    if (execModeInt == 0)
+        cached = &seqTraceData;
+    else
+        cached = &ForkJoinSlice::Current()->traceData;
+
+    if (bblock == 0xDEADBEEF) {
+        if (execModeInt == 0)
+            printTrace("BAILOUT", cached);
+        else
+            SpewBailoutIR(cached->bblock, cached->lir,
+                          cached->lirOpName, cached->mirOpName,
+                          cached->script, cached->pc);
+    }
+
+    cached->bblock = bblock;
+    cached->lir = lir;
+    cached->execModeInt = execModeInt;
+    cached->lirOpName = lirOpName;
+    cached->mirOpName = mirOpName;
+    cached->script = script;
+    cached->pc = pc;
+
+    if (traceMode == All)
+        printTrace("Exec", cached);
+#endif
+}
+
+bool
+ion::ParCheckOverRecursed(ForkJoinSlice *slice)
+{
+    JS_ASSERT(ForkJoinSlice::Current() == slice);
+
+    // When an interrupt is triggered, we currently overwrite the
+    // stack limit with a sentinel value that brings us here.
+    // Therefore, we must check whether this is really a stack overrun
+    // and, if not, check whether an interrupt is needed.
+    if (slice->isMainThread()) {
+        int stackDummy_;
+        if (!JS_CHECK_STACK_SIZE(js::GetNativeStackLimit(slice->runtime()), &stackDummy_))
+            return false;
+        return ParCheckInterrupt(slice);
+    } else {
+        // FIXME---we don't ovewrite the stack limit for worker
+        // threads, which means that technically they can recurse
+        // forever---or at least a long time---without ever checking
+        // the interrupt.  it also means that if we get here on a
+        // worker thread, this is a real stack overrun!
+        return false;
+    }
+}
+
+bool
+ion::ParCheckInterrupt(ForkJoinSlice *slice)
+{
+    JS_ASSERT(ForkJoinSlice::Current() == slice);
+    bool result = slice->check();
+    if (!result)
+        return false;
+    return true;
+}
+
+void
+ion::ParDumpValue(Value *v)
+{
+#ifdef DEBUG
+    js_DumpValue(*v);
+#endif
+}
+
+JSObject*
+ion::ParPush(ParPushArgs *args)
+{
+    // It is awkward to have the MIR pass the current slice in, so
+    // just fetch it from TLS.  Extending the array is kind of the
+    // slow path anyhow as it reallocates the elements vector.
+    ForkJoinSlice *slice = js::ForkJoinSlice::Current();
+    JSObject::EnsureDenseResult res =
+        args->object->parExtendDenseElements(slice->allocator,
+                                             &args->value, 1);
+    if (res != JSObject::ED_OK)
+        return NULL;
+    return args->object;
+}
+
+JSObject *
+ion::ParExtendArray(ForkJoinSlice *slice, JSObject *array, uint32_t length)
+{
+    JSObject::EnsureDenseResult res =
+        array->parExtendDenseElements(slice->allocator, NULL, length);
+    if (res != JSObject::ED_OK)
+        return NULL;
+    return array;
+}
+
+ParCompareResult
+ion::ParCompareStrings(JSString *str1, JSString *str2)
+{
+    // NYI---the rope case
+    if (!str1->isLinear())
+        return ParCompareUnknown;
+    if (!str2->isLinear())
+        return ParCompareUnknown;
+
+    JSLinearString &linearStr1 = str1->asLinear();
+    JSLinearString &linearStr2 = str2->asLinear();
+    if (EqualStrings(&linearStr1, &linearStr2))
+        return ParCompareEq;
+    return ParCompareNe;
+}
+
+void
+ion::ParallelAbort(JSScript *script)
+{
+    JS_ASSERT(ForkJoinSlice::InParallelSection());
+
+    ForkJoinSlice *slice = ForkJoinSlice::Current();
+
+    Spew(SpewBailouts, "Parallel abort in %p:%s:%d", script, script->filename, script->lineno);
+
+    if (!slice->abortedScript)
+        slice->abortedScript = script;
+}
+
+void
+ion::ParCallToUncompiledScript(JSFunction *func)
+{
+    JS_ASSERT(ForkJoinSlice::InParallelSection());
+
+#ifdef DEBUG
+    RawScript script = func->nonLazyScript();
+    Spew(SpewBailouts, "Call to uncompiled script: %p:%s:%d", script, script->filename, script->lineno);
+#endif
+}
new file mode 100644
--- /dev/null
+++ b/js/src/ion/ParallelFunctions.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+#ifndef jsion_parallel_functions_h__
+#define jsion_parallel_functions_h__
+
+#include "vm/ThreadPool.h"
+#include "vm/ForkJoin.h"
+#include "gc/Heap.h"
+
+namespace js {
+namespace ion {
+
+ForkJoinSlice *ParForkJoinSlice();
+JSObject *ParNewGCThing(gc::AllocKind allocKind);
+bool ParWriteGuard(ForkJoinSlice *context, JSObject *object);
+void ParBailout(uint32_t id);
+bool ParCheckOverRecursed(ForkJoinSlice *slice);
+bool ParCheckInterrupt(ForkJoinSlice *context);
+
+void ParDumpValue(Value *v);
+
+// We pass the arguments to ParPush in a structure because, in code
+// gen, it is convenient to store them on the stack to avoid
+// constraining the reg alloc for the slow path.
+struct ParPushArgs {
+    JSObject *object;
+    Value value;
+};
+
+// Extends the given object with the given value (like `Array.push`).
+// Returns NULL on failure or else `args->object`, which is convenient
+// during code generation.
+JSObject* ParPush(ParPushArgs *args);
+
+// Extends the given array with `length` new holes.  Returns NULL on
+// failure or else `array`, which is convenient during code
+// generation.
+JSObject *ParExtendArray(ForkJoinSlice *slice, JSObject *array, uint32_t length);
+
+enum ParCompareResult {
+    ParCompareNe = false,
+    ParCompareEq = true,
+    ParCompareUnknown = 2
+};
+ParCompareResult ParCompareStrings(JSString *str1, JSString *str2);
+
+void ParallelAbort(JSScript *script);
+
+void TraceLIR(uint32_t bblock, uint32_t lir, uint32_t execModeInt,
+              const char *lirOpName, const char *mirOpName,
+              JSScript *script, jsbytecode *pc);
+
+void ParCallToUncompiledScript(JSFunction *func);
+
+} // namespace ion
+} // namespace js
+
+#endif // jsion_parallel_functions_h__
--- a/js/src/ion/TypeOracle.cpp
+++ b/js/src/ion/TypeOracle.cpp
@@ -285,42 +285,29 @@ TypeInferenceOracle::inArrayIsPacked(Unr
 {
     StackTypeSet *types = script->analysis()->poppedTypes(pc, 0);
     return !types->hasObjectFlags(cx, types::OBJECT_FLAG_NON_PACKED);
 }
 
 bool
 TypeInferenceOracle::elementReadIsDenseNative(RawScript script, jsbytecode *pc)
 {
-    // Check whether the object is a dense array and index is int32 or double.
-    StackTypeSet *obj = script->analysis()->poppedTypes(pc, 1);
-    StackTypeSet *id = script->analysis()->poppedTypes(pc, 0);
-
-    JSValueType idType = id->getKnownTypeTag();
-    if (idType != JSVAL_TYPE_INT32 && idType != JSVAL_TYPE_DOUBLE)
-        return false;
-
-    Class *clasp = obj->getKnownClass();
-    return clasp && clasp->isNative();
+    return elementAccessIsDenseNative(script->analysis()->poppedTypes(pc, 1),
+                                     script->analysis()->poppedTypes(pc, 0));
 }
 
 bool
 TypeInferenceOracle::elementReadIsTypedArray(HandleScript script, jsbytecode *pc, int *arrayType)
 {
-    // Check whether the object is a typed array and index is int32 or double.
-    StackTypeSet *obj = script->analysis()->poppedTypes(pc, 1);
-    StackTypeSet *id = DropUnrooted(script)->analysis()->poppedTypes(pc, 0);
-
-    JSValueType idType = id->getKnownTypeTag();
-    if (idType != JSVAL_TYPE_INT32 && idType != JSVAL_TYPE_DOUBLE)
+    if (!elementAccessIsTypedArray(script->analysis()->poppedTypes(pc, 1),
+                                   script->analysis()->poppedTypes(pc, 0),
+                                   arrayType))
+    {
         return false;
-
-    *arrayType = obj->getTypedArrayType();
-    if (*arrayType == TypedArray::TYPE_MAX)
-        return false;
+    }
 
     JS_ASSERT(*arrayType >= 0 && *arrayType < TypedArray::TYPE_MAX);
 
     // Unlike dense arrays, the types of elements in typed arrays are not
     // guaranteed to be present in the object's type, and we need to use
     // knowledge about the possible contents of the array vs. the types
     // that have been read out of it to figure out how to do the load.
     types::TypeSet *result = propertyRead(script, pc);
@@ -399,37 +386,46 @@ TypeInferenceOracle::elementReadGeneric(
         *monitorResult = (id == MIRType_String || script->analysis()->getCode(pc).getStringElement);
     else
         *monitorResult = true;
 }
 
 bool
 TypeInferenceOracle::elementWriteIsDenseNative(HandleScript script, jsbytecode *pc)
 {
-    // Check whether the object is a dense array and index is int32 or double.
-    StackTypeSet *obj = script->analysis()->poppedTypes(pc, 2);
-    StackTypeSet *id = script->analysis()->poppedTypes(pc, 1);
+    return elementAccessIsDenseNative(script->analysis()->poppedTypes(pc, 2),
+                                     script->analysis()->poppedTypes(pc, 1));
+}
 
+bool
+TypeInferenceOracle::elementAccessIsDenseNative(StackTypeSet *obj, StackTypeSet *id)
+{
     JSValueType idType = id->getKnownTypeTag();
     if (idType != JSVAL_TYPE_INT32 && idType != JSVAL_TYPE_DOUBLE)
         return false;
 
     Class *clasp = obj->getKnownClass();
     if (!clasp || !clasp->isNative())
         return false;
 
     return obj->convertDoubleElements(cx) != StackTypeSet::AmbiguousDoubleConversion;
 }
 
 bool
 TypeInferenceOracle::elementWriteIsTypedArray(RawScript script, jsbytecode *pc, int *arrayType)
 {
-    // Check whether the object is a dense array and index is int32 or double.
-    StackTypeSet *obj = script->analysis()->poppedTypes(pc, 2);
-    StackTypeSet *id = script->analysis()->poppedTypes(pc, 1);
+    return elementAccessIsTypedArray(script->analysis()->poppedTypes(pc, 2),
+                                     script->analysis()->poppedTypes(pc, 1),
+                                     arrayType);
+}
+
+bool
+TypeInferenceOracle::elementAccessIsTypedArray(StackTypeSet *obj, StackTypeSet *id, int *arrayType)
+{
+    // Check whether the object is a typed array and index is int32 or double.
 
     JSValueType idType = id->getKnownTypeTag();
     if (idType != JSVAL_TYPE_INT32 && idType != JSVAL_TYPE_DOUBLE)
         return false;
 
     *arrayType = obj->getTypedArrayType();
     if (*arrayType == TypedArray::TYPE_MAX)
         return false;
--- a/js/src/ion/TypeOracle.h
+++ b/js/src/ion/TypeOracle.h
@@ -8,16 +8,17 @@
 #ifndef js_ion_type_oracle_h__
 #define js_ion_type_oracle_h__
 
 #include "jsscript.h"
 #include "IonTypes.h"
 
 namespace js {
 namespace ion {
+
 enum LazyArgumentsType {
     MaybeArguments = 0,
     DefinitelyArguments,
     NotArguments
 };
 
 class TypeOracle
 {
@@ -116,16 +117,22 @@ class TypeOracle
         return false;
     }
     virtual bool elementWriteHasExtraIndexedProperty(UnrootedScript script, jsbytecode *pc) {
         return false;
     }
     virtual bool elementWriteIsPacked(UnrootedScript script, jsbytecode *pc) {
         return false;
     }
+    virtual bool elementAccessIsDenseNative(types::StackTypeSet *obj, types::StackTypeSet *id) {
+        return false;
+    }
+    virtual bool elementAccessIsTypedArray(types::StackTypeSet *obj, types::StackTypeSet *id, int *arrayType) {
+        return false;
+    }
     virtual bool arrayResultShouldHaveDoubleConversion(UnrootedScript script, jsbytecode *pc) {
         return false;
     }
     virtual bool propertyWriteCanSpecialize(UnrootedScript script, jsbytecode *pc) {
         return true;
     }
     virtual bool propertyWriteNeedsBarrier(UnrootedScript script, jsbytecode *pc, RawId id) {
         return true;
@@ -246,17 +253,19 @@ class TypeInferenceOracle : public TypeO
     bool elementReadIsDenseNative(RawScript script, jsbytecode *pc);
     bool elementReadIsTypedArray(HandleScript script, jsbytecode *pc, int *atype);
     bool elementReadIsString(UnrootedScript script, jsbytecode *pc);
     bool elementReadShouldAlwaysLoadDoubles(UnrootedScript script, jsbytecode *pc);
     bool elementReadHasExtraIndexedProperty(UnrootedScript, jsbytecode *pc);
     bool elementReadIsPacked(UnrootedScript script, jsbytecode *pc);
     void elementReadGeneric(UnrootedScript script, jsbytecode *pc, bool *cacheable, bool *monitorResult);
     bool elementWriteIsDenseNative(HandleScript script, jsbytecode *pc);
+    bool elementAccessIsDenseNative(types::StackTypeSet *obj, types::StackTypeSet *id);
     bool elementWriteIsTypedArray(RawScript script, jsbytecode *pc, int *arrayType);
+    bool elementAccessIsTypedArray(types::StackTypeSet *obj, types::StackTypeSet *id, int *arrayType);
     bool elementWriteNeedsDoubleConversion(UnrootedScript script, jsbytecode *pc);
     bool elementWriteHasExtraIndexedProperty(UnrootedScript script, jsbytecode *pc);
     bool elementWriteIsPacked(UnrootedScript script, jsbytecode *pc);
     bool arrayResultShouldHaveDoubleConversion(UnrootedScript script, jsbytecode *pc);
     bool setElementHasWrittenHoles(UnrootedScript script, jsbytecode *pc);
     bool propertyWriteCanSpecialize(UnrootedScript script, jsbytecode *pc);
     bool propertyWriteNeedsBarrier(UnrootedScript script, jsbytecode *pc, RawId id);
     bool elementWriteNeedsBarrier(UnrootedScript script, jsbytecode *pc);
@@ -355,16 +364,18 @@ StringFromMIRType(MIRType type)
     case MIRType_None:
       return "None";
     case MIRType_Slots:
       return "Slots";
     case MIRType_Elements:
       return "Elements";
     case MIRType_StackFrame:
       return "StackFrame";
+    case MIRType_ForkJoinSlice:
+      return "ForkJoinSlice";
     default:
       JS_NOT_REACHED("Unknown MIRType.");
       return "";
   }
 }
 
 static inline bool
 IsNumberType(MIRType type)
--- a/js/src/ion/UnreachableCodeElimination.cpp
+++ b/js/src/ion/UnreachableCodeElimination.cpp
@@ -30,27 +30,46 @@ UnreachableCodeElimination::analyze()
     // Once the initial DFS is complete, we do a second pass over the
     // blocks to find those that were not reached.  Those blocks are
     // simply removed wholesale.  We must also correct any phis that
     // may be affected..
 
     // Pass 1: Identify unreachable blocks (if any).
     if (!prunePointlessBranchesAndMarkReachableBlocks())
         return false;
+
+    return removeUnmarkedBlocksAndCleanup();
+}
+
+bool
+UnreachableCodeElimination::removeUnmarkedBlocks(size_t marked)
+{
+    marked_ = marked;
+    return removeUnmarkedBlocksAndCleanup();
+}
+
+bool
+UnreachableCodeElimination::removeUnmarkedBlocksAndCleanup()
+{
+    // Everything is reachable, no work required.
+    JS_ASSERT(marked_ <= graph_.numBlocks());
     if (marked_ == graph_.numBlocks()) {
-        // Everything is reachable.
         graph_.unmarkBlocks();
         return true;
     }
 
-    // Pass 2: Remove unmarked blocks.
+    // Pass 2: Remove unmarked blocks (see analyze() above).
     if (!removeUnmarkedBlocksAndClearDominators())
         return false;
     graph_.unmarkBlocks();
 
+    AssertGraphCoherency(graph_);
+
+    IonSpewPass("UCEMidPoint");
+
     // Pass 3: Recompute dominators and tweak phis.
     BuildDominatorTree(graph_);
     if (redundantPhis_ && !EliminatePhis(mir_, graph_, ConservativeObservability))
         return false;
 
     return true;
 }
 
--- a/js/src/ion/UnreachableCodeElimination.h
+++ b/js/src/ion/UnreachableCodeElimination.h
@@ -21,24 +21,31 @@ class UnreachableCodeElimination
     MIRGenerator *mir_;
     MIRGraph &graph_;
     uint32_t marked_;
     bool redundantPhis_;
 
     bool prunePointlessBranchesAndMarkReachableBlocks();
     void removeUsesFromUnmarkedBlocks(MDefinition *instr);
     bool removeUnmarkedBlocksAndClearDominators();
+    bool removeUnmarkedBlocksAndCleanup();
 
   public:
     UnreachableCodeElimination(MIRGenerator *mir, MIRGraph &graph)
       : mir_(mir),
         graph_(graph),
         marked_(0),
         redundantPhis_(false)
     {}
 
+    // Walks the graph and discovers what is reachable. Removes everything else.
     bool analyze();
+
+    // Removes any blocks that are not marked.  Assumes that these blocks are not
+    // reachable.  The parameter |marked| should be the number of blocks that
+    // are marked.
+    bool removeUnmarkedBlocks(size_t marked);
 };
 
 } /* namespace ion */
 } /* namespace js */
 
 #endif
--- a/js/src/ion/VMFunctions.cpp
+++ b/js/src/ion/VMFunctions.cpp
@@ -8,16 +8,18 @@
 #include "Ion.h"
 #include "IonCompartment.h"
 #include "jsinterp.h"
 #include "ion/IonFrames.h"
 #include "ion/IonFrames-inl.h" // for GetTopIonJSScript
 
 #include "vm/StringObject-inl.h"
 
+#include "builtin/ParallelArray.h"
+
 #include "jsboolinlines.h"
 #include "jsinterpinlines.h"
 
 using namespace js;
 using namespace js::ion;
 
 namespace js {
 namespace ion {
--- a/js/src/ion/arm/MacroAssembler-arm.cpp
+++ b/js/src/ion/arm/MacroAssembler-arm.cpp
@@ -1482,16 +1482,22 @@ MacroAssemblerARMCompat::freeStack(Regis
 
 void
 MacroAssemblerARMCompat::add32(Imm32 imm, Register dest)
 {
     ma_add(imm, dest, SetCond);
 }
 
 void
+MacroAssemblerARMCompat::xor32(Imm32 imm, Register dest)
+{
+    ma_eor(imm, dest, SetCond);
+}
+
+void
 MacroAssemblerARMCompat::add32(Imm32 imm, const Address &dest)
 {
     load32(dest, ScratchRegister);
     ma_add(imm, ScratchRegister, SetCond);
     store32(ScratchRegister, dest);
 }
 
 void
--- a/js/src/ion/arm/MacroAssembler-arm.h
+++ b/js/src/ion/arm/MacroAssembler-arm.h
@@ -708,16 +708,19 @@ class MacroAssemblerARMCompat : public M
     void branchTest32(Condition cond, const Register &lhs, Imm32 imm, Label *label) {
         ma_tst(lhs, imm);
         ma_b(label, cond);
     }
     void branchTest32(Condition cond, const Address &address, Imm32 imm, Label *label) {
         ma_ldr(Operand(address.base, address.offset), ScratchRegister);
         branchTest32(cond, ScratchRegister, imm, label);
     }
+    void branchTestBool(Condition cond, const Register &lhs, const Register &rhs, Label *label) {
+        branchTest32(cond, lhs, rhs, label);
+    }
     void branchTestPtr(Condition cond, const Register &lhs, const Register &rhs, Label *label) {
         branchTest32(cond, lhs, rhs, label);
     }
     void branchPtr(Condition cond, Register lhs, Register rhs, Label *label) {
         branch32(cond, lhs, rhs, label);
     }
     void branchPtr(Condition cond, Register lhs, ImmGCPtr ptr, Label *label) {
         movePtr(ptr, ScratchRegister);
@@ -924,16 +927,17 @@ class MacroAssemblerARMCompat : public M
 
     void reserveStack(uint32_t amount);
     void freeStack(uint32_t amount);
     void freeStack(Register amount);
 
     void add32(Imm32 imm, Register dest);
     void add32(Imm32 imm, const Address &dest);
     void sub32(Imm32 imm, Register dest);
+    void xor32(Imm32 imm, Register dest);
 
     void and32(Imm32 imm, Register dest);
     void and32(Imm32 imm, const Address &dest);
     void or32(Imm32 imm, const Address &dest);
     void xorPtr(Imm32 imm, Register dest);
     void orPtr(Imm32 imm, Register dest);
     void orPtr(Register src, Register dest);
     void andPtr(Imm32 imm, Register dest);
--- a/js/src/ion/shared/CodeGenerator-shared.cpp
+++ b/js/src/ion/shared/CodeGenerator-shared.cpp
@@ -9,27 +9,30 @@
 
 #include "CodeGenerator-shared.h"
 #include "ion/MIRGenerator.h"
 #include "ion/IonFrames-inl.h"
 #include "ion/MIR.h"
 #include "CodeGenerator-shared-inl.h"
 #include "ion/IonSpewer.h"
 #include "ion/IonMacroAssembler.h"
+#include "ion/ParallelFunctions.h"
+#include "builtin/ParallelArray.h"
 
 using namespace js;
 using namespace js::ion;
 
 using mozilla::DebugOnly;
 
 namespace js {
 namespace ion {
 
 CodeGeneratorShared::CodeGeneratorShared(MIRGenerator *gen, LIRGraph *graph)
   : oolIns(NULL),
+    oolParallelAbort_(NULL),
     masm(&sps_),
     gen(gen),
     graph(*graph),
     current(NULL),
     deoptTable_(NULL),
 #ifdef DEBUG
     pushedArgs_(0),
 #endif
@@ -496,10 +499,94 @@ CodeGeneratorShared::markArgumentSlots(L
 {
     for (size_t i = 0; i < pushedArgumentSlots_.length(); i++) {
         if (!safepoint->addValueSlot(pushedArgumentSlots_[i]))
             return false;
     }
     return true;
 }
 
+bool
+CodeGeneratorShared::ensureOutOfLineParallelAbort(Label **result)
+{
+    if (!oolParallelAbort_) {
+        oolParallelAbort_ = new OutOfLineParallelAbort();
+        if (!addOutOfLineCode(oolParallelAbort_))
+            return false;
+    }
+
+    *result = oolParallelAbort_->entry();
+    return true;
+}
+
+bool
+OutOfLineParallelAbort::generate(CodeGeneratorShared *codegen)
+{
+    codegen->callTraceLIR(0xDEADBEEF, NULL, "ParallelBailout");
+    return codegen->visitOutOfLineParallelAbort(this);
+}
+
+bool
+CodeGeneratorShared::callTraceLIR(uint32_t blockIndex, LInstruction *lir,
+                                    const char *bailoutName)
+{
+    JS_ASSERT_IF(!lir, bailoutName);
+
+    uint32_t emi = (uint32_t) gen->info().executionMode();
+
+    if (!IonSpewEnabled(IonSpew_Trace))
+        return true;
+    masm.PushRegsInMask(RegisterSet::All());
+
+    RegisterSet regSet(RegisterSet::All());
+
+    Register blockIndexReg = regSet.takeGeneral();
+    Register lirIndexReg = regSet.takeGeneral();
+    Register emiReg = regSet.takeGeneral();
+    Register lirOpNameReg = regSet.takeGeneral();
+    Register mirOpNameReg = regSet.takeGeneral();
+    Register scriptReg = regSet.takeGeneral();
+    Register pcReg = regSet.takeGeneral();
+
+    // This first move is here so that when you scan the disassembly,
+    // you can easily pick out where each instruction begins.  The
+    // next few items indicate to you the Basic Block / LIR.
+    masm.move32(Imm32(0xDEADBEEF), blockIndexReg);
+
+    if (lir) {
+        masm.move32(Imm32(blockIndex), blockIndexReg);
+        masm.move32(Imm32(lir->id()), lirIndexReg);
+        masm.move32(Imm32(emi), emiReg);
+        masm.movePtr(ImmWord(lir->opName()), lirOpNameReg);
+        if (MDefinition *mir = lir->mirRaw()) {
+            masm.movePtr(ImmWord(mir->opName()), mirOpNameReg);
+            masm.movePtr(ImmWord((void *)mir->block()->info().script()), scriptReg);
+            masm.movePtr(ImmWord(mir->trackedPc()), pcReg);
+        } else {
+            masm.movePtr(ImmWord((void *)NULL), mirOpNameReg);
+            masm.movePtr(ImmWord((void *)NULL), scriptReg);
+            masm.movePtr(ImmWord((void *)NULL), pcReg);
+        }
+    } else {
+        masm.move32(Imm32(0xDEADBEEF), blockIndexReg);
+        masm.move32(Imm32(0xDEADBEEF), lirIndexReg);
+        masm.move32(Imm32(emi), emiReg);
+        masm.movePtr(ImmWord(bailoutName), lirOpNameReg);
+        masm.movePtr(ImmWord(bailoutName), mirOpNameReg);
+        masm.movePtr(ImmWord((void *)NULL), scriptReg);
+        masm.movePtr(ImmWord((void *)NULL), pcReg);
+    }
+
+    masm.setupUnalignedABICall(7, CallTempReg4);
+    masm.passABIArg(blockIndexReg);
+    masm.passABIArg(lirIndexReg);
+    masm.passABIArg(emiReg);
+    masm.passABIArg(lirOpNameReg);
+    masm.passABIArg(mirOpNameReg);
+    masm.passABIArg(scriptReg);
+    masm.passABIArg(pcReg);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, TraceLIR));
+    masm.PopRegsInMask(RegisterSet::All());
+    return true;
+}
+
 } // namespace ion
 } // namespace js
--- a/js/src/ion/shared/CodeGenerator-shared.h
+++ b/js/src/ion/shared/CodeGenerator-shared.h
@@ -20,25 +20,27 @@
 #include "ion/SnapshotWriter.h"
 
 namespace js {
 namespace ion {
 
 class OutOfLineCode;
 class CodeGenerator;
 class MacroAssembler;
+class OutOfLineParallelAbort;
 
 template <class ArgSeq, class StoreOutputTo>
 class OutOfLineCallVM;
 class OutOfLineTruncateSlow;
 
 class CodeGeneratorShared : public LInstructionVisitor
 {
     js::Vector<OutOfLineCode *, 0, SystemAllocPolicy> outOfLineCode_;
     OutOfLineCode *oolIns;
+    OutOfLineParallelAbort *oolParallelAbort_;
 
   public:
     MacroAssembler masm;
 
   protected:
     MIRGenerator *gen;
     LIRGraph &graph;
     LBlock *current;
@@ -288,16 +290,25 @@ class CodeGeneratorShared : public LInst
   public:
     CodeGeneratorShared(MIRGenerator *gen, LIRGraph *graph);
 
   public:
     template <class ArgSeq, class StoreOutputTo>
     bool visitOutOfLineCallVM(OutOfLineCallVM<ArgSeq, StoreOutputTo> *ool);
 
     bool visitOutOfLineTruncateSlow(OutOfLineTruncateSlow *ool);
+
+  public:
+    // When compiling parallel code, all bailouts just abort funnel to
+    // this same point and hence abort execution altogether:
+    virtual bool visitOutOfLineParallelAbort(OutOfLineParallelAbort *ool) = 0;
+    bool callTraceLIR(uint32_t blockIndex, LInstruction *lir, const char *bailoutName = NULL);
+
+  protected:
+    bool ensureOutOfLineParallelAbort(Label **result);
 };
 
 // Wrapper around Label, on the heap, to avoid a bogus assert with OOM.
 struct HeapLabel
   : public TempObject,
     public Label
 {
 };
@@ -536,13 +547,24 @@ CodeGeneratorShared::visitOutOfLineCallV
     if (!callVM(ool->function(), lir))
         return false;
     ool->out().generate(this);
     restoreLiveIgnore(lir, ool->out().clobbered());
     masm.jump(ool->rejoin());
     return true;
 }
 
+
+// An out-of-line parallel abort thunk.
+class OutOfLineParallelAbort : public OutOfLineCode
+{
+  public:
+    OutOfLineParallelAbort()
+    { }
+
+    bool generate(CodeGeneratorShared *codegen);
+};
+
 } // namespace ion
 } // namespace js
 
 #endif // jsion_codegen_shared_h__
 
--- a/js/src/ion/shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/ion/shared/CodeGenerator-x86-shared.cpp
@@ -10,16 +10,17 @@
 #include "jscntxt.h"
 #include "jscompartment.h"
 #include "jsmath.h"
 #include "CodeGenerator-x86-shared.h"
 #include "CodeGenerator-shared-inl.h"
 #include "ion/IonFrames.h"
 #include "ion/MoveEmitter.h"
 #include "ion/IonCompartment.h"
+#include "ion/ParallelFunctions.h"
 
 using namespace js;
 using namespace js::ion;
 
 namespace js {
 namespace ion {
 
 CodeGeneratorX86Shared::CodeGeneratorX86Shared(MIRGenerator *gen, LIRGraph *graph)
@@ -285,16 +286,30 @@ class BailoutLabel {
     void operator()(MacroAssembler &masm, Label *label) const {
         masm.retarget(label_, label);
     }
 };
 
 template <typename T> bool
 CodeGeneratorX86Shared::bailout(const T &binder, LSnapshot *snapshot)
 {
+    CompileInfo &info = snapshot->mir()->block()->info();
+    switch (info.executionMode()) {
+      case ParallelExecution: {
+        // in parallel mode, make no attempt to recover, just signal an error.
+        Label *ool;
+        if (!ensureOutOfLineParallelAbort(&ool))
+            return false;
+        binder(masm, ool);
+        return true;
+      }
+
+      case SequentialExecution: break;
+    }
+
     if (!encode(snapshot))
         return false;
 
     // Though the assembler doesn't track all frame pushes, at least make sure
     // the known value makes sense. We can't use bailout tables if the stack
     // isn't properly aligned to the static frame size.
     JS_ASSERT_IF(frameClass_ != FrameSizeClass::None() && deoptTable_,
                  frameClass_.frameSize() == masm.framePushed());
--- a/js/src/ion/shared/Lowering-shared.h
+++ b/js/src/ion/shared/Lowering-shared.h
@@ -20,17 +20,17 @@ namespace ion {
 class MBasicBlock;
 class MTableSwitch;
 class MIRGenerator;
 class MIRGraph;
 class MDefinition;
 class MInstruction;
 class LOsiPoint;
 
-class LIRGeneratorShared : public MInstructionVisitor
+class LIRGeneratorShared : public MInstructionVisitorWithDefaults
 {
   protected:
     MIRGenerator *gen;
     MIRGraph &graph;
     LIRGraph &lirGraph_;
     LBlock *current;
     MResumePoint *lastResumePoint_;
     LOsiPoint *osiPoint_;
--- a/js/src/ion/shared/MacroAssembler-x86-shared.h
+++ b/js/src/ion/shared/MacroAssembler-x86-shared.h
@@ -106,16 +106,19 @@ class MacroAssemblerX86Shared : public A
         addl(imm, dest);
     }
     void add32(Imm32 imm, const Address &dest) {
         addl(imm, Operand(dest));
     }
     void sub32(Imm32 imm, Register dest) {
         subl(imm, dest);
     }
+    void xor32(Imm32 imm, Register dest) {
+        xorl(imm, dest);
+    }
 
     void branch32(Condition cond, const Address &lhs, const Register &rhs, Label *label) {
         cmpl(Operand(lhs), rhs);
         j(cond, label);
     }
     void branch32(Condition cond, const Address &lhs, Imm32 imm, Label *label) {
         cmpl(Operand(lhs), imm);
         j(cond, label);
@@ -135,16 +138,20 @@ class MacroAssemblerX86Shared : public A
     void branchTest32(Condition cond, const Register &lhs, Imm32 imm, Label *label) {
         testl(lhs, imm);
         j(cond, label);
     }
     void branchTest32(Condition cond, const Address &address, Imm32 imm, Label *label) {
         testl(Operand(address), imm);
         j(cond, label);
     }
+    void branchTestBool(Condition cond, const Register &lhs, const Register &rhs, Label *label) {
+        testb(lhs, rhs);
+        j(cond, label);
+    }
 
     // The following functions are exposed for use in platform-shared code.
     template <typename T>
     void Push(const T &t) {
         push(t);
         framePushed_ += STACK_SLOT_SIZE;
     }
     void Push(const FloatRegister &t) {
--- a/js/src/ion/x86/CodeGenerator-x86.cpp
+++ b/js/src/ion/x86/CodeGenerator-x86.cpp
@@ -11,16 +11,17 @@
 
 #include "CodeGenerator-x86.h"
 #include "ion/MIR.h"
 #include "ion/MIRGraph.h"
 #include "ion/shared/CodeGenerator-shared-inl.h"
 #include "vm/Shape.h"
 
 #include "jsscriptinlines.h"
+#include "ion/ExecutionModeInlines.h"
 
 using namespace js;
 using namespace js::ion;
 
 using mozilla::DebugOnly;
 
 CodeGeneratorX86::CodeGeneratorX86(MIRGenerator *gen, LIRGraph *graph)
   : CodeGeneratorX86Shared(gen, graph)
@@ -136,22 +137,24 @@ CodeGeneratorX86::visitUnbox(LUnbox *unb
             return false;
     }
     return true;
 }
 
 void
 CodeGeneratorX86::linkAbsoluteLabels()
 {
+    ExecutionMode executionMode = gen->info().executionMode();
     UnrootedScript script = gen->info().script();
-    IonCode *method = script->ion->method();
+    IonScript *ionScript = GetIonScript(script, executionMode);
+    IonCode *method = ionScript->method();
 
     for (size_t i = 0; i < deferredDoubles_.length(); i++) {
         DeferredDouble *d = deferredDoubles_[i];
-        const Value &v = script->ion->getConstant(d->index());
+        const Value &v = ionScript->getConstant(d->index());
         MacroAssembler::Bind(method, d->label(), &v);
     }
 }
 
 bool
 CodeGeneratorX86::visitDouble(LDouble *ins)
 {
     const LDefinition *out = ins->getDef(0);
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -877,16 +877,17 @@ JSRuntime::JSRuntime(JSUseHelperThreads 
     preserveWrapperCallback(NULL),
 #ifdef DEBUG
     noGCOrAllocationCheck(0),
 #endif
     jitHardening(false),
     ionPcScriptCache(NULL),
     threadPool(this),
     ctypesActivityCallback(NULL),
+    parallelWarmup(0),
     ionReturnOverride_(MagicValue(JS_ARG_POISON)),
     useHelperThreads_(useHelperThreads),
     requestedHelperThreadCount(-1),
     rngNonce(0)
 {
     /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
     JS_INIT_CLIST(&onNewGlobalObjectWatchers);
 
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -1150,16 +1150,20 @@ struct JSRuntime : js::RuntimeFriendFiel
 
     // Cache for ion::GetPcScript().
     js::ion::PcScriptCache *ionPcScriptCache;
 
     js::ThreadPool threadPool;
 
     js::CTypesActivityCallback  ctypesActivityCallback;
 
+    // Non-zero if this is a parallel warmup execution.  See
+    // js::parallel::Do() for more information.
+    uint32_t parallelWarmup;
+
   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.
     //
@@ -2256,16 +2260,26 @@ class ContextAllocPolicy
     JSContext *context() const { return cx; }
     void *malloc_(size_t bytes) { return cx->malloc_(bytes); }
     void *calloc_(size_t bytes) { return cx->calloc_(bytes); }
     void *realloc_(void *p, size_t oldBytes, size_t bytes) { return cx->realloc_(p, oldBytes, bytes); }
     void free_(void *p) { js_free(p); }
     void reportAllocOverflow() const { js_ReportAllocationOverflow(cx); }
 };
 
+JSBool intrinsic_ThrowError(JSContext *cx, unsigned argc, Value *vp);
+JSBool intrinsic_NewDenseArray(JSContext *cx, unsigned argc, Value *vp);
+JSBool intrinsic_UnsafeSetElement(JSContext *cx, unsigned argc, Value *vp);
+JSBool intrinsic_ForceSequential(JSContext *cx, unsigned argc, Value *vp);
+JSBool intrinsic_NewParallelArray(JSContext *cx, unsigned argc, Value *vp);
+
+#ifdef DEBUG
+JSBool intrinsic_Dump(JSContext *cx, unsigned argc, Value *vp);
+#endif
+
 } /* namespace js */
 
 #ifdef _MSC_VER
 #pragma warning(pop)
 #pragma warning(pop)
 #endif
 
 #endif /* jscntxt_h___ */
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -75,16 +75,17 @@
 #include "gc/GCInternals.h"
 #include "gc/Marking.h"
 #include "gc/Memory.h"
 #include "methodjit/MethodJIT.h"
 #include "vm/Debugger.h"
 #include "vm/ForkJoin.h"
 #include "vm/Shape.h"
 #include "vm/String.h"
+#include "vm/ForkJoin.h"
 #include "ion/IonCode.h"
 #ifdef JS_ION
 # include "ion/IonMacroAssembler.h"
 #include "ion/IonFrameIterator.h"
 #endif
 
 #include "jsgcinlines.h"
 #include "jsinterpinlines.h"
@@ -1191,20 +1192,20 @@ PushArenaAllocatedDuringSweep(JSRuntime 
     arena->setNextAllocDuringSweep(runtime->gcArenasAllocatedDuringSweep);
     runtime->gcArenasAllocatedDuringSweep = arena;
 }
 
 void *
 ArenaLists::parallelAllocate(Zone *zone, AllocKind thingKind, size_t thingSize)
 {
     /*
-     * During parallel Rivertrail sections, no GC is permitted. If no
-     * existing arena can satisfy the allocation, then a new one is
-     * allocated. If that fails, then we return NULL which will cause
-     * the parallel section to abort.
+     * During parallel Rivertrail sections, if no existing arena can
+     * satisfy the allocation, then a new one is allocated. If that
+     * fails, then we return NULL which will cause the parallel
+     * section to abort.
      */
 
     void *t = allocateFromFreeList(thingKind, thingSize);
     if (t)
         return t;
 
     return allocateFromArena(zone, thingKind);
 }
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -277,16 +277,21 @@ struct ArenaLists {
             ArenaHeader **headp = &arenaLists[i].head;
             while (ArenaHeader *aheader = *headp) {
                 *headp = aheader->next;
                 aheader->chunk()->releaseArena(aheader);
             }
         }
     }
 
+    static uintptr_t getFreeListOffset(AllocKind thingKind) {
+        uintptr_t offset = offsetof(ArenaLists, freeLists);
+        return offset + thingKind * sizeof(FreeSpan);
+    }
+
     const FreeSpan *getFreeList(AllocKind thingKind) const {
         return &freeLists[thingKind];
     }
 
     ArenaHeader *getFirstArena(AllocKind thingKind) const {
         return arenaLists[thingKind].head;
     }
 
--- a/js/src/jspubtd.h
+++ b/js/src/jspubtd.h
@@ -324,16 +324,24 @@ struct PerThreadDataFriendFields
 #endif
         } mainThread;
     };
 
   public:
 
     PerThreadDataFriendFields();
 
+#if defined(JSGC_ROOT_ANALYSIS) || defined(JSGC_USE_EXACT_ROOTING)
+    /*
+     * Stack allocated GC roots for stack GC heap pointers, which may be
+     * overwritten if moved during a GC.
+     */
+    Rooted<void*> *thingGCRooters[THING_ROOT_LIMIT];
+#endif
+
 #if defined(DEBUG) && defined(JS_GC_ZEAL) && defined(JSGC_ROOT_ANALYSIS) && !defined(JS_THREADSAFE)
     /*
      * Stack allocated list of stack locations which hold non-relocatable
      * GC heap pointers (where the target is rooted somewhere else) or integer
      * values which may be confused for GC heap pointers. These are used to
      * suppress false positives which occur when a rooting analysis treats the
      * location as holding a relocatable pointer, but have no other effect on
      * GC behavior.
--- a/js/src/vm/ForkJoin.cpp
+++ b/js/src/vm/ForkJoin.cpp
@@ -51,29 +51,25 @@ class js::ForkJoinShared : public TaskEx
     bool gcRequested_;             // True if a worker requested a GC
     gcreason::Reason gcReason_;    // Reason given to request GC
     Zone *gcZone_;                 // Zone for GC, or NULL for full
 
     /////////////////////////////////////////////////////////////////////////
     // Asynchronous Flags
     //
     // These can be read without the lock (hence the |volatile| declaration).
+    // All fields should be *written with the lock*, however.
 
-    // A thread has bailed and others should follow suit.  Set and read
-    // asynchronously.  After setting abort, workers will acquire the lock,
-    // decrement uncompleted, and then notify if uncompleted has reached
-    // blocked.
+    // Set to true when parallel execution should abort.
     volatile bool abort_;
 
     // Set to true when a worker bails for a fatal reason.
     volatile bool fatal_;
 
-    // A thread has request a rendezvous.  Only *written* with the lock (in
-    // |initiateRendezvous()| and |endRendezvous()|) but may be *read* without
-    // the lock.
+    // The main thread has requested a rendezvous.
     volatile bool rendezvous_;
 
     // Invoked only from the main thread:
     void executeFromMainThread();
 
     // Executes slice #threadId of the work, either from a worker or
     // the main thread.
     void executePortion(PerThreadData *perThread, uint32_t threadId);
@@ -116,25 +112,22 @@ class js::ForkJoinShared : public TaskEx
     // processes any pending requests for a GC.  This can only safely
     // be invoked on the main thread, either during a rendezvous or
     // after the workers have completed.
     void transferArenasToCompartmentAndProcessGCRequests();
 
     // Invoked during processing by worker threads to "check in".
     bool check(ForkJoinSlice &threadCx);
 
-    // See comment on |ForkJoinSlice::setFatal()| in forkjoin.h
-    bool setFatal();
-
-    // Requests a GC, either full or specific to a compartment.
+    // Requests a GC, either full or specific to a zone.
     void requestGC(gcreason::Reason reason);
     void requestZoneGC(JS::Zone *zone, gcreason::Reason reason);
 
     // Requests that computation abort.
-    void setAbortFlag();
+    void setAbortFlag(bool fatal);
 
     JSRuntime *runtime() { return cx_->runtime; }
 };
 
 class js::AutoRendezvous
 {
   private:
     ForkJoinSlice &threadCx;
@@ -305,62 +298,54 @@ ForkJoinShared::executeFromWorker(uint32
         // complete.
         lock.notify();
     }
 }
 
 void
 ForkJoinShared::executeFromMainThread()
 {
-    executePortion(&cx_->runtime->mainThread, numSlices_ - 1);
+    executePortion(&cx_->mainThread(), numSlices_ - 1);
 }
 
 void
 ForkJoinShared::executePortion(PerThreadData *perThread,
                                uint32_t threadId)
 {
     Allocator *allocator = allocators_[threadId];
     ForkJoinSlice slice(perThread, threadId, numSlices_, allocator, this);
     AutoSetForkJoinSlice autoContext(&slice);
 
     if (!op_.parallel(slice))
-        setAbortFlag();
-}
-
-bool
-ForkJoinShared::setFatal()
-{
-    // Might as well set the abort flag to true, as it will make propagation
-    // faster.
-    setAbortFlag();
-    fatal_ = true;
-    return false;
+        setAbortFlag(false);
 }
 
 bool
 ForkJoinShared::check(ForkJoinSlice &slice)
 {
+    JS_ASSERT(cx_->runtime->interrupt);
+
     if (abort_)
         return false;
 
     if (slice.isMainThread()) {
         JS_ASSERT(!cx_->runtime->gcIsNeeded);
 
         if (cx_->runtime->interrupt) {
             // The GC Needed flag should not be set during parallel
             // execution.  Instead, one of the requestGC() or
             // requestZoneGC() methods should be invoked.
             JS_ASSERT(!cx_->runtime->gcIsNeeded);
 
             // If interrupt is requested, bring worker threads to a halt,
             // service the interrupt, then let them start back up again.
             // AutoRendezvous autoRendezvous(slice);
             // if (!js_HandleExecutionInterrupt(cx_))
-            //     return setFatal();
-            setAbortFlag();
+            //     return setAbortFlag(true);
+            setAbortFlag(false);
             return false;
         }
     } else if (rendezvous_) {
         joinRendezvous(slice);
     }
 
     return true;
 }
@@ -394,16 +379,17 @@ ForkJoinShared::initiateRendezvous(ForkJ
     //
     //   Note that the main thread cannot ever get more than one rendezvous
     //   ahead of the workers, because it must wait for all of them to enter
     //   the rendezvous before it can end it, so the solution of using a
     //   counter is perfectly general and we need not fear rollover.
 
     JS_ASSERT(slice.isMainThread());
     JS_ASSERT(!rendezvous_ && blocked_ == 0);
+    JS_ASSERT(cx_->runtime->interrupt);
 
     AutoLockMonitor lock(*this);
 
     // Signal other threads we want to start a rendezvous.
     rendezvous_ = true;
 
     // Wait until all the other threads blocked themselves.
     while (blocked_ != uncompleted_)
@@ -435,26 +421,31 @@ ForkJoinShared::joinRendezvous(ForkJoinS
 void
 ForkJoinShared::endRendezvous(ForkJoinSlice &slice)
 {
     JS_ASSERT(slice.isMainThread());
 
     AutoLockMonitor lock(*this);
     rendezvous_ = false;
     blocked_ = 0;
-    rendezvousIndex_ += 1;
+    rendezvousIndex_++;
 
     // Signal other threads that rendezvous is over.
     PR_NotifyAllCondVar(rendezvousEnd_);
 }
 
 void
-ForkJoinShared::setAbortFlag()
+ForkJoinShared::setAbortFlag(bool fatal)
 {
+    AutoLockMonitor lock(*this);
+
     abort_ = true;
+    fatal_ = fatal_ || fatal;
+
+    cx_->runtime->triggerOperationCallback();
 }
 
 void
 ForkJoinShared::requestGC(gcreason::Reason reason)
 {
     AutoLockMonitor lock(*this);
 
     gcZone_ = NULL;
@@ -517,27 +508,20 @@ ForkJoinSlice::runtime()
     return NULL;
 #endif
 }
 
 bool
 ForkJoinSlice::check()
 {
 #ifdef JS_THREADSAFE
-    return shared->check(*this);
-#else
-    return false;
-#endif
-}
-
-bool
-ForkJoinSlice::setFatal()
-{
-#ifdef JS_THREADSAFE
-    return shared->setFatal();
+    if (runtime()->interrupt)
+        return shared->check(*this);
+    else
+        return true;
 #else
     return false;
 #endif
 }
 
 bool
 ForkJoinSlice::Initialize()
 {
@@ -566,17 +550,17 @@ ForkJoinSlice::requestZoneGC(JS::Zone *z
     triggerAbort();
 #endif
 }
 
 #ifdef JS_THREADSAFE
 void
 ForkJoinSlice::triggerAbort()
 {
-    shared->setAbortFlag();
+    shared->setAbortFlag(false);
 
     // set iontracklimit to -1 so that on next entry to a function,
     // the thread will trigger the overrecursedcheck.  If the thread
     // is in a loop, then it will be calling ForkJoinSlice::check(),
     // in which case it will notice the shared abort_ flag.
     //
     // In principle, we probably ought to set the ionStackLimit's for
     // the other threads too, but right now the various slice objects
--- a/js/src/vm/ForkJoin.h
+++ b/js/src/vm/ForkJoin.h
@@ -4,16 +4,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 ForkJoin_h__
 #define ForkJoin_h__
 
 #include "vm/ThreadPool.h"
+#include "jsgc.h"
 
 // ForkJoin
 //
 // This is the building block for executing multi-threaded JavaScript with
 // shared memory (as distinct from Web Workers).  The idea is that you have
 // some (typically data-parallel) operation which you wish to execute in
 // parallel across as many threads as you have available.  An example might be
 // applying |map()| to a vector in parallel. To implement such a thing, you
@@ -120,18 +121,17 @@ ForkJoinSlices(JSContext *cx);
 ParallelResult ExecuteForkJoinOp(JSContext *cx, ForkJoinOp &op);
 
 class PerThreadData;
 class ForkJoinShared;
 class AutoRendezvous;
 class AutoSetForkJoinSlice;
 
 #ifdef DEBUG
-struct IonTraceData
-{
+struct IonLIRTraceData {
     uint32_t bblock;
     uint32_t lir;
     uint32_t execModeInt;
     const char *lirOpName;
     const char *mirOpName;
     JSScript *script;
     jsbytecode *pc;
 };
@@ -152,52 +152,51 @@ struct ForkJoinSlice
     // Allocator to use when allocating on this thread.  See
     // |ion::ParFunctions::ParNewGCThing()|.  This should move into
     // |perThreadData|.
     Allocator *const allocator;
 
     // If we took a parallel bailout, the script that bailed out is stored here.
     JSScript *abortedScript;
 
-    // Records the last instruction to execute on this thread.
 #ifdef DEBUG
-    IonTraceData traceData;
+    // Records the last instr. to execute on this thread.
+    IonLIRTraceData traceData;
 #endif
 
     ForkJoinSlice(PerThreadData *perThreadData, uint32_t sliceId, uint32_t numSlices,
                   Allocator *arenaLists, ForkJoinShared *shared);
 
     // True if this is the main thread, false if it is one of the parallel workers.
     bool isMainThread();
 
-    // Generally speaking, if a thread returns false, that is interpreted as a
-    // "bailout"---meaning, a recoverable error.  If however you call this
-    // function before returning false, then the error will be interpreted as
-    // *fatal*.  This doesn't strike me as the most elegant solution here but
-    // I don't know what'd be better.
-    //
-    // For convenience, *always* returns false.
-    bool setFatal();
-
     // When the code would normally trigger a GC, we don't trigger it
     // immediately but instead record that request here.  This will
     // cause |ExecuteForkJoinOp()| to invoke |TriggerGC()| or
     // |TriggerCompartmentGC()| as appropriate once the parallel
     // section is complete. This is done because those routines do
     // various preparations that are not thread-safe, and because the
     // full set of arenas is not available until the end of the
     // parallel section.
     void requestGC(gcreason::Reason reason);
     void requestZoneGC(JS::Zone *zone, gcreason::Reason reason);
 
-    // During the parallel phase, this method should be invoked periodically,
-    // for example on every backedge, similar to the interrupt check.  If it
-    // returns false, then the parallel phase has been aborted and so you
-    // should bailout.  The function may also rendesvous to perform GC or do
-    // other similar things.
+    // During the parallel phase, this method should be invoked
+    // periodically, for example on every backedge, similar to the
+    // interrupt check.  If it returns false, then the parallel phase
+    // has been aborted and so you should bailout.  The function may
+    // also rendesvous to perform GC or do other similar things.
+    //
+    // This function is guaranteed to have no effect if both
+    // runtime()->interrupt is zero.  Ion-generated code takes
+    // advantage of this by inlining the checks on those flags before
+    // actually calling this function.  If this function ends up
+    // getting called a lot from outside ion code, we can refactor
+    // it into an inlined version with this check that calls a slower
+    // version.
     bool check();
 
     // Be wary, the runtime is shared between all threads!
     JSRuntime *runtime();
 
     // Check the current state of parallel execution.
     static inline ForkJoinSlice *Current();
     static inline bool InParallelSection();
new file mode 100644
--- /dev/null
+++ b/js/src/vm/ParallelDo.cpp
@@ -0,0 +1,359 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et tw=99 ft=cpp:
+ *
+ * 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 "vm/ParallelDo.h"
+
+#include "jsapi.h"
+#include "jsobj.h"
+#include "jsarray.h"
+
+#include "vm/String.h"
+#include "vm/GlobalObject.h"
+#include "vm/ThreadPool.h"
+#include "vm/ForkJoin.h"
+
+#include "jsinterpinlines.h"
+#include "jsobjinlines.h"
+
+#if defined(DEBUG) && defined(JS_THREADSAFE) && defined(JS_ION)
+#include "ion/Ion.h"
+#include "ion/MIR.h"
+#include "ion/MIRGraph.h"
+#include "ion/IonCompartment.h"
+
+#include "prprf.h"
+#endif
+
+using namespace js;
+using namespace js::parallel;
+using namespace js::ion;
+
+//
+// Debug spew
+//
+
+#if defined(DEBUG) && defined(JS_THREADSAFE) && defined(JS_ION)
+
+static const char *
+ExecutionStatusToString(ExecutionStatus status)
+{
+    switch (status) {
+      case ExecutionFatal:
+        return "fatal";
+      case ExecutionSequential:
+        return "sequential";
+      case ExecutionParallel:
+        return "parallel";
+    }
+    return "(unknown status)";
+}
+
+static const char *
+MethodStatusToString(MethodStatus status)
+{
+    switch (status) {
+      case Method_Error:
+        return "error";
+      case Method_CantCompile:
+        return "can't compile";
+      case Method_Skipped:
+        return "skipped";
+      case Method_Compiled:
+        return "compiled";
+    }
+    return "(unknown status)";
+}
+
+static const size_t BufferSize = 4096;
+
+class ParallelSpewer
+{
+    uint32_t depth;
+    bool colorable;
+    bool active[NumSpewChannels];
+
+    const char *color(const char *colorCode) {
+        if (!colorable)
+            return "";
+        return colorCode;
+    }
+
+    const char *reset() { return color("\x1b[0m"); }
+    const char *bold() { return color("\x1b[1m"); }
+    const char *red() { return color("\x1b[31m"); }
+    const char *green() { return color("\x1b[32m"); }
+    const char *yellow() { return color("\x1b[33m"); }
+    const char *cyan() { return color("\x1b[36m"); }
+    const char *sliceColor(uint32_t id) {
+        static const char *colors[] = {
+            "\x1b[7m\x1b[31m", "\x1b[7m\x1b[32m", "\x1b[7m\x1b[33m",
+            "\x1b[7m\x1b[34m", "\x1b[7m\x1b[35m", "\x1b[7m\x1b[36m",
+            "\x1b[7m\x1b[37m",
+            "\x1b[31m", "\x1b[32m", "\x1b[33m",
+            "\x1b[34m", "\x1b[35m", "\x1b[36m",
+            "\x1b[37m"
+        };
+        return color(colors[id % 14]);
+    }
+
+  public:
+    ParallelSpewer()
+      : depth(0)
+    {
+        const char *env;
+
+        PodArrayZero(active);
+        env = getenv("PAFLAGS");
+        if (env) {
+            if (strstr(env, "ops"))
+                active[SpewOps] = true;
+            if (strstr(env, "compile"))
+                active[SpewCompile] = true;
+            if (strstr(env, "bailouts"))
+                active[SpewBailouts] = true;
+            if (strstr(env, "full")) {
+                for (uint32_t i = 0; i < NumSpewChannels; i++)
+                    active[i] = true;
+            }
+        }
+
+        env = getenv("TERM");
+        if (env) {
+            if (strcmp(env, "xterm-color") == 0 || strcmp(env, "xterm-256color") == 0)
+                colorable = true;
+        }
+    }
+
+    bool isActive(SpewChannel channel) {
+        return active[channel];
+    }
+
+    void spewVA(SpewChannel channel, const char *fmt, va_list ap) {
+        if (!active[channel])
+            return;
+
+        // Print into a buffer first so we use one fprintf, which usually
+        // doesn't get interrupted when running with multiple threads.
+        char buf[BufferSize];
+
+        if (ForkJoinSlice *slice = ForkJoinSlice::Current()) {
+            PR_snprintf(buf, BufferSize, "[%sParallel:%u%s] ",
+                        sliceColor(slice->sliceId), slice->sliceId, reset());
+        } else {
+            PR_snprintf(buf, BufferSize, "[Parallel:M] ");
+        }
+
+        for (uint32_t i = 0; i < depth; i++)
+            PR_snprintf(buf + strlen(buf), BufferSize, "  ");
+
+        PR_vsnprintf(buf + strlen(buf), BufferSize, fmt, ap);
+        PR_snprintf(buf + strlen(buf), BufferSize, "\n");
+
+        fprintf(stderr, "%s", buf);
+    }
+
+    void spew(SpewChannel channel, const char *fmt, ...) {
+        va_list ap;
+        va_start(ap, fmt);
+        spewVA(channel, fmt, ap);
+        va_end(ap);
+    }
+
+    void beginOp(JSContext *cx, const char *name) {
+        if (!active[SpewOps])
+            return;
+
+        if (cx) {
+            jsbytecode *pc;
+            JSScript *script = cx->stack.currentScript(&pc);
+            if (script && pc) {
+                NonBuiltinScriptFrameIter iter(cx);
+                if (iter.done()) {
+                    spew(SpewOps, "%sBEGIN %s%s (%s:%u)", bold(), name, reset(),
+                         script->filename, PCToLineNumber(script, pc));
+                } else {
+                    spew(SpewOps, "%sBEGIN %s%s (%s:%u -> %s:%u)", bold(), name, reset(),
+                         iter.script()->filename, PCToLineNumber(iter.script(), iter.pc()),
+                         script->filename, PCToLineNumber(script, pc));
+                }
+            } else {
+                spew(SpewOps, "%sBEGIN %s%s", bold(), name, reset());
+            }
+        } else {
+            spew(SpewOps, "%sBEGIN %s%s", bold(), name, reset());
+        }
+
+        depth++;
+    }
+
+    void endOp(ExecutionStatus status) {
+        if (!active[SpewOps])
+            return;
+
+        JS_ASSERT(depth > 0);
+        depth--;
+
+        const char *statusColor;
+        switch (status) {
+          case ExecutionFatal:
+            statusColor = red();
+            break;
+          case ExecutionSequential:
+            statusColor = yellow();
+            break;
+          case ExecutionParallel:
+            statusColor = green();
+            break;
+          default:
+            statusColor = reset();
+            break;
+        }
+
+        spew(SpewOps, "%sEND %s%s%s", bold(),
+             statusColor, ExecutionStatusToString(status), reset());
+    }
+
+    void bailout(uint32_t count) {
+        if (!active[SpewOps])
+            return;
+
+        spew(SpewOps, "%s%sBAILOUT %d%s", bold(), yellow(), count, reset());
+    }
+
+    void beginCompile(HandleFunction fun) {
+        if (!active[SpewCompile])
+            return;
+
+        spew(SpewCompile, "COMPILE %p:%s:%u",
+             fun.get(), fun->nonLazyScript()->filename, fun->nonLazyScript()->lineno);
+        depth++;
+    }
+
+    void endCompile(MethodStatus status) {
+        if (!active[SpewCompile])
+            return;
+
+        JS_ASSERT(depth > 0);
+        depth--;
+
+        const char *statusColor;
+        switch (status) {
+          case Method_Error:
+          case Method_CantCompile:
+            statusColor = red();
+            break;
+          case Method_Skipped:
+            statusColor = yellow();
+            break;
+          case Method_Compiled:
+            statusColor = green();
+            break;
+          default:
+            statusColor = reset();
+            break;
+        }
+
+        spew(SpewCompile, "END %s%s%s", statusColor, MethodStatusToString(status), reset());
+    }
+
+    void spewMIR(MDefinition *mir, const char *fmt, va_list ap) {
+        if (!active[SpewCompile])
+            return;
+
+        char buf[BufferSize];
+        PR_vsnprintf(buf, BufferSize, fmt, ap);
+
+        JSScript *script = mir->block()->info().script();
+        spew(SpewCompile, "%s%s%s: %s (%s:%u)", cyan(), mir->opName(), reset(), buf,
+             script->filename, PCToLineNumber(script, mir->trackedPc()));
+    }
+
+    void spewBailoutIR(uint32_t bblockId, uint32_t lirId,
+                       const char *lir, const char *mir, JSScript *script, jsbytecode *pc) {
+        if (!active[SpewBailouts])
+            return;
+
+        // If we didn't bail from a LIR/MIR but from a propagated parallel
+        // bailout, don't bother printing anything since we've printed it
+        // elsewhere.
+        if (mir && script) {
+            spew(SpewBailouts, "%sBailout%s: %s / %s%s%s (block %d lir %d) (%s:%u)", yellow(), reset(),
+                 lir, cyan(), mir, reset(),
+                 bblockId, lirId,
+                 script->filename, PCToLineNumber(script, pc));
+        }
+    }
+};
+
+// Singleton instance of the spewer.
+static ParallelSpewer spewer;
+
+bool
+parallel::SpewEnabled(SpewChannel channel)
+{
+    return spewer.isActive(channel);
+}
+
+void
+parallel::Spew(SpewChannel channel, const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    spewer.spewVA(channel, fmt, ap);
+    va_end(ap);
+}
+
+void
+parallel::SpewBeginOp(JSContext *cx, const char *name)
+{
+    spewer.beginOp(cx, name);
+}
+
+ExecutionStatus
+parallel::SpewEndOp(ExecutionStatus status)
+{
+    spewer.endOp(status);
+    return status;
+}
+
+void
+parallel::SpewBailout(uint32_t count)
+{
+    spewer.bailout(count);
+}
+
+void
+parallel::SpewBeginCompile(HandleFunction fun)
+{
+    spewer.beginCompile(fun);
+}
+
+MethodStatus
+parallel::SpewEndCompile(MethodStatus status)
+{
+    spewer.endCompile(status);
+    return status;
+}
+
+void
+parallel::SpewMIR(MDefinition *mir, const char *fmt, ...)
+{
+    va_list ap;
+    va_start(ap, fmt);
+    spewer.spewMIR(mir, fmt, ap);
+    va_end(ap);
+}
+
+void
+parallel::SpewBailoutIR(uint32_t bblockId, uint32_t lirId,
+                        const char *lir, const char *mir,
+                        JSScript *script, jsbytecode *pc)
+{
+    spewer.spewBailoutIR(bblockId, lirId, lir, mir, script, pc);
+}
+
+#endif // DEBUG && JS_THREADSAFE && JS_ION
new file mode 100644
--- /dev/null
+++ b/js/src/vm/ParallelDo.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et tw=99 ft=cpp:
+ *
+ * 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 ParallelDo_h__
+#define ParallelDo_h__
+
+#include "jsapi.h"
+#include "jscntxt.h"
+#include "jsobj.h"
+#include "ion/Ion.h"
+
+namespace js {
+namespace parallel {
+
+///////////////////////////////////////////////////////////////////////////
+// Debug Spew
+
+enum ExecutionStatus {
+    // Parallel or seq execution terminated in a fatal way, operation failed
+    ExecutionFatal,
+
+    // Parallel exec failed and so we fell back to sequential
+    ExecutionSequential,
+
+    // Parallel exec was successful after some number of bailouts
+    ExecutionParallel
+};
+
+enum SpewChannel {
+    SpewOps,
+    SpewCompile,
+    SpewBailouts,
+    NumSpewChannels
+};
+
+#if defined(DEBUG) && defined(JS_THREADSAFE) && defined(JS_ION)
+
+bool SpewEnabled(SpewChannel channel);
+void Spew(SpewChannel channel, const char *fmt, ...);
+void SpewBeginOp(JSContext *cx, const char *name);
+void SpewBailout(uint32_t count);
+ExecutionStatus SpewEndOp(ExecutionStatus status);
+void SpewBeginCompile(HandleFunction fun);
+ion::MethodStatus SpewEndCompile(ion::MethodStatus status);
+void SpewMIR(ion::MDefinition *mir, const char *fmt, ...);
+void SpewBailoutIR(uint32_t bblockId, uint32_t lirId,
+                   const char *lir, const char *mir, JSScript *script, jsbytecode *pc);
+
+#else
+
+static inline bool SpewEnabled(SpewChannel channel) { return false; }
+static inline void Spew(SpewChannel channel, const char *fmt, ...) { }
+static inline void SpewBeginOp(JSContext *cx, const char *name) { }
+static inline void SpewBailout(uint32_t count) {}
+static inline ExecutionStatus SpewEndOp(ExecutionStatus status) { return status; }
+static inline void SpewBeginCompile(HandleFunction fun) { }
+#ifdef JS_ION
+static inline ion::MethodStatus SpewEndCompile(ion::MethodStatus status) { return status; }
+static inline void SpewMIR(ion::MDefinition *mir, const char *fmt, ...) { }
+#endif
+static inline void SpewBailoutIR(uint32_t bblockId, uint32_t lirId,
+                                 const char *lir, const char *mir,
+                                 JSScript *script, jsbytecode *pc) { }
+
+#endif // DEBUG && JS_THREADSAFE && JS_ION
+
+} // namespace parallel
+} // namespace js
+
+#endif
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -8,16 +8,21 @@
 #include "jscntxt.h"
 #include "jscompartment.h"
 #include "jsinterp.h"
 #include "jsnum.h"
 #include "jsobj.h"
 
 #include "gc/Marking.h"
 
+#include "vm/ForkJoin.h"
+#include "vm/ThreadPool.h"
+
+#include "builtin/ParallelArray.h"
+
 #include "jsfuninlines.h"
 #include "jstypedarrayinlines.h"
 
 #include "vm/BooleanObject-inl.h"
 #include "vm/NumberObject-inl.h"
 #include "vm/RegExpObject-inl.h"
 #include "vm/StringObject-inl.h"
 
@@ -67,18 +72,18 @@ intrinsic_IsCallable(JSContext *cx, unsi
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     Value val = args[0];
     bool isCallable = val.isObject() && val.toObject().isCallable();
     args.rval().setBoolean(isCallable);
     return true;
 }
 
-static JSBool
-intrinsic_ThrowError(JSContext *cx, unsigned argc, Value *vp)
+JSBool
+js::intrinsic_ThrowError(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     JS_ASSERT(args.length() >= 1);
     uint32_t errorNumber = args[0].toInt32();
 
     char *errorArgs[3] = {NULL, NULL, NULL};
     for (unsigned i = 1; i < 4 && i < args.length(); i++) {
         RootedValue val(cx, args[i]);
@@ -124,16 +129,27 @@ intrinsic_AssertionFailed(JSContext *cx,
             }
         }
     }
 #endif
     JS_ASSERT(false);
     return false;
 }
 
+static JSBool
+intrinsic_MakeConstructible(JSContext *cx, unsigned argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    JS_ASSERT(args.length() >= 1);
+    JS_ASSERT(args[0].isObject());
+    JS_ASSERT(args[0].toObject().isFunction());
+    args[0].toObject().toFunction()->setIsSelfHostedConstructor();
+    return true;
+}
+
 /*
  * Used to decompile values in the nearest non-builtin stack frame, falling
  * back to decompiling in the current frame. Helpful for printing higher-order
  * function arguments.
  *
  * The user must supply the argument number of the value in question; it
  * _cannot_ be automatically determined.
  */
@@ -149,24 +165,118 @@ intrinsic_DecompileArg(JSContext *cx, un
         return false;
     RootedAtom atom(cx, Atomize(cx, str, strlen(str)));
     if (!atom)
         return false;
     args.rval().setString(atom);
     return true;
 }
 
-static JSBool
-intrinsic_MakeConstructible(JSContext *cx, unsigned argc, Value *vp)
+#ifdef DEBUG
+JSBool
+js::intrinsic_Dump(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    JS_ASSERT(args.length() >= 1);
-    JS_ASSERT(args[0].isObject());
-    JS_ASSERT(args[0].toObject().isFunction());
-    args[0].toObject().toFunction()->setIsSelfHostedConstructor();
+    RootedValue val(cx, args[0]);
+    js_DumpValue(val);
+    fprintf(stderr, "\n");
+    args.rval().setUndefined();
+    return true;
+}
+#endif
+
+JSBool
+js::intrinsic_NewDenseArray(JSContext *cx, unsigned argc, Value *vp)
+{
+    // Usage: %NewDenseArray(length)
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // Check that index is an int32
+    if (!args[0].isInt32()) {
+        JS_ReportError(cx, "Expected int32 as second argument");
+        return false;
+    }
+    uint32_t length = args[0].toInt32();
+
+    // Make a new buffer and initialize it up to length.
+    RootedObject buffer(cx, NewDenseAllocatedArray(cx, length));
+    if (!buffer)
+        return false;
+
+    types::TypeObject *newtype = types::GetTypeCallerInitObject(cx, JSProto_Array);
+    if (!newtype)
+        return false;
+    buffer->setType(newtype);
+
+    JSObject::EnsureDenseResult edr = buffer->ensureDenseElements(cx, length, 0);
+    switch (edr) {
+      case JSObject::ED_OK:
+        args.rval().setObject(*buffer);
+        return true;
+
+      case JSObject::ED_SPARSE: // shouldn't happen!
+        JS_ASSERT(!"%EnsureDenseArrayElements() would yield sparse array");
+        JS_ReportError(cx, "%EnsureDenseArrayElements() would yield sparse array");
+        break;
+
+      case JSObject::ED_FAILED:
+        break;
+    }
+    return false;
+}
+
+JSBool
+js::intrinsic_UnsafeSetElement(JSContext *cx, unsigned argc, Value *vp)
+{
+    // Usage: %UnsafeSetElement(arr0, idx0, elem0,
+    //                          ...,
+    //                          arrN, idxN, elemN)
+    //
+    // For each set of |(arr, idx, elem)| arguments that are passed,
+    // performs the assignment |arr[idx] = elem|. |arr| must be either
+    // a dense array or a typed array.
+    //
+    // If |arr| is a dense array, the index must be an int32 less than the
+    // initialized length of |arr|. Use |%EnsureDenseResultArrayElements| to
+    // ensure that the initialized length is long enough.
+    //
+    // If |arr| is a typed array, the index must be an int32 less than the
+    // length of |arr|.
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    if ((args.length() % 3) != 0) {
+        JS_ReportError(cx, "Incorrect number of arguments, not divisible by 3");
+        return false;
+    }
+
+    for (uint32_t base = 0; base < args.length(); base += 3) {
+        uint32_t arri = base;
+        uint32_t idxi = base+1;
+        uint32_t elemi = base+2;
+
+        JS_ASSERT(args[arri].isObject());
+        JS_ASSERT(args[arri].toObject().isNative() ||
+                  args[arri].toObject().isTypedArray());
+        JS_ASSERT(args[idxi].isInt32());
+
+        RootedObject arrobj(cx, &args[arri].toObject());
+        uint32_t idx = args[idxi].toInt32();
+
+        if (arrobj->isNative()) {
+            JS_ASSERT(idx < arrobj->getDenseInitializedLength());
+            JSObject::setDenseElementWithType(cx, arrobj, idx, args[elemi]);
+        } else {
+            JS_ASSERT(idx < TypedArray::length(arrobj));
+            RootedValue tmp(cx, args[elemi]);
+            // XXX: Always non-strict.
+            JSObject::setElement(cx, arrobj, arrobj, idx, &tmp, false);
+        }
+    }
+
+    args.rval().setUndefined();
     return true;
 }
 
 /**
  * Returns the default locale as a well-formed, but not necessarily canonicalized,
  * BCP-47 language tag.
  */
 static JSBool
@@ -182,24 +292,32 @@ intrinsic_RuntimeDefaultLocale(JSContext
     if (!jslocale)
         return false;
 
     args.rval().setString(jslocale);
     return true;
 }
 
 JSFunctionSpec intrinsic_functions[] = {
-    JS_FN("ToObject",           intrinsic_ToObject,             1,0),
-    JS_FN("ToInteger",          intrinsic_ToInteger,            1,0),
-    JS_FN("IsCallable",         intrinsic_IsCallable,           1,0),
-    JS_FN("ThrowError",         intrinsic_ThrowError,           4,0),
-    JS_FN("AssertionFailed",    intrinsic_AssertionFailed,      1,0),
-    JS_FN("MakeConstructible",  intrinsic_MakeConstructible,    1,0),
-    JS_FN("DecompileArg",       intrinsic_DecompileArg,         2,0),
+    JS_FN("ToObject",             intrinsic_ToObject,             1,0),
+    JS_FN("ToInteger",            intrinsic_ToInteger,            1,0),
+    JS_FN("IsCallable",           intrinsic_IsCallable,           1,0),
+    JS_FN("ThrowError",           intrinsic_ThrowError,           4,0),
+    JS_FN("AssertionFailed",      intrinsic_AssertionFailed,      1,0),
+    JS_FN("MakeConstructible",    intrinsic_MakeConstructible,    1,0),
+    JS_FN("DecompileArg",         intrinsic_DecompileArg,         2,0),
     JS_FN("RuntimeDefaultLocale", intrinsic_RuntimeDefaultLocale, 0,0),
+
+    JS_FN("NewDenseArray",        intrinsic_NewDenseArray,           1,0),
+    JS_FN("UnsafeSetElement",     intrinsic_UnsafeSetElement,     3,0),
+
+#ifdef DEBUG
+    JS_FN("Dump",                 intrinsic_Dump,                 1,0),
+#endif
+
     JS_FS_END
 };
 
 bool
 JSRuntime::initSelfHosting(JSContext *cx)
 {
     JS_ASSERT(!selfHostingGlobal_);
     RootedObject savedGlobal(cx, JS_GetGlobalObject(cx));