Bug 1019304 - Part 2: Overhaul PJS bailout mechanism to be like the normal bailout mechanism. (r=nmatsakis)
☠☠ backed out by 724d46a1b00a ☠ ☠
authorShu-yu Guo <shu@rfrn.org>
Fri, 20 Jun 2014 11:08:14 -0700
changeset 189990 5322e67211418151d6eb46cfb5c15dcdc293fe90
parent 189989 45f24290b96ebf3e768130bf9ac876b34cbfa217
child 189991 adc7e2d717a900ced212e10e57be2466d91c9b2c
push id8288
push userryanvm@gmail.com
push dateMon, 23 Jun 2014 14:59:00 +0000
treeherderb2g-inbound@c65bf5a0595c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnmatsakis
bugs1019304
milestone33.0a1
Bug 1019304 - Part 2: Overhaul PJS bailout mechanism to be like the normal bailout mechanism. (r=nmatsakis) - Remove ad-hoc IR tracing and AbortPar machinery. - PJS bailouts use a different handler that unwinds the entire ForkJoin stack, but otherwise shares the same code as sequential bailouts. - Each thread's stack is snapshotted as a RematerializedFrame vector.
js/src/jit/Bailouts.cpp
js/src/jit/Bailouts.h
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/Ion.cpp
js/src/jit/IonFrames.cpp
js/src/jit/IonSpewer.cpp
js/src/jit/IonSpewer.h
js/src/jit/JitCompartment.h
js/src/jit/LIR-Common.h
js/src/jit/LOpcodes.h
js/src/jit/Lowering.cpp
js/src/jit/Lowering.h
js/src/jit/MIR.h
js/src/jit/MIRGraph.cpp
js/src/jit/MIRGraph.h
js/src/jit/MOpcodes.h
js/src/jit/ParallelFunctions.cpp
js/src/jit/ParallelFunctions.h
js/src/jit/ParallelSafetyAnalysis.cpp
js/src/jit/ParallelSafetyAnalysis.h
js/src/jit/arm/CodeGenerator-arm.cpp
js/src/jit/arm/CodeGenerator-arm.h
js/src/jit/arm/Trampoline-arm.cpp
js/src/jit/mips/CodeGenerator-mips.cpp
js/src/jit/mips/CodeGenerator-mips.h
js/src/jit/mips/Trampoline-mips.cpp
js/src/jit/shared/CodeGenerator-shared.cpp
js/src/jit/shared/CodeGenerator-shared.h
js/src/jit/shared/CodeGenerator-x86-shared.cpp
js/src/jit/shared/CodeGenerator-x86-shared.h
js/src/jit/x64/Trampoline-x64.cpp
js/src/jit/x86/Trampoline-x86.cpp
js/src/vm/ForkJoin.cpp
js/src/vm/ForkJoin.h
--- a/js/src/jit/Bailouts.cpp
+++ b/js/src/jit/Bailouts.cpp
@@ -64,20 +64,16 @@ IonBailoutIterator::dump() const
                 break;
             ++frames;
         }
     } else {
         JitFrameIterator::dump();
     }
 }
 
-// This address is a magic number made to cause crashes while indicating that we
-// are making an attempt to mark the stack during a bailout.
-static uint8_t * const FAKE_JIT_TOP_FOR_BAILOUT = reinterpret_cast<uint8_t *>(0xba1);
-
 uint32_t
 jit::Bailout(BailoutStack *sp, BaselineBailoutInfo **bailoutInfo)
 {
     JSContext *cx = GetJSContextFromJitCode();
     JS_ASSERT(bailoutInfo);
 
     // We don't have an exit frame.
     MOZ_ASSERT(size_t(FAKE_JIT_TOP_FOR_BAILOUT + sizeof(IonCommonFrameLayout)) < 0x1000 &&
--- a/js/src/jit/Bailouts.h
+++ b/js/src/jit/Bailouts.h
@@ -95,16 +95,20 @@ static const BailoutId INVALID_BAILOUT_I
 static const uint32_t BAILOUT_TABLE_SIZE = 16;
 
 // Bailout return codes.
 // N.B. the relative order of these values is hard-coded into ::GenerateBailoutThunk.
 static const uint32_t BAILOUT_RETURN_OK = 0;
 static const uint32_t BAILOUT_RETURN_FATAL_ERROR = 1;
 static const uint32_t BAILOUT_RETURN_OVERRECURSED = 2;
 
+// This address is a magic number made to cause crashes while indicating that we
+// are making an attempt to mark the stack during a bailout.
+static uint8_t * const FAKE_JIT_TOP_FOR_BAILOUT = reinterpret_cast<uint8_t *>(0xba1);
+
 class JitCompartment;
 
 // BailoutStack is an architecture specific pointer to the stack, given by the
 // bailout handler.
 class BailoutStack;
 class InvalidationBailoutStack;
 
 // Must be implemented by each architecture.
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -1224,17 +1224,20 @@ class OutOfLineInterruptCheckImplicit : 
     { }
 
     bool accept(CodeGenerator *codegen) {
         return codegen->visitOutOfLineInterruptCheckImplicit(this);
     }
 };
 
 typedef bool (*InterruptCheckFn)(JSContext *);
-static const VMFunction InterruptCheckInfo = FunctionInfo<InterruptCheckFn>(InterruptCheck);
+typedef bool (*InterruptCheckParFn)(ForkJoinContext *);
+static const VMFunctionsModal InterruptCheckInfo = VMFunctionsModal(
+    FunctionInfo<InterruptCheckFn>(InterruptCheck),
+    FunctionInfo<InterruptCheckParFn>(InterruptCheckPar));
 
 bool
 CodeGenerator::visitOutOfLineInterruptCheckImplicit(OutOfLineInterruptCheckImplicit *ool)
 {
 #ifdef CHECK_OSIPOINT_REGISTERS
     // This is path is entered from the patched back-edge of the loop. This
     // means that the JitAtivation flags used for checking the validity of the
     // OSI points are not reseted by the path generated by generateBody, so we
@@ -1844,23 +1847,17 @@ CodeGenerator::visitGuardThreadExclusive
     JS_ASSERT(gen->info().executionMode() == ParallelExecution);
 
     const Register tempReg = ToRegister(lir->getTempReg());
     masm.setupUnalignedABICall(2, tempReg);
     masm.passABIArg(ToRegister(lir->forkJoinContext()));
     masm.passABIArg(ToRegister(lir->object()));
     masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParallelWriteGuard));
 
-    OutOfLineAbortPar *bail = oolAbortPar(ParallelBailoutIllegalWrite, lir);
-    if (!bail)
-        return false;
-
-    // branch to the OOL failure code if false is returned
-    masm.branchIfFalseBool(ReturnReg, bail->entry());
-    return true;
+    return bailoutIfFalseBool(ReturnReg, lir->snapshot());
 }
 
 bool
 CodeGenerator::visitGuardObjectIdentity(LGuardObjectIdentity *guard)
 {
     Register obj = ToRegister(guard->input());
 
     Assembler::Condition cond =
@@ -2378,37 +2375,33 @@ CodeGenerator::visitCallGeneric(LCallGen
     // 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 (!checkForAbortPar(call))
-        return false;
-
     dropArguments(call->numStackArgs() + 1);
     return true;
 }
 
+typedef bool (*CallToUncompiledScriptParFn)(ForkJoinContext *, JSObject *);
+static const VMFunction CallToUncompiledScriptParInfo =
+    FunctionInfo<CallToUncompiledScriptParFn>(CallToUncompiledScriptPar);
+
 // Generates a call to CallToUncompiledScriptPar() and then bails out.
 // |calleeReg| should contain the JSFunction*.
 bool
 CodeGenerator::emitCallToUncompiledScriptPar(LInstruction *lir, Register calleeReg)
 {
-    OutOfLineCode *bail = oolAbortPar(ParallelBailoutCalledToUncompiledScript, lir);
-    if (!bail)
-        return false;
-
-    masm.movePtr(calleeReg, CallTempReg0);
-    masm.setupUnalignedABICall(1, CallTempReg1);
-    masm.passABIArg(CallTempReg0);
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, CallToUncompiledScriptPar));
-    masm.jump(bail->entry());
+    pushArg(calleeReg);
+    if (!callVM(CallToUncompiledScriptParInfo, lir))
+        return false;
+    masm.assumeUnreachable("CallToUncompiledScriptParInfo always returns false.");
     return true;
 }
 
 bool
 CodeGenerator::visitCallKnown(LCallKnown *call)
 {
     Register calleereg = ToRegister(call->getFunction());
     Register objreg    = ToRegister(call->getTempObject());
@@ -2473,49 +2466,30 @@ CodeGenerator::visitCallKnown(LCallKnown
         break;
 
       default:
         MOZ_ASSUME_UNREACHABLE("No such execution mode");
     }
 
     masm.bind(&end);
 
-    if (!checkForAbortPar(call))
-        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::checkForAbortPar(LInstruction *lir)
-{
-    // 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) {
-        OutOfLinePropagateAbortPar *bail = oolPropagateAbortPar(lir);
-        if (!bail)
-            return false;
-        masm.branchTestMagic(Assembler::Equal, JSReturnOperand, bail->entry());
-    }
-    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);
@@ -2917,28 +2891,28 @@ CodeGenerator::generateArgumentsChecks(b
     masm.freeStack(frameSize());
 
     return true;
 }
 
 // Out-of-line path to report over-recursed error and fail.
 class CheckOverRecursedFailure : public OutOfLineCodeBase<CodeGenerator>
 {
-    LCheckOverRecursed *lir_;
+    LInstruction *lir_;
 
   public:
-    explicit CheckOverRecursedFailure(LCheckOverRecursed *lir)
+    explicit CheckOverRecursedFailure(LInstruction *lir)
       : lir_(lir)
     { }
 
     bool accept(CodeGenerator *codegen) {
         return codegen->visitCheckOverRecursedFailure(this);
     }
 
-    LCheckOverRecursed *lir() const {
+    LInstruction *lir() const {
         return lir_;
     }
 };
 
 bool
 CodeGenerator::visitCheckOverRecursed(LCheckOverRecursed *lir)
 {
     // If we don't push anything on the stack, skip the check.
@@ -2998,19 +2972,21 @@ CodeGenerator::visitDefFun(LDefFun *lir)
 
     pushArg(ImmGCPtr(lir->mir()->fun()));
     pushArg(scopeChain);
     pushArg(ImmGCPtr(current->mir()->info().script()));
 
     return callVM(DefFunOperationInfo, lir);
 }
 
-typedef bool (*ReportOverRecursedFn)(JSContext *);
-static const VMFunction CheckOverRecursedInfo =
-    FunctionInfo<ReportOverRecursedFn>(CheckOverRecursed);
+typedef bool (*CheckOverRecursedFn)(JSContext *);
+typedef bool (*CheckOverRecursedParFn)(ForkJoinContext *);
+static const VMFunctionsModal CheckOverRecursedInfo = VMFunctionsModal(
+    FunctionInfo<CheckOverRecursedFn>(CheckOverRecursed),
+    FunctionInfo<CheckOverRecursedParFn>(CheckOverRecursedPar));
 
 bool
 CodeGenerator::visitCheckOverRecursedFailure(CheckOverRecursedFailure *ool)
 {
     // The OOL path is hit if the recursion depth has been exceeded.
     // Throw an InternalError for over-recursion.
 
     // LFunctionEnvironment can appear before LCheckOverRecursed, so we have
@@ -3021,35 +2997,16 @@ 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 CheckOverRecursedFailurePar : public OutOfLineCodeBase<CodeGenerator>
-{
-    LCheckOverRecursedPar *lir_;
-
-  public:
-    explicit CheckOverRecursedFailurePar(LCheckOverRecursedPar *lir)
-      : lir_(lir)
-    { }
-
-    bool accept(CodeGenerator *codegen) {
-        return codegen->visitCheckOverRecursedFailurePar(this);
-    }
-
-    LCheckOverRecursedPar *lir() const {
-        return lir_;
-    }
-};
-
 bool
 CodeGenerator::visitCheckOverRecursedPar(LCheckOverRecursedPar *lir)
 {
     // See above: unlike visitCheckOverRecursed(), this code runs in
     // parallel mode and hence uses the jitStackLimit 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
@@ -3057,111 +3014,41 @@ CodeGenerator::visitCheckOverRecursedPar
 
     Register cxReg = ToRegister(lir->forkJoinContext());
     Register tempReg = ToRegister(lir->getTempReg());
 
     masm.loadPtr(Address(cxReg, offsetof(ForkJoinContext, perThreadData)), tempReg);
     masm.loadPtr(Address(tempReg, offsetof(PerThreadData, jitStackLimit)), tempReg);
 
     // Conditional forward (unlikely) branch to failure.
-    CheckOverRecursedFailurePar *ool = new(alloc()) CheckOverRecursedFailurePar(lir);
+    CheckOverRecursedFailure *ool = new(alloc()) CheckOverRecursedFailure(lir);
     if (!addOutOfLineCode(ool))
         return false;
+
     masm.branchPtr(Assembler::BelowOrEqual, StackPointer, tempReg, ool->entry());
     masm.checkInterruptFlagPar(tempReg, ool->entry());
     masm.bind(ool->rejoin());
 
     return true;
 }
 
 bool
-CodeGenerator::visitCheckOverRecursedFailurePar(CheckOverRecursedFailurePar *ool)
-{
-    OutOfLinePropagateAbortPar *bail = oolPropagateAbortPar(ool->lir());
-    if (!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():
-    LCheckOverRecursedPar *lir = ool->lir();
-    Register tempReg = ToRegister(lir->getTempReg());
-    RegisterSet saveSet(lir->safepoint()->liveRegs());
-    saveSet.takeUnchecked(tempReg);
-
-    masm.PushRegsInMask(saveSet);
-    masm.movePtr(ToRegister(lir->forkJoinContext()), CallTempReg0);
-    masm.setupUnalignedABICall(1, CallTempReg1);
-    masm.passABIArg(CallTempReg0);
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, CheckOverRecursedPar));
-    masm.movePtr(ReturnReg, tempReg);
-    masm.PopRegsInMask(saveSet);
-    masm.branchIfFalseBool(tempReg, bail->entry());
-    masm.jump(ool->rejoin());
-
-    return true;
-}
-
-// Out-of-line path to report over-recursed error and fail.
-class OutOfLineInterruptCheckPar : public OutOfLineCodeBase<CodeGenerator>
-{
-  public:
-    LInterruptCheckPar *const lir;
-
-    explicit OutOfLineInterruptCheckPar(LInterruptCheckPar *lir)
-      : lir(lir)
-    { }
-
-    bool accept(CodeGenerator *codegen) {
-        return codegen->visitOutOfLineInterruptCheckPar(this);
-    }
-};
-
-bool
 CodeGenerator::visitInterruptCheckPar(LInterruptCheckPar *lir)
 {
     // First check for cx->shared->interrupt_.
-    OutOfLineInterruptCheckPar *ool = new(alloc()) OutOfLineInterruptCheckPar(lir);
-    if (!addOutOfLineCode(ool))
+    OutOfLineCode *ool = oolCallVM(InterruptCheckInfo, lir, (ArgList()), StoreNothing());
+    if (!ool)
         return false;
 
     Register tempReg = ToRegister(lir->getTempReg());
     masm.checkInterruptFlagPar(tempReg, ool->entry());
     masm.bind(ool->rejoin());
     return true;
 }
 
-bool
-CodeGenerator::visitOutOfLineInterruptCheckPar(OutOfLineInterruptCheckPar *ool)
-{
-    OutOfLinePropagateAbortPar *bail = oolPropagateAbortPar(ool->lir);
-    if (!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():
-    LInterruptCheckPar *lir = ool->lir;
-    Register tempReg = ToRegister(lir->getTempReg());
-    RegisterSet saveSet(lir->safepoint()->liveRegs());
-    saveSet.takeUnchecked(tempReg);
-
-    masm.PushRegsInMask(saveSet);
-    masm.movePtr(ToRegister(ool->lir->forkJoinContext()), CallTempReg0);
-    masm.setupUnalignedABICall(1, CallTempReg1);
-    masm.passABIArg(CallTempReg0);
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, InterruptCheckPar));
-    masm.movePtr(ReturnReg, tempReg);
-    masm.PopRegsInMask(saveSet);
-    masm.branchIfFalseBool(tempReg, bail->entry());
-    masm.jump(ool->rejoin());
-
-    return true;
-}
-
 IonScriptCounts *
 CodeGenerator::maybeCreateScriptCounts()
 {
     // If scripts are being profiled, create a new IonScriptCounts for the
     // profiling data, which will be attached to the associated JSScript or
     // AsmJS module after code generation finishes.
     if (!GetIonContext()->runtime->profilingScripts())
         return nullptr;
@@ -3454,19 +3341,16 @@ CodeGenerator::generateBody()
                     return false;
             }
 
 #ifdef CHECK_OSIPOINT_REGISTERS
             if (iter->safepoint())
                 resetOsiPointRegs(iter->safepoint());
 #endif
 
-            if (!callTraceLIR(i, *iter))
-                return false;
-
             if (!iter->accept(this))
                 return false;
 
 #ifdef DEBUG
             if (!counts && !emitDebugResultChecks(*iter))
                 return false;
 #endif
         }
@@ -3905,22 +3789,16 @@ CodeGenerator::visitNewDenseArrayPar(LNe
     saveLive(lir);
     pushArg(lengthReg);
     pushArg(tempReg2);
     if (!callVM(ExtendArrayParInfo, lir))
         return false;
     storeResultTo(ToRegister(lir->output()));
     restoreLive(lir);
 
-    Register resultReg = ToRegister(lir->output());
-    OutOfLineAbortPar *bail = oolAbortPar(ParallelBailoutOutOfMemory, lir);
-    if (!bail)
-        return false;
-    masm.branchTestPtr(Assembler::Zero, resultReg, resultReg, bail->entry());
-
     return true;
 }
 
 typedef JSObject *(*NewStringObjectFn)(JSContext *, HandleString);
 static const VMFunction NewStringObjectInfo = FunctionInfo<NewStringObjectFn>(NewStringObject);
 
 bool
 CodeGenerator::visitNewStringObject(LNewStringObject *lir)
@@ -4018,35 +3896,20 @@ CodeGenerator::visitOutOfLineNewGCThingP
     masm.setupUnalignedABICall(2, out);
     masm.passABIArg(ool->cxReg);
     masm.move32(Imm32(ool->allocKind), out);
     masm.passABIArg(out);
     masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, NewGCThingPar));
     masm.storeCallResult(out);
     restoreVolatile(out);
 
-    OutOfLineAbortPar *bail = oolAbortPar(ParallelBailoutOutOfMemory, ool->lir);
-    if (!bail)
-        return false;
-    masm.branchTestPtr(Assembler::Zero, out, out, bail->entry());
-    masm.jump(ool->rejoin());
-    return true;
+    return bailoutTestPtr(Assembler::Zero, out, out, ool->lir->snapshot());
 }
 #endif // JSGC_FJGENERATIONAL
 
-bool
-CodeGenerator::visitAbortPar(LAbortPar *lir)
-{
-    OutOfLineAbortPar *bail = oolAbortPar(ParallelBailoutUnsupported, lir);
-    if (!bail)
-        return false;
-    masm.jump(bail->entry());
-    return true;
-}
-
 typedef bool(*InitElemFn)(JSContext *cx, HandleObject obj,
                           HandleValue id, HandleValue value);
 static const VMFunction InitElemInfo =
     FunctionInfo<InitElemFn>(InitElemOperation);
 
 bool
 CodeGenerator::visitInitElem(LInitElem *lir)
 {
@@ -8548,39 +8411,16 @@ CodeGenerator::visitProfilerStackOp(LPro
             return true;
 
         default:
             MOZ_ASSUME_UNREACHABLE("invalid LProfilerStackOp type");
     }
 }
 
 bool
-CodeGenerator::visitOutOfLineAbortPar(OutOfLineAbortPar *ool)
-{
-    ParallelBailoutCause cause = ool->cause();
-    jsbytecode *bytecode = ool->bytecode();
-
-    masm.move32(Imm32(cause), CallTempReg0);
-    loadOutermostJSScript(CallTempReg1);
-    loadJSScriptForBlock(ool->basicBlock(), CallTempReg2);
-    masm.movePtr(ImmPtr(bytecode), CallTempReg3);
-
-    masm.setupUnalignedABICall(4, CallTempReg4);
-    masm.passABIArg(CallTempReg0);
-    masm.passABIArg(CallTempReg1);
-    masm.passABIArg(CallTempReg2);
-    masm.passABIArg(CallTempReg3);
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, AbortPar));
-
-    masm.moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand);
-    masm.jump(&returnLabel_);
-    return true;
-}
-
-bool
 CodeGenerator::visitIsCallable(LIsCallable *ins)
 {
     Register object = ToRegister(ins->object());
     Register output = ToRegister(ins->output());
 
     masm.loadObjClass(object, output);
 
     // An object is callable iff (is<JSFunction>() || getClass()->call).
@@ -8614,32 +8454,16 @@ CodeGenerator::loadJSScriptForBlock(MBas
     // The current JSScript means the script for the current
     // basic block. This may be an inlined script.
 
     JSScript *script = block->info().script();
     masm.movePtr(ImmGCPtr(script), reg);
 }
 
 bool
-CodeGenerator::visitOutOfLinePropagateAbortPar(OutOfLinePropagateAbortPar *ool)
-{
-    loadOutermostJSScript(CallTempReg0);
-    loadJSScriptForBlock(ool->lir()->mirRaw()->block(), CallTempReg1);
-
-    masm.setupUnalignedABICall(2, CallTempReg2);
-    masm.passABIArg(CallTempReg0);
-    masm.passABIArg(CallTempReg1);
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, PropagateAbortPar));
-
-    masm.moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand);
-    masm.jump(&returnLabel_);
-    return true;
-}
-
-bool
 CodeGenerator::visitHaveSameClass(LHaveSameClass *ins)
 {
     Register lhs = ToRegister(ins->lhs());
     Register rhs = ToRegister(ins->rhs());
     Register temp = ToRegister(ins->getTemp(0));
     Register output = ToRegister(ins->output());
 
     masm.loadObjClass(lhs, temp);
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -147,17 +147,16 @@ class CodeGenerator : public CodeGenerat
     bool visitNewDeclEnvObject(LNewDeclEnvObject *lir);
     bool visitNewCallObject(LNewCallObject *lir);
     bool visitNewSingletonCallObject(LNewSingletonCallObject *lir);
     bool visitNewCallObjectPar(LNewCallObjectPar *lir);
     bool visitNewStringObject(LNewStringObject *lir);
     bool visitNewPar(LNewPar *lir);
     bool visitNewDenseArrayPar(LNewDenseArrayPar *lir);
     bool visitNewDerivedTypedObject(LNewDerivedTypedObject *lir);
-    bool visitAbortPar(LAbortPar *lir);
     bool visitInitElem(LInitElem *lir);
     bool visitInitElemGetterSetter(LInitElemGetterSetter *lir);
     bool visitMutateProto(LMutateProto *lir);
     bool visitInitProp(LInitProp *lir);
     bool visitInitPropGetterSetter(LInitPropGetterSetter *lir);
     bool visitCreateThis(LCreateThis *lir);
     bool visitCreateThisWithProto(LCreateThisWithProto *lir);
     bool visitCreateThisWithTemplate(LCreateThisWithTemplate *lir);
@@ -296,31 +295,28 @@ class CodeGenerator : public CodeGenerat
     bool visitAsmJSParameter(LAsmJSParameter *lir);
     bool visitAsmJSReturn(LAsmJSReturn *ret);
     bool visitAsmJSVoidReturn(LAsmJSVoidReturn *ret);
 
     bool visitCheckOverRecursed(LCheckOverRecursed *lir);
     bool visitCheckOverRecursedFailure(CheckOverRecursedFailure *ool);
 
     bool visitCheckOverRecursedPar(LCheckOverRecursedPar *lir);
-    bool visitCheckOverRecursedFailurePar(CheckOverRecursedFailurePar *ool);
 
     bool visitInterruptCheckPar(LInterruptCheckPar *lir);
     bool visitOutOfLineInterruptCheckPar(OutOfLineInterruptCheckPar *ool);
 
     bool visitInterruptCheckImplicit(LInterruptCheckImplicit *ins);
     bool visitOutOfLineInterruptCheckImplicit(OutOfLineInterruptCheckImplicit *ins);
 
     bool visitUnboxFloatingPoint(LUnboxFloatingPoint *lir);
     bool visitOutOfLineUnboxFloatingPoint(OutOfLineUnboxFloatingPoint *ool);
     bool visitOutOfLineStoreElementHole(OutOfLineStoreElementHole *ool);
 
     bool visitOutOfLineNewGCThingPar(OutOfLineNewGCThingPar *ool);
-    bool visitOutOfLineAbortPar(OutOfLineAbortPar *ool);
-    bool visitOutOfLinePropagateAbortPar(OutOfLinePropagateAbortPar *ool);
     void loadJSScriptForBlock(MBasicBlock *block, Register reg);
     void loadOutermostJSScript(Register reg);
 
     // Inline caches visitors.
     bool visitOutOfLineCache(OutOfLineUpdateCache *ool);
 
     bool visitGetPropertyCacheV(LGetPropertyCacheV *ins);
     bool visitGetPropertyCacheT(LGetPropertyCacheT *ins);
@@ -369,17 +365,16 @@ class CodeGenerator : public CodeGenerat
                             TypedOrValueRegister output, bool monitoredResult,
                             bool allowDoubleResult, jsbytecode *profilerLeavePc);
     bool addSetPropertyCache(LInstruction *ins, RegisterSet liveRegs, Register objReg,
                              PropertyName *name, ConstantOrRegister value, bool strict,
                              bool needsTypeBarrier, jsbytecode *profilerLeavePc);
     bool addSetElementCache(LInstruction *ins, Register obj, Register unboxIndex, Register temp,
                             FloatRegister tempFloat, ValueOperand index, ConstantOrRegister value,
                             bool strict, bool guardHoles, jsbytecode *profilerLeavePc);
-    bool checkForAbortPar(LInstruction *lir);
 
     bool generateBranchV(const ValueOperand &value, Label *ifTrue, Label *ifFalse, FloatRegister fr);
 
     bool emitAllocateGCThingPar(LInstruction *lir, Register objReg, Register cxReg,
                                 Register tempReg1, Register tempReg2,
                                 JSObject *templateObj);
 
     bool emitCallToUncompiledScriptPar(LInstruction *lir, Register calleeReg);
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -221,20 +221,27 @@ JitRuntime::initialize(JSContext *cx)
                 break;
             bailoutTables_.infallibleAppend((JitCode *)nullptr);
             bailoutTables_[id] = generateBailoutTable(cx, id);
             if (!bailoutTables_[id])
                 return false;
         }
 
         IonSpew(IonSpew_Codegen, "# Emitting bailout handler");
-        bailoutHandler_ = generateBailoutHandler(cx);
+        bailoutHandler_ = generateBailoutHandler(cx, SequentialExecution);
         if (!bailoutHandler_)
             return false;
 
+#ifdef JS_THREADSAFE
+        IonSpew(IonSpew_Codegen, "# Emitting parallel bailout handler");
+        parallelBailoutHandler_ = generateBailoutHandler(cx, ParallelExecution);
+        if (!parallelBailoutHandler_)
+            return false;
+#endif
+
         IonSpew(IonSpew_Codegen, "# Emitting invalidator");
         invalidator_ = generateInvalidator(cx);
         if (!invalidator_)
             return false;
     }
 
     IonSpew(IonSpew_Codegen, "# Emitting sequential arguments rectifier");
     argumentsRectifier_ = generateArgumentsRectifier(cx, SequentialExecution, &argumentsRectifierReturnAddr_);
--- a/js/src/jit/IonFrames.cpp
+++ b/js/src/jit/IonFrames.cpp
@@ -750,38 +750,28 @@ HandleException(ResumeFromException *rfe
     }
 
     rfe->stackPointer = iter.fp();
 }
 
 void
 HandleParallelFailure(ResumeFromException *rfe)
 {
-    ForkJoinContext *cx = ForkJoinContext::current();
-    JitFrameIterator iter(cx->perThreadData->jitTop, ParallelExecution);
-
     parallel::Spew(parallel::SpewBailouts, "Bailing from VM reentry");
 
-    while (!iter.isEntry()) {
-        if (iter.isScripted()) {
-            cx->bailoutRecord->updateCause(ParallelBailoutUnsupportedVM,
-                                           iter.script(), iter.script(), nullptr);
-            break;
-        }
-        ++iter;
-    }
+    ForkJoinContext *cx = ForkJoinContext::current();
+    JitFrameIterator frameIter(cx);
 
-    while (!iter.isEntry()) {
-        if (iter.isScripted())
-            PropagateAbortPar(iter.script(), iter.script());
-        ++iter;
-    }
+    cx->bailoutRecord->joinCause(ParallelBailoutUnsupportedVM);
+    cx->bailoutRecord->rematerializeFrames(cx, frameIter);
 
     rfe->kind = ResumeFromException::RESUME_ENTRY_FRAME;
-    rfe->stackPointer = iter.fp();
+
+    MOZ_ASSERT(frameIter.done());
+    rfe->stackPointer = frameIter.fp();
 }
 
 void
 EnsureExitFrame(IonCommonFrameLayout *frame)
 {
     if (frame->prevType() == JitFrame_Unwound_IonJS ||
         frame->prevType() == JitFrame_Unwound_BaselineStub ||
         frame->prevType() == JitFrame_Unwound_Rectifier)
--- a/js/src/jit/IonSpewer.cpp
+++ b/js/src/jit/IonSpewer.cpp
@@ -244,17 +244,16 @@ jit::CheckLogging()
             "  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"
             "  range      Range Analysis\n"
             "  logs       C1 and JSON visualization logging\n"
-            "  trace      Generate calls to js::jit::Trace() for effectful instructions\n"
             "  all        Everything\n"
             "\n"
             "  bl-aborts  Baseline compiler abort messages\n"
             "  bl-scripts Baseline script-compilation\n"
             "  bl-op      Baseline compiler detailed op-specific messages\n"
             "  bl-ic      Baseline inline-cache messages\n"
             "  bl-ic-fb   Baseline IC fallback stub messages\n"
             "  bl-osr     Baseline IC OSR messages\n"
@@ -297,18 +296,16 @@ jit::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 (ContainsFlag(env, "bl-aborts"))
         EnableChannel(IonSpew_BaselineAbort);
     if (ContainsFlag(env, "bl-scripts"))
         EnableChannel(IonSpew_BaselineScripts);
     if (ContainsFlag(env, "bl-op"))
--- a/js/src/jit/IonSpewer.h
+++ b/js/src/jit/IonSpewer.h
@@ -49,18 +49,16 @@ namespace jit {
     /* Debug info about snapshots */        \
     _(Snapshots)                            \
     /* Generated inline cache stubs */      \
     _(InlineCaches)                         \
     /* Debug info about safepoints */       \
     _(Safepoints)                           \
     /* Debug info about Pools*/             \
     _(Pools)                                \
-    /* Calls to js::jit::Trace() */         \
-    _(Trace)                                \
     /* Debug info about the I$ */           \
     _(CacheFlush)                           \
                                             \
     /* BASELINE COMPILER SPEW */            \
                                             \
     /* Aborting Script Compilation. */      \
     _(BaselineAbort)                        \
     /* Script Compilation. */               \
--- a/js/src/jit/JitCompartment.h
+++ b/js/src/jit/JitCompartment.h
@@ -166,16 +166,19 @@ class JitRuntime
     JitCode *enterBaselineJIT_;
 
     // Vector mapping frame class sizes to bailout tables.
     Vector<JitCode*, 4, SystemAllocPolicy> bailoutTables_;
 
     // Generic bailout table; used if the bailout table overflows.
     JitCode *bailoutHandler_;
 
+    // Bailout handler for parallel execution.
+    JitCode *parallelBailoutHandler_;
+
     // Argument-rectifying thunk, in the case of insufficient arguments passed
     // to a function call site.
     JitCode *argumentsRectifier_;
     void *argumentsRectifierReturnAddr_;
 
     // Arguments-rectifying thunk which loads |parallelIon| instead of |ion|.
     JitCode *parallelArgumentsRectifier_;
 
@@ -232,17 +235,17 @@ class JitRuntime
     js::Value ionReturnOverride_;
 
   private:
     JitCode *generateExceptionTailStub(JSContext *cx);
     JitCode *generateBailoutTailStub(JSContext *cx);
     JitCode *generateEnterJIT(JSContext *cx, EnterJitType type);
     JitCode *generateArgumentsRectifier(JSContext *cx, ExecutionMode mode, void **returnAddrOut);
     JitCode *generateBailoutTable(JSContext *cx, uint32_t frameClass);
-    JitCode *generateBailoutHandler(JSContext *cx);
+    JitCode *generateBailoutHandler(JSContext *cx, ExecutionMode mode);
     JitCode *generateInvalidator(JSContext *cx);
     JitCode *generatePreBarrier(JSContext *cx, MIRType type);
     JitCode *generateMallocStub(JSContext *cx);
     JitCode *generateFreeStub(JSContext *cx);
     JitCode *generateDebugTrapHandler(JSContext *cx);
     JitCode *generateForkJoinGetSliceStub(JSContext *cx);
     JitCode *generateBaselineDebugModeOSRHandler(JSContext *cx, uint32_t *noFrameRegPopOffsetOut);
     JitCode *generateVMWrapper(JSContext *cx, const VMFunction &f);
@@ -299,18 +302,22 @@ class JitRuntime
 
     bool handleAccessViolation(JSRuntime *rt, void *faultingAddress);
 
     JitCode *getVMWrapper(const VMFunction &f) const;
     JitCode *debugTrapHandler(JSContext *cx);
     JitCode *getBaselineDebugModeOSRHandler(JSContext *cx);
     void *getBaselineDebugModeOSRHandlerAddress(JSContext *cx, bool popFrameReg);
 
-    JitCode *getGenericBailoutHandler() const {
-        return bailoutHandler_;
+    JitCode *getGenericBailoutHandler(ExecutionMode mode) const {
+        switch (mode) {
+          case SequentialExecution: return bailoutHandler_;
+          case ParallelExecution:   return parallelBailoutHandler_;
+          default:                  MOZ_ASSUME_UNREACHABLE("No such execution mode");
+        }
     }
 
     JitCode *getExceptionTail() const {
         return exceptionTail_;
     }
 
     JitCode *getBailoutTail() const {
         return bailoutTail_;
--- a/js/src/jit/LIR-Common.h
+++ b/js/src/jit/LIR-Common.h
@@ -566,22 +566,16 @@ class LNewStringObject : public LInstruc
     const LDefinition *temp() {
         return getTemp(0);
     }
     MNewStringObject *mir() const {
         return mir_->toNewStringObject();
     }
 };
 
-class LAbortPar : public LInstructionHelper<0, 0, 0>
-{
-  public:
-    LIR_HEADER(AbortPar);
-};
-
 class LInitElem : public LCallInstructionHelper<0, 1 + 2*BOX_PIECES, 0>
 {
   public:
     LIR_HEADER(InitElem)
 
     explicit LInitElem(const LAllocation &object) {
         setOperand(0, object);
     }
--- a/js/src/jit/LOpcodes.h
+++ b/js/src/jit/LOpcodes.h
@@ -29,17 +29,16 @@
     _(NewDeclEnvObject)             \
     _(NewCallObject)                \
     _(NewSingletonCallObject)       \
     _(NewStringObject)              \
     _(NewPar)                       \
     _(NewDenseArrayPar)             \
     _(NewCallObjectPar)             \
     _(NewDerivedTypedObject)        \
-    _(AbortPar)                     \
     _(InitElem)                     \
     _(InitElemGetterSetter)         \
     _(MutateProto)                  \
     _(InitProp)                     \
     _(InitPropGetterSetter)         \
     _(CheckOverRecursed)            \
     _(CheckOverRecursedPar)         \
     _(DefVar)                       \
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -113,23 +113,17 @@ LIRGenerator::visitTableSwitch(MTableSwi
     }
     return add(newLTableSwitch(index, tempInt, tableswitch));
 }
 
 bool
 LIRGenerator::visitCheckOverRecursed(MCheckOverRecursed *ins)
 {
     LCheckOverRecursed *lir = new(alloc()) LCheckOverRecursed();
-
-    if (!add(lir, ins))
-        return false;
-    if (!assignSafepoint(lir, ins))
-        return false;
-
-    return true;
+    return add(lir, ins) && assignSafepoint(lir, ins);
 }
 
 bool
 LIRGenerator::visitCheckOverRecursedPar(MCheckOverRecursedPar *ins)
 {
     LCheckOverRecursedPar *lir =
         new(alloc()) LCheckOverRecursedPar(useRegister(ins->forkJoinContext()), temp());
     return add(lir, ins) && assignSafepoint(lir, ins);
@@ -233,23 +227,16 @@ LIRGenerator::visitNewStringObject(MNewS
 {
     JS_ASSERT(ins->input()->type() == MIRType_String);
 
     LNewStringObject *lir = new(alloc()) LNewStringObject(useRegister(ins->input()), temp());
     return define(lir, ins) && assignSafepoint(lir, ins);
 }
 
 bool
-LIRGenerator::visitAbortPar(MAbortPar *ins)
-{
-    LAbortPar *lir = new(alloc()) LAbortPar();
-    return add(lir, ins);
-}
-
-bool
 LIRGenerator::visitInitElem(MInitElem *ins)
 {
     LInitElem *lir = new(alloc()) LInitElem(useRegisterAtStart(ins->getObject()));
     if (!useBoxAtStart(lir, LInitElem::IdIndex, ins->getId()))
         return false;
     if (!useBoxAtStart(lir, LInitElem::ValueIndex, ins->getValue()))
         return false;
 
@@ -2174,17 +2161,17 @@ LIRGenerator::visitGuardThreadExclusive(
     // general form of write guard check. we could employ TI feedback
     // to optimize this if we know that the object being tested is a
     // typed object or know that it is definitely NOT a typed object.
     LGuardThreadExclusive *lir =
         new(alloc()) LGuardThreadExclusive(useFixed(ins->forkJoinContext(), CallTempReg0),
                                            useFixed(ins->object(), CallTempReg1),
                                            tempFixed(CallTempReg2));
     lir->setMir(ins);
-    return add(lir, ins);
+    return assignSnapshot(lir, Bailout_GuardThreadExclusive) && add(lir, ins);
 }
 
 bool
 LIRGenerator::visitInterruptCheck(MInterruptCheck *ins)
 {
     // Implicit interrupt checks require asm.js signal handlers to be
     // installed. ARM does not yet use implicit interrupt checks, see
     // bug 864220.
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -72,17 +72,16 @@ class LIRGenerator : public LIRGenerator
     bool visitNewDeclEnvObject(MNewDeclEnvObject *ins);
     bool visitNewCallObject(MNewCallObject *ins);
     bool visitNewRunOnceCallObject(MNewRunOnceCallObject *ins);
     bool visitNewStringObject(MNewStringObject *ins);
     bool visitNewDerivedTypedObject(MNewDerivedTypedObject *ins);
     bool visitNewPar(MNewPar *ins);
     bool visitNewCallObjectPar(MNewCallObjectPar *ins);
     bool visitNewDenseArrayPar(MNewDenseArrayPar *ins);
-    bool visitAbortPar(MAbortPar *ins);
     bool visitInitElem(MInitElem *ins);
     bool visitInitElemGetterSetter(MInitElemGetterSetter *ins);
     bool visitMutateProto(MMutateProto *ins);
     bool visitInitProp(MInitProp *ins);
     bool visitInitPropGetterSetter(MInitPropGetterSetter *ins);
     bool visitCheckOverRecursed(MCheckOverRecursed *ins);
     bool visitCheckOverRecursedPar(MCheckOverRecursedPar *ins);
     bool visitDefVar(MDefVar *ins);
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -1816,34 +1816,16 @@ class MNewDerivedTypedObject
     }
 
     bool writeRecoverData(CompactBufferWriter &writer) const;
     bool canRecoverOnBailout() const {
         return true;
     }
 };
 
-// Abort parallel execution.
-class MAbortPar : public MAryControlInstruction<0, 0>
-{
-    MAbortPar()
-      : MAryControlInstruction<0, 0>()
-    {
-        setResultType(MIRType_None);
-        setGuard();
-    }
-
-  public:
-    INSTRUCTION_HEADER(AbortPar);
-
-    static MAbortPar *New(TempAllocator &alloc) {
-        return new(alloc) MAbortPar();
-    }
-};
-
 // Setting __proto__ in an object literal.
 class MMutateProto
   : public MAryInstruction<2>,
     public MixPolicy<ObjectPolicy<0>, BoxPolicy<1> >
 {
   protected:
     MMutateProto(MDefinition *obj, MDefinition *value)
     {
@@ -2287,20 +2269,21 @@ class MApplyArgs
     TypePolicy *typePolicy() {
         return this;
     }
     bool possiblyCalls() const {
         return true;
     }
 };
 
-class MBail : public MNullaryInstruction
+class MBail : public MAryControlInstruction<0, 0>
 {
   protected:
     MBail(BailoutKind kind)
+      : MAryControlInstruction<0, 0>()
     {
         bailoutKind_ = kind;
         setGuard();
     }
 
   private:
     BailoutKind bailoutKind_;
 
@@ -5248,29 +5231,31 @@ class MCheckOverRecursedPar : public MUn
 // Check for an interrupt (or rendezvous) in parallel mode.
 class MInterruptCheckPar : public MUnaryInstruction
 {
     explicit MInterruptCheckPar(MDefinition *cx)
       : MUnaryInstruction(cx)
     {
         setResultType(MIRType_None);
         setGuard();
-        setMovable();
     }
 
   public:
     INSTRUCTION_HEADER(InterruptCheckPar);
 
     static MInterruptCheckPar *New(TempAllocator &alloc, MDefinition *cx) {
         return new(alloc) MInterruptCheckPar(cx);
     }
 
     MDefinition *forkJoinContext() const {
         return getOperand(0);
     }
+    AliasSet getAliasSet() const {
+        return AliasSet::None();
+    }
 };
 
 // Check whether we need to fire the interrupt handler.
 class MInterruptCheck : public MNullaryInstruction
 {
     MInterruptCheck() {
         setGuard();
     }
--- a/js/src/jit/MIRGraph.cpp
+++ b/js/src/jit/MIRGraph.cpp
@@ -743,17 +743,25 @@ MBasicBlock::discardDefAt(MDefinitionIte
         iter.iter_ = iter.block_->discardAt(iter.iter_);
 
     return iter;
 }
 
 void
 MBasicBlock::discardAllInstructions()
 {
-    for (MInstructionIterator iter = begin(); iter != end(); ) {
+    MInstructionIterator iter = begin();
+    discardAllInstructionsStartingAt(iter);
+
+}
+
+void
+MBasicBlock::discardAllInstructionsStartingAt(MInstructionIterator &iter)
+{
+    while (iter != end()) {
         for (size_t i = 0, e = iter->numOperands(); i < e; i++)
             iter->discardOperand(i);
         iter = instructions_.removeAt(iter);
     }
 }
 
 void
 MBasicBlock::discardAllPhiOperands()
--- a/js/src/jit/MIRGraph.h
+++ b/js/src/jit/MIRGraph.h
@@ -227,16 +227,17 @@ class MBasicBlock : public TempObject, p
 
     // Removes an instruction with the intention to discard it.
     void discard(MInstruction *ins);
     void discardLastIns();
     MInstructionIterator discardAt(MInstructionIterator &iter);
     MInstructionReverseIterator discardAt(MInstructionReverseIterator &iter);
     MDefinitionIterator discardDefAt(MDefinitionIterator &iter);
     void discardAllInstructions();
+    void discardAllInstructionsStartingAt(MInstructionIterator &iter);
     void discardAllPhiOperands();
     void discardAllPhis();
     void discardAllResumePoints(bool discardEntry = true);
 
     // Discards a phi instruction and updates predecessor successorWithPhis.
     MPhiIterator discardPhiAt(MPhiIterator &at);
 
     // Mark this block as having been removed from the graph.
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -212,17 +212,16 @@ namespace jit {
     _(AsmJSVoidReturn)                                                      \
     _(AsmJSPassStackArg)                                                    \
     _(AsmJSCall)                                                            \
     _(CheckOverRecursedPar)                                                 \
     _(NewCallObjectPar)                                                     \
     _(NewPar)                                                               \
     _(NewDenseArrayPar)                                                     \
     _(NewDerivedTypedObject)                                                \
-    _(AbortPar)                                                             \
     _(LambdaPar)                                                            \
     _(RestPar)                                                              \
     _(ForkJoinContext)                                                      \
     _(ForkJoinGetSlice)                                                     \
     _(GuardThreadExclusive)                                                 \
     _(InterruptCheckPar)                                                    \
     _(RecompileCheck)
 
--- a/js/src/jit/ParallelFunctions.cpp
+++ b/js/src/jit/ParallelFunctions.cpp
@@ -4,30 +4,30 @@
  * 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 "jit/ParallelFunctions.h"
 
 #include "builtin/TypedObject.h"
 #include "jit/arm/Simulator-arm.h"
 #include "jit/mips/Simulator-mips.h"
+#include "jit/RematerializedFrame.h"
 #include "vm/ArrayObject.h"
 
 #include "jsgcinlines.h"
 #include "jsobjinlines.h"
 
 using namespace js;
 using namespace jit;
 
 using JS::AutoCheckCannotGC;
 
 using parallel::Spew;
 using parallel::SpewOps;
 using parallel::SpewBailouts;
-using parallel::SpewBailoutIR;
 
 // Load the current thread context.
 ForkJoinContext *
 jit::ForkJoinContextPar()
 {
     return ForkJoinContext::current();
 }
 
@@ -120,73 +120,16 @@ bool
 jit::IsInTargetRegion(ForkJoinContext *cx, TypedObject *typedObj)
 {
     JS_ASSERT(typedObj->is<TypedObject>()); // in case JIT supplies something bogus
     uint8_t *typedMem = typedObj->typedMem();
     return (typedMem >= cx->targetRegionStart &&
             typedMem <  cx->targetRegionEnd);
 }
 
-#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->blockIndex, cached->lirIndex, cached->execModeInt, cached->lirOpName);
-}
-
-static struct IonLIRTraceData seqTraceData;
-#endif
-
-void
-jit::TraceLIR(IonLIRTraceData *current)
-{
-#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 TraceLIR
-    // 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 (current->execModeInt == 0)
-        cached = &seqTraceData;
-    else
-        cached = &ForkJoinContext::current()->traceData;
-
-    if (current->blockIndex == 0xDEADBEEF) {
-        if (current->execModeInt == 0)
-            printTrace("BAILOUT", cached);
-        else
-            SpewBailoutIR(cached);
-    }
-
-    memcpy(cached, current, sizeof(IonLIRTraceData));
-
-    if (traceMode == All)
-        printTrace("Exec", cached);
-#endif
-}
-
 bool
 jit::CheckOverRecursedPar(ForkJoinContext *cx)
 {
     JS_ASSERT(ForkJoinContext::current() == cx);
     int stackDummy_;
 
     // When an interrupt is requested, the main thread stack limit is
     // overwritten with a sentinel value that brings us here.
@@ -194,58 +137,56 @@ jit::CheckOverRecursedPar(ForkJoinContex
     // and, if not, check whether an interrupt was requested.
     //
     // When not on the main thread, we don't overwrite the stack
     // limit, but we do still call into this routine if the interrupt
     // flag is set, so we still need to double check.
 
 #if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
     if (Simulator::Current()->overRecursed()) {
-        cx->bailoutRecord->setCause(ParallelBailoutOverRecursed);
+        cx->bailoutRecord->joinCause(ParallelBailoutOverRecursed);
         return false;
     }
 #endif
 
     uintptr_t realStackLimit;
     if (cx->isMainThread())
         realStackLimit = GetNativeStackLimit(cx);
     else
         realStackLimit = cx->perThreadData->jitStackLimit;
 
     if (!JS_CHECK_STACK_SIZE(realStackLimit, &stackDummy_)) {
-        cx->bailoutRecord->setCause(ParallelBailoutOverRecursed);
+        cx->bailoutRecord->joinCause(ParallelBailoutOverRecursed);
         return false;
     }
 
     return InterruptCheckPar(cx);
 }
 
 bool
 jit::InterruptCheckPar(ForkJoinContext *cx)
 {
     JS_ASSERT(ForkJoinContext::current() == cx);
     bool result = cx->check();
     if (!result) {
-        // Do not set the cause here.  Either it was set by this
-        // thread already by some code that then triggered an abort,
-        // or else we are just picking up an abort from some other
-        // thread.  Either way we have nothing useful to contribute so
-        // we might as well leave our bailout case unset.
+        cx->bailoutRecord->joinCause(ParallelBailoutInterrupt);
         return false;
     }
     return true;
 }
 
 JSObject *
 jit::ExtendArrayPar(ForkJoinContext *cx, JSObject *array, uint32_t length)
 {
     JSObject::EnsureDenseResult res =
         array->ensureDenseElementsPreservePackedFlag(cx, 0, length);
-    if (res != JSObject::ED_OK)
+    if (res != JSObject::ED_OK) {
+        fprintf(stderr, "==== NGNG\n");
         return nullptr;
+    }
     return array;
 }
 
 bool
 jit::SetPropertyPar(ForkJoinContext *cx, HandleObject obj, HandlePropertyName name,
                     HandleValue value, bool strict, jsbytecode *pc)
 {
     JS_ASSERT(cx->isThreadLocal(obj));
@@ -568,68 +509,46 @@ jit::UrshValuesPar(ForkJoinContext *cx, 
     if (!NonObjectToUint32(cx, lhs, &left) || !NonObjectToInt32(cx, rhs, &right))
         return false;
     left >>= right & 31;
     out.setNumber(uint32_t(left));
     return true;
 }
 
 void
-jit::AbortPar(ParallelBailoutCause cause, JSScript *outermostScript, JSScript *currentScript,
-              jsbytecode *bytecode)
+jit::BailoutPar(BailoutStack *sp, uint8_t **entryFramePointer)
 {
-    // Spew before asserts to help with diagnosing failures.
-    Spew(SpewBailouts,
-         "Parallel abort with cause %d in %p:%s:%d "
-         "(%p:%s:%d at line %d)",
-         cause,
-         outermostScript, outermostScript->filename(), outermostScript->lineno(),
-         currentScript, currentScript->filename(), currentScript->lineno(),
-         (currentScript ? PCToLineNumber(currentScript, bytecode) : 0));
-
-    JS_ASSERT(InParallelSection());
-    JS_ASSERT(outermostScript != nullptr);
-    JS_ASSERT(currentScript != nullptr);
-    JS_ASSERT(outermostScript->hasParallelIonScript());
+    parallel::Spew(parallel::SpewBailouts, "Bailing");
 
     ForkJoinContext *cx = ForkJoinContext::current();
 
-    JS_ASSERT(cx->bailoutRecord->depth == 0);
-    cx->bailoutRecord->setCause(cause, outermostScript, currentScript, bytecode);
+    // We don't have an exit frame.
+    MOZ_ASSERT(size_t(FAKE_JIT_TOP_FOR_BAILOUT + sizeof(IonCommonFrameLayout)) < 0x1000 &&
+               size_t(FAKE_JIT_TOP_FOR_BAILOUT) >= 0,
+               "Fake jitTop pointer should be within the first page.");
+    cx->perThreadData->jitTop = FAKE_JIT_TOP_FOR_BAILOUT;
+
+    JitActivationIterator jitActivations(cx->perThreadData);
+    IonBailoutIterator frameIter(jitActivations, sp);
+    cx->bailoutRecord->joinCause(ParallelBailoutUnsupported);
+    cx->bailoutRecord->rematerializeFrames(cx, frameIter);
+
+    MOZ_ASSERT(frameIter.done());
+    *entryFramePointer = frameIter.fp();
 }
 
-void
-jit::PropagateAbortPar(JSScript *outermostScript, JSScript *currentScript)
+bool
+jit::CallToUncompiledScriptPar(ForkJoinContext *cx, JSObject *obj)
 {
-    Spew(SpewBailouts,
-         "Propagate parallel abort via %p:%s:%d (%p:%s:%d)",
-         outermostScript, outermostScript->filename(), outermostScript->lineno(),
-         currentScript, currentScript->filename(), currentScript->lineno());
-
-    JS_ASSERT(InParallelSection());
-    JS_ASSERT(outermostScript->hasParallelIonScript());
-
-    outermostScript->parallelIonScript()->setHasUncompiledCallTarget();
-
-    ForkJoinContext *cx = ForkJoinContext::current();
-    if (currentScript)
-        cx->bailoutRecord->addTrace(currentScript, nullptr);
-}
-
-void
-jit::CallToUncompiledScriptPar(JSObject *obj)
-{
-    JS_ASSERT(InParallelSection());
-
 #ifdef DEBUG
     static const int max_bound_function_unrolling = 5;
 
     if (!obj->is<JSFunction>()) {
         Spew(SpewBailouts, "Call to non-function");
-        return;
+        return false;
     }
 
     JSFunction *func = &obj->as<JSFunction>();
     if (func->hasScript()) {
         JSScript *script = func->nonLazyScript();
         Spew(SpewBailouts, "Call to uncompiled script: %p:%s:%d",
              script, script->filename(), script->lineno());
     } else if (func->isInterpretedLazy()) {
@@ -651,16 +570,18 @@ jit::CallToUncompiledScriptPar(JSObject 
         } else {
             Spew(SpewBailouts, "Call to bound function (excessive depth: %d)", depth);
         }
     } else {
         JS_ASSERT(func->isNative());
         Spew(SpewBailouts, "Call to native function");
     }
 #endif
+
+    return false;
 }
 
 JSObject *
 jit::InitRestParameterPar(ForkJoinContext *cx, uint32_t length, Value *rest,
                           HandleObject templateObj, HandleObject res)
 {
     // In parallel execution, we should always have succeeded in allocation
     // before this point. We can do the allocation here like in the sequential
--- a/js/src/jit/ParallelFunctions.h
+++ b/js/src/jit/ParallelFunctions.h
@@ -67,20 +67,15 @@ bool BitRshPar(ForkJoinContext *cx, Hand
 
 bool UrshValuesPar(ForkJoinContext *cx, HandleValue lhs, HandleValue rhs, MutableHandleValue out);
 
 // Make a new rest parameter in parallel.
 JSObject *InitRestParameterPar(ForkJoinContext *cx, uint32_t length, Value *rest,
                                HandleObject templateObj, HandleObject res);
 
 // Abort and debug tracing functions.
-void AbortPar(ParallelBailoutCause cause, JSScript *outermostScript, JSScript *currentScript,
-              jsbytecode *bytecode);
-void PropagateAbortPar(JSScript *outermostScript, JSScript *currentScript);
-
-void TraceLIR(IonLIRTraceData *current);
-
-void CallToUncompiledScriptPar(JSObject *obj);
+void BailoutPar(BailoutStack *sp, uint8_t **entryFramePointer);
+bool CallToUncompiledScriptPar(ForkJoinContext *cx, JSObject *obj);
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_ParallelFunctions_h */
--- a/js/src/jit/ParallelSafetyAnalysis.cpp
+++ b/js/src/jit/ParallelSafetyAnalysis.cpp
@@ -70,16 +70,17 @@ class ParallelSafetyVisitor : public MIn
     MIRGraph &graph_;
     bool unsafe_;
     MDefinition *cx_;
 
     bool insertWriteGuard(MInstruction *writeInstruction, MDefinition *valueBeingWritten);
 
     bool replaceWithNewPar(MInstruction *newInstruction, JSObject *templateObject);
     bool replace(MInstruction *oldInstruction, MInstruction *replacementInstruction);
+    bool replaceLastIns(MInstruction *oldInstruction, MControlInstruction *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_);
@@ -101,17 +102,17 @@ class ParallelSafetyVisitor : public MIn
     void clearUnsafe() { unsafe_ = false; }
     bool unsafe() { return unsafe_; }
     MDefinition *ForkJoinContext() {
         if (!cx_)
             cx_ = graph_.forkJoinContext();
         return cx_;
     }
 
-    bool convertToBailout(MBasicBlock *block, MInstruction *ins);
+    bool convertToBailout(MInstructionIterator &iter);
 
     // I am taking the policy of blacklisting everything that's not
     // obviously safe for now.  We can loosen as we need.
 
     SAFE_OP(Constant)
     UNSAFE_OP(CloneLiteral)
     SAFE_OP(Parameter)
     SAFE_OP(Callee)
@@ -276,17 +277,16 @@ class ParallelSafetyVisitor : public MIn
     UNSAFE_OP(InstanceOf)
     CUSTOM_OP(InterruptCheck)
     SAFE_OP(ForkJoinContext)
     SAFE_OP(ForkJoinGetSlice)
     SAFE_OP(NewPar)
     SAFE_OP(NewDenseArrayPar)
     SAFE_OP(NewCallObjectPar)
     SAFE_OP(LambdaPar)
-    SAFE_OP(AbortPar)
     UNSAFE_OP(ArrayConcat)
     UNSAFE_OP(GetDOMProperty)
     UNSAFE_OP(GetDOMMember)
     UNSAFE_OP(SetDOMProperty)
     UNSAFE_OP(NewStringObject)
     UNSAFE_OP(Random)
     SAFE_OP(Pow)
     SAFE_OP(PowHalf)
@@ -324,16 +324,26 @@ class ParallelSafetyVisitor : public MIn
     UNSAFE_OP(AsmJSParameter)
     UNSAFE_OP(AsmJSCall)
     DROP_OP(RecompileCheck)
 
     // It looks like this could easily be made safe:
     UNSAFE_OP(ConvertElementsToDoubles)
 };
 
+static void
+TransplantResumePoint(MInstruction *oldInstruction, MInstruction *replacementInstruction)
+{
+    if (MResumePoint *rp = oldInstruction->resumePoint()) {
+        replacementInstruction->setResumePoint(rp);
+        if (rp->instruction() == oldInstruction)
+            rp->setInstruction(replacementInstruction);
+    }
+}
+
 bool
 ParallelSafetyAnalysis::analyze()
 {
     // 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
@@ -349,31 +359,30 @@ ParallelSafetyAnalysis::analyze()
         if (block->isMarked()) {
             // Count the number of reachable blocks.
             marked++;
 
             // 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 = nullptr;
-            for (MInstructionIterator ins(block->begin());
-                 ins != block->end() && !visitor.unsafe();)
-            {
+            MInstruction *ins = nullptr;
+            MInstructionIterator iter(block->begin());
+            while (iter != block->end() && !visitor.unsafe()) {
                 if (mir_->shouldCancel("ParallelSafetyAnalysis"))
                     return false;
 
                 // We may be removing or replacing the current
-                // instruction, so advance `ins` now.  Remember the
+                // instruction, so advance `iter` now.  Remember the
                 // last instr. we looked at for use later if it should
                 // prove unsafe.
-                instr = *ins++;
+                ins = *iter++;
 
-                if (!instr->accept(&visitor)) {
-                    SpewMIR(instr, "Unaccepted");
+                if (!ins->accept(&visitor)) {
+                    SpewMIR(ins, "Unaccepted");
                     return false;
                 }
             }
 
             if (!visitor.unsafe()) {
                 // Block consists of only safe instructions.  Visit its successors.
                 for (uint32_t i = 0; i < block->numSuccessors(); i++)
                     block->getSuccessor(i)->markUnchecked();
@@ -385,105 +394,59 @@ ParallelSafetyAnalysis::analyze()
                 // in even trying to execute this function as it will
                 // always bailout.
                 if (*block == graph_.entryBlock()) {
                     Spew(SpewCompile, "Entry block contains unsafe MIR");
                     mir_->disable();
                     return false;
                 }
 
-                // Otherwise, create a replacement that will.
-                if (!visitor.convertToBailout(*block, instr))
+                // Otherwise, create a replacement that will. We seek back one
+                // position on the instruction iterator, as we will be
+                // discarding all instructions starting at the unsafe
+                // instruction.
+                if (!visitor.convertToBailout(--iter))
                     return false;
             }
         }
     }
 
     Spew(SpewCompile, "Safe");
     IonSpewPass("ParallelSafetyAnalysis");
 
     UnreachableCodeElimination uce(mir_, graph_);
     if (!uce.removeUnmarkedBlocks(marked))
         return false;
     IonSpewPass("UCEAfterParallelSafetyAnalysis");
     AssertExtendedGraphCoherency(graph_);
 
-    if (!removeResumePointOperands())
-        return false;
-    IonSpewPass("RemoveResumePointOperands");
-    AssertExtendedGraphCoherency(graph_);
-
     return true;
 }
 
 bool
-ParallelSafetyAnalysis::removeResumePointOperands()
+ParallelSafetyVisitor::convertToBailout(MInstructionIterator &iter)
 {
-    // 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 = nullptr;
-    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 == nullptr);
-                udef = MConstant::New(graph_.alloc(), UndefinedValue());
-                block->insertAfter(*ins, udef);
-            } else if (udef) {
-                if (MResumePoint *resumePoint = ins->resumePoint())
-                    replaceOperandsOnResumePoint(resumePoint, udef);
-            }
-        }
-    }
-    return true;
-}
-
-void
-ParallelSafetyAnalysis::replaceOperandsOnResumePoint(MResumePoint *resumePoint,
-                                                     MDefinition *withDef)
-{
-    for (size_t i = 0, e = resumePoint->numOperands(); i < e; i++)
-        resumePoint->replaceOperand(i, withDef);
-}
-
-bool
-ParallelSafetyVisitor::convertToBailout(MBasicBlock *block, MInstruction *ins)
-{
+    // We expect iter to be settled on the unsafe instruction.
+    MInstruction *ins = *iter;
+    MBasicBlock *block = ins->block();
     JS_ASSERT(unsafe()); // `block` must have contained unsafe items
     JS_ASSERT(block->isMarked()); // `block` must have been reachable to get here
 
-    // Convert the block to a bailout block.  In principle, we
-    // only need one bailout block per graph! But I
-    // found this approach easier to implement given the design of the
-    // MIR Graph construction routines. Using multiple blocks helps to
-    // keep the PC information more accurate.
-    for (size_t i = 0, e = block->numSuccessors(); i < e; i++)
+    clearUnsafe();
+
+    // Discard the rest of the block and sever its link to its successors in
+    // the CFG.
+    for (size_t i = 0; i < block->numSuccessors(); i++)
         block->getSuccessor(i)->removePredecessor(block);
-    clearUnsafe();
-    block->discardAllPhis();
-    block->discardAllInstructions();
-    block->end(MAbortPar::New(graph_.alloc()));
+    block->discardAllInstructionsStartingAt(iter);
+
+    // End the block in the bail.
+    MBail *bailout = MBail::New(graph_.alloc());
+    TransplantResumePoint(ins, bailout);
+    block->end(bailout);
     return true;
 }
 
 /////////////////////////////////////////////////////////////////////////////
 // Memory allocation
 //
 // Simple memory allocation opcodes---those which ultimately compile
 // down to a (possibly inlined) invocation of NewGCThing()---are
@@ -498,42 +461,41 @@ ParallelSafetyVisitor::visitCreateThisWi
 
 bool
 ParallelSafetyVisitor::visitNewCallObject(MNewCallObject *ins)
 {
     if (ins->templateObject()->hasDynamicSlots()) {
         SpewMIR(ins, "call with dynamic slots");
         return markUnsafe();
     }
-    replace(ins, MNewCallObjectPar::New(alloc(), ForkJoinContext(), ins));
-    return true;
+
+    return replace(ins, MNewCallObjectPar::New(alloc(), ForkJoinContext(), ins));
 }
 
 bool
 ParallelSafetyVisitor::visitNewRunOnceCallObject(MNewRunOnceCallObject *ins)
 {
     if (ins->templateObject()->hasDynamicSlots()) {
         SpewMIR(ins, "call with dynamic slots");
         return markUnsafe();
     }
-    replace(ins, MNewCallObjectPar::New(alloc(), ForkJoinContext(), ins));
-    return true;
+
+    return replace(ins, MNewCallObjectPar::New(alloc(), ForkJoinContext(), ins));
 }
 
 bool
 ParallelSafetyVisitor::visitLambda(MLambda *ins)
 {
     if (ins->info().singletonType || ins->info().useNewTypeForClone) {
         // slow path: bail on parallel execution.
         return markUnsafe();
     }
 
     // fast path: replace with LambdaPar op
-    replace(ins, MLambdaPar::New(alloc(), ForkJoinContext(), ins));
-    return true;
+    return replace(ins, MLambdaPar::New(alloc(), ForkJoinContext(), ins));
 }
 
 bool
 ParallelSafetyVisitor::visitNewObject(MNewObject *newInstruction)
 {
     if (newInstruction->shouldUseVM()) {
         SpewMIR(newInstruction, "should use VM");
         return markUnsafe();
@@ -593,32 +555,28 @@ ParallelSafetyVisitor::visitToString(MTo
         return markUnsafe();
     return true;
 }
 
 bool
 ParallelSafetyVisitor::replaceWithNewPar(MInstruction *newInstruction,
                                          JSObject *templateObject)
 {
-    replace(newInstruction, MNewPar::New(alloc(), ForkJoinContext(), templateObject));
-    return true;
+    return replace(newInstruction, MNewPar::New(alloc(), ForkJoinContext(), templateObject));
 }
 
 bool
 ParallelSafetyVisitor::replace(MInstruction *oldInstruction,
                                MInstruction *replacementInstruction)
 {
+    TransplantResumePoint(oldInstruction, replacementInstruction);
+
     MBasicBlock *block = oldInstruction->block();
     block->insertBefore(oldInstruction, replacementInstruction);
     oldInstruction->replaceAllUsesWith(replacementInstruction);
-    MResumePoint *rp = oldInstruction->resumePoint();
-    if (rp && rp->instruction() == oldInstruction) {
-        rp->setInstruction(replacementInstruction);
-        replacementInstruction->setResumePoint(rp);
-    }
     block->discard(oldInstruction);
 
     // We may have replaced a specialized Float32 instruction by its
     // non-specialized version, so just retry to specialize it. This relies on
     // the fact that Phis' types don't change during the ParallelSafetyAnalysis;
     // otherwise we'd have to run the entire TypeAnalyzer Float32 analysis once
     // instructions have been replaced.
     if (replacementInstruction->isFloat32Commutative() &&
@@ -626,16 +584,27 @@ ParallelSafetyVisitor::replace(MInstruct
     {
         replacementInstruction->trySpecializeFloat32(alloc());
     }
     JS_ASSERT(oldInstruction->type() == replacementInstruction->type());
 
     return true;
 }
 
+bool
+ParallelSafetyVisitor::replaceLastIns(MInstruction *oldInstruction,
+                                      MControlInstruction *replacementInstruction)
+{
+    TransplantResumePoint(oldInstruction, replacementInstruction);
+    MBasicBlock *block = oldInstruction->block();
+    block->discardLastIns();
+    block->end(replacementInstruction);
+    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
@@ -789,23 +758,18 @@ ParallelSafetyVisitor::visitSpecializedI
 }
 
 /////////////////////////////////////////////////////////////////////////////
 // Throw
 
 bool
 ParallelSafetyVisitor::visitThrow(MThrow *thr)
 {
-    MBasicBlock *block = thr->block();
-    JS_ASSERT(block->lastIns() == thr);
-    block->discardLastIns();
-    MAbortPar *bailout = MAbortPar::New(alloc());
-    if (!bailout)
-        return false;
-    block->end(bailout);
+    JS_ASSERT(thr->block()->lastIns() == thr);
+    replaceLastIns(thr, MBail::New(alloc()));
     return true;
 }
 
 ///////////////////////////////////////////////////////////////////////////
 // Callee extraction
 //
 // See comments in header file.
 
--- a/js/src/jit/ParallelSafetyAnalysis.h
+++ b/js/src/jit/ParallelSafetyAnalysis.h
@@ -15,25 +15,22 @@ class InterpreterFrame;
 
 namespace jit {
 
 class MIRGraph;
 class AutoDestroyAllocator;
 
 // Determines whether a function is compatible for parallel execution.
 // Removes basic blocks containing unsafe MIR operations from the
-// graph and replaces them with MAbortPar blocks.
+// graph and replaces them with MBail blocks.
 class ParallelSafetyAnalysis
 {
     MIRGenerator *mir_;
     MIRGraph &graph_;
 
-    bool removeResumePointOperands();
-    void replaceOperandsOnResumePoint(MResumePoint *resumePoint, MDefinition *withDef);
-
   public:
     ParallelSafetyAnalysis(MIRGenerator *mir,
                            MIRGraph &graph)
       : mir_(mir),
         graph_(graph)
     {}
 
     bool analyze();
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -173,42 +173,26 @@ CodeGeneratorARM::generateOutOfLineCode(
 
     if (deoptLabel_.used()) {
         // All non-table-based bailouts will go here.
         masm.bind(&deoptLabel_);
 
         // Push the frame size, so the handler can recover the IonScript.
         masm.ma_mov(Imm32(frameSize()), lr);
 
-        JitCode *handler = gen->jitRuntime()->getGenericBailoutHandler();
+        JitCode *handler = gen->jitRuntime()->getGenericBailoutHandler(gen->info().executionMode());
         masm.branch(handler);
     }
 
     return true;
 }
 
 bool
 CodeGeneratorARM::bailoutIf(Assembler::Condition condition, 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.
-        OutOfLineAbortPar *ool = oolAbortPar(ParallelBailoutUnsupported,
-                                             snapshot->mir()->block(),
-                                             snapshot->mir()->pc());
-        masm.ma_b(ool->entry(), condition);
-        return true;
-      }
-      case SequentialExecution:
-        break;
-      default:
-        MOZ_ASSUME_UNREACHABLE("No such execution mode");
-    }
     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(),
                  frameClass_.frameSize() == masm.framePushed());
@@ -233,33 +217,16 @@ CodeGeneratorARM::bailoutIf(Assembler::C
 bool
 CodeGeneratorARM::bailoutFrom(Label *label, LSnapshot *snapshot)
 {
     if (masm.bailed())
         return false;
     JS_ASSERT(label->used());
     JS_ASSERT(!label->bound());
 
-    CompileInfo &info = snapshot->mir()->block()->info();
-    switch (info.executionMode()) {
-
-      case ParallelExecution: {
-        // in parallel mode, make no attempt to recover, just signal an error.
-        OutOfLineAbortPar *ool = oolAbortPar(ParallelBailoutUnsupported,
-                                             snapshot->mir()->block(),
-                                             snapshot->mir()->pc());
-        masm.retarget(label, ool->entry());
-        return true;
-      }
-      case SequentialExecution:
-        break;
-      default:
-        MOZ_ASSUME_UNREACHABLE("No such execution mode");
-    }
-
     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(),
                  frameClass_.frameSize() == masm.framePushed());
--- a/js/src/jit/arm/CodeGenerator-arm.h
+++ b/js/src/jit/arm/CodeGenerator-arm.h
@@ -63,16 +63,20 @@ class CodeGeneratorARM : public CodeGene
         masm.cmp32(lhs, rhs);
         return bailoutIf(c, snapshot);
     }
     template <typename T1, typename T2>
     bool bailoutTest32(Assembler::Condition c, T1 lhs, T2 rhs, LSnapshot *snapshot) {
         masm.test32(lhs, rhs);
         return bailoutIf(c, snapshot);
     }
+    bool bailoutIfFalseBool(Register reg, LSnapshot *snapshot) {
+        masm.test32(reg, Imm32(0xFF));
+        return bailoutIf(Assembler::Zero, snapshot);
+    }
 
   protected:
     bool generatePrologue();
     bool generateAsmJSPrologue(Label *stackOverflowLabel);
     bool generateEpilogue();
     bool generateOutOfLineCode();
 
     void emitRoundDouble(FloatRegister src, Register dest, Label *fail);
--- a/js/src/jit/arm/Trampoline-arm.cpp
+++ b/js/src/jit/arm/Trampoline-arm.cpp
@@ -11,16 +11,17 @@
 #include "jit/Bailouts.h"
 #include "jit/IonFrames.h"
 #include "jit/IonLinker.h"
 #include "jit/IonSpewer.h"
 #include "jit/JitCompartment.h"
 #ifdef JS_ION_PERF
 # include "jit/PerfSpewer.h"
 #endif
+#include "jit/ParallelFunctions.h"
 #include "jit/VMFunctions.h"
 
 #include "jit/ExecutionMode-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 static const FloatRegisterSet NonVolatileFloatRegs =
@@ -509,17 +510,17 @@ JitRuntime::generateArgumentsRectifier(J
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "ArgumentsRectifier");
 #endif
 
     return code;
 }
 
 static void
-GenerateBailoutThunk(JSContext *cx, MacroAssembler &masm, uint32_t frameClass)
+PushBailoutFrame(MacroAssembler &masm, uint32_t frameClass, Register spArg)
 {
     // the stack should look like:
     // [IonFrame]
     // bailoutFrame.registersnapshot
     // bailoutFrame.fpsnapshot
     // bailoutFrame.snapshotOffset
     // bailoutFrame.frameSize
 
@@ -554,20 +555,27 @@ GenerateBailoutThunk(JSContext *cx, Macr
     masm.startDataTransferM(IsStore, sp, DB, WriteBack);
     // set frameClassId_
     masm.transferReg(r4);
     // Set tableOffset_; higher registers are stored at higher locations on
     // the stack.
     masm.transferReg(lr);
     masm.finishDataTransfer();
 
+    masm.ma_mov(sp, spArg);
+}
+
+static void
+GenerateBailoutThunk(JSContext *cx, MacroAssembler &masm, uint32_t frameClass)
+{
+    PushBailoutFrame(masm, frameClass, r0);
+
     // SP % 8 == 4
     // STEP 1c: Call the bailout function, giving a pointer to the
     //          structure we just blitted onto the stack
-    masm.ma_mov(sp, r0);
     const int sizeOfBailoutInfo = sizeof(void *)*2;
     masm.reserveStack(sizeOfBailoutInfo);
     masm.mov(sp, r1);
     masm.setupAlignedABICall(2);
 
     // Decrement sp by another 4, so we keep alignment
     // Not Anymore!  pushing both the snapshotoffset as well as the
     // masm.as_sub(sp, sp, Imm8(4));
@@ -605,16 +613,42 @@ GenerateBailoutThunk(JSContext *cx, Macr
                     , sp);
     }
 
     // Jump to shared bailout tail. The BailoutInfo pointer has to be in r2.
     JitCode *bailoutTail = cx->runtime()->jitRuntime()->getBailoutTail();
     masm.branch(bailoutTail);
 }
 
+static void
+GenerateParallelBailoutThunk(MacroAssembler &masm, uint32_t frameClass)
+{
+    // As GenerateBailoutThunk, except we return an error immediately. We do
+    // the bailout dance so that we can walk the stack and have accurate
+    // reporting of frame information.
+
+    PushBailoutFrame(masm, frameClass, r0);
+
+    // Parallel bailout is like parallel failure in that we unwind all the way
+    // to the entry frame. Reserve space for the frame pointer of the entry frame.
+    const int sizeOfEntryFramePointer = sizeof(uint8_t *) * 2;
+    masm.reserveStack(sizeOfEntryFramePointer);
+    masm.mov(sp, r1);
+
+    masm.setupAlignedABICall(2);
+    masm.passABIArg(r0);
+    masm.passABIArg(r1);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, BailoutPar));
+
+    // Get the frame pointer of the entry fram and return.
+    masm.moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand);
+    masm.ma_ldr(Address(sp, 0), sp);
+    masm.as_dtr(IsLoad, 32, PostIndex, pc, DTRAddr(sp, DtrOffImm(4)));
+}
+
 JitCode *
 JitRuntime::generateBailoutTable(JSContext *cx, uint32_t frameClass)
 {
     MacroAssembler masm(cx);
 
     Label bailout;
     for (size_t i = 0; i < BAILOUT_TABLE_SIZE; i++)
         masm.ma_bl(&bailout);
@@ -629,20 +663,30 @@ JitRuntime::generateBailoutTable(JSConte
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "BailoutTable");
 #endif
 
     return code;
 }
 
 JitCode *
-JitRuntime::generateBailoutHandler(JSContext *cx)
+JitRuntime::generateBailoutHandler(JSContext *cx, ExecutionMode mode)
 {
     MacroAssembler masm(cx);
-    GenerateBailoutThunk(cx, masm, NO_FRAME_SIZE_CLASS_ID);
+
+    switch (mode) {
+      case SequentialExecution:
+        GenerateBailoutThunk(cx, masm, NO_FRAME_SIZE_CLASS_ID);
+        break;
+      case ParallelExecution:
+        GenerateParallelBailoutThunk(masm, NO_FRAME_SIZE_CLASS_ID);
+        break;
+      default:
+        MOZ_ASSUME_UNREACHABLE("No such execution mode");
+    }
 
     Linker linker(masm);
     AutoFlushICache afc("BailoutHandler");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "BailoutHandler");
 #endif
--- a/js/src/jit/mips/CodeGenerator-mips.cpp
+++ b/js/src/jit/mips/CodeGenerator-mips.cpp
@@ -208,32 +208,16 @@ CodeGeneratorMIPS::generateOutOfLineCode
 bool
 CodeGeneratorMIPS::bailoutFrom(Label *label, LSnapshot *snapshot)
 {
     if (masm.bailed())
         return false;
     MOZ_ASSERT(label->used());
     MOZ_ASSERT(!label->bound());
 
-    CompileInfo &info = snapshot->mir()->block()->info();
-    switch (info.executionMode()) {
-      case ParallelExecution: {
-        // in parallel mode, make no attempt to recover, just signal an error.
-        OutOfLineAbortPar *ool = oolAbortPar(ParallelBailoutUnsupported,
-                                             snapshot->mir()->block(),
-                                             snapshot->mir()->pc());
-        masm.retarget(label, ool->entry());
-        return true;
-      }
-      case SequentialExecution:
-        break;
-      default:
-        MOZ_ASSUME_UNREACHABLE("No such execution mode");
-    }
-
     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.
     MOZ_ASSERT_IF(frameClass_ != FrameSizeClass::None(),
                   frameClass_.frameSize() == masm.framePushed());
--- a/js/src/jit/mips/CodeGenerator-mips.h
+++ b/js/src/jit/mips/CodeGenerator-mips.h
@@ -88,16 +88,21 @@ class CodeGeneratorMIPS : public CodeGen
     bool bailoutCmpPtr(Assembler::Condition c, T1 lhs, T2 rhs, LSnapshot *snapshot) {
         return bailoutCmp32(c, lhs, rhs, snapshot);
     }
     bool bailoutTestPtr(Assembler::Condition c, Register lhs, Register rhs, LSnapshot *snapshot) {
         Label bail;
         masm.branchTestPtr(c, lhs, rhs, &bail);
         return bailoutFrom(&bail, snapshot);
     }
+    bool bailoutIfFalseBool(Register reg, LSnapshot *snapshot) {
+        Label bail;
+        masm.branchTest32(Assembler::Zero, reg, Imm32(0xFF), &bail);
+        return bailoutFrom(&bail, snapshot);
+    }
 
     bool bailoutFrom(Label *label, LSnapshot *snapshot);
     bool bailout(LSnapshot *snapshot);
 
   protected:
     bool generatePrologue();
     bool generateAsmJSPrologue(Label *stackOverflowLabel);
     bool generateEpilogue();
--- a/js/src/jit/mips/Trampoline-mips.cpp
+++ b/js/src/jit/mips/Trampoline-mips.cpp
@@ -637,20 +637,30 @@ JitRuntime::generateBailoutTable(JSConte
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "BailoutTable");
 #endif
 
     return code;
 }
 
 JitCode *
-JitRuntime::generateBailoutHandler(JSContext *cx)
+JitRuntime::generateBailoutHandler(JSContext *cx, ExecutionMode mode)
 {
     MacroAssembler masm(cx);
-    GenerateBailoutThunk(cx, masm, NO_FRAME_SIZE_CLASS_ID);
+
+    switch (mode) {
+      case SequentialExecution:
+        GenerateBailoutThunk(cx, masm, NO_FRAME_SIZE_CLASS_ID);
+        break;
+      case ParallelExecution:
+        GenerateParallelBailoutThunk(masm, NO_FRAME_SIZE_CLASS_ID);
+        break;
+      default:
+        MOZ_ASSUME_UNREACHABLE("No such execution mode");
+    }
 
     Linker linker(masm);
     AutoFlushICache afc("BailoutHandler");
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "BailoutHandler");
 #endif
--- a/js/src/jit/shared/CodeGenerator-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-shared.cpp
@@ -335,16 +335,23 @@ bool
 CodeGeneratorShared::assignBailoutId(LSnapshot *snapshot)
 {
     JS_ASSERT(snapshot->snapshotOffset() != INVALID_SNAPSHOT_OFFSET);
 
     // Can we not use bailout tables at all?
     if (!deoptTable_)
         return false;
 
+    // We do not generate a bailout table for parallel code.
+    switch (gen->info().executionMode()) {
+      case SequentialExecution: break;
+      case ParallelExecution: return false;
+      default: MOZ_ASSUME_UNREACHABLE("No such execution mode");
+    }
+
     JS_ASSERT(frameClass_ != FrameSizeClass::None());
 
     if (snapshot->bailoutId() != INVALID_BAILOUT_ID)
         return true;
 
     // Is the bailout table full?
     if (bailouts_.length() >= BAILOUT_TABLE_SIZE)
         return false;
@@ -817,133 +824,16 @@ CodeGeneratorShared::markArgumentSlots(L
 {
     for (size_t i = 0; i < pushedArgumentSlots_.length(); i++) {
         if (!safepoint->addValueSlot(pushedArgumentSlots_[i]))
             return false;
     }
     return true;
 }
 
-OutOfLineAbortPar *
-CodeGeneratorShared::oolAbortPar(ParallelBailoutCause cause, MBasicBlock *basicBlock,
-                                 jsbytecode *bytecode)
-{
-    OutOfLineAbortPar *ool = new(alloc()) OutOfLineAbortPar(cause, basicBlock, bytecode);
-    if (!ool || !addOutOfLineCode(ool))
-        return nullptr;
-    return ool;
-}
-
-OutOfLineAbortPar *
-CodeGeneratorShared::oolAbortPar(ParallelBailoutCause cause, LInstruction *lir)
-{
-    MDefinition *mir = lir->mirRaw();
-    MBasicBlock *block = mir->block();
-    jsbytecode *pc = mir->trackedPc();
-    if (!pc) {
-        if (lir->snapshot())
-            pc = lir->snapshot()->mir()->pc();
-        else
-            pc = block->pc();
-    }
-    return oolAbortPar(cause, block, pc);
-}
-
-OutOfLinePropagateAbortPar *
-CodeGeneratorShared::oolPropagateAbortPar(LInstruction *lir)
-{
-    OutOfLinePropagateAbortPar *ool = new(alloc()) OutOfLinePropagateAbortPar(lir);
-    if (!ool || !addOutOfLineCode(ool))
-        return nullptr;
-    return ool;
-}
-
-bool
-OutOfLineAbortPar::generate(CodeGeneratorShared *codegen)
-{
-    codegen->callTraceLIR(0xDEADBEEF, nullptr, "AbortPar");
-    return codegen->visitOutOfLineAbortPar(this);
-}
-
-bool
-OutOfLinePropagateAbortPar::generate(CodeGeneratorShared *codegen)
-{
-    codegen->callTraceLIR(0xDEADBEEF, nullptr, "AbortPar");
-    return codegen->visitOutOfLinePropagateAbortPar(this);
-}
-
-bool
-CodeGeneratorShared::callTraceLIR(uint32_t blockIndex, LInstruction *lir,
-                                  const char *bailoutName)
-{
-    JS_ASSERT_IF(!lir, bailoutName);
-
-    if (!IonSpewEnabled(IonSpew_Trace))
-        return true;
-
-    uint32_t execMode = (uint32_t) gen->info().executionMode();
-    uint32_t lirIndex;
-    const char *lirOpName;
-    const char *mirOpName;
-    JSScript *script;
-    jsbytecode *pc;
-
-    masm.PushRegsInMask(RegisterSet::Volatile());
-    masm.reserveStack(sizeof(IonLIRTraceData));
-
-    // 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), CallTempReg0);
-
-    if (lir) {
-        lirIndex = lir->id();
-        lirOpName = lir->opName();
-        if (MDefinition *mir = lir->mirRaw()) {
-            mirOpName = mir->opName();
-            script = mir->block()->info().script();
-            pc = mir->trackedPc();
-        } else {
-            mirOpName = nullptr;
-            script = nullptr;
-            pc = nullptr;
-        }
-    } else {
-        blockIndex = lirIndex = 0xDEADBEEF;
-        lirOpName = mirOpName = bailoutName;
-        script = nullptr;
-        pc = nullptr;
-    }
-
-    masm.store32(Imm32(blockIndex),
-                 Address(StackPointer, offsetof(IonLIRTraceData, blockIndex)));
-    masm.store32(Imm32(lirIndex),
-                 Address(StackPointer, offsetof(IonLIRTraceData, lirIndex)));
-    masm.store32(Imm32(execMode),
-                 Address(StackPointer, offsetof(IonLIRTraceData, execModeInt)));
-    masm.storePtr(ImmPtr(lirOpName),
-                  Address(StackPointer, offsetof(IonLIRTraceData, lirOpName)));
-    masm.storePtr(ImmPtr(mirOpName),
-                  Address(StackPointer, offsetof(IonLIRTraceData, mirOpName)));
-    masm.storePtr(ImmGCPtr(script),
-                  Address(StackPointer, offsetof(IonLIRTraceData, script)));
-    masm.storePtr(ImmPtr(pc),
-                  Address(StackPointer, offsetof(IonLIRTraceData, pc)));
-
-    masm.movePtr(StackPointer, CallTempReg0);
-    masm.setupUnalignedABICall(1, CallTempReg1);
-    masm.passABIArg(CallTempReg0);
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, TraceLIR));
-
-    masm.freeStack(sizeof(IonLIRTraceData));
-    masm.PopRegsInMask(RegisterSet::Volatile());
-
-    return true;
-}
-
 Label *
 CodeGeneratorShared::labelForBackedgeWithImplicitCheck(MBasicBlock *mir)
 {
     // If this is a loop backedge to a loop header with an implicit interrupt
     // check, use a patchable jump. Skip this search if compiling without a
     // script for asm.js, as there will be no interrupt check instruction.
     // Due to critical edge unsplitting there may no longer be unique loop
     // backedges, so just look for any edge going to an earlier block in RPO.
--- a/js/src/jit/shared/CodeGenerator-shared.h
+++ b/js/src/jit/shared/CodeGenerator-shared.h
@@ -21,18 +21,16 @@
 
 namespace js {
 namespace jit {
 
 class OutOfLineCode;
 class CodeGenerator;
 class MacroAssembler;
 class IonCache;
-class OutOfLineAbortPar;
-class OutOfLinePropagateAbortPar;
 
 template <class ArgSeq, class StoreOutputTo>
 class OutOfLineCallVM;
 
 class OutOfLineTruncateSlow;
 
 struct PatchableBackedgeInfo
 {
@@ -463,38 +461,16 @@ class CodeGeneratorShared : public LInst
   public:
     template <class ArgSeq, class StoreOutputTo>
     bool visitOutOfLineCallVM(OutOfLineCallVM<ArgSeq, StoreOutputTo> *ool);
 
     bool visitOutOfLineTruncateSlow(OutOfLineTruncateSlow *ool);
 
     bool omitOverRecursedCheck() const;
 
-  public:
-    bool callTraceLIR(uint32_t blockIndex, LInstruction *lir, const char *bailoutName = nullptr);
-
-    // Parallel aborts:
-    //
-    //    Parallel aborts work somewhat differently from sequential
-    //    bailouts.  When an abort occurs, we first invoke
-    //    ReportAbortPar() and then we return JS_ION_ERROR.  Each
-    //    call on the stack will check for this error return and
-    //    propagate it upwards until the C++ code that invoked the ion
-    //    code is reached.
-    //
-    //    The snapshot that is provided to `oolAbortPar` is currently
-    //    only used for error reporting, so that we can provide feedback
-    //    to the user about which instruction aborted and (perhaps) why.
-    OutOfLineAbortPar *oolAbortPar(ParallelBailoutCause cause, MBasicBlock *basicBlock,
-                                   jsbytecode *bytecode);
-    OutOfLineAbortPar *oolAbortPar(ParallelBailoutCause cause, LInstruction *lir);
-    OutOfLinePropagateAbortPar *oolPropagateAbortPar(LInstruction *lir);
-    virtual bool visitOutOfLineAbortPar(OutOfLineAbortPar *ool) = 0;
-    virtual bool visitOutOfLinePropagateAbortPar(OutOfLinePropagateAbortPar *ool) = 0;
-
 #ifdef JS_TRACE_LOGGING
   protected:
     bool emitTracelogScript(bool isStart);
     bool emitTracelogTree(bool isStart, uint32_t textId);
 
   public:
     bool emitTracelogScriptStart() {
         return emitTracelogScript(/* isStart =*/ true);
@@ -765,59 +741,12 @@ CodeGeneratorShared::visitOutOfLineCallV
     if (!callVM(ool->function(), lir))
         return false;
     ool->out().generate(this);
     restoreLiveIgnore(lir, ool->out().clobbered());
     masm.jump(ool->rejoin());
     return true;
 }
 
-// Initiate a parallel abort.  The snapshot is used to record the
-// cause.
-class OutOfLineAbortPar : public OutOfLineCode
-{
-  private:
-    ParallelBailoutCause cause_;
-    MBasicBlock *basicBlock_;
-    jsbytecode *bytecode_;
-
-  public:
-    OutOfLineAbortPar(ParallelBailoutCause cause, MBasicBlock *basicBlock, jsbytecode *bytecode)
-      : cause_(cause),
-        basicBlock_(basicBlock),
-        bytecode_(bytecode)
-    { }
-
-    ParallelBailoutCause cause() {
-        return cause_;
-    }
-
-    MBasicBlock *basicBlock() {
-        return basicBlock_;
-    }
-
-    jsbytecode *bytecode() {
-        return bytecode_;
-    }
-
-    bool generate(CodeGeneratorShared *codegen);
-};
-
-// Used when some callee has aborted.
-class OutOfLinePropagateAbortPar : public OutOfLineCode
-{
-  private:
-    LInstruction *lir_;
-
-  public:
-    explicit OutOfLinePropagateAbortPar(LInstruction *lir)
-      : lir_(lir)
-    { }
-
-    LInstruction *lir() { return lir_; }
-
-    bool generate(CodeGeneratorShared *codegen);
-};
-
 } // namespace jit
 } // namespace js
 
 #endif /* jit_shared_CodeGenerator_shared_h */
--- a/js/src/jit/shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-x86-shared.cpp
@@ -351,17 +351,17 @@ CodeGeneratorX86Shared::generateOutOfLin
 
     if (deoptLabel_.used()) {
         // All non-table-based bailouts will go here.
         masm.bind(&deoptLabel_);
 
         // Push the frame size, so the handler can recover the IonScript.
         masm.push(Imm32(frameSize()));
 
-        JitCode *handler = gen->jitRuntime()->getGenericBailoutHandler();
+        JitCode *handler = gen->jitRuntime()->getGenericBailoutHandler(gen->info().executionMode());
         masm.jmp(ImmPtr(handler->raw()), Relocation::JITCODE);
     }
 
     return true;
 }
 
 class BailoutJump {
     Assembler::Condition cond_;
@@ -393,32 +393,16 @@ 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.
-        OutOfLineAbortPar *ool = oolAbortPar(ParallelBailoutUnsupported,
-                                             snapshot->mir()->block(),
-                                             snapshot->mir()->pc());
-        binder(masm, ool->entry());
-        return true;
-      }
-      case SequentialExecution:
-        break;
-      default:
-        MOZ_ASSUME_UNREACHABLE("No such execution mode");
-    }
-
     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/jit/shared/CodeGenerator-x86-shared.h
+++ b/js/src/jit/shared/CodeGenerator-x86-shared.h
@@ -70,16 +70,20 @@ class CodeGeneratorX86Shared : public Co
         masm.cmp32(lhs, rhs);
         return bailoutIf(c, snapshot);
     }
     template <typename T1, typename T2>
     bool bailoutTest32(Assembler::Condition c, T1 lhs, T2 rhs, LSnapshot *snapshot) {
         masm.test32(lhs, rhs);
         return bailoutIf(c, snapshot);
     }
+    bool bailoutIfFalseBool(Register reg, LSnapshot *snapshot) {
+        masm.test32(reg, Imm32(0xFF));
+        return bailoutIf(Assembler::Zero, snapshot);
+    }
     bool bailoutCvttsd2si(FloatRegister src, Register dest, LSnapshot *snapshot) {
         // cvttsd2si returns 0x80000000 on failure. Test for it by
         // subtracting 1 and testing overflow. The other possibility is to test
         // equality for INT_MIN after a comparison, but 1 costs fewer bytes to
         // materialize.
         masm.cvttsd2si(src, dest);
         masm.cmp32(dest, Imm32(1));
         return bailoutIf(Assembler::Overflow, snapshot);
--- a/js/src/jit/x64/Trampoline-x64.cpp
+++ b/js/src/jit/x64/Trampoline-x64.cpp
@@ -6,16 +6,17 @@
 
 #include "jit/Bailouts.h"
 #include "jit/IonFrames.h"
 #include "jit/IonLinker.h"
 #include "jit/JitCompartment.h"
 #ifdef JS_ION_PERF
 # include "jit/PerfSpewer.h"
 #endif
+#include "jit/ParallelFunctions.h"
 #include "jit/VMFunctions.h"
 #include "jit/x64/BaselineHelpers-x64.h"
 
 using namespace js;
 using namespace js::jit;
 
 // All registers to save and restore. This includes the stack pointer, since we
 // use the ability to reference register values on the stack by index.
@@ -441,23 +442,29 @@ JitRuntime::generateArgumentsRectifier(J
     CodeOffsetLabel returnLabel(returnOffset);
     returnLabel.fixup(&masm);
     if (returnAddrOut)
         *returnAddrOut = (void *) (code->raw() + returnLabel.offset());
     return code;
 }
 
 static void
-GenerateBailoutThunk(JSContext *cx, MacroAssembler &masm, uint32_t frameClass)
+PushBailoutFrame(MacroAssembler &masm, Register spArg)
 {
     // Push registers such that we can access them from [base + code].
     masm.PushRegsInMask(AllRegs);
 
     // Get the stack pointer into a register, pre-alignment.
-    masm.movq(rsp, r8);
+    masm.movq(rsp, spArg);
+}
+
+static void
+GenerateBailoutThunk(JSContext *cx, MacroAssembler &masm, uint32_t frameClass)
+{
+    PushBailoutFrame(masm, r8);
 
     // Make space for Bailout's bailoutInfo outparam.
     masm.reserveStack(sizeof(void *));
     masm.movq(rsp, r9);
 
     // Call the bailout function.
     masm.setupUnalignedABICall(2, rax);
     masm.passABIArg(r8);
@@ -469,38 +476,72 @@ GenerateBailoutThunk(JSContext *cx, Macr
     // Stack is:
     //     [frame]
     //     snapshotOffset
     //     frameSize
     //     [bailoutFrame]
     //
     // Remove both the bailout frame and the topmost Ion frame's stack.
     static const uint32_t BailoutDataSize = sizeof(void *) * Registers::Total +
-                                          sizeof(double) * FloatRegisters::Total;
+                                            sizeof(double) * FloatRegisters::Total;
     masm.addq(Imm32(BailoutDataSize), rsp);
     masm.pop(rcx);
     masm.lea(Operand(rsp, rcx, TimesOne, sizeof(void *)), rsp);
 
     // Jump to shared bailout tail. The BailoutInfo pointer has to be in r9.
     JitCode *bailoutTail = cx->runtime()->jitRuntime()->getBailoutTail();
     masm.jmp(bailoutTail);
 }
 
+static void
+GenerateParallelBailoutThunk(MacroAssembler &masm)
+{
+    // As GenerateBailoutThunk, except we return an error immediately. We do
+    // the bailout dance so that we can walk the stack and have accurate
+    // reporting of frame information.
+
+    PushBailoutFrame(masm, r8);
+
+    // Parallel bailout is like parallel failure in that we unwind all the way
+    // to the entry frame. Reserve space for the frame pointer of the entry frame.
+    masm.reserveStack(sizeof(uint8_t *));
+    masm.movePtr(rsp, r9);
+
+    masm.setupUnalignedABICall(2, rax);
+    masm.passABIArg(r8);
+    masm.passABIArg(r9);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, BailoutPar));
+
+    // Get the frame pointer of the entry frame and return.
+    masm.moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand);
+    masm.loadPtr(Address(rsp, 0), rsp);
+    masm.ret();
+}
+
 JitCode *
 JitRuntime::generateBailoutTable(JSContext *cx, uint32_t frameClass)
 {
     MOZ_ASSUME_UNREACHABLE("x64 does not use bailout tables");
 }
 
 JitCode *
-JitRuntime::generateBailoutHandler(JSContext *cx)
+JitRuntime::generateBailoutHandler(JSContext *cx, ExecutionMode mode)
 {
     MacroAssembler masm;
 
-    GenerateBailoutThunk(cx, masm, NO_FRAME_SIZE_CLASS_ID);
+    switch (mode) {
+      case SequentialExecution:
+        GenerateBailoutThunk(cx, masm, NO_FRAME_SIZE_CLASS_ID);
+        break;
+      case ParallelExecution:
+        GenerateParallelBailoutThunk(masm);
+        break;
+      default:
+        MOZ_ASSUME_UNREACHABLE("No such execution mode");
+    }
 
     Linker linker(masm);
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "BailoutHandler");
 #endif
 
--- a/js/src/jit/x86/Trampoline-x86.cpp
+++ b/js/src/jit/x86/Trampoline-x86.cpp
@@ -11,16 +11,17 @@
 #include "jit/BaselineJIT.h"
 #include "jit/IonFrames.h"
 #include "jit/IonLinker.h"
 #include "jit/IonSpewer.h"
 #include "jit/JitCompartment.h"
 #ifdef JS_ION_PERF
 # include "jit/PerfSpewer.h"
 #endif
+#include "jit/ParallelFunctions.h"
 #include "jit/VMFunctions.h"
 #include "jit/x86/BaselineHelpers-x86.h"
 
 #include "jsscriptinlines.h"
 
 #include "jit/ExecutionMode-inl.h"
 
 using namespace js;
@@ -450,26 +451,32 @@ JitRuntime::generateArgumentsRectifier(J
     CodeOffsetLabel returnLabel(returnOffset);
     returnLabel.fixup(&masm);
     if (returnAddrOut)
         *returnAddrOut = (void *) (code->raw() + returnLabel.offset());
     return code;
 }
 
 static void
-GenerateBailoutThunk(JSContext *cx, MacroAssembler &masm, uint32_t frameClass)
+PushBailoutFrame(MacroAssembler &masm, uint32_t frameClass, Register spArg)
 {
     // Push registers such that we can access them from [base + code].
     masm.PushRegsInMask(AllRegs);
 
     // Push the bailout table number.
     masm.push(Imm32(frameClass));
 
     // The current stack pointer is the first argument to jit::Bailout.
-    masm.movl(esp, eax);
+    masm.movl(esp, spArg);
+}
+
+static void
+GenerateBailoutThunk(JSContext *cx, MacroAssembler &masm, uint32_t frameClass)
+{
+    PushBailoutFrame(masm, frameClass, eax);
 
     // Make space for Bailout's baioutInfo outparam.
     masm.reserveStack(sizeof(void *));
     masm.movl(esp, ebx);
 
     // Call the bailout function. This will correct the size of the bailout.
     masm.setupUnalignedABICall(2, ecx);
     masm.passABIArg(eax);
@@ -503,16 +510,41 @@ GenerateBailoutThunk(JSContext *cx, Macr
         masm.addl(Imm32(BailoutDataSize + sizeof(void *) + frameSize), esp);
     }
 
     // Jump to shared bailout tail. The BailoutInfo pointer has to be in ecx.
     JitCode *bailoutTail = cx->runtime()->jitRuntime()->getBailoutTail();
     masm.jmp(bailoutTail);
 }
 
+static void
+GenerateParallelBailoutThunk(MacroAssembler &masm, uint32_t frameClass)
+{
+    // As GenerateBailoutThunk, except we return an error immediately. We do
+    // the bailout dance so that we can walk the stack and have accurate
+    // reporting of frame information.
+
+    PushBailoutFrame(masm, frameClass, eax);
+
+    // Parallel bailout is like parallel failure in that we unwind all the way
+    // to the entry frame. Reserve space for the frame pointer of the entry frame.
+    masm.reserveStack(sizeof(uint8_t *));
+    masm.movePtr(esp, ebx);
+
+    masm.setupUnalignedABICall(2, ecx);
+    masm.passABIArg(eax);
+    masm.passABIArg(ebx);
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, BailoutPar));
+
+    // Get the frame pointer of the entry frame and return.
+    masm.moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand);
+    masm.loadPtr(Address(esp, 0), esp);
+    masm.ret();
+}
+
 JitCode *
 JitRuntime::generateBailoutTable(JSContext *cx, uint32_t frameClass)
 {
     MacroAssembler masm;
 
     Label bailout;
     for (size_t i = 0; i < BAILOUT_TABLE_SIZE; i++)
         masm.call(&bailout);
@@ -526,21 +558,30 @@ JitRuntime::generateBailoutTable(JSConte
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "BailoutHandler");
 #endif
 
     return code;
 }
 
 JitCode *
-JitRuntime::generateBailoutHandler(JSContext *cx)
+JitRuntime::generateBailoutHandler(JSContext *cx, ExecutionMode mode)
 {
     MacroAssembler masm;
 
-    GenerateBailoutThunk(cx, masm, NO_FRAME_SIZE_CLASS_ID);
+    switch (mode) {
+      case SequentialExecution:
+        GenerateBailoutThunk(cx, masm, NO_FRAME_SIZE_CLASS_ID);
+        break;
+      case ParallelExecution:
+        GenerateParallelBailoutThunk(masm, NO_FRAME_SIZE_CLASS_ID);
+        break;
+      default:
+        MOZ_ASSUME_UNREACHABLE("No such execution mode");
+    }
 
     Linker linker(masm);
     JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE);
 
 #ifdef JS_ION_PERF
     writePerfSpewerJitCodeProfile(code, "BailoutHandler");
 #endif
 
--- a/js/src/vm/ForkJoin.cpp
+++ b/js/src/vm/ForkJoin.cpp
@@ -22,16 +22,17 @@
 
 #ifdef JS_THREADSAFE
 # include "jit/BaselineJIT.h"
 # include "vm/Monitor.h"
 #endif
 
 #if defined(JS_THREADSAFE) && defined(JS_ION)
 # include "jit/JitCommon.h"
+# include "jit/RematerializedFrame.h"
 # ifdef FORKJOIN_SPEW
 #  include "jit/Ion.h"
 #  include "jit/JitCompartment.h"
 #  include "jit/MIR.h"
 #  include "jit/MIRGraph.h"
 # endif
 #endif // THREADSAFE && ION
 
@@ -112,36 +113,23 @@ ForkJoinContext::requestZoneGC(JS::Zone 
 bool
 ForkJoinContext::setPendingAbortFatal(ParallelBailoutCause cause)
 {
     MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build");
     return false;
 }
 
 void
-ParallelBailoutRecord::setCause(ParallelBailoutCause cause,
-                                JSScript *outermostScript,
-                                JSScript *currentScript,
-                                jsbytecode *currentPc)
+ParallelBailoutRecord::rematerializeFrames(ForkJoinContext *cx, JitFrameIterator &frameIter)
 {
     MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build");
 }
 
 void
-js::ParallelBailoutRecord::updateCause(ParallelBailoutCause cause,
-                                       JSScript *outermostScript,
-                                       JSScript *currentScript,
-                                       jsbytecode *currentPc)
-{
-    MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build");
-}
-
-void
-ParallelBailoutRecord::addTrace(JSScript *script,
-                                jsbytecode *pc)
+ParallelBailoutRecord::rematerializeFrames(ForkJoinContext *cx, IonBailoutIterator &frameIter)
 {
     MOZ_ASSUME_UNREACHABLE("Not THREADSAFE build");
 }
 
 bool
 js::InExclusiveParallelSection()
 {
     return false;
@@ -269,21 +257,16 @@ enum ForkJoinMode {
 
 class ForkJoinOperation
 {
   public:
     // For tests, make sure to keep this in sync with minItemsTestingThreshold.
     static const uint32_t MAX_BAILOUTS = 3;
     uint32_t bailouts;
 
-    // Information about the bailout:
-    ParallelBailoutCause bailoutCause;
-    RootedScript bailoutScript;
-    jsbytecode *bailoutBytecode;
-
     ForkJoinOperation(JSContext *cx, HandleFunction fun, uint16_t sliceStart,
                       uint16_t sliceEnd, ForkJoinMode mode, HandleObject updatable);
     ExecutionStatus apply();
 
   private:
     // Most of the functions involved in managing the parallel
     // compilation follow a similar control-flow. They return RedLight
     // if they have either encountered a fatal error or completed the
@@ -330,17 +313,19 @@ class ForkJoinOperation
     TrafficLight enqueueInitialScript(ExecutionStatus *status);
     TrafficLight compileForParallelExecution(ExecutionStatus *status);
     TrafficLight warmupExecution(bool stopIfComplete, ExecutionStatus *status);
     TrafficLight parallelExecution(ExecutionStatus *status);
     TrafficLight sequentialExecution(bool disqualified, ExecutionStatus *status);
     TrafficLight recoverFromBailout(ExecutionStatus *status);
     TrafficLight fatalError(ExecutionStatus *status);
     bool isInitialScript(HandleScript script);
-    void determineBailoutCause();
+    void getBailoutScriptAndPc(MutableHandleScript bailoutScript, jsbytecode **bailoutPc,
+                               ParallelBailoutCause *bailoutCause);
+    bool reportBailoutWarnings();
     bool invalidateBailedOutScripts();
     ExecutionStatus sequentialExecution(bool disqualified);
 
     TrafficLight appendCallTargetsToWorklist(uint32_t index, ExecutionStatus *status);
     TrafficLight appendCallTargetToWorklist(HandleScript script, ExecutionStatus *status);
     bool addToWorklist(HandleScript script);
     inline bool hasScript(Vector<types::RecompileInfo> &scripts, JSScript *script);
 }; // class ForkJoinOperation
@@ -587,19 +572,16 @@ ForkJoinModeString(ForkJoinMode mode) {
       case NumForkJoinModes: return "max";
     }
     return "???";
 }
 
 ForkJoinOperation::ForkJoinOperation(JSContext *cx, HandleFunction fun, uint16_t sliceStart,
                                      uint16_t sliceEnd, ForkJoinMode mode, HandleObject updatable)
   : bailouts(0),
-    bailoutCause(ParallelBailoutNone),
-    bailoutScript(cx),
-    bailoutBytecode(nullptr),
     cx_(cx),
     fun_(fun),
     updatable_(updatable),
     sliceStart_(sliceStart),
     sliceEnd_(sliceEnd),
     bailoutRecords_(cx),
     worklist_(cx),
     worklistData_(cx),
@@ -640,18 +622,20 @@ ForkJoinOperation::apply()
     SpewBeginOp(cx_, "ForkJoinOperation");
 
     // How many workers do we have, counting the main thread.
     unsigned numWorkers = cx_->runtime()->threadPool.numWorkers();
 
     if (!bailoutRecords_.resize(numWorkers))
         return SpewEndOp(ExecutionFatal);
 
-    for (uint32_t i = 0; i < numWorkers; i++)
-        bailoutRecords_[i].init(cx_);
+    for (uint32_t i = 0; i < numWorkers; i++) {
+        if (!bailoutRecords_[i].init(cx_))
+            return SpewEndOp(ExecutionFatal);
+    }
 
     if (enqueueInitialScript(&status) == RedLight)
         return SpewEndOp(status);
 
     Spew(SpewOps, "Execution mode: %s", ForkJoinModeString(mode_));
     switch (mode_) {
       case ForkJoinModeNormal:
       case ForkJoinModeCompile:
@@ -671,17 +655,17 @@ ForkJoinOperation::apply()
         break;
 
       case NumForkJoinModes:
         MOZ_ASSUME_UNREACHABLE("Invalid mode");
     }
 
     while (bailouts < MAX_BAILOUTS) {
         for (uint32_t i = 0; i < numWorkers; i++)
-            bailoutRecords_[i].reset(cx_);
+            bailoutRecords_[i].reset();
 
         if (compileForParallelExecution(&status) == RedLight)
             return SpewEndOp(status);
 
         JS_ASSERT(worklist_.length() == 0);
         if (parallelExecution(&status) == RedLight)
             return SpewEndOp(status);
 
@@ -1098,96 +1082,195 @@ BailoutExplanation(ParallelBailoutCause 
 
 bool
 ForkJoinOperation::isInitialScript(HandleScript script)
 {
     return fun_->is<JSFunction>() && (fun_->as<JSFunction>().nonLazyScript() == script);
 }
 
 void
-ForkJoinOperation::determineBailoutCause()
+ForkJoinOperation::getBailoutScriptAndPc(MutableHandleScript bailoutScript, jsbytecode **bailoutPc,
+                                         ParallelBailoutCause *bailoutCause)
 {
-    bailoutCause = ParallelBailoutNone;
     for (uint32_t i = 0; i < bailoutRecords_.length(); i++) {
-        if (bailoutRecords_[i].cause == ParallelBailoutNone)
+        switch (bailoutRecords_[i].cause) {
+          case ParallelBailoutNone:
+          case ParallelBailoutInterrupt:
             continue;
+          default:
+            break;
+        }
 
-        if (bailoutRecords_[i].cause == ParallelBailoutInterrupt)
+        if (bailoutRecords_[i].hasFrames()) {
+            RematerializedFrame *frame = bailoutRecords_[i].frames()[0];
+            bailoutScript.set(frame->script());
+            *bailoutPc = frame->pc();
+            *bailoutCause = bailoutRecords_[i].cause;
+            return;
+        }
+    }
+}
+
+static const char *
+ValueToChar(JSContext *cx, HandleValue val, JSAutoByteString &bytes)
+{
+    if (val.isMagic()) {
+        switch (val.whyMagic()) {
+          case JS_OPTIMIZED_OUT: return "<optimized out>";
+          default: return "<unknown magic?>";
+        }
+    }
+
+    RootedString str(cx, ToString<CanGC>(cx, val));
+    if (!str)
+        return nullptr;
+    return bytes.encodeUtf8(cx, str);
+}
+
+bool
+ForkJoinOperation::reportBailoutWarnings()
+{
+    Sprinter sp(cx_);
+    if (SpewEnabled(SpewBailouts)) {
+        sp.init();
+        sp.printf("Bailed out of parallel operation");
+    }
+
+    for (uint32_t threadId = 0; threadId < bailoutRecords_.length(); threadId++) {
+        ParallelBailoutCause cause = bailoutRecords_[threadId].cause;
+        if (cause == ParallelBailoutNone)
             continue;
 
-        bailoutCause = bailoutRecords_[i].cause;
-        const char *causeStr = BailoutExplanation(bailoutCause);
-        if (bailoutRecords_[i].depth) {
-            bailoutScript = bailoutRecords_[i].trace[0].script;
-            bailoutBytecode = bailoutRecords_[i].trace[0].bytecode;
+        if (bailoutRecords_[threadId].hasFrames()) {
+            Vector<RematerializedFrame *> &frames = bailoutRecords_[threadId].frames();
+
+            if (!SpewEnabled(SpewBailouts)) {
+                RematerializedFrame *frame = frames[0];
+                RootedScript bailoutScript(cx_, frame->script());
+                SpewBailout(bailouts, bailoutScript, frame->pc(), cause);
+                JS_ReportWarning(cx_, "Bailed out of parallel operation: %s at %s:%u",
+                                 BailoutExplanation(cause), bailoutScript->filename(),
+                                 PCToLineNumber(bailoutScript, frame->pc()));
+                return true;
+            }
+
+            sp.printf("\n  in thread %d due to %s", threadId, BailoutExplanation(cause));
 
-            const char *filename = bailoutScript->filename();
-            int line = JS_PCToLineNumber(cx_, bailoutScript, bailoutBytecode);
-            JS_ReportWarning(cx_, "Bailed out of parallel operation: %s at %s:%d",
-                             causeStr, filename, line);
+            for (uint32_t frameIndex = 0; frameIndex < frames.length(); frameIndex++) {
+                RematerializedFrame *frame = frames[frameIndex];
+                RootedScript script(cx_, frame->script());
+
+                // Format the frame's location.
+                sp.printf("\n    at ");
+                if (JSFunction *fun = frame->maybeFun()) {
+                    if (fun->displayAtom()) {
+                        JSAutoByteString displayBytes;
+                        RootedString displayAtom(cx_, fun->displayAtom());
+                        const char *displayChars = displayBytes.encodeUtf8(cx_, displayAtom);
+                        if (!displayChars)
+                            return false;
+                        sp.printf("%s", displayChars);
+                    } else {
+                        sp.printf("<anonymous>");
+                    }
+                } else {
+                    sp.printf("<global>");
+                }
+                sp.printf(" (%s:%u)", script->filename(), PCToLineNumber(script, frame->pc()));
 
-            Spew(SpewBailouts, "Bailout from thread %d: cause %d at loc %s:%d",
-                 i,
-                 bailoutCause,
-                 bailoutScript->filename(),
-                 PCToLineNumber(bailoutScript, bailoutBytecode));
-        } else {
-            JS_ReportWarning(cx_, "Bailed out of parallel operation: %s",
-                             causeStr);
+                // Format bindings.
+                BindingVector bindings(cx_);
+                if (!FillBindingVector(script, &bindings))
+                    return false;
+
+                unsigned scopeSlot = 0;
+                for (unsigned i = 0; i < bindings.length(); i++) {
+                    JSAutoByteString nameBytes;
+                    const char *nameChars = nullptr;
+                    RootedPropertyName bindingName(cx_, bindings[i].name());
+                    nameChars = nameBytes.encodeUtf8(cx_, bindingName);
+                    if (!nameChars)
+                        return false;
 
-            Spew(SpewBailouts, "Bailout from thread %d: cause %d, unknown loc",
-                 i,
-                 bailoutCause);
+                    RootedValue arg(cx_);
+                    if (bindings[i].aliased()) {
+                        arg = frame->callObj().getSlot(scopeSlot);
+                        scopeSlot++;
+                    } else if (i < frame->numFormalArgs()) {
+                        if (script->argsObjAliasesFormals() && frame->hasArgsObj())
+                            arg = frame->argsObj().arg(i);
+                        else
+                            arg = frame->unaliasedActual(i, DONT_CHECK_ALIASING);
+                    } else {
+                        arg = frame->unaliasedLocal(i - frame->numFormalArgs(), DONT_CHECK_ALIASING);
+                    }
+
+                    JSAutoByteString valueBytes;
+                    const char *valueChars = ValueToChar(cx_, arg, valueBytes);
+                    if (!valueChars)
+                        return false;
+
+                    sp.printf("\n      %s %s = %s",
+                              bindings[i].kind() == Binding::ARGUMENT ? "arg" : "var",
+                              nameChars, valueChars);
+                }
+            }
         }
     }
+
+    if (SpewEnabled(SpewBailouts))
+        JS_ReportWarning(cx_, "%s", sp.string());
+
+    return true;
 }
 
 bool
 ForkJoinOperation::invalidateBailedOutScripts()
 {
     Vector<types::RecompileInfo> invalid(cx_);
     for (uint32_t i = 0; i < bailoutRecords_.length(); i++) {
-        RootedScript script(cx_, bailoutRecords_[i].topScript);
+        switch (bailoutRecords_[i].cause) {
+          // No bailout.
+          case ParallelBailoutNone:
+            continue;
 
-        // No script to invalidate.
-        if (!script || !script->hasParallelIonScript())
+          // An interrupt is not the fault of the script, so don't
+          // invalidate it.
+          case ParallelBailoutInterrupt:
+            continue;
+
+          // An illegal write will not be made legal by invalidation.
+          case ParallelBailoutIllegalWrite:
             continue;
 
-        Spew(SpewBailouts,
-             "Bailout from thread %d: cause %d, topScript %p:%s:%d",
-             i,
-             bailoutRecords_[i].cause,
-             script.get(), script->filename(), script->lineno());
-
-        switch (bailoutRecords_[i].cause) {
-          // An interrupt is not the fault of the script, so don't
-          // invalidate it.
-          case ParallelBailoutInterrupt: continue;
-
-          // An illegal write will not be made legal by invalidation.
-          case ParallelBailoutIllegalWrite: continue;
-
           // For other cases, consider invalidation.
-          default: break;
+          default:
+            break;
         }
 
+        if (!bailoutRecords_[i].hasFrames())
+            continue;
+
+        // Get the script of the youngest frame.
+        RootedScript script(cx_, bailoutRecords_[i].frames()[0]->script());
+
         // Already invalidated.
-        if (hasScript(invalid, script))
+        if (!script->hasParallelIonScript() || hasScript(invalid, script))
             continue;
 
         Spew(SpewBailouts, "Invalidating script %p:%s:%d due to cause %d",
              script.get(), script->filename(), script->lineno(),
              bailoutRecords_[i].cause);
 
         types::RecompileInfo co = script->parallelIonScript()->recompileInfo();
 
         if (!invalid.append(co))
             return false;
 
-        // any script that we have marked for invalidation will need
+        // Any script that we have marked for invalidation will need
         // to be recompiled
         if (!addToWorklist(script))
             return false;
     }
 
     Invalidate(cx_, invalid);
 
     return true;
@@ -1276,19 +1359,19 @@ ForkJoinOperation::parallelExecution(Exe
 
 ForkJoinOperation::TrafficLight
 ForkJoinOperation::recoverFromBailout(ExecutionStatus *status)
 {
     // GreenLight: bailout recovered, try to compile-and-run again
     // RedLight: fatal error
 
     bailouts += 1;
-    determineBailoutCause();
 
-    SpewBailout(bailouts, bailoutScript, bailoutBytecode, bailoutCause);
+    if (!reportBailoutWarnings())
+        return fatalError(status);
 
     // After any bailout, we always scan over callee list of main
     // function, if nothing else
     RootedScript mainScript(cx_, fun_->nonLazyScript());
     if (!addToWorklist(mainScript))
         return fatalError(status);
 
     // Also invalidate and recompile any callees that were implicated
@@ -1342,18 +1425,17 @@ class ParallelIonInvoke
         JitCode *code = ion->method();
         jitcode_ = code->raw();
         enter_ = rt->jitRuntime()->enterIon();
         calleeToken_ = CalleeToToken(callee);
     }
 
     bool invoke(ForkJoinContext *cx) {
         JitActivation activation(cx);
-        // In-out parameter: on input it denotes the number of values to preserve after the call.
-        Value result = Int32Value(0);
+        Value result = Int32Value(argc_);
         CALL_GENERATED_CODE(enter_, jitcode_, argc_ + 1, argv_ + 1, nullptr, calleeToken_,
                             nullptr, 0, &result);
         return !result.isMagic();
     }
 };
 
 /////////////////////////////////////////////////////////////////////////////
 // ForkJoinShared
@@ -1561,35 +1643,35 @@ ForkJoinShared::executePortion(PerThread
     Spew(SpewOps, "Up");
 
     // Make a new IonContext for the slice, which is needed if we need to
     // re-enter the VM.
     IonContext icx(CompileRuntime::get(cx_->runtime()),
                    CompileCompartment::get(cx_->compartment()),
                    nullptr);
 
-    JS_ASSERT(cx.bailoutRecord->topScript == nullptr);
+    JS_ASSERT(!cx.bailoutRecord->bailedOut());
 
     if (!fun_->nonLazyScript()->hasParallelIonScript()) {
         // Sometimes, particularly with GCZeal, the parallel ion
         // script can be collected between starting the parallel
         // op and reaching this point.  In that case, we just fail
         // and fallback.
         Spew(SpewOps, "Down (Script no longer present)");
-        cx.bailoutRecord->setCause(ParallelBailoutMainScriptNotPresent);
+        cx.bailoutRecord->joinCause(ParallelBailoutMainScriptNotPresent);
         setAbortFlagAndRequestInterrupt(false);
     } else {
         ParallelIonInvoke<3> fii(runtime(), fun_, 3);
 
         fii.args[0] = Int32Value(worker->id());
         fii.args[1] = Int32Value(sliceStart_);
         fii.args[2] = Int32Value(sliceEnd_);
 
         bool ok = fii.invoke(&cx);
-        JS_ASSERT(ok == !cx.bailoutRecord->topScript);
+        JS_ASSERT(ok == !cx.bailoutRecord->bailedOut());
         if (!ok) {
             setAbortFlagAndRequestInterrupt(false);
 #ifdef JSGC_FJGENERATIONAL
             // TODO: See bugs 1010169, 993347.
             //
             // It is not desirable to promote here, but if we don't do
             // this then we can't unconditionally transfer arenas to
             // the compartment, since the arenas can contain objects
@@ -1625,17 +1707,17 @@ ForkJoinShared::setAbortFlagDueToInterru
 {
     JS_ASSERT(cx_->runtime()->interruptPar);
     // 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()->gc.isNeeded);
 
     if (!abort_) {
-        cx.bailoutRecord->setCause(ParallelBailoutInterrupt);
+        cx.bailoutRecord->joinCause(ParallelBailoutInterrupt);
         setAbortFlagAndRequestInterrupt(false);
     }
 }
 
 void
 ForkJoinShared::setAbortFlagAndRequestInterrupt(bool fatal)
 {
     AutoLockMonitor lock(*this);
@@ -1799,99 +1881,108 @@ ForkJoinContext::check()
     }
     return true;
 }
 
 void
 ForkJoinContext::requestGC(JS::gcreason::Reason reason)
 {
     shared_->requestGC(reason);
-    bailoutRecord->setCause(ParallelBailoutRequestedGC);
+    bailoutRecord->joinCause(ParallelBailoutRequestedGC);
     shared_->setAbortFlagAndRequestInterrupt(false);
 }
 
 void
 ForkJoinContext::requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason)
 {
     shared_->requestZoneGC(zone, reason);
-    bailoutRecord->setCause(ParallelBailoutRequestedZoneGC);
+    bailoutRecord->joinCause(ParallelBailoutRequestedZoneGC);
     shared_->setAbortFlagAndRequestInterrupt(false);
 }
 
 bool
 ForkJoinContext::setPendingAbortFatal(ParallelBailoutCause cause)
 {
     shared_->setPendingAbortFatal();
-    bailoutRecord->setCause(cause);
+    bailoutRecord->joinCause(cause);
     return false;
 }
 
 //////////////////////////////////////////////////////////////////////////////
 // ParallelBailoutRecord
 
-void
-js::ParallelBailoutRecord::init(JSContext *cx)
+ParallelBailoutRecord::~ParallelBailoutRecord()
 {
-    reset(cx);
+    reset();
+    js_delete(frames_);
 }
 
-void
-js::ParallelBailoutRecord::reset(JSContext *cx)
+bool
+ParallelBailoutRecord::init(JSContext *cx)
 {
-    topScript = nullptr;
-    cause = ParallelBailoutNone;
-    depth = 0;
-}
-
-void
-js::ParallelBailoutRecord::setCause(ParallelBailoutCause cause,
-                                    JSScript *outermostScript,
-                                    JSScript *currentScript,
-                                    jsbytecode *currentPc)
-{
-    this->cause = cause;
-    updateCause(cause, outermostScript, currentScript, currentPc);
+    MOZ_ASSERT(!frames_);
+    frames_ = cx->new_<Vector<RematerializedFrame *> >(cx);
+    return !!frames_;
 }
 
 void
-js::ParallelBailoutRecord::updateCause(ParallelBailoutCause cause,
-                                       JSScript *outermostScript,
-                                       JSScript *currentScript,
-                                       jsbytecode *currentPc)
+ParallelBailoutRecord::reset()
+{
+    RematerializedFrame::FreeInVector(frames());
+    cause = ParallelBailoutNone;
+}
+
+template <class T>
+static void
+RematerializeFramesWithIter(ForkJoinContext *cx, T &frameIter,
+                            Vector<RematerializedFrame *> &frames)
 {
-    JS_ASSERT_IF(outermostScript, currentScript);
-    JS_ASSERT_IF(outermostScript, outermostScript->hasParallelIonScript());
-    JS_ASSERT_IF(currentScript, outermostScript);
-    JS_ASSERT_IF(!currentScript, !currentPc);
+    // This function as well as |rematerializeFrames| methods below are
+    // infallible. These are only called when we are already erroring out. If
+    // we OOM here, free what we've allocated and return. Error reporting is
+    // then unable to give the user detailed stack information.
+
+    MOZ_ASSERT(frames.empty());
+
+    for (; !frameIter.done(); ++frameIter) {
+        if (!frameIter.isIonJS())
+            continue;
+
+        InlineFrameIterator inlineIter(cx, &frameIter);
+        Vector<RematerializedFrame *> inlineFrames(cx);
 
-    if (this->cause == ParallelBailoutNone)
-        this->cause = cause;
+        if (!RematerializedFrame::RematerializeInlineFrames(cx, frameIter.fp(),
+                                                            inlineIter, inlineFrames))
+        {
+            RematerializedFrame::FreeInVector(inlineFrames);
+            RematerializedFrame::FreeInVector(frames);
+            return;
+        }
 
-    if (outermostScript)
-        this->topScript = outermostScript;
-
-    if (currentScript)
-        addTrace(currentScript, currentPc);
+        // Reverse the inline frames into the main vector.
+        while (!inlineFrames.empty()) {
+            if (!frames.append(inlineFrames.popCopy())) {
+                RematerializedFrame::FreeInVector(inlineFrames);
+                RematerializedFrame::FreeInVector(frames);
+                return;
+            }
+        }
+    }
 }
 
 void
-js::ParallelBailoutRecord::addTrace(JSScript *script,
-                                    jsbytecode *pc)
+ParallelBailoutRecord::rematerializeFrames(ForkJoinContext *cx, JitFrameIterator &frameIter)
 {
-    // Ideally, this should never occur, because we should always have
-    // a script when we invoke setCause, but I havent' fully
-    // refactored things to that point yet:
-    if (topScript == nullptr && script != nullptr)
-        topScript = script;
+    RematerializeFramesWithIter(cx, frameIter, frames());
+}
 
-    if (depth < MaxDepth) {
-        trace[depth].script = script;
-        trace[depth].bytecode = pc;
-        depth += 1;
-    }
+void
+ParallelBailoutRecord::rematerializeFrames(ForkJoinContext *cx, IonBailoutIterator &frameIter)
+{
+    RematerializeFramesWithIter(cx, frameIter, frames());
 }
 
 //////////////////////////////////////////////////////////////////////////////
 
 //
 // Debug spew
 //
 
@@ -2155,31 +2246,16 @@ class ParallelSpewer
 
         char buf[BufferSize];
         JS_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(IonLIRTraceData *data) {
-        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 (data->mirOpName && data->script) {
-            spew(SpewBailouts, "%sBailout%s: %s / %s%s%s (block %d lir %d) (%s:%u)", yellow(), reset(),
-                 data->lirOpName, cyan(), data->mirOpName, reset(),
-                 data->blockIndex, data->lirIndex, data->script->filename(),
-                 PCToLineNumber(data->script, data->pc));
-        }
-    }
 };
 
 // Singleton instance of the spewer.
 static ParallelSpewer spewer;
 
 bool
 parallel::SpewEnabled(SpewChannel channel)
 {
@@ -2238,22 +2314,16 @@ 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(IonLIRTraceData *data)
-{
-    spewer.spewBailoutIR(data);
-}
-
 #endif // FORKJOIN_SPEW
 
 bool
 js::InExclusiveParallelSection()
 {
     return InParallelSection() && ForkJoinContext::current()->hasAcquiredJSContext();
 }
 
--- a/js/src/vm/ForkJoin.h
+++ b/js/src/vm/ForkJoin.h
@@ -267,103 +267,116 @@ class ForkJoinActivation : public Activa
     explicit ForkJoinActivation(JSContext *cx);
     ~ForkJoinActivation();
 };
 
 class ForkJoinContext;
 
 bool ForkJoin(JSContext *cx, CallArgs &args);
 
-struct IonLIRTraceData {
-    uint32_t blockIndex;
-    uint32_t lirIndex;
-    uint32_t execModeInt;
-    const char *lirOpName;
-    const char *mirOpName;
-    JSScript *script;
-    jsbytecode *pc;
-};
-
 ///////////////////////////////////////////////////////////////////////////
 // Bailout tracking
 
+//
+// The lattice of causes goes:
+//
+//       { everything else }
+//               |
+//           Interrupt
+//           /       \
+//   Unsupported   UnsupportedVM
+//           \       /
+//              None
+//
 enum ParallelBailoutCause {
-    ParallelBailoutNone,
+    ParallelBailoutNone = 0,
+
+    ParallelBailoutUnsupported,
+    ParallelBailoutUnsupportedVM,
+
+    // The periodic interrupt failed, which can mean that either
+    // another thread canceled, the user interrupted us, etc
+    ParallelBailoutInterrupt,
 
     // Compiler returned Method_Skipped
     ParallelBailoutCompilationSkipped,
 
     // Compiler returned Method_CantCompile
     ParallelBailoutCompilationFailure,
 
-    // The periodic interrupt failed, which can mean that either
-    // another thread canceled, the user interrupted us, etc
-    ParallelBailoutInterrupt,
+    // Propagating a failure, i.e., another thread requested the computation
+    // be aborted.
+    ParallelBailoutPropagate,
 
     // An IC update failed
     ParallelBailoutFailedIC,
 
     // Heap busy flag was set during interrupt
     ParallelBailoutHeapBusy,
 
     ParallelBailoutMainScriptNotPresent,
     ParallelBailoutCalledToUncompiledScript,
     ParallelBailoutIllegalWrite,
     ParallelBailoutAccessToIntrinsic,
     ParallelBailoutOverRecursed,
     ParallelBailoutOutOfMemory,
-    ParallelBailoutUnsupported,
-    ParallelBailoutUnsupportedVM,
     ParallelBailoutUnsupportedStringComparison,
     ParallelBailoutRequestedGC,
-    ParallelBailoutRequestedZoneGC,
+    ParallelBailoutRequestedZoneGC
 };
 
-struct ParallelBailoutTrace {
-    JSScript *script;
-    jsbytecode *bytecode;
-};
+namespace jit {
+class BailoutStack;
+class JitFrameIterator;
+class RematerializedFrame;
+}
 
 // See "Bailouts" section in comment above.
-struct ParallelBailoutRecord {
-    JSScript *topScript;
+struct ParallelBailoutRecord
+{
+    // Captured Ion frames at the point of bailout. Stored younger-to-older,
+    // i.e., the 0th frame is the youngest frame.
+    Vector<jit::RematerializedFrame *> *frames_;
     ParallelBailoutCause cause;
 
-    // Eventually we will support deeper traces,
-    // but for now we gather at most a single frame.
-    static const uint32_t MaxDepth = 1;
-    uint32_t depth;
-    ParallelBailoutTrace trace[MaxDepth];
+    ParallelBailoutRecord()
+      : frames_(nullptr),
+        cause(ParallelBailoutNone)
+    { }
+
+    ~ParallelBailoutRecord();
+
+    bool init(JSContext *cx);
+    void reset();
 
-    void init(JSContext *cx);
-    void reset(JSContext *cx);
-    void setCause(ParallelBailoutCause cause,
-                  JSScript *outermostScript = nullptr,   // inliner (if applicable)
-                  JSScript *currentScript = nullptr,     // inlinee (if applicable)
-                  jsbytecode *currentPc = nullptr);
-    void updateCause(ParallelBailoutCause cause,
-                     JSScript *outermostScript,
-                     JSScript *currentScript,
-                     jsbytecode *currentPc);
-    void addTrace(JSScript *script,
-                  jsbytecode *pc);
+    Vector<jit::RematerializedFrame *> &frames() { MOZ_ASSERT(frames_); return *frames_; }
+    bool hasFrames() const { return frames_ && !frames_->empty(); }
+    bool bailedOut() const { return cause != ParallelBailoutNone; }
+
+    void joinCause(ParallelBailoutCause cause) {
+        if (this->cause <= ParallelBailoutInterrupt &&
+            (cause > ParallelBailoutInterrupt || cause > this->cause))
+        {
+            this->cause = cause;
+        }
+    }
+
+    void rematerializeFrames(ForkJoinContext *cx, jit::JitFrameIterator &frameIter);
+    void rematerializeFrames(ForkJoinContext *cx, jit::IonBailoutIterator &frameIter);
 };
 
 class ForkJoinShared;
 
 class ForkJoinContext : public ThreadSafeContext
 {
   public:
     // Bailout record used to record the reason this thread stopped executing
     ParallelBailoutRecord *const bailoutRecord;
 
 #ifdef FORKJOIN_SPEW
-    // Records the last instr. to execute on this thread.
-    IonLIRTraceData traceData;
-
     // The maximum worker id.
     uint32_t maxWorkerId;
 #endif
 
     // When we run a par operation like mapPar, we create an out pointer
     // into a specific region of the destination buffer. Even though the
     // destination buffer is not thread-local, it is permissible to write into
     // it via the handles provided. These two fields identify the memory
@@ -574,33 +587,31 @@ void Spew(SpewChannel channel, const cha
 void SpewVA(SpewChannel channel, const char *fmt, va_list args);
 void SpewBeginOp(JSContext *cx, const char *name);
 void SpewBailout(uint32_t count, HandleScript script, jsbytecode *pc,
                  ParallelBailoutCause cause);
 ExecutionStatus SpewEndOp(ExecutionStatus status);
 void SpewBeginCompile(HandleScript script);
 jit::MethodStatus SpewEndCompile(jit::MethodStatus status);
 void SpewMIR(jit::MDefinition *mir, const char *fmt, ...);
-void SpewBailoutIR(IonLIRTraceData *data);
 
 #else
 
 static inline bool SpewEnabled(SpewChannel channel) { return false; }
 static inline void Spew(SpewChannel channel, const char *fmt, ...) { }
 static inline void SpewVA(SpewChannel channel, const char *fmt, va_list args) { }
 static inline void SpewBeginOp(JSContext *cx, const char *name) { }
 static inline void SpewBailout(uint32_t count, HandleScript script,
                                jsbytecode *pc, ParallelBailoutCause cause) {}
 static inline ExecutionStatus SpewEndOp(ExecutionStatus status) { return status; }
 static inline void SpewBeginCompile(HandleScript script) { }
 #ifdef JS_ION
 static inline jit::MethodStatus SpewEndCompile(jit::MethodStatus status) { return status; }
 static inline void SpewMIR(jit::MDefinition *mir, const char *fmt, ...) { }
 #endif
-static inline void SpewBailoutIR(IonLIRTraceData *data) { }
 
 #endif // FORKJOIN_SPEW && JS_THREADSAFE && JS_ION
 
 } // namespace parallel
 } // namespace js
 
 /* static */ inline js::ForkJoinContext *
 js::ForkJoinContext::current()