Bug 1004169. Make sure MTest always uses TI information for deciding whether its operand might emulate undefined. r=jandem
authorBoris Zbarsky <bzbarsky@mit.edu>
Sat, 03 May 2014 01:08:14 -0400
changeset 181819 2fbc044027e6c8730426951ec23193c6f2cdb979
parent 181818 4c874962ee0205a28cf3f94c4462950039aa5570
child 181838 8a21f9e10a754d6592867a66a473cbaf65ef1d65
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersjandem
bugs1004169
milestone32.0a1
Bug 1004169. Make sure MTest always uses TI information for deciding whether its operand might emulate undefined. r=jandem
js/src/jit/CodeGenerator.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/IonBuilder.h
js/src/jit/MCallOptimize.cpp
js/src/jit/MIR.cpp
js/src/jit/MIR.h
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -678,20 +678,21 @@ CodeGenerator::visitTestOAndBranch(LTest
     return true;
 
 }
 
 bool
 CodeGenerator::visitTestVAndBranch(LTestVAndBranch *lir)
 {
     OutOfLineTestObject *ool = nullptr;
-    // XXXbz operandMightEmulateUndefined lies a lot.  See bug 1004169.  In
-    // practice, we don't need the OutOfLineTestObject if the input to our test
-    // is not an object.
     MDefinition* input = lir->mir()->input();
+    // Unfortunately, it's possible that someone (e.g. phi elimination) switched
+    // out our input after we did cacheOperandMightEmulateUndefined.  So we
+    // might think it can emulate undefined _and_ know that it can't be an
+    // object.
     if (lir->mir()->operandMightEmulateUndefined() && input->mightBeType(MIRType_Object)) {
         ool = new(alloc()) OutOfLineTestObject();
         if (!addOutOfLineCode(ool))
             return false;
     }
 
     Label *truthy = getJumpLabelForBranch(lir->ifTruthy());
     Label *falsy = getJumpLabelForBranch(lir->ifFalsy());
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -2163,17 +2163,17 @@ IonBuilder::processDoWhileCondEnd(CFGSta
             current = nullptr;
 
             state.loop.successor = successor;
             return processBrokenLoop(state);
         }
     }
 
     // Create the test instruction and end the current block.
-    MTest *test = MTest::New(alloc(), vins, state.loop.entry, successor);
+    MTest *test = newTest(vins, state.loop.entry, successor);
     current->end(test);
     return finishLoop(state, successor);
 }
 
 IonBuilder::ControlStatus
 IonBuilder::processWhileCondEnd(CFGState &state)
 {
     JS_ASSERT(JSOp(*pc) == JSOP_IFNE || JSOp(*pc) == JSOP_IFEQ);
@@ -2184,19 +2184,19 @@ IonBuilder::processWhileCondEnd(CFGState
     // Create the body and successor blocks.
     MBasicBlock *body = newBlock(current, state.loop.bodyStart);
     state.loop.successor = newBlock(current, state.loop.exitpc, loopDepth_ - 1);
     if (!body || !state.loop.successor)
         return ControlStatus_Error;
 
     MTest *test;
     if (JSOp(*pc) == JSOP_IFNE)
-        test = MTest::New(alloc(), ins, body, state.loop.successor);
+        test = newTest(ins, body, state.loop.successor);
     else
-        test = MTest::New(alloc(), ins, state.loop.successor, body);
+        test = newTest(ins, state.loop.successor, body);
     current->end(test);
 
     state.state = CFGState::WHILE_LOOP_BODY;
     state.stopAt = state.loop.bodyEnd;
     pc = state.loop.bodyStart;
     if (!setCurrentAndSpecializePhis(body))
         return ControlStatus_Error;
     return ControlStatus_Jumped;
@@ -2224,17 +2224,17 @@ IonBuilder::processForCondEnd(CFGState &
     MDefinition *ins = current->pop();
 
     // Create the body and successor blocks.
     MBasicBlock *body = newBlock(current, state.loop.bodyStart);
     state.loop.successor = newBlock(current, state.loop.exitpc, loopDepth_ - 1);
     if (!body || !state.loop.successor)
         return ControlStatus_Error;
 
-    MTest *test = MTest::New(alloc(), ins, body, state.loop.successor);
+    MTest *test = newTest(ins, body, state.loop.successor);
     current->end(test);
 
     state.state = CFGState::FOR_LOOP_BODY;
     state.stopAt = state.loop.bodyEnd;
     pc = state.loop.bodyStart;
     if (!setCurrentAndSpecializePhis(body))
         return ControlStatus_Error;
     return ControlStatus_Jumped;
@@ -3347,17 +3347,17 @@ IonBuilder::processCondSwitchCase(CFGSta
     // corresponding to JSOP_CASE bytecode.
     if (bodyBlock != caseBlock) {
         MDefinition *caseOperand = current->pop();
         MDefinition *switchOperand = current->peek(-1);
         MCompare *cmpResult = MCompare::New(alloc(), switchOperand, caseOperand, JSOP_STRICTEQ);
         cmpResult->infer(inspector, pc);
         JS_ASSERT(!cmpResult->isEffectful());
         current->add(cmpResult);
-        current->end(MTest::New(alloc(), cmpResult, bodyBlock, caseBlock));
+        current->end(newTest(cmpResult, bodyBlock, caseBlock));
 
         // Add last case as predecessor of the body if the body is aliasing
         // the previous case body.
         if (!bodyIsNew && !bodyBlock->addPredecessorPopN(alloc(), current, 1))
             return ControlStatus_Error;
 
         // Add last case as predecessor of the non-matching case if the
         // non-matching case is an aliased default case. We need to pop the
@@ -3458,19 +3458,18 @@ IonBuilder::jsop_andor(JSOp op)
     MDefinition *lhs = current->peek(-1);
 
     MBasicBlock *evalRhs = newBlock(current, rhsStart);
     MBasicBlock *join = newBlock(current, joinStart);
     if (!evalRhs || !join)
         return false;
 
     MTest *test = (op == JSOP_AND)
-                  ? MTest::New(alloc(), lhs, evalRhs, join)
-                  : MTest::New(alloc(), lhs, join, evalRhs);
-    test->infer();
+                  ? newTest(lhs, evalRhs, join)
+                  : newTest(lhs, join, evalRhs);
     current->end(test);
 
     if (!cfgStack_.append(CFGState::AndOr(joinStart, join)))
         return false;
 
     return setCurrentAndSpecializePhis(evalRhs);
 }
 
@@ -3511,17 +3510,17 @@ IonBuilder::jsop_ifeq(JSOp op)
     MDefinition *ins = current->pop();
 
     // Create true and false branches.
     MBasicBlock *ifTrue = newBlock(current, trueStart);
     MBasicBlock *ifFalse = newBlock(current, falseStart);
     if (!ifTrue || !ifFalse)
         return false;
 
-    MTest *test = MTest::New(alloc(), ins, ifTrue, ifFalse);
+    MTest *test = newTest(ins, ifTrue, ifFalse);
     current->end(test);
 
     // The bytecode for if/ternary gets emitted either like this:
     //
     //    IFEQ X  ; src note (IF_ELSE, COND) points to the GOTO
     //    ...
     //    GOTO Z
     // X: ...     ; else/else if
@@ -3640,17 +3639,17 @@ IonBuilder::jsop_try()
     if (analysis().maybeInfo(afterTry)) {
         successor = newBlock(current, afterTry);
         if (!successor)
             return false;
 
         // Add MTest(true, tryBlock, successorBlock).
         MConstant *true_ = MConstant::New(alloc(), BooleanValue(true));
         current->add(true_);
-        current->end(MTest::New(alloc(), true_, tryBlock, successor));
+        current->end(newTest(true_, tryBlock, successor));
     } else {
         successor = nullptr;
         current->end(MGoto::New(alloc(), tryBlock));
     }
 
     if (!cfgStack_.append(CFGState::Try(endpc, successor)))
         return false;
 
@@ -6000,16 +5999,24 @@ IonBuilder::newPendingLoopHeader(MBasicB
             if (!phi->addBackedgeType(type, typeSet))
                 return nullptr;
         }
     }
 
     return block;
 }
 
+MTest *
+IonBuilder::newTest(MDefinition *ins, MBasicBlock *ifTrue, MBasicBlock *ifFalse)
+{
+    MTest *test = MTest::New(alloc(), ins, ifTrue, ifFalse);
+    test->cacheOperandMightEmulateUndefined();
+    return test;
+}
+
 // A resume point is a mapping of stack slots to MDefinitions. It is used to
 // capture the environment such that if a guard fails, and IonMonkey needs
 // to exit back to the interpreter, the interpreter state can be
 // reconstructed.
 //
 // We capture stack state at critical points:
 //   * (1) At the beginning of every basic block.
 //   * (2) After every effectful operation.
@@ -8279,17 +8286,17 @@ IonBuilder::jsop_runonce()
 bool
 IonBuilder::jsop_not()
 {
     MDefinition *value = current->pop();
 
     MNot *ins = MNot::New(alloc(), value);
     current->add(ins);
     current->push(ins);
-    ins->infer();
+    ins->cacheOperandMightEmulateUndefined();
     return true;
 }
 
 bool
 IonBuilder::objectsHaveCommonPrototype(types::TemporaryTypeSet *types, PropertyName *name,
                                        bool isGetter, JSObject *foundProto)
 {
     // With foundProto a prototype with a getter or setter for name, return
@@ -9767,17 +9774,17 @@ IonBuilder::jsop_this()
 }
 
 bool
 IonBuilder::jsop_typeof()
 {
     MDefinition *input = current->pop();
     MTypeOf *ins = MTypeOf::New(alloc(), input, input->type());
 
-    ins->infer();
+    ins->cacheInputMaybeCallableOrEmulatesUndefined();
 
     current->add(ins);
     current->push(ins);
 
     return true;
 }
 
 bool
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -284,16 +284,24 @@ class IonBuilder : public MIRGenerator
                                       unsigned stackPhiCount);
     MBasicBlock *newBlock(jsbytecode *pc) {
         return newBlock(nullptr, pc);
     }
     MBasicBlock *newBlockAfter(MBasicBlock *at, jsbytecode *pc) {
         return newBlockAfter(at, nullptr, pc);
     }
 
+    // We want to make sure that our MTest instructions all check whether the
+    // thing being tested might emulate undefined.  So we funnel their creation
+    // through this method, to make sure that happens.  We don't want to just do
+    // the check in MTest::New, because that can run on background compilation
+    // threads, and we're not sure it's safe to touch that part of the typeset
+    // from a background thread.
+    MTest *newTest(MDefinition *ins, MBasicBlock *ifTrue, MBasicBlock *ifFalse);
+
     // Given a list of pending breaks, creates a new block and inserts a Goto
     // linking each break to the new block.
     MBasicBlock *createBreakCatchBlock(DeferredEdge *edge, jsbytecode *pc);
 
     // Finishes loops that do not actually loop, containing only breaks and
     // returns or a do while loop with a condition that is constant false.
     ControlStatus processBrokenLoop(CFGState &state);
 
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -1632,20 +1632,20 @@ IonBuilder::inlineHasClasses(CallInfo &c
             // The following turns into branch-free, box-free code on x86, and should do so on ARM.
             MHasClass *hasClass2 = MHasClass::New(alloc(), callInfo.getArg(0), clasp2);
             current->add(hasClass2);
             MBitOr *either = MBitOr::New(alloc(), hasClass1, hasClass2);
             either->infer(inspector, pc);
             current->add(either);
             // Convert to bool with the '!!' idiom
             MNot *resultInverted = MNot::New(alloc(), either);
-            resultInverted->infer();
+            resultInverted->cacheOperandMightEmulateUndefined();
             current->add(resultInverted);
             MNot *result = MNot::New(alloc(), resultInverted);
-            result->infer();
+            result->cacheOperandMightEmulateUndefined();
             current->add(result);
             current->push(result);
         }
     }
 
     callInfo.setImplicitlyUsedUnchecked();
     return InliningStatus_Inlined;
 }
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -225,17 +225,17 @@ MaybeCallable(MDefinition *op)
 
 MTest *
 MTest::New(TempAllocator &alloc, MDefinition *ins, MBasicBlock *ifTrue, MBasicBlock *ifFalse)
 {
     return new(alloc) MTest(ins, ifTrue, ifFalse);
 }
 
 void
-MTest::infer()
+MTest::cacheOperandMightEmulateUndefined()
 {
     JS_ASSERT(operandMightEmulateUndefined());
 
     if (!MaybeEmulatesUndefined(getOperand(0)))
         markOperandCantEmulateUndefined();
 }
 
 MDefinition *
@@ -2169,17 +2169,17 @@ MTypeOf::foldsTo(TempAllocator &alloc, b
       default:
         return this;
     }
 
     return MConstant::New(alloc, StringValue(TypeName(type, GetIonContext()->runtime->names())));
 }
 
 void
-MTypeOf::infer()
+MTypeOf::cacheInputMaybeCallableOrEmulatesUndefined()
 {
     JS_ASSERT(inputMaybeCallableOrEmulatesUndefined());
 
     if (!MaybeEmulatesUndefined(input()) && !MaybeCallable(input()))
         markInputNotCallableOrEmulatesUndefined();
 }
 
 MBitAnd *
@@ -2664,17 +2664,17 @@ MCompare::filtersUndefinedOrNull(bool tr
     } else {
         *filtersUndefined = *filtersNull = true;
     }
 
     *subject = lhs();
 }
 
 void
-MNot::infer()
+MNot::cacheOperandMightEmulateUndefined()
 {
     JS_ASSERT(operandMightEmulateUndefined());
 
     if (!MaybeEmulatesUndefined(getOperand(0)))
         markOperandCantEmulateUndefined();
 }
 
 MDefinition *
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -1299,17 +1299,23 @@ class MTest
     }
     TypePolicy *typePolicy() {
         return this;
     }
 
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
-    void infer();
+
+    // We cache whether our operand might emulate undefined, but we don't want
+    // to do that from New() or the constructor, since those can be called on
+    // background threads.  So make callers explicitly call it if they want us
+    // to check whether the operand might do this.  If this method is never
+    // called, we'll assume our operand can emulate undefined.
+    void cacheOperandMightEmulateUndefined();
     MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers);
     void filtersUndefinedOrNull(bool trueBranch, MDefinition **subject, bool *filtersUndefined,
                                 bool *filtersNull);
 
     void markOperandCantEmulateUndefined() {
         operandMightEmulateUndefined_ = false;
     }
     bool operandMightEmulateUndefined() const {
@@ -3267,17 +3273,17 @@ class MTypeOf
     TypePolicy *typePolicy() {
         return this;
     }
     MIRType inputType() const {
         return inputType_;
     }
 
     MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers);
-    void infer();
+    void cacheInputMaybeCallableOrEmulatesUndefined();
 
     bool inputMaybeCallableOrEmulatesUndefined() const {
         return inputMaybeCallableOrEmulatesUndefined_;
     }
     void markInputNotCallableOrEmulatesUndefined() {
         inputMaybeCallableOrEmulatesUndefined_ = false;
     }
 
@@ -5834,17 +5840,17 @@ class MNot
     static MNot *NewAsmJS(TempAllocator &alloc, MDefinition *elements) {
         MNot *ins = new(alloc) MNot(elements);
         ins->setResultType(MIRType_Int32);
         return ins;
     }
 
     INSTRUCTION_HEADER(Not);
 
-    void infer();
+    void cacheOperandMightEmulateUndefined();
     MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers);
 
     void markOperandCantEmulateUndefined() {
         operandMightEmulateUndefined_ = false;
     }
     bool operandMightEmulateUndefined() const {
         return operandMightEmulateUndefined_;
     }