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 185871 f76ccce1f2fd65666b60c988c83bc2c70b50ca6f
parent 185870 ad7777f1c0f58cdcc09d627dfbf127d29aecf215
child 185872 6c899a1064f3a8536f8b214cc6112387943c1740
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnmatsakis
bugs949296
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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