Bug 1092544 - Assert that instructions are recovered on bailouts. r=bbouvier
authorNicolas B. Pierron <nicolas.b.pierron@mozilla.com>
Wed, 25 Mar 2015 15:50:35 +0100
changeset 266043 486e2ec002fef511360999a3f06ec86bdec41409
parent 266042 3089ef8e88c309fac99d16411e960db4f90f4c14
child 266044 865c86092a5eb1bb84fa9bd9997a4f002a28350a
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1092544
milestone39.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 1092544 - Assert that instructions are recovered on bailouts. r=bbouvier
js/src/builtin/TestingFunctions.cpp
js/src/builtin/TestingFunctions.h
js/src/jit-test/tests/ion/dce-with-rinstructions.js
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/IonBuilder.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/MCallOptimize.cpp
js/src/jit/MIR.h
js/src/jit/MOpcodes.h
js/src/jit/Recover.cpp
js/src/jit/Recover.h
js/src/shell/js.cpp
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1563,16 +1563,26 @@ TestingFunc_assertJitStackInvariants(JSC
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     jit::AssertJitStackInvariants(cx);
     args.rval().setUndefined();
     return true;
 }
 
+bool
+js::testingFunc_assertRecoveredOnBailout(JSContext *cx, unsigned argc, jsval *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    // NOP when not in IonMonkey
+    args.rval().setUndefined();
+    return true;
+}
+
 static bool
 SetJitCompilerOption(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedObject callee(cx, &args.callee());
 
     if (args.length() != 2) {
         ReportUsageError(cx, callee, "Wrong number of arguments.");
--- a/js/src/builtin/TestingFunctions.h
+++ b/js/src/builtin/TestingFunctions.h
@@ -16,16 +16,19 @@ DefineTestingFunctions(JSContext *cx, Ha
 
 bool
 testingFunc_bailout(JSContext *cx, unsigned argc, Value *vp);
 
 bool
 testingFunc_assertFloat32(JSContext *cx, unsigned argc, Value *vp);
 
 bool
+testingFunc_assertRecoveredOnBailout(JSContext *cx, unsigned argc, Value *vp);
+
+bool
 testingFunc_inJit(JSContext *cx, unsigned argc, Value *vp);
 
 bool
 testingFunc_inIon(JSContext *cx, unsigned argc, Value *vp);
 
 } /* namespace js */
 
 #endif /* builtin_TestingFunctions_h */
--- a/js/src/jit-test/tests/ion/dce-with-rinstructions.js
+++ b/js/src/jit-test/tests/ion/dce-with-rinstructions.js
@@ -145,16 +145,17 @@ function rursh_object(i) {
     return i;
 }
 
 var uceFault_add_number = eval(uneval(uceFault).replace('uceFault', 'uceFault_add_number'));
 function radd_number(i) {
     var x = 1 + i;
     if (uceFault_add_number(i) || uceFault_add_number(i))
         assertEq(x, 100  /* = 1 + 99 */);
+    assertRecoveredOnBailout(x);
     return i;
 }
 
 var uceFault_add_float = eval(uneval(uceFault).replace('uceFault', 'uceFault_add_float'));
 function radd_float(i) {
     var t = Math.fround(1/3);
     var fi = Math.fround(i);
     var x = Math.fround(Math.fround(Math.fround(Math.fround(t + fi) + t) + fi) + t);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -3378,16 +3378,22 @@ CodeGenerator::visitBail(LBail *lir)
 
 void
 CodeGenerator::visitUnreachable(LUnreachable *lir)
 {
     masm.assumeUnreachable("end-of-block assumed unreachable");
 }
 
 void
+CodeGenerator::visitEncodeSnapshot(LEncodeSnapshot *lir)
+{
+    encode(lir->snapshot());
+}
+
+void
 CodeGenerator::visitGetDynamicName(LGetDynamicName *lir)
 {
     Register scopeChain = ToRegister(lir->getScopeChain());
     Register name = ToRegister(lir->getName());
     Register temp1 = ToRegister(lir->temp1());
     Register temp2 = ToRegister(lir->temp2());
     Register temp3 = ToRegister(lir->temp3());
 
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -135,16 +135,17 @@ class CodeGenerator : public CodeGenerat
     void visitCallGeneric(LCallGeneric *call);
     void visitCallKnown(LCallKnown *call);
     void emitCallInvokeFunction(LApplyArgsGeneric *apply, Register extraStackSize);
     void emitPushArguments(LApplyArgsGeneric *apply, Register extraStackSpace);
     void emitPopArguments(LApplyArgsGeneric *apply, Register extraStackSize);
     void visitApplyArgsGeneric(LApplyArgsGeneric *apply);
     void visitBail(LBail *lir);
     void visitUnreachable(LUnreachable *unreachable);
+    void visitEncodeSnapshot(LEncodeSnapshot *lir);
     void visitGetDynamicName(LGetDynamicName *lir);
     void visitFilterArgumentsOrEvalS(LFilterArgumentsOrEvalS *lir);
     void visitFilterArgumentsOrEvalV(LFilterArgumentsOrEvalV *lir);
     void visitCallDirectEvalS(LCallDirectEvalS *lir);
     void visitCallDirectEvalV(LCallDirectEvalV *lir);
     void visitDoubleToInt32(LDoubleToInt32 *lir);
     void visitFloat32ToInt32(LFloat32ToInt32 *lir);
     void visitNewArrayCallVM(LNewArray *lir);
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -859,16 +859,17 @@ class IonBuilder
                                   const Class *clasp3 = nullptr,
                                   const Class *clasp4 = nullptr);
     InliningStatus inlineIsConstructing(CallInfo &callInfo);
     InliningStatus inlineSubstringKernel(CallInfo &callInfo);
 
     // Testing functions.
     InliningStatus inlineBailout(CallInfo &callInfo);
     InliningStatus inlineAssertFloat32(CallInfo &callInfo);
+    InliningStatus inlineAssertRecoveredOnBailout(CallInfo &callInfo);
     InliningStatus inlineTrue(CallInfo &callInfo);
 
     // Bind function.
     InliningStatus inlineBoundFunction(CallInfo &callInfo, JSFunction *target);
 
     // Main inlining functions
     InliningStatus inlineNativeCall(CallInfo &callInfo, JSFunction *target);
     InliningStatus inlineNativeGetter(CallInfo &callInfo, JSFunction *target);
--- a/js/src/jit/LIR-Common.h
+++ b/js/src/jit/LIR-Common.h
@@ -1681,16 +1681,22 @@ class LBail : public LInstructionHelper<
 };
 
 class LUnreachable : public LControlInstructionHelper<0, 0, 0>
 {
   public:
     LIR_HEADER(Unreachable)
 };
 
+class LEncodeSnapshot : public LInstructionHelper<0, 0, 0>
+{
+  public:
+    LIR_HEADER(EncodeSnapshot)
+};
+
 template <size_t defs, size_t ops>
 class LDOMPropertyInstructionHelper : public LCallInstructionHelper<defs, 1 + ops, 3>
 {
   protected:
     LDOMPropertyInstructionHelper(const LDefinition &JSContextReg, const LAllocation &ObjectReg,
                                   const LDefinition &PrivReg, const LDefinition &ValueReg)
     {
         this->setOperand(0, ObjectReg);
--- a/js/src/jit/LOpcodes.h
+++ b/js/src/jit/LOpcodes.h
@@ -70,16 +70,17 @@
     _(DefVar)                       \
     _(DefFun)                       \
     _(CallKnown)                    \
     _(CallGeneric)                  \
     _(CallNative)                   \
     _(ApplyArgsGeneric)             \
     _(Bail)                         \
     _(Unreachable)                  \
+    _(EncodeSnapshot)               \
     _(GetDynamicName)               \
     _(FilterArgumentsOrEvalS)       \
     _(FilterArgumentsOrEvalV)       \
     _(CallDirectEvalS)              \
     _(CallDirectEvalV)              \
     _(StackArgT)                    \
     _(StackArgV)                    \
     _(CreateThis)                   \
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -517,28 +517,42 @@ LIRGenerator::visitBail(MBail *bail)
 void
 LIRGenerator::visitUnreachable(MUnreachable *unreachable)
 {
     LUnreachable *lir = new(alloc()) LUnreachable();
     add(lir, unreachable);
 }
 
 void
+LIRGenerator::visitEncodeSnapshot(MEncodeSnapshot *mir)
+{
+    LEncodeSnapshot *lir = new(alloc()) LEncodeSnapshot();
+    assignSnapshot(lir, Bailout_Inevitable);
+    add(lir, mir);
+}
+
+void
 LIRGenerator::visitAssertFloat32(MAssertFloat32 *assertion)
 {
     MIRType type = assertion->input()->type();
     DebugOnly<bool> checkIsFloat32 = assertion->mustBeFloat32();
 
     if (type != MIRType_Value && !js_JitOptions.eagerCompilation) {
         MOZ_ASSERT_IF(checkIsFloat32, type == MIRType_Float32);
         MOZ_ASSERT_IF(!checkIsFloat32, type != MIRType_Float32);
     }
 }
 
 void
+LIRGenerator::visitAssertRecoveredOnBailout(MAssertRecoveredOnBailout *assertion)
+{
+    MOZ_CRASH("AssertRecoveredOnBailout nodes are always recovered on bailouts.");
+}
+
+void
 LIRGenerator::visitArraySplice(MArraySplice *ins)
 {
     LArraySplice *lir = new(alloc()) LArraySplice(useRegisterAtStart(ins->object()),
                                                   useRegisterAtStart(ins->start()),
                                                   useRegisterAtStart(ins->deleteCount()));
     add(lir, ins);
     assignSafepoint(lir, ins);
 }
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -95,17 +95,19 @@ class LIRGenerator : public LIRGenerator
     void visitReturnFromCtor(MReturnFromCtor *ins);
     void visitComputeThis(MComputeThis *ins);
     void visitLoadArrowThis(MLoadArrowThis *ins);
     void visitCall(MCall *call);
     void visitApplyArgs(MApplyArgs *apply);
     void visitArraySplice(MArraySplice *splice);
     void visitBail(MBail *bail);
     void visitUnreachable(MUnreachable *unreachable);
+    void visitEncodeSnapshot(MEncodeSnapshot *ins);
     void visitAssertFloat32(MAssertFloat32 *ins);
+    void visitAssertRecoveredOnBailout(MAssertRecoveredOnBailout *ins);
     void visitGetDynamicName(MGetDynamicName *ins);
     void visitFilterArgumentsOrEval(MFilterArgumentsOrEval *ins);
     void visitCallDirectEval(MCallDirectEval *ins);
     void visitTest(MTest *test);
     void visitGotoWithFake(MGotoWithFake *ins);
     void visitFunctionDispatch(MFunctionDispatch *ins);
     void visitObjectGroupDispatch(MObjectGroupDispatch *ins);
     void visitCompare(MCompare *comp);
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -245,16 +245,18 @@ IonBuilder::inlineNativeCall(CallInfo &c
     if (native == js::SetTypedObjectOffset)
         return inlineSetTypedObjectOffset(callInfo);
 
     // Testing Functions
     if (native == testingFunc_bailout)
         return inlineBailout(callInfo);
     if (native == testingFunc_assertFloat32)
         return inlineAssertFloat32(callInfo);
+    if (native == testingFunc_assertRecoveredOnBailout)
+        return inlineAssertRecoveredOnBailout(callInfo);
     if (native == testingFunc_inIon || native == testingFunc_inJit)
         return inlineTrue(callInfo);
 
     // Bound function
     if (native == js::CallOrConstructBoundFunction)
         return inlineBoundFunction(callInfo, target);
 
     // Simd functions
@@ -2594,16 +2596,48 @@ IonBuilder::inlineAssertFloat32(CallInfo
 
     MConstant *undefined = MConstant::New(alloc(), UndefinedValue());
     current->add(undefined);
     current->push(undefined);
     return InliningStatus_Inlined;
 }
 
 IonBuilder::InliningStatus
+IonBuilder::inlineAssertRecoveredOnBailout(CallInfo &callInfo)
+{
+    if (js_JitOptions.checkRangeAnalysis) {
+        // If we are checking the range of all instructions, then the guards
+        // inserted by Range Analysis prevent the use of recover
+        // instruction. Thus, we just disable these checks.
+        current->push(constant(UndefinedValue()));
+        callInfo.setImplicitlyUsedUnchecked();
+        return InliningStatus_Inlined;
+    }
+
+    MAssertRecoveredOnBailout *assert =
+        MAssertRecoveredOnBailout::New(alloc(), callInfo.getArg(0));
+    current->add(assert);
+    current->push(assert);
+
+    // Create an instruction sequence which implies that the argument of the
+    // assertRecoveredOnBailout function would be encoded at least in one
+    // Snapshot.
+    MNop *nop = MNop::New(alloc());
+    current->add(nop);
+    if (!resumeAfter(nop))
+        return InliningStatus_Error;
+    current->add(MEncodeSnapshot::New(alloc()));
+
+    current->pop();
+    current->push(constant(UndefinedValue()));
+    callInfo.setImplicitlyUsedUnchecked();
+    return InliningStatus_Inlined;
+}
+
+IonBuilder::InliningStatus
 IonBuilder::inlineBoundFunction(CallInfo &nativeCallInfo, JSFunction *target)
 {
     trackOptimizationOutcome(TrackedOutcome::CantInlineBound);
 
     if (!target->getBoundFunctionTarget()->is<JSFunction>())
         return InliningStatus_NotInlined;
 
     JSFunction *scriptedTarget = &(target->getBoundFunctionTarget()->as<JSFunction>());
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -3876,16 +3876,66 @@ class MUnreachable
         return new(alloc) MUnreachable();
     }
 
     AliasSet getAliasSet() const override {
         return AliasSet::None();
     }
 };
 
+// This class serve as a way to force the encoding of a snapshot, even if there
+// is no resume point using it.  This is useful to run MAssertRecoveredOnBailout
+// assertions.
+class MEncodeSnapshot : public MNullaryInstruction
+{
+  protected:
+    MEncodeSnapshot()
+      : MNullaryInstruction()
+    {
+        setGuard();
+    }
+
+  public:
+    INSTRUCTION_HEADER(EncodeSnapshot)
+
+    static MEncodeSnapshot *
+    New(TempAllocator &alloc) {
+        return new(alloc) MEncodeSnapshot();
+    }
+};
+
+class MAssertRecoveredOnBailout
+  : public MUnaryInstruction,
+    public NoTypePolicy::Data
+{
+  protected:
+    MAssertRecoveredOnBailout(MDefinition *ins)
+      : MUnaryInstruction(ins)
+    {
+        setResultType(MIRType_Value);
+        setRecoveredOnBailout();
+        setGuard();
+    }
+
+  public:
+    INSTRUCTION_HEADER(AssertRecoveredOnBailout)
+
+    static MAssertRecoveredOnBailout *New(TempAllocator &alloc, MDefinition *ins) {
+        return new(alloc) MAssertRecoveredOnBailout(ins);
+    }
+
+    // Needed to assert that float32 instructions are correctly recovered.
+    bool canConsumeFloat32(MUse *use) const override { return true; }
+
+    bool writeRecoverData(CompactBufferWriter &writer) const override;
+    bool canRecoverOnBailout() const override {
+        return true;
+    }
+};
+
 class MAssertFloat32
   : public MUnaryInstruction,
     public NoTypePolicy::Data
 {
   protected:
     bool mustBeFloat32_;
 
     MAssertFloat32(MDefinition *value, bool mustBeFloat32)
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -61,17 +61,19 @@ namespace jit {
     _(SetArgumentsObjectArg)                                                \
     _(ComputeThis)                                                          \
     _(LoadArrowThis)                                                        \
     _(Call)                                                                 \
     _(ApplyArgs)                                                            \
     _(ArraySplice)                                                          \
     _(Bail)                                                                 \
     _(Unreachable)                                                          \
+    _(EncodeSnapshot)                                                       \
     _(AssertFloat32)                                                        \
+    _(AssertRecoveredOnBailout)                                             \
     _(GetDynamicName)                                                       \
     _(FilterArgumentsOrEval)                                                \
     _(CallDirectEval)                                                       \
     _(BitNot)                                                               \
     _(TypeOf)                                                               \
     _(ToId)                                                                 \
     _(BitAnd)                                                               \
     _(BitOr)                                                                \
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -1427,16 +1427,36 @@ RArrayState::recover(JSContext *cx, Snap
     }
 
     result.setObject(*object);
     iter.storeInstructionResult(result);
     return true;
 }
 
 bool
+MAssertRecoveredOnBailout::writeRecoverData(CompactBufferWriter &writer) const
+{
+    MOZ_ASSERT(canRecoverOnBailout());
+    MOZ_ASSERT(input()->canRecoverOnBailout());
+    writer.writeUnsigned(uint32_t(RInstruction::Recover_AssertRecoveredOnBailout));
+    return true;
+}
+
+RAssertRecoveredOnBailout::RAssertRecoveredOnBailout(CompactBufferReader &reader)
+{ }
+
+bool RAssertRecoveredOnBailout::recover(JSContext *cx, SnapshotIterator &iter) const
+{
+    RootedValue result(cx);
+    result.setUndefined();
+    iter.storeInstructionResult(result);
+    return true;
+}
+
+bool
 MStringReplace::writeRecoverData(CompactBufferWriter &writer) const
 {
     MOZ_ASSERT(canRecoverOnBailout());
     writer.writeUnsigned(uint32_t(RInstruction::Recover_StringReplace));
     return true;
 }
 
 RStringReplace::RStringReplace(CompactBufferReader &reader)
--- a/js/src/jit/Recover.h
+++ b/js/src/jit/Recover.h
@@ -61,17 +61,18 @@ namespace jit {
     _(TruncateToInt32)                          \
     _(NewObject)                                \
     _(NewArray)                                 \
     _(NewDerivedTypedObject)                    \
     _(CreateThisWithTemplate)                   \
     _(Lambda)                                   \
     _(SimdBox)                                  \
     _(ObjectState)                              \
-    _(ArrayState)
+    _(ArrayState)                               \
+    _(AssertRecoveredOnBailout)
 
 class RResumePoint;
 class SnapshotIterator;
 
 class RInstruction
 {
   public:
     enum Opcode
@@ -734,16 +735,28 @@ class RArrayState final : public RInstru
         // +1 for the array.
         // +1 for the initalized length.
         return numElements() + 2;
     }
 
     bool recover(JSContext *cx, SnapshotIterator &iter) const;
 };
 
+class RAssertRecoveredOnBailout final : public RInstruction
+{
+  public:
+    RINSTRUCTION_HEADER_(AssertRecoveredOnBailout)
+
+    virtual uint32_t numOperands() const {
+        return 1;
+    }
+
+    bool recover(JSContext *cx, SnapshotIterator &iter) const;
+};
+
 #undef RINSTRUCTION_HEADER_
 
 const RResumePoint *
 RInstruction::toResumePoint() const
 {
     MOZ_ASSERT(isResumePoint());
     return static_cast<const RResumePoint *>(this);
 }
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -4884,16 +4884,20 @@ static const JSFunctionSpecWithHelp fuzz
 "  of the current shell (which is assumed to be the actual path to the shell.\n"
 "  arguments[0] (of the call to nestedShell) will be argv[1], arguments[1] will\n"
 "  be argv[2], etc."),
 
     JS_FN_HELP("assertFloat32", testingFunc_assertFloat32, 2, 0,
 "assertFloat32(value, isFloat32)",
 "  In IonMonkey only, asserts that value has (resp. hasn't) the MIRType_Float32 if isFloat32 is true (resp. false)."),
 
+    JS_FN_HELP("assertRecoveredOnBailout", testingFunc_assertRecoveredOnBailout, 1, 0,
+"assertRecoveredOnBailout(var)",
+"  In IonMonkey only, asserts that variable has RecoveredOnBailout flag."),
+
     JS_FN_HELP("withSourceHook", WithSourceHook, 1, 0,
 "withSourceHook(hook, fun)",
 "  Set this JS runtime's lazy source retrieval hook (that is, the hook\n"
 "  used to find sources compiled with |CompileOptions::LAZY_SOURCE|) to\n"
 "  |hook|; call |fun| with no arguments; and then restore the runtime's\n"
 "  original hook. Return or throw whatever |fun| did. |hook| gets\n"
 "  passed the requested code's URL, and should return a string.\n"
 "\n"