Bug 949296 - Ignore DontStopIon interrupt triggers during ForkJoin. (r=nmatsakis)
authorShu-yu Guo <shu@rfrn.org>
Fri, 07 Feb 2014 14:40:31 -0800
changeset 167665 f76ccce1f2fd65666b60c988c83bc2c70b50ca6f
parent 167664 ad7777f1c0f58cdcc09d627dfbf127d29aecf215
child 167666 6c899a1064f3a8536f8b214cc6112387943c1740
push id4961
push userphilringnalda@gmail.com
push dateSun, 09 Feb 2014 03:35:40 +0000
treeherderfx-team@f9c10b1ce31e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnmatsakis
bugs949296
milestone30.0a1
Bug 949296 - Ignore DontStopIon interrupt triggers during ForkJoin. (r=nmatsakis)
js/src/jit-test/tests/parallel/Array-mapPar-nested.js
js/src/jit/BaselineJIT.cpp
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/CompileWrappers.cpp
js/src/jit/CompileWrappers.h
js/src/jit/Ion.cpp
js/src/jit/IonMacroAssembler.cpp
js/src/jit/IonMacroAssembler.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/MOpcodes.h
js/src/jit/ParallelFunctions.cpp
js/src/jit/ParallelFunctions.h
js/src/jit/ParallelSafetyAnalysis.cpp
js/src/jit/shared/CodeGenerator-shared.cpp
js/src/jscntxt.cpp
js/src/vm/ForkJoin.cpp
js/src/vm/ForkJoin.h
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
js/src/vm/SelfHosting.cpp
--- a/js/src/jit-test/tests/parallel/Array-mapPar-nested.js
+++ b/js/src/jit-test/tests/parallel/Array-mapPar-nested.js
@@ -12,11 +12,10 @@ function test() {
   for (var x = 0; x < 256; x++) {
     var pax = pa1[x];
     for (var y = 0; y < 256; y++) {
       assertEq(pax[y], x * 1000 + y);
     }
   }
 }
 
-// FIXME: Bug 949296. Broken due to all interrupt triggers aborting PJS.
-//if (getBuildConfiguration().parallelJS)
-//  test();
+if (getBuildConfiguration().parallelJS)
+  test();
--- a/js/src/jit/BaselineJIT.cpp
+++ b/js/src/jit/BaselineJIT.cpp
@@ -270,17 +270,17 @@ CanEnterBaselineJIT(JSContext *cx, Handl
     // is enabled, so that we don't have to OSR and don't have to update the
     // frame pointer stored in JSD's frames list.
     //
     // Also eagerly compile if we are in parallel warmup, the point of which
     // is to gather type information so that the script may be compiled for
     // parallel execution. We want to avoid the situation of OSRing during
     // warmup and only gathering type information for the loop, and not the
     // rest of the function.
-    if (IsJSDEnabled(cx) || cx->runtime()->parallelWarmup > 0) {
+    if (IsJSDEnabled(cx) || cx->runtime()->forkJoinWarmup > 0) {
         if (osr)
             return Method_Skipped;
     } else if (script->incUseCount() <= js_JitOptions.baselineUsesBeforeCompile) {
         return Method_Skipped;
     }
 
     if (script->isCallsiteClone()) {
         // Ensure the original function is compiled too, so that bailouts from
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -2846,17 +2846,17 @@ CodeGenerator::visitCheckOverRecursedPar
     masm.loadPtr(Address(cxReg, offsetof(ForkJoinContext, perThreadData)), tempReg);
     masm.loadPtr(Address(tempReg, offsetof(PerThreadData, ionStackLimit)), tempReg);
 
     // Conditional forward (unlikely) branch to failure.
     CheckOverRecursedFailurePar *ool = new(alloc()) CheckOverRecursedFailurePar(lir);
     if (!addOutOfLineCode(ool))
         return false;
     masm.branchPtr(Assembler::BelowOrEqual, StackPointer, tempReg, ool->entry());
-    masm.checkInterruptFlagsPar(tempReg, ool->entry());
+    masm.checkInterruptFlagPar(tempReg, ool->entry());
     masm.bind(ool->rejoin());
 
     return true;
 }
 
 bool
 CodeGenerator::visitCheckOverRecursedFailurePar(CheckOverRecursedFailurePar *ool)
 {
@@ -2881,69 +2881,64 @@ CodeGenerator::visitCheckOverRecursedFai
     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 OutOfLineCheckInterruptPar : public OutOfLineCodeBase<CodeGenerator>
+class OutOfLineInterruptCheckPar : public OutOfLineCodeBase<CodeGenerator>
 {
   public:
-    LCheckInterruptPar *const lir;
-
-    OutOfLineCheckInterruptPar(LCheckInterruptPar *lir)
+    LInterruptCheckPar *const lir;
+
+    OutOfLineInterruptCheckPar(LInterruptCheckPar *lir)
       : lir(lir)
     { }
 
     bool accept(CodeGenerator *codegen) {
-        return codegen->visitOutOfLineCheckInterruptPar(this);
+        return codegen->visitOutOfLineInterruptCheckPar(this);
     }
 };
 
 bool
-CodeGenerator::visitCheckInterruptPar(LCheckInterruptPar *lir)
+CodeGenerator::visitInterruptCheckPar(LInterruptCheckPar *lir)
 {
     // First check for cx->shared->interrupt_.
-    OutOfLineCheckInterruptPar *ool = new(alloc()) OutOfLineCheckInterruptPar(lir);
+    OutOfLineInterruptCheckPar *ool = new(alloc()) OutOfLineInterruptCheckPar(lir);
     if (!addOutOfLineCode(ool))
         return false;
 
-    // We must check two flags:
-    // - runtime->interrupt
-    // - runtime->parallelAbort
-    // See vm/ForkJoin.h for discussion on why we use this design.
-
     Register tempReg = ToRegister(lir->getTempReg());
-    masm.checkInterruptFlagsPar(tempReg, ool->entry());
+    masm.checkInterruptFlagPar(tempReg, ool->entry());
     masm.bind(ool->rejoin());
     return true;
 }
 
 bool
-CodeGenerator::visitOutOfLineCheckInterruptPar(OutOfLineCheckInterruptPar *ool)
+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():
-    LCheckInterruptPar *lir = ool->lir;
+    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 *, CheckInterruptPar));
+    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;
 }
 
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -25,17 +25,17 @@
 namespace js {
 namespace jit {
 
 class OutOfLineTestObject;
 class OutOfLineNewArray;
 class OutOfLineNewObject;
 class CheckOverRecursedFailure;
 class CheckOverRecursedFailurePar;
-class OutOfLineCheckInterruptPar;
+class OutOfLineInterruptCheckPar;
 class OutOfLineInterruptCheckImplicit;
 class OutOfLineUnboxFloatingPoint;
 class OutOfLineStoreElementHole;
 class OutOfLineTypeOfV;
 class OutOfLineLoadTypedArray;
 class OutOfLineNewGCThingPar;
 class OutOfLineUpdateCache;
 class OutOfLineCallPostWriteBarrier;
@@ -286,18 +286,18 @@ class CodeGenerator : public CodeGenerat
 
     bool visitCheckOverRecursed(LCheckOverRecursed *lir);
     bool visitCheckOverRecursedFailure(CheckOverRecursedFailure *ool);
     bool visitAsmJSCheckOverRecursed(LAsmJSCheckOverRecursed *lir);
 
     bool visitCheckOverRecursedPar(LCheckOverRecursedPar *lir);
     bool visitCheckOverRecursedFailurePar(CheckOverRecursedFailurePar *ool);
 
-    bool visitCheckInterruptPar(LCheckInterruptPar *lir);
-    bool visitOutOfLineCheckInterruptPar(OutOfLineCheckInterruptPar *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);
 
--- a/js/src/jit/CompileWrappers.cpp
+++ b/js/src/jit/CompileWrappers.cpp
@@ -66,16 +66,24 @@ CompileRuntime::addressOfGCZeal()
 #endif
 
 const void *
 CompileRuntime::addressOfInterrupt()
 {
     return &runtime()->interrupt;
 }
 
+#ifdef JS_THREADSAFE
+const void *
+CompileRuntime::addressOfInterruptPar()
+{
+    return &runtime()->interruptPar;
+}
+#endif
+
 const JitRuntime *
 CompileRuntime::jitRuntime()
 {
     return runtime()->jitRuntime();
 }
 
 SPSProfiler &
 CompileRuntime::spsProfiler()
--- a/js/src/jit/CompileWrappers.h
+++ b/js/src/jit/CompileWrappers.h
@@ -47,16 +47,20 @@ class CompileRuntime
     const void *addressOfLastCachedNativeIterator();
 
 #ifdef JS_GC_ZEAL
     const void *addressOfGCZeal();
 #endif
 
     const void *addressOfInterrupt();
 
+#ifdef JS_THREADSAFE
+    const void *addressOfInterruptPar();
+#endif
+
     const JitRuntime *jitRuntime();
 
     // Compilation does not occur off thread when the SPS profiler is enabled.
     SPSProfiler &spsProfiler();
 
     bool signalHandlersInstalled();
     bool jitSupportsFloatingPoint();
     bool hadOutOfMemory();
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -428,16 +428,17 @@ jit::TriggerOperationCallbackForIonCode(
         // Ion code memory so that the main thread will fault and enter a
         // signal handler when trying to execute the code. The signal
         // handler will unprotect the code and patch loop backedges so
         // that the interrupt handler is invoked afterwards.
         jitRuntime->ensureIonCodeProtected(rt);
         break;
 
       case JSRuntime::TriggerCallbackAnyThreadDontStopIon:
+      case JSRuntime::TriggerCallbackAnyThreadForkJoin:
         // When the trigger does not require Ion code to be interrupted,
         // nothing more needs to be done.
         break;
 
       default:
         MOZ_ASSUME_UNREACHABLE("Bad trigger");
     }
 }
--- a/js/src/jit/IonMacroAssembler.cpp
+++ b/js/src/jit/IonMacroAssembler.cpp
@@ -870,21 +870,24 @@ MacroAssembler::compareStrings(JSOp op, 
     rshiftPtr(Imm32(JSString::LENGTH_SHIFT), temp);
     branchPtr(Assembler::Equal, result, temp, fail);
     move32(Imm32(op == JSOP_NE || op == JSOP_STRICTNE), result);
 
     bind(&done);
 }
 
 void
-MacroAssembler::checkInterruptFlagsPar(const Register &tempReg,
-                                            Label *fail)
+MacroAssembler::checkInterruptFlagPar(const Register &tempReg, Label *fail)
 {
-    movePtr(ImmPtr(GetIonContext()->runtime->addressOfInterrupt()), tempReg);
+#ifdef JS_THREADSAFE
+    movePtr(ImmPtr(GetIonContext()->runtime->addressOfInterruptPar()), tempReg);
     branch32(Assembler::NonZero, Address(tempReg, 0), Imm32(0), fail);
+#else
+    MOZ_ASSUME_UNREACHABLE("JSRuntime::interruptPar doesn't exist on non-threadsafe builds.");
+#endif
 }
 
 static void
 ReportOverRecursed(JSContext *cx)
 {
     js_ReportOverRecursed(cx);
 }
 
--- a/js/src/jit/IonMacroAssembler.h
+++ b/js/src/jit/IonMacroAssembler.h
@@ -792,17 +792,17 @@ class MacroAssembler : public MacroAssem
 
     // Compares two strings for equality based on the JSOP.
     // This checks for identical pointers, atoms and length and fails for everything else.
     void compareStrings(JSOp op, Register left, Register right, Register result,
                         Register temp, Label *fail);
 
     // Checks the flags that signal that parallel code may need to interrupt or
     // abort.  Branches to fail in that case.
-    void checkInterruptFlagsPar(const Register &tempReg, Label *fail);
+    void checkInterruptFlagPar(const Register &tempReg, Label *fail);
 
     // If the JitCode that created this assembler needs to transition into the VM,
     // we want to store the JitCode on the stack in order to mark it during a GC.
     // This is a reference to a patch location where the JitCode* will be written.
   private:
     CodeOffsetLabel exitCodePatch_;
 
   public:
--- a/js/src/jit/LIR-Common.h
+++ b/js/src/jit/LIR-Common.h
@@ -732,22 +732,22 @@ class LInterruptCheckImplicit : public L
         return oolEntry_;
     }
 
     void setOolEntry(Label *oolEntry) {
         oolEntry_ = oolEntry;
     }
 };
 
-class LCheckInterruptPar : public LInstructionHelper<0, 1, 1>
-{
-  public:
-    LIR_HEADER(CheckInterruptPar);
-
-    LCheckInterruptPar(const LAllocation &cx, const LDefinition &tempReg) {
+class LInterruptCheckPar : public LInstructionHelper<0, 1, 1>
+{
+  public:
+    LIR_HEADER(InterruptCheckPar);
+
+    LInterruptCheckPar(const LAllocation &cx, const LDefinition &tempReg) {
         setOperand(0, cx);
         setTemp(0, tempReg);
     }
 
     const LAllocation *forkJoinContext() {
         return getOperand(0);
     }
 
--- a/js/src/jit/LOpcodes.h
+++ b/js/src/jit/LOpcodes.h
@@ -280,17 +280,17 @@
     _(AsmJSStoreGlobalVar)          \
     _(AsmJSLoadFFIFunc)             \
     _(AsmJSParameter)               \
     _(AsmJSReturn)                  \
     _(AsmJSVoidReturn)              \
     _(AsmJSPassStackArg)            \
     _(AsmJSCall)                    \
     _(AsmJSCheckOverRecursed)       \
-    _(CheckInterruptPar)            \
+    _(InterruptCheckPar)            \
     _(RecompileCheck)               \
     _(AssertRangeI)                 \
     _(AssertRangeD)                 \
     _(AssertRangeF)                 \
     _(AssertRangeV)
 
 #if defined(JS_CODEGEN_X86)
 # include "jit/x86/LOpcodes-x86.h"
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -2135,20 +2135,20 @@ LIRGenerator::visitInterruptCheck(MInter
     }
 #endif
 
     LInterruptCheck *lir = new(alloc()) LInterruptCheck();
     return add(lir, ins) && assignSafepoint(lir, ins);
 }
 
 bool
-LIRGenerator::visitCheckInterruptPar(MCheckInterruptPar *ins)
+LIRGenerator::visitInterruptCheckPar(MInterruptCheckPar *ins)
 {
-    LCheckInterruptPar *lir =
-        new(alloc()) LCheckInterruptPar(useRegister(ins->forkJoinContext()), temp());
+    LInterruptCheckPar *lir =
+        new(alloc()) LInterruptCheckPar(useRegister(ins->forkJoinContext()), temp());
     if (!add(lir, ins))
         return false;
     if (!assignSafepoint(lir, ins))
         return false;
     return true;
 }
 
 bool
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -159,17 +159,17 @@ class LIRGenerator : public LIRGenerator
     bool visitConstantElements(MConstantElements *ins);
     bool visitConvertElementsToDoubles(MConvertElementsToDoubles *ins);
     bool visitMaybeToDoubleElement(MMaybeToDoubleElement *ins);
     bool visitLoadSlot(MLoadSlot *ins);
     bool visitFunctionEnvironment(MFunctionEnvironment *ins);
     bool visitForkJoinContext(MForkJoinContext *ins);
     bool visitGuardThreadExclusive(MGuardThreadExclusive *ins);
     bool visitInterruptCheck(MInterruptCheck *ins);
-    bool visitCheckInterruptPar(MCheckInterruptPar *ins);
+    bool visitInterruptCheckPar(MInterruptCheckPar *ins);
     bool visitStoreSlot(MStoreSlot *ins);
     bool visitTypeBarrier(MTypeBarrier *ins);
     bool visitMonitorTypes(MMonitorTypes *ins);
     bool visitPostWriteBarrier(MPostWriteBarrier *ins);
     bool visitArrayLength(MArrayLength *ins);
     bool visitSetArrayLength(MSetArrayLength *ins);
     bool visitTypedArrayLength(MTypedArrayLength *ins);
     bool visitTypedArrayElements(MTypedArrayElements *ins);
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -4742,31 +4742,31 @@ class MCheckOverRecursedPar : public MUn
     }
 
     MDefinition *forkJoinContext() const {
         return getOperand(0);
     }
 };
 
 // Check for an interrupt (or rendezvous) in parallel mode.
-class MCheckInterruptPar : public MUnaryInstruction
-{
-    MCheckInterruptPar(MDefinition *cx)
+class MInterruptCheckPar : public MUnaryInstruction
+{
+    MInterruptCheckPar(MDefinition *cx)
       : MUnaryInstruction(cx)
     {
         setResultType(MIRType_None);
         setGuard();
         setMovable();
     }
 
   public:
-    INSTRUCTION_HEADER(CheckInterruptPar);
-
-    static MCheckInterruptPar *New(TempAllocator &alloc, MDefinition *cx) {
-        return new(alloc) MCheckInterruptPar(cx);
+    INSTRUCTION_HEADER(InterruptCheckPar);
+
+    static MInterruptCheckPar *New(TempAllocator &alloc, MDefinition *cx) {
+        return new(alloc) MInterruptCheckPar(cx);
     }
 
     MDefinition *forkJoinContext() const {
         return getOperand(0);
     }
 };
 
 // Check whether we need to fire the interrupt handler.
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -208,17 +208,17 @@ namespace jit {
     _(NewPar)                                                               \
     _(NewDenseArrayPar)                                                     \
     _(NewDerivedTypedObject)                                                \
     _(AbortPar)                                                             \
     _(LambdaPar)                                                            \
     _(RestPar)                                                              \
     _(ForkJoinContext)                                                      \
     _(GuardThreadExclusive)                                                 \
-    _(CheckInterruptPar)                                                    \
+    _(InterruptCheckPar)                                                    \
     _(RecompileCheck)
 
 // Forward declarations of MIR types.
 #define FORWARD_DECLARE(op) class M##op;
  MIR_OPCODE_LIST(FORWARD_DECLARE)
 #undef FORWARD_DECLARE
 
 class MInstructionVisitor // interface i.e. pure abstract class
--- a/js/src/jit/ParallelFunctions.cpp
+++ b/js/src/jit/ParallelFunctions.cpp
@@ -203,21 +203,21 @@ jit::CheckOverRecursedPar(ForkJoinContex
     else
         realStackLimit = cx->perThreadData->ionStackLimit;
 
     if (!JS_CHECK_STACK_SIZE(realStackLimit, &stackDummy_)) {
         cx->bailoutRecord->setCause(ParallelBailoutOverRecursed);
         return false;
     }
 
-    return CheckInterruptPar(cx);
+    return InterruptCheckPar(cx);
 }
 
 bool
-jit::CheckInterruptPar(ForkJoinContext *cx)
+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
--- a/js/src/jit/ParallelFunctions.h
+++ b/js/src/jit/ParallelFunctions.h
@@ -16,17 +16,17 @@ class TypedDatum; // subclass of JSObjec
 
 namespace jit {
 
 ForkJoinContext *ForkJoinContextPar();
 JSObject *NewGCThingPar(ForkJoinContext *cx, gc::AllocKind allocKind);
 bool ParallelWriteGuard(ForkJoinContext *cx, JSObject *object);
 bool IsInTargetRegion(ForkJoinContext *cx, TypedDatum *object);
 bool CheckOverRecursedPar(ForkJoinContext *cx);
-bool CheckInterruptPar(ForkJoinContext *cx);
+bool InterruptCheckPar(ForkJoinContext *cx);
 
 // Extends the given array with `length` new holes.  Returns nullptr on
 // failure or else `array`, which is convenient during code
 // generation.
 JSObject *ExtendArrayPar(ForkJoinContext *cx, JSObject *array, uint32_t length);
 
 // Set properties and elements on thread local objects.
 bool SetPropertyPar(ForkJoinContext *cx, HandleObject obj, HandlePropertyName name,
--- a/js/src/jit/ParallelSafetyAnalysis.cpp
+++ b/js/src/jit/ParallelSafetyAnalysis.cpp
@@ -286,17 +286,17 @@ class ParallelSafetyVisitor : public MIn
     UNSAFE_OP(StringReplace)
     UNSAFE_OP(CallInstanceOf)
     UNSAFE_OP(FunctionBoundary)
     UNSAFE_OP(GuardString)
     UNSAFE_OP(NewDeclEnvObject)
     UNSAFE_OP(In)
     UNSAFE_OP(InArray)
     SAFE_OP(GuardThreadExclusive)
-    SAFE_OP(CheckInterruptPar)
+    SAFE_OP(InterruptCheckPar)
     SAFE_OP(CheckOverRecursedPar)
     SAFE_OP(FunctionDispatch)
     SAFE_OP(TypeObjectDispatch)
     SAFE_OP(IsCallable)
     SAFE_OP(HaveSameClass)
     UNSAFE_OP(EffectiveAddress)
     UNSAFE_OP(AsmJSUnsignedToDouble)
     UNSAFE_OP(AsmJSUnsignedToFloat32)
@@ -741,17 +741,17 @@ bool
 ParallelSafetyVisitor::visitCheckOverRecursed(MCheckOverRecursed *ins)
 {
     return replace(ins, MCheckOverRecursedPar::New(alloc(), ForkJoinContext()));
 }
 
 bool
 ParallelSafetyVisitor::visitInterruptCheck(MInterruptCheck *ins)
 {
-    return replace(ins, MCheckInterruptPar::New(alloc(), ForkJoinContext()));
+    return replace(ins, MInterruptCheckPar::New(alloc(), ForkJoinContext()));
 }
 
 /////////////////////////////////////////////////////////////////////////////
 // Specialized ops
 //
 // Some ops, like +, can be specialized to ints/doubles.  Anything
 // else is terrifying.
 //
--- a/js/src/jit/shared/CodeGenerator-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-shared.cpp
@@ -946,17 +946,17 @@ CodeGeneratorShared::labelForBackedgeWit
         for (LInstructionIterator iter = mir->lir()->begin(); iter != mir->lir()->end(); iter++) {
             if (iter->isLabel() || iter->isMoveGroup()) {
                 // Continue searching for an interrupt check.
             } else if (iter->isInterruptCheckImplicit()) {
                 return iter->toInterruptCheckImplicit()->oolEntry();
             } else {
                 // The interrupt check should be the first instruction in the
                 // loop header other than the initial label and move groups.
-                JS_ASSERT(iter->isInterruptCheck() || iter->isCheckInterruptPar());
+                JS_ASSERT(iter->isInterruptCheck() || iter->isInterruptCheckPar());
                 return nullptr;
             }
         }
     }
 
     return nullptr;
 }
 
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -1014,16 +1014,20 @@ js_InvokeOperationCallback(JSContext *cx
      * IonMonkey sets its stack limit to UINTPTR_MAX to trigger operation
      * callbacks.
      */
     rt->resetIonStackLimit();
 
     js::gc::GCIfNeeded(cx);
 
 #ifdef JS_ION
+#ifdef JS_THREADSAFE
+    rt->interruptPar = false;
+#endif
+
     /*
      * A worker thread may have set the callback after finishing an Ion
      * compilation.
      */
     jit::AttachFinishedCompilations(cx);
 #endif
 
     /*
--- a/js/src/vm/ForkJoin.cpp
+++ b/js/src/vm/ForkJoin.cpp
@@ -169,17 +169,17 @@ static bool
 ExecuteSequentially(JSContext *cx, HandleValue funVal)
 {
     FastInvokeGuard fig(cx, funVal);
     InvokeArgs &args = fig.args();
     if (!args.init(1))
         return false;
     args.setCallee(funVal);
     args.setThis(UndefinedValue());
-    args[0].setBoolean(!!cx->runtime()->parallelWarmup);
+    args[0].setBoolean(!!cx->runtime()->forkJoinWarmup);
     return fig.invoke(cx);
 }
 
 ThreadLocal<ForkJoinContext*> ForkJoinContext::tlsForkJoinContext;
 
 /* static */ bool
 ForkJoinContext::initialize()
 {
@@ -384,17 +384,17 @@ class ForkJoinShared : public ParallelJo
     // Invoked during processing by worker threads to "check in".
     bool check(ForkJoinContext &cx);
 
     // Requests a GC, either full or specific to a zone.
     void requestGC(JS::gcreason::Reason reason);
     void requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason);
 
     // Requests that computation abort.
-    void setAbortFlag(bool fatal);
+    void setAbortFlagAndTriggerOperationCallback(bool fatal);
 
     // Set the fatal flag for the next abort.
     void setPendingAbortFatal() { fatal_ = true; }
 
     JSRuntime *runtime() { return cx_->runtime(); }
     JS::Zone *zone() { return cx_->zone(); }
     JSCompartment *compartment() { return cx_->compartment(); }
 
@@ -402,18 +402,18 @@ class ForkJoinShared : public ParallelJo
     void releaseJSContext() { PR_Unlock(cxLock_); }
 };
 
 class AutoEnterWarmup
 {
     JSRuntime *runtime_;
 
   public:
-    AutoEnterWarmup(JSRuntime *runtime) : runtime_(runtime) { runtime_->parallelWarmup++; }
-    ~AutoEnterWarmup() { runtime_->parallelWarmup--; }
+    AutoEnterWarmup(JSRuntime *runtime) : runtime_(runtime) { runtime_->forkJoinWarmup++; }
+    ~AutoEnterWarmup() { runtime_->forkJoinWarmup--; }
 };
 
 class AutoSetForkJoinContext
 {
   public:
     AutoSetForkJoinContext(ForkJoinContext *threadCx) {
         ForkJoinContext::tlsForkJoinContext.set(threadCx);
     }
@@ -836,28 +836,18 @@ ForkJoinOperation::compileForParallelExe
                          "Script %p:%s:%d is not stalled, "
                          "but no parallel ion script found, "
                          "restarting loop",
                          script.get(), script->filename(), script->lineno());
                 }
             }
         }
 
-        if (allScriptsPresent) {
-            // For testing modes, we want to make sure that all off thread
-            // compilation tasks are finished, so we don't race with
-            // off-main-thread-compilation setting an interrupt flag while we
-            // are in the middle of a test, causing unexpected bailouts.
-            if (mode_ != ForkJoinModeNormal) {
-                StopAllOffThreadCompilations(cx_->compartment());
-                if (!js_HandleExecutionInterrupt(cx_))
-                    return fatalError(status);
-            }
+        if (allScriptsPresent)
             break;
-        }
     }
 
     Spew(SpewCompile, "Compilation complete (final worklist length %d)",
          worklist_.length());
 
     // At this point, all scripts and their transitive callees are
     // either stalled (indicating they are unlikely to be called) or
     // in a compiled state.  Therefore we can clear the
@@ -1401,17 +1391,17 @@ ForkJoinShared::~ForkJoinShared()
 }
 
 ParallelResult
 ForkJoinShared::execute()
 {
     // Sometimes a GC request occurs *just before* we enter into the
     // parallel section.  Rather than enter into the parallel section
     // and then abort, we just check here and abort early.
-    if (cx_->runtime()->interrupt)
+    if (cx_->runtime()->interruptPar)
         return TP_RETRY_SEQUENTIALLY;
 
     AutoLockMonitor lock(*this);
 
     ParallelResult jobResult = TP_SUCCESS;
     {
         AutoUnlockMonitor unlock(*this);
 
@@ -1459,17 +1449,17 @@ ForkJoinShared::transferArenasToCompartm
     }
 }
 
 bool
 ForkJoinShared::executeFromWorker(uint32_t workerId, uintptr_t stackLimit)
 {
     PerThreadData thisThread(cx_->runtime());
     if (!thisThread.init()) {
-        setAbortFlag(true);
+        setAbortFlagAndTriggerOperationCallback(true);
         return false;
     }
     TlsPerThreadData.set(&thisThread);
 
 #ifdef JS_ARM_SIMULATOR
     stackLimit = Simulator::StackLimit();
 #endif
 
@@ -1521,71 +1511,71 @@ ForkJoinShared::executePortion(PerThread
 
     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);
-        setAbortFlag(false);
+        setAbortFlagAndTriggerOperationCallback(false);
     } else {
         ParallelIonInvoke<2> fii(cx_->runtime(), fun_, 1);
 
         fii.args[0] = BooleanValue(false);
 
         bool ok = fii.invoke(perThread);
         JS_ASSERT(ok == !cx.bailoutRecord->topScript);
         if (!ok)
-            setAbortFlag(false);
+            setAbortFlagAndTriggerOperationCallback(false);
     }
 
     Spew(SpewOps, "Down");
 }
 
 bool
 ForkJoinShared::check(ForkJoinContext &cx)
 {
-    JS_ASSERT(cx_->runtime()->interrupt);
+    JS_ASSERT(cx_->runtime()->interruptPar);
 
     if (abort_)
         return false;
 
     // Note: We must check if the main thread has exited successfully here, as
     // without a main thread the worker threads which are tripping on the
     // interrupt flag would never exit.
     if (cx.isMainThread() || !threadPool_->isMainThreadActive()) {
         JS_ASSERT(!cx_->runtime()->gcIsNeeded);
 
-        if (cx_->runtime()->interrupt) {
+        if (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()->gcIsNeeded);
 
             cx.bailoutRecord->setCause(ParallelBailoutInterrupt);
-            setAbortFlag(false);
+            setAbortFlagAndTriggerOperationCallback(false);
             return false;
         }
     }
 
     return true;
 }
 
 void
-ForkJoinShared::setAbortFlag(bool fatal)
+ForkJoinShared::setAbortFlagAndTriggerOperationCallback(bool fatal)
 {
     AutoLockMonitor lock(*this);
 
     abort_ = true;
     fatal_ = fatal_ || fatal;
 
-    // Note: DontStopIon here avoids the expensive memory protection needed to
+    // Note: The ForkJoin trigger here avoids the expensive memory protection needed to
     // interrupt Ion code compiled for sequential execution.
-    cx_->runtime()->triggerOperationCallback(JSRuntime::TriggerCallbackAnyThreadDontStopIon);
+    cx_->runtime()->triggerOperationCallback(JSRuntime::TriggerCallbackAnyThreadForkJoin);
 }
 
 void
 ForkJoinShared::requestGC(JS::gcreason::Reason reason)
 {
     AutoLockMonitor lock(*this);
 
     gcZone_ = nullptr;
@@ -1676,36 +1666,36 @@ bool
 ForkJoinContext::hasAcquiredJSContext() const
 {
     return acquiredJSContext_;
 }
 
 bool
 ForkJoinContext::check()
 {
-    if (runtime()->interrupt)
+    if (runtime()->interruptPar)
         return shared->check(*this);
     else
         return true;
 }
 
 void
 ForkJoinContext::requestGC(JS::gcreason::Reason reason)
 {
     shared->requestGC(reason);
     bailoutRecord->setCause(ParallelBailoutRequestedGC);
-    shared->setAbortFlag(false);
+    shared->setAbortFlagAndTriggerOperationCallback(false);
 }
 
 void
 ForkJoinContext::requestZoneGC(JS::Zone *zone, JS::gcreason::Reason reason)
 {
     shared->requestZoneGC(zone, reason);
     bailoutRecord->setCause(ParallelBailoutRequestedZoneGC);
-    shared->setAbortFlag(false);
+    shared->setAbortFlagAndTriggerOperationCallback(false);
 }
 
 bool
 ForkJoinContext::setPendingAbortFatal(ParallelBailoutCause cause)
 {
     shared->setPendingAbortFatal();
     bailoutRecord->setCause(cause);
     return false;
@@ -2140,16 +2130,24 @@ js::ParallelTestsShouldPass(JSContext *c
 {
     return jit::IsIonEnabled(cx) &&
            jit::IsBaselineEnabled(cx) &&
            !jit::js_JitOptions.eagerCompilation &&
            jit::js_JitOptions.baselineUsesBeforeCompile != 0 &&
            cx->runtime()->gcZeal() == 0;
 }
 
+void
+js::TriggerOperationCallbackForForkJoin(JSRuntime *rt,
+                                        JSRuntime::OperationCallbackTrigger trigger)
+{
+    if (trigger != JSRuntime::TriggerCallbackAnyThreadDontStopIon)
+        rt->interruptPar = true;
+}
+
 bool
 js::intrinsic_SetForkJoinTargetRegion(JSContext *cx, unsigned argc, Value *vp)
 {
     // This version of SetForkJoinTargetRegion is called during
     // sequential execution. It is a no-op. The parallel version
     // is intrinsic_SetForkJoinTargetRegionPar(), below.
     return true;
 }
--- a/js/src/vm/ForkJoin.h
+++ b/js/src/vm/ForkJoin.h
@@ -460,16 +460,19 @@ class LockedJSContext
     operator JSContext *() { return jscx_; }
     JSContext *operator->() { return jscx_; }
 };
 
 bool InExclusiveParallelSection();
 
 bool ParallelTestsShouldPass(JSContext *cx);
 
+void TriggerOperationCallbackForForkJoin(JSRuntime *rt,
+                                         JSRuntime::OperationCallbackTrigger trigger);
+
 bool intrinsic_SetForkJoinTargetRegion(JSContext *cx, unsigned argc, Value *vp);
 extern const JSJitInfo intrinsic_SetForkJoinTargetRegionInfo;
 
 ///////////////////////////////////////////////////////////////////////////
 // Debug Spew
 
 namespace jit {
     class MDefinition;
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -115,16 +115,19 @@ static const JSWrapObjectCallbacks Defau
 JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
   : JS::shadow::Runtime(
 #ifdef JSGC_GENERATIONAL
         &gcStoreBuffer
 #endif
     ),
     mainThread(this),
     interrupt(0),
+#if defined(JS_THREADSAFE) && defined(JS_ION)
+    interruptPar(false),
+#endif
     handlingSignal(false),
     operationCallback(nullptr),
 #ifdef JS_THREADSAFE
     operationCallbackLock(nullptr),
     operationCallbackOwner(nullptr),
     exclusiveAccessLock(nullptr),
     exclusiveAccessOwner(nullptr),
     mainThreadHasExclusiveAccess(false),
@@ -288,17 +291,17 @@ JSRuntime::JSRuntime(JSUseHelperThreads 
 #ifdef DEBUG
     noGCOrAllocationCheck(0),
 #endif
     jitSupportsFloatingPoint(false),
     ionPcScriptCache(nullptr),
     threadPool(this),
     defaultJSContextCallback(nullptr),
     ctypesActivityCallback(nullptr),
-    parallelWarmup(0),
+    forkJoinWarmup(0),
     ionReturnOverride_(MagicValue(JS_ARG_POISON)),
     useHelperThreads_(useHelperThreads),
     parallelIonCompilationEnabled_(true),
     parallelParsingEnabled_(true),
     isWorkerRuntime_(false)
 #ifdef DEBUG
     , enteredPolicy(nullptr)
 #endif
@@ -649,16 +652,20 @@ JSRuntime::triggerOperationCallback(Oper
      * into a weird state where interrupt is stuck at 0 but ionStackLimit is
      * MAXADDR.
      */
     mainThread.setIonStackLimit(-1);
 
     interrupt = 1;
 
 #ifdef JS_ION
+#ifdef JS_THREADSAFE
+    TriggerOperationCallbackForForkJoin(this, trigger);
+#endif
+
     /*
      * asm.js and, optionally, normal Ion code use memory protection and signal
      * handlers to halt running code.
      */
     if (!SignalBasedTriggersDisabled()) {
         TriggerOperationCallbackForAsmJSCode(this);
         jit::TriggerOperationCallbackForIonCode(this, trigger);
     }
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -684,16 +684,25 @@ struct JSRuntime : public JS::shadow::Ru
      * as possible.
      */
 #ifdef JS_THREADSAFE
     mozilla::Atomic<int32_t, mozilla::Relaxed> interrupt;
 #else
     int32_t interrupt;
 #endif
 
+#if defined(JS_THREADSAFE) && defined(JS_ION)
+    /*
+     * If non-zero, ForkJoin should service an interrupt. This is a separate
+     * flag from |interrupt| because we cannot use the mprotect trick with PJS
+     * code and ignore the TriggerCallbackAnyThreadDontStopIon trigger.
+     */
+    mozilla::Atomic<bool, mozilla::Relaxed> interruptPar;
+#endif
+
     /* Set when handling a signal for a thread associated with this runtime. */
     bool handlingSignal;
 
     /* Branch callback */
     JSOperationCallback operationCallback;
 
 #ifdef DEBUG
     void assertCanLock(js::RuntimeLock which);
@@ -1622,19 +1631,19 @@ struct JSRuntime : public JS::shadow::Ru
     js::jit::PcScriptCache *ionPcScriptCache;
 
     js::ThreadPool threadPool;
 
     js::DefaultJSContextCallback defaultJSContextCallback;
 
     js::CTypesActivityCallback  ctypesActivityCallback;
 
-    // Non-zero if this is a parallel warmup execution.  See
-    // js::parallel::Do() for more information.
-    uint32_t parallelWarmup;
+    // Non-zero if this is a ForkJoin warmup execution.  See
+    // js::ForkJoin() for more information.
+    uint32_t forkJoinWarmup;
 
   private:
     // In certain cases, we want to optimize certain opcodes to typed instructions,
     // to avoid carrying an extra register to feed into an unbox. Unfortunately,
     // that's not always possible. For example, a GetPropertyCacheT could return a
     // typed double, but if it takes its out-of-line path, it could return an
     // object, and trigger invalidation. The invalidation bailout will consider the
     // return value to be a double, and create a garbage Value.
@@ -1717,17 +1726,18 @@ struct JSRuntime : public JS::shadow::Ru
     JS_FRIEND_API(void *) onOutOfMemory(void *p, size_t nbytes);
     JS_FRIEND_API(void *) onOutOfMemory(void *p, size_t nbytes, JSContext *cx);
 
     // Ways in which the operation callback on the runtime can be triggered,
     // varying based on which thread is triggering the callback.
     enum OperationCallbackTrigger {
         TriggerCallbackMainThread,
         TriggerCallbackAnyThread,
-        TriggerCallbackAnyThreadDontStopIon
+        TriggerCallbackAnyThreadDontStopIon,
+        TriggerCallbackAnyThreadForkJoin
     };
 
     void triggerOperationCallback(OperationCallbackTrigger trigger);
 
     void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes *runtime);
 
   private:
 
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -600,17 +600,17 @@ intrinsic_ParallelTestsShouldPass(JSCont
  * ShouldForceSequential(): Returns true if parallel ops should take
  * the sequential fallback path.
  */
 bool
 js::intrinsic_ShouldForceSequential(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 #ifdef JS_THREADSAFE
-    args.rval().setBoolean(cx->runtime()->parallelWarmup ||
+    args.rval().setBoolean(cx->runtime()->forkJoinWarmup ||
                            InParallelSection());
 #else
     args.rval().setBoolean(true);
 #endif
     return true;
 }
 
 bool