merge m-c to inbound
authorWes Kocher <wkocher@mozilla.com>
Fri, 02 May 2014 23:07:16 -0700
changeset 181838 8a21f9e10a75
parent 181837 8d591a3f6fea (current diff)
parent 181819 2fbc044027e6 (diff)
child 181839 cc42442f4728
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
milestone32.0a1
merge m-c to inbound
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -2961,16 +2961,19 @@ class CGWrapGlobalMethod(CGAbstractMetho
               JS::Rooted<JSObject*> obj(aCx);
               obj = CreateGlobal<${nativeType}, GetProtoObject>(aCx,
                                                      aObject,
                                                      aCache,
                                                      Class.ToJSClass(),
                                                      aOptions,
                                                      aPrincipal);
 
+              // obj is a new global, so has a new compartment.  Enter it
+              // before doing anything with it.
+              JSAutoCompartment ac(aCx, obj);
               $*{unforgeable}
 
               $*{slots}
 
               // XXXkhuey can't do this yet until workers can lazy resolve.
               // JS_FireOnNewGlobalObject(aCx, obj);
 
               return obj;
--- a/js/src/devtools/rootAnalysis/run_complete
+++ b/js/src/devtools/rootAnalysis/run_complete
@@ -194,32 +194,39 @@ sub get_manager_address
     my $log_data = `cat $log_file`;
     my ($port) = $log_data =~ /Listening on ([\.\:0-9]*)/
       or die "no manager found";
     print OUT "Connecting to manager on port $port\n" unless $suppress_logs;
     print "Connecting to manager on port $port.\n";
     return $1;
 }
 
+sub logging_suffix {
+    my ($show_logs, $log_file) = @_;
+    return $show_logs ? "2>&1 | tee $log_file"
+                      : "> $log_file 2>&1";
+}
+
 sub run_build
 {
     print "build started: ";
     print scalar(localtime());
     print "\n";
 
     # fork off a process to run the build.
     defined(my $pid = fork) or die;
 
     # log file for the manager.
     my $manager_log_file = "$result_dir/build_manager.log";
 
     if (!$pid) {
         # this is the child process, fork another process to run a manager.
         defined(my $pid = fork) or die;
-        exec("$xmanager -terminate-on-assert > $manager_log_file 2>&1") if (!$pid);
+        my $logging = logging_suffix($suppress_logs, $manager_log_file);
+        exec("$xmanager -terminate-on-assert $logging") if (!$pid);
         $kill_on_exit{$pid} = 1;
 
         if (!$suppress_logs) {
             # open new streams to redirect stdout and stderr.
             open(LOGOUT, "> $result_dir/build.log");
             open(LOGERR, "> $result_dir/build_err.log");
             STDOUT->fdopen(\*LOGOUT, "w");
             STDERR->fdopen(\*LOGERR, "w");
@@ -281,17 +288,18 @@ sub run_pass
     my $log_file = "$result_dir/manager.$name.log";
 
     # extra commands to pass to the manager.
     my $manager_extra = "";
     $manager_extra .= "-modset-wait=10" if ($name eq "xmemlocal");
 
     # fork off a manager process for the analysis.
     defined(my $pid = fork) or die;
-    exec("$xmanager $manager_extra > $log_file 2>&1") if (!$pid);
+    my $logging = logging_suffix($suppress_logs, $log_file);
+    exec("$xmanager $manager_extra $logging") if (!$pid);
 
     my $address = get_manager_address($log_file);
 
     # write the poll file for this pass.
     if (! -d dirname($poll_file)) {
         system("mkdir", "-p", dirname($poll_file));
     }
     open(POLL, "> $poll_file");
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -503,74 +503,145 @@ CodeGenerator::testObjectEmulatesUndefin
     masm.jump(ifDoesntEmulateUndefined);
 }
 
 void
 CodeGenerator::testValueTruthyKernel(const ValueOperand &value,
                                      const LDefinition *scratch1, const LDefinition *scratch2,
                                      FloatRegister fr,
                                      Label *ifTruthy, Label *ifFalsy,
-                                     OutOfLineTestObject *ool)
-{
+                                     OutOfLineTestObject *ool,
+                                     MDefinition *valueMIR)
+{
+    // Count the number of possible type tags we might have, so we'll know when
+    // we've checked them all and hence can avoid emitting a tag check for the
+    // last one.  In particular, whenever tagCount is 1 that means we've tried
+    // all but one of them already so we know exactly what's left based on the
+    // mightBe* booleans.
+    bool mightBeUndefined = valueMIR->mightBeType(MIRType_Undefined);
+    bool mightBeNull = valueMIR->mightBeType(MIRType_Null);
+    bool mightBeBoolean = valueMIR->mightBeType(MIRType_Boolean);
+    bool mightBeInt32 = valueMIR->mightBeType(MIRType_Int32);
+    bool mightBeObject = valueMIR->mightBeType(MIRType_Object);
+    bool mightBeString = valueMIR->mightBeType(MIRType_String);
+    bool mightBeDouble = valueMIR->mightBeType(MIRType_Double);
+    int tagCount = int(mightBeUndefined) + int(mightBeNull) +
+        int(mightBeBoolean) + int(mightBeInt32) + int(mightBeObject) +
+        int(mightBeString) + int(mightBeDouble);
+
+    MOZ_ASSERT_IF(!valueMIR->emptyResultTypeSet(), tagCount > 0);
+
+    // If we know we're null or undefined, we're definitely falsy, no
+    // need to even check the tag.
+    if (int(mightBeNull) + int(mightBeUndefined) == tagCount) {
+        masm.jump(ifFalsy);
+        return;
+    }
+
     Register tag = masm.splitTagForTest(value);
 
-    // Eventually we will want some sort of type filter here. For now, just
-    // emit all easy cases. For speed we use the cached tag for all comparison,
-    // except for doubles, which we test last (as the operation can clobber the
-    // tag, which may be in ScratchReg).
-    masm.branchTestUndefined(Assembler::Equal, tag, ifFalsy);
-    masm.branchTestNull(Assembler::Equal, tag, ifFalsy);
-
-    Label notBoolean;
-    masm.branchTestBoolean(Assembler::NotEqual, tag, &notBoolean);
-    masm.branchTestBooleanTruthy(false, value, ifFalsy);
-    masm.jump(ifTruthy);
-    masm.bind(&notBoolean);
-
-    Label notInt32;
-    masm.branchTestInt32(Assembler::NotEqual, tag, &notInt32);
-    masm.branchTestInt32Truthy(false, value, ifFalsy);
-    masm.jump(ifTruthy);
-    masm.bind(&notInt32);
-
-    if (ool) {
-        Label notObject;
-
-        masm.branchTestObject(Assembler::NotEqual, tag, &notObject);
-
-        Register objreg = masm.extractObject(value, ToRegister(scratch1));
-        testObjectEmulatesUndefined(objreg, ifFalsy, ifTruthy, ToRegister(scratch2), ool);
-
-        masm.bind(&notObject);
+    if (mightBeUndefined) {
+        MOZ_ASSERT(tagCount > 1);
+        masm.branchTestUndefined(Assembler::Equal, tag, ifFalsy);
+        --tagCount;
+    }
+
+    if (mightBeNull) {
+        MOZ_ASSERT(tagCount > 1);
+        masm.branchTestNull(Assembler::Equal, tag, ifFalsy);
+        --tagCount;
+    }
+
+    if (mightBeBoolean) {
+        MOZ_ASSERT(tagCount != 0);
+        Label notBoolean;
+        if (tagCount != 1)
+            masm.branchTestBoolean(Assembler::NotEqual, tag, &notBoolean);
+        masm.branchTestBooleanTruthy(false, value, ifFalsy);
+        if (tagCount != 1)
+            masm.jump(ifTruthy);
+        // Else just fall through to truthiness.
+        masm.bind(&notBoolean);
+        --tagCount;
+    }
+
+    if (mightBeInt32) {
+        MOZ_ASSERT(tagCount != 0);
+        Label notInt32;
+        if (tagCount != 1)
+            masm.branchTestInt32(Assembler::NotEqual, tag, &notInt32);
+        masm.branchTestInt32Truthy(false, value, ifFalsy);
+        if (tagCount != 1)
+            masm.jump(ifTruthy);
+        // Else just fall through to truthiness.
+        masm.bind(&notInt32);
+        --tagCount;
+    }
+
+    if (mightBeObject) {
+        MOZ_ASSERT(tagCount != 0);
+        if (ool) {
+            Label notObject;
+
+            if (tagCount != 1)
+                masm.branchTestObject(Assembler::NotEqual, tag, &notObject);
+
+            Register objreg = masm.extractObject(value, ToRegister(scratch1));
+            testObjectEmulatesUndefined(objreg, ifFalsy, ifTruthy, ToRegister(scratch2), ool);
+
+            masm.bind(&notObject);
+        } else {
+            if (tagCount != 1)
+                masm.branchTestObject(Assembler::Equal, tag, ifTruthy);
+            // Else just fall through to truthiness.
+        }
+        --tagCount;
     } else {
-        masm.branchTestObject(Assembler::Equal, tag, ifTruthy);
-    }
-
-    // Test if a string is non-empty.
-    Label notString;
-    masm.branchTestString(Assembler::NotEqual, tag, &notString);
-    masm.branchTestStringTruthy(false, value, ifFalsy);
-    masm.jump(ifTruthy);
-    masm.bind(&notString);
-
-    // If we reach here the value is a double.
-    masm.unboxDouble(value, fr);
-    masm.branchTestDoubleTruthy(false, fr, ifFalsy);
+        MOZ_ASSERT(!ool,
+                   "We better not have an unused OOL path, since the code generator will try to "
+                   "generate code for it but we never set up its labels, which will cause null "
+                   "derefs of those labels.");
+    }
+
+    if (mightBeString) {
+        // Test if a string is non-empty.
+        MOZ_ASSERT(tagCount != 0);
+        Label notString;
+        if (tagCount != 1)
+            masm.branchTestString(Assembler::NotEqual, tag, &notString);
+        masm.branchTestStringTruthy(false, value, ifFalsy);
+        if (tagCount != 1)
+            masm.jump(ifTruthy);
+        // Else just fall through to truthiness.
+        masm.bind(&notString);
+        --tagCount;
+    }
+
+    if (mightBeDouble) {
+        MOZ_ASSERT(tagCount == 1);
+        // If we reach here the value is a double.
+        masm.unboxDouble(value, fr);
+        masm.branchTestDoubleTruthy(false, fr, ifFalsy);
+        --tagCount;
+    }
+
+    MOZ_ASSERT(tagCount == 0);
 
     // Fall through for truthy.
 }
 
 void
 CodeGenerator::testValueTruthy(const ValueOperand &value,
                                const LDefinition *scratch1, const LDefinition *scratch2,
                                FloatRegister fr,
                                Label *ifTruthy, Label *ifFalsy,
-                               OutOfLineTestObject *ool)
-{
-    testValueTruthyKernel(value, scratch1, scratch2, fr, ifTruthy, ifFalsy, ool);
+                               OutOfLineTestObject *ool,
+                               MDefinition *valueMIR)
+{
+    testValueTruthyKernel(value, scratch1, scratch2, fr, ifTruthy, ifFalsy, ool, valueMIR);
     masm.jump(ifTruthy);
 }
 
 Label *
 CodeGenerator::getJumpLabelForBranch(MBasicBlock *block)
 {
     if (!labelForBackedgeWithImplicitCheck(block))
         return block->lir()->label();
@@ -607,29 +678,34 @@ CodeGenerator::visitTestOAndBranch(LTest
     return true;
 
 }
 
 bool
 CodeGenerator::visitTestVAndBranch(LTestVAndBranch *lir)
 {
     OutOfLineTestObject *ool = nullptr;
-    if (lir->mir()->operandMightEmulateUndefined()) {
+    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());
 
     testValueTruthy(ToValue(lir, LTestVAndBranch::Input),
                     lir->temp1(), lir->temp2(),
                     ToFloatRegister(lir->tempFloat()),
-                    truthy, falsy, ool);
+                    truthy, falsy, ool, input);
     return true;
 }
 
 bool
 CodeGenerator::visitFunctionDispatch(LFunctionDispatch *lir)
 {
     MFunctionDispatch *mir = lir->mir();
     Register input = ToRegister(lir->input());
@@ -5286,31 +5362,32 @@ bool
 CodeGenerator::visitNotV(LNotV *lir)
 {
     Maybe<Label> ifTruthyLabel, ifFalsyLabel;
     Label *ifTruthy;
     Label *ifFalsy;
 
     OutOfLineTestObjectWithLabels *ool = nullptr;
     if (lir->mir()->operandMightEmulateUndefined()) {
+        MOZ_ASSERT(lir->mir()->operand()->mightBeType(MIRType_Object));
         ool = new(alloc()) OutOfLineTestObjectWithLabels();
         if (!addOutOfLineCode(ool))
             return false;
         ifTruthy = ool->label1();
         ifFalsy = ool->label2();
     } else {
         ifTruthyLabel.construct();
         ifFalsyLabel.construct();
         ifTruthy = ifTruthyLabel.addr();
         ifFalsy = ifFalsyLabel.addr();
     }
 
     testValueTruthyKernel(ToValue(lir, LNotV::Input), lir->temp1(), lir->temp2(),
                           ToFloatRegister(lir->tempFloat()),
-                          ifTruthy, ifFalsy, ool);
+                          ifTruthy, ifFalsy, ool, lir->mir()->operand());
 
     Label join;
     Register output = ToRegister(lir->output());
 
     // Note that the testValueTruthyKernel call above may choose to fall through
     // to ifTruthy instead of branching there.
     masm.bind(ifTruthy);
     masm.move32(Imm32(0), output);
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -384,28 +384,30 @@ class CodeGenerator : public CodeGenerat
     // This function behaves like testValueTruthy with the exception that it can
     // choose to let control flow fall through when the object is truthy, as
     // an optimization. Use testValueTruthy when it's required to branch to one
     // of the two labels.
     void testValueTruthyKernel(const ValueOperand &value,
                                const LDefinition *scratch1, const LDefinition *scratch2,
                                FloatRegister fr,
                                Label *ifTruthy, Label *ifFalsy,
-                               OutOfLineTestObject *ool);
+                               OutOfLineTestObject *ool,
+                               MDefinition *valueMIR);
 
     // Test whether value is truthy or not and jump to the corresponding label.
     // If the value can be an object that emulates |undefined|, |ool| must be
     // non-null; otherwise it may be null (and the scratch definitions should
     // be bogus), in which case an object encountered here will always be
     // truthy.
     void testValueTruthy(const ValueOperand &value,
                          const LDefinition *scratch1, const LDefinition *scratch2,
                          FloatRegister fr,
                          Label *ifTruthy, Label *ifFalsy,
-                         OutOfLineTestObject *ool);
+                         OutOfLineTestObject *ool,
+                         MDefinition *valueMIR);
 
     // This function behaves like testObjectEmulatesUndefined with the exception
     // that it can choose to let control flow fall through when the object
     // doesn't emulate undefined, as an optimization. Use the regular
     // testObjectEmulatesUndefined when it's required to branch to one of the
     // two labels.
     void testObjectEmulatesUndefinedKernel(Register objreg,
                                            Label *ifEmulatesUndefined,
--- 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_;
     }