Bug 881390 - IonMonkey: Hoist floating-point constants out of loops. r=h4writer
authorDan Gohman <sunfish@google.com>
Thu, 25 Jul 2013 09:40:25 -0700
changeset 152317 01824dc4f1baaecc5192a6a1d3b6cf79bebc0759
parent 152316 21f094a18c5c477788288a22dc355fc1448189a1
child 152318 7ecaf40de72f976cce658b64d2da8265f4ce2331
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersh4writer
bugs881390
milestone25.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 881390 - IonMonkey: Hoist floating-point constants out of loops. r=h4writer
js/src/ion/LICM.cpp
js/src/ion/MIR.h
--- a/js/src/ion/LICM.cpp
+++ b/js/src/ion/LICM.cpp
@@ -47,16 +47,22 @@ class Loop
   private:
     // These blocks define the loop.  header_ points to the loop header
     MBasicBlock *header_;
 
     // The pre-loop block is the first predecessor of the loop header.  It is where
     // the loop is first entered and where hoisted instructions will be placed.
     MBasicBlock* preLoop_;
 
+    // This indicates whether the loop contains calls or other things which
+    // clobber most or all floating-point registers. In such loops,
+    // floating-point constants should not be hoisted unless it enables further
+    // hoisting.
+    bool containsPossibleCall_;
+
     bool hoistInstructions(InstructionQueue &toHoist);
 
     // Utility methods for invariance testing and instruction hoisting.
     bool isInLoop(MDefinition *ins);
     bool isBeforeLoop(MDefinition *ins);
     bool isLoopInvariant(MInstruction *ins);
     bool isLoopInvariant(MDefinition *ins);
 
@@ -67,16 +73,18 @@ class Loop
     // Worklist and worklist usage methods
     InstructionQueue worklist_;
     bool insertInWorklist(MInstruction *ins);
     MInstruction* popFromWorklist();
 
     inline bool isHoistable(const MDefinition *ins) const {
         return ins->isMovable() && !ins->isEffectful() && !ins->neverHoist();
     }
+
+    bool requiresHoistedUse(const MDefinition *ins) const;
 };
 
 } /* namespace anonymous */
 
 LICM::LICM(MIRGenerator *mir, MIRGraph &graph)
   : mir(mir), graph(graph)
 {
 }
@@ -111,17 +119,18 @@ LICM::analyze()
         graph.unmarkBlocks();
     }
 
     return true;
 }
 
 Loop::Loop(MIRGenerator *mir, MBasicBlock *header)
   : mir(mir),
-    header_(header)
+    header_(header),
+    containsPossibleCall_(false)
 {
     preLoop_ = header_->getPredecessor(0);
 }
 
 Loop::LoopReturn
 Loop::init()
 {
     IonSpew(IonSpew_LICM, "Loop identified, headed by block %d", header_->id());
@@ -169,16 +178,21 @@ Loop::init()
         // If any block was added, process them first.
         if (block != inlooplist.back())
             continue;
 
         // Add all instructions in this block (but the control instruction) to the worklist
         for (MInstructionIterator i = block->begin(); i != block->end(); i++) {
             MInstruction *ins = *i;
 
+            // Remember whether this loop contains anything which clobbers most
+            // or all floating-point registers. This is just a rough heuristic.
+            if (ins->possiblyCalls())
+                containsPossibleCall_ = true;
+
             if (isHoistable(ins)) {
                 if (!insertInWorklist(ins))
                     return LoopReturn_Error;
             }
         }
 
         // All successors of this block are visited.
         inlooplist.popBack();
@@ -221,26 +235,42 @@ Loop::optimize()
     }
 
     if (!hoistInstructions(invariantInstructions))
         return false;
     return true;
 }
 
 bool
+Loop::requiresHoistedUse(const MDefinition *ins) const
+{
+    if (ins->isConstantElements() || ins->isBox())
+        return true;
+
+    // Integer constants can often be folded as immediates and aren't worth
+    // hoisting on their own, in general. Floating-point constants typically
+    // are worth hoisting, unless they'll end up being spilled (eg. due to a
+    // call).
+    if (ins->isConstant() && (ins->type() != MIRType_Double || containsPossibleCall_))
+        return true;
+
+    return false;
+}
+
+bool
 Loop::hoistInstructions(InstructionQueue &toHoist)
 {
     // Iterate in post-order (uses before definitions)
     for (int32_t i = toHoist.length() - 1; i >= 0; i--) {
         MInstruction *ins = toHoist[i];
 
-        // Don't hoist MConstantElements, MConstant and MBox
-        // if it doesn't enable us to hoist one of its uses.
-        // We want those instructions as close as possible to their use.
-        if (ins->isConstantElements() || ins->isConstant() || ins->isBox()) {
+        // Don't hoist a cheap constant if it doesn't enable us to hoist one of
+        // its uses. We want those instructions as close as possible to their
+        // use, to facilitate folding and minimize register pressure.
+        if (requiresHoistedUse(ins)) {
             bool loopInvariantUse = false;
             for (MUseDefIterator use(ins); use; use++) {
                 if (use.def()->isLoopInvariant()) {
                     loopInvariantUse = true;
                     break;
                 }
             }
 
--- a/js/src/ion/MIR.h
+++ b/js/src/ion/MIR.h
@@ -336,16 +336,22 @@ class MDefinition : public MNode
     void printName(FILE *fp) const;
     static void PrintOpcodeName(FILE *fp, Opcode op);
     virtual void printOpcode(FILE *fp) const;
     void dump(FILE *fp) const;
 
     // For LICM.
     virtual bool neverHoist() const { return false; }
 
+    // Also for LICM. Test whether this definition is likely to be a call, which
+    // would clobber all or many of the floating-point registers, such that
+    // hoisting floating-point constants out of containing loops isn't likely to
+    // be worthwhile.
+    virtual bool possiblyCalls() const { return false; }
+
     void setTrackedPc(jsbytecode *pc) {
         trackedPc_ = pc;
     }
 
     jsbytecode *trackedPc() {
         return trackedPc_;
     }
 
@@ -1108,16 +1114,19 @@ class MThrow
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
     virtual AliasSet getAliasSet() const {
         return AliasSet::None();
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MNewParallelArray : public MNullaryInstruction
 {
     CompilerRootObject templateObject_;
 
     MNewParallelArray(JSObject *templateObject)
       : templateObject_(templateObject)
@@ -1308,16 +1317,19 @@ class MInitProp
     }
 
     PropertyName *propertyName() const {
         return name_;
     }
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MInitElem
   : public MAryInstruction<3>,
     public Mix3Policy<ObjectPolicy<0>, BoxPolicy<1>, BoxPolicy<2> >
 {
     MInitElem(MDefinition *obj, MDefinition *id, MDefinition *value)
     {
@@ -1341,16 +1353,19 @@ class MInitElem
         return getOperand(1);
     }
     MDefinition *getValue() const {
         return getOperand(2);
     }
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 // Designates the start of call frame construction.
 // Generates code to adjust the stack pointer for the argument vector.
 // Argc is inferred by checking the use chain during lowering.
 class MPrepareCall : public MNullaryInstruction
 {
   public:
@@ -1494,16 +1509,20 @@ class MCall
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
     AliasSet getAliasSet() const {
         return AliasSet::Store(AliasSet::Any);
     }
+
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 // fun.apply(self, arguments)
 class MApplyArgs
   : public MAryInstruction<3>,
     public MixPolicy<ObjectPolicy<0>, MixPolicy<IntPolicy<1>, BoxPolicy<2> > >
 {
   protected:
@@ -1538,16 +1557,19 @@ class MApplyArgs
     }
     MDefinition *getThis() const {
         return getOperand(2);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MGetDynamicName
   : public MAryInstruction<2>,
     public MixPolicy<ObjectPolicy<0>, StringPolicy<1> >
 {
   protected:
     MGetDynamicName(MDefinition *scopeChain, MDefinition *name)
@@ -1570,16 +1592,19 @@ class MGetDynamicName
     }
     MDefinition *getName() const {
         return getOperand(1);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 // Bailout if the input string contains 'arguments'
 class MFilterArguments
   : public MAryInstruction<1>,
     public StringPolicy<0>
 {
   protected:
@@ -1600,16 +1625,19 @@ class MFilterArguments
 
     MDefinition *getString() const {
         return getOperand(0);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MCallDirectEval
   : public MAryInstruction<3>,
     public MixPolicy<ObjectPolicy<0>, MixPolicy<StringPolicy<1>, BoxPolicy<2> > >
 {
   protected:
     MCallDirectEval(MDefinition *scopeChain, MDefinition *string, MDefinition *thisValue,
@@ -1644,16 +1672,20 @@ class MCallDirectEval
     jsbytecode  *pc() const {
         return pc_;
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
 
+    bool possiblyCalls() const {
+        return true;
+    }
+
   private:
     jsbytecode *pc_;
 };
 
 class MBinaryInstruction : public MAryInstruction<2>
 {
   protected:
     MBinaryInstruction(MDefinition *left, MDefinition *right)
@@ -2177,16 +2209,19 @@ class MCreateThisWithProto
 
     // Although creation of |this| modifies global state, it is safely repeatable.
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 // Caller-side allocation of |this| for |new|:
 // Constructs |this| when possible, else MagicValue(JS_IS_CONSTRUCTING).
 class MCreateThis
   : public MUnaryInstruction,
     public ObjectPolicy<0>
 {
@@ -2209,16 +2244,19 @@ class MCreateThis
 
     // Although creation of |this| modifies global state, it is safely repeatable.
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 // Eager initialization of arguments object.
 class MCreateArgumentsObject
   : public MUnaryInstruction,
     public ObjectPolicy<0>
 {
     MCreateArgumentsObject(MDefinition *callObj)
@@ -2240,16 +2278,19 @@ class MCreateArgumentsObject
 
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MGetArgumentsObjectArg
   : public MUnaryInstruction,
     public ObjectPolicy<0>
 {
     size_t argno_;
 
@@ -2334,16 +2375,19 @@ class MRunOncePrologue
     }
 
   public:
     INSTRUCTION_HEADER(RunOncePrologue)
 
     static MRunOncePrologue *New() {
         return new MRunOncePrologue();
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 // Given a MIRType_Value A and a MIRType_Object B:
 // If the Value may be safely unboxed to an Object, return Object(A).
 // Otherwise, return B.
 // Used to implement return behavior for inlined constructors.
 class MReturnFromCtor
   : public MAryInstruction<2>,
@@ -3115,16 +3159,20 @@ class MAtan2
 
     bool congruentTo(MDefinition *const &ins) const {
         return congruentIfOperandsEqual(ins);
     }
 
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
+
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 // Inline implementation of Math.pow().
 class MPow
   : public MBinaryInstruction,
     public PowPolicy
 {
     MPow(MDefinition *input, MDefinition *power, MIRType powerType)
@@ -3151,16 +3199,19 @@ class MPow
         return congruentIfOperandsEqual(ins);
     }
     TypePolicy *typePolicy() {
         return this;
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 // Inline implementation of Math.pow(x, 0.5), which subtly differs from Math.sqrt(x).
 class MPowHalf
   : public MUnaryInstruction,
     public DoublePolicy<0>
 {
     MPowHalf(MDefinition *input)
@@ -3198,16 +3249,20 @@ class MRandom : public MNullaryInstructi
     INSTRUCTION_HEADER(Random)
     static MRandom *New() {
         return new MRandom;
     }
 
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
+
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MMathFunction
   : public MUnaryInstruction,
     public DoublePolicy<0>
 {
   public:
     enum Function {
@@ -3265,16 +3320,20 @@ class MMathFunction
         if (ins->toMathFunction()->function() != function())
             return false;
         return congruentIfOperandsEqual(ins);
     }
 
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
+
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MAdd : public MBinaryArithInstruction
 {
     // Is this instruction really an int at heart?
     MAdd(MDefinition *left, MDefinition *right)
       : MBinaryArithInstruction(left, right)
     {
@@ -3944,17 +4003,19 @@ class MDefVar : public MUnaryInstruction
         return name_;
     }
     unsigned attrs() const {
         return attrs_;
     }
     MDefinition *scopeChain() const {
         return getOperand(0);
     }
-
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MDefFun : public MUnaryInstruction
 {
     CompilerRootFunction fun_;
 
   private:
     MDefFun(HandleFunction fun, MDefinition *scopeChain)
@@ -3970,16 +4031,19 @@ class MDefFun : public MUnaryInstruction
     }
 
     JSFunction *fun() const {
         return fun_;
     }
     MDefinition *scopeChain() const {
         return getOperand(0);
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MRegExp : public MNullaryInstruction
 {
     CompilerRoot<RegExpObject *> source_;
     CompilerRootObject prototype_;
 
     MRegExp(RegExpObject *source, JSObject *prototype)
@@ -4003,16 +4067,19 @@ class MRegExp : public MNullaryInstructi
         return source_;
     }
     JSObject *getRegExpPrototype() const {
         return prototype_;
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MRegExpTest
   : public MBinaryInstruction,
     public MixPolicy<ObjectPolicy<1>, StringPolicy<0> >
 {
   private:
 
@@ -4034,16 +4101,20 @@ class MRegExpTest
     }
     MDefinition *regexp() const {
         return getOperand(1);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
+
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MLambda
   : public MUnaryInstruction,
     public SingleObjectPolicy
 {
     CompilerRootFunction fun_;
 
@@ -4952,16 +5023,19 @@ class MArrayConcat
         return templateObj_;
     }
     TypePolicy *typePolicy() {
         return this;
     }
     AliasSet getAliasSet() const {
         return AliasSet::Store(AliasSet::Element | AliasSet::ObjectFields);
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MLoadTypedArrayElement
   : public MBinaryInstruction
 {
     int arrayType_;
 
     MLoadTypedArrayElement(MDefinition *elements, MDefinition *index, int arrayType)
@@ -6325,16 +6399,20 @@ class MForkJoinSlice
 
     INSTRUCTION_HEADER(ForkJoinSlice);
 
     AliasSet getAliasSet() const {
         // Indicate that this instruction reads nothing, stores nothing.
         // (For all intents and purposes)
         return AliasSet::None();
     }
+
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 // Store to vp[slot] (slots that are not inline in an object).
 class MStoreSlot
   : public MBinaryInstruction,
     public SingleObjectPolicy
 {
     uint32_t slot_;
@@ -6449,16 +6527,19 @@ class MCallGetIntrinsicValue : public MN
         return new MCallGetIntrinsicValue(name);
     }
     PropertyName *name() const {
         return name_;
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MCallsiteCloneCache
   : public MUnaryInstruction,
     public SingleObjectPolicy
 {
     jsbytecode *callPc_;
 
@@ -6593,16 +6674,19 @@ class MCallSetProperty
 
     static MCallSetProperty *New(MDefinition *obj, MDefinition *value, HandlePropertyName name, bool strict) {
         return new MCallSetProperty(obj, value, name, strict);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MSetPropertyCache
   : public MSetPropertyInstruction,
     public SingleObjectPolicy
 {
     MSetPropertyCache(MDefinition *obj, MDefinition *value, HandlePropertyName name, bool strict)
       : MSetPropertyInstruction(obj, value, name, strict)
@@ -6686,16 +6770,19 @@ class MCallGetProperty
     void setIdempotent() {
         idempotent_ = true;
     }
     AliasSet getAliasSet() const {
         if (!idempotent_)
             return AliasSet::Store(AliasSet::Any);
         return AliasSet::None();
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 // Inline call to handle lhs[rhs]. The first input is a Value so that this
 // instruction can handle both objects and strings.
 class MCallGetElement
   : public MBinaryInstruction,
     public BoxInputsPolicy
 {
@@ -6709,16 +6796,19 @@ class MCallGetElement
     INSTRUCTION_HEADER(CallGetElement)
 
     static MCallGetElement *New(MDefinition *lhs, MDefinition *rhs) {
         return new MCallGetElement(lhs, rhs);
     }
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MCallSetElement
   : public MSetElementInstruction,
     public CallSetElementPolicy
 {
     MCallSetElement(MDefinition *object, MDefinition *index, MDefinition *value)
       : MSetElementInstruction(object, index, value)
@@ -6730,16 +6820,19 @@ class MCallSetElement
 
     static MCallSetElement *New(MDefinition *object, MDefinition *index, MDefinition *value) {
         return new MCallSetElement(object, index, value);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MCallInitElementArray
   : public MAryInstruction<2>,
     public MixPolicy<ObjectPolicy<0>, BoxPolicy<1> >
 {
     uint32_t index_;
 
@@ -6768,16 +6861,19 @@ class MCallInitElementArray
 
     MDefinition *value() const {
         return getOperand(1);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MSetDOMProperty
   : public MAryInstruction<2>,
     public MixPolicy<ObjectPolicy<0>, BoxPolicy<1> >
 {
     const JSJitSetterOp func_;
 
@@ -6807,16 +6903,20 @@ class MSetDOMProperty
     MDefinition *value()
     {
         return getOperand(1);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
+
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MGetDOMProperty
   : public MAryInstruction<2>,
     public ObjectPolicy<0>
 {
     const JSJitInfo *info_;
 
@@ -6892,16 +6992,19 @@ class MGetDOMProperty
             return AliasSet::None();
         // Pure DOM attributes can only alias things that alias the world or
         // explicitly alias DOM properties.
         if (isDomPure())
             return AliasSet::Load(AliasSet::DOMProperty);
         return AliasSet::Store(AliasSet::Any);
     }
 
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MStringLength
   : public MUnaryInstruction,
     public StringPolicy<0>
 {
     MStringLength(MDefinition *string)
       : MUnaryInstruction(string)
@@ -7102,16 +7205,19 @@ class MIn
         setResultType(MIRType_Boolean);
     }
 
     INSTRUCTION_HEADER(In)
 
     TypePolicy *typePolicy() {
         return this;
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 
 // Test whether the index is in the array bounds or a hole.
 class MInArray
   : public MQuaternaryInstruction,
     public ObjectPolicy<3>
 {
@@ -7318,16 +7424,19 @@ class MRest
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MRestPar
   : public MBinaryInstruction,
     public MRestCommon,
     public IntPolicy<1>
 {
     MRestPar(MDefinition *slice, MDefinition *numActuals, unsigned numFormals,
@@ -7359,16 +7468,19 @@ class MRestPar
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 // Guard on an object being allocated in the current slice.
 class MGuardThreadLocalObject
   : public MBinaryInstruction,
     public ObjectPolicy<1>
 {
     MGuardThreadLocalObject(MDefinition *slice, MDefinition *obj)
@@ -7392,16 +7504,19 @@ class MGuardThreadLocalObject
         return getOperand(1);
     }
     BailoutKind bailoutKind() const {
         return Bailout_Normal;
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 // Given a value, guard that the value is in a particular TypeSet, then returns
 // that value.
 class MTypeBarrier
   : public MUnaryInstruction,
     public BoxInputsPolicy
 {
@@ -7532,16 +7647,19 @@ class MNewSlots : public MNullaryInstruc
         return new MNewSlots(nslots);
     }
     unsigned nslots() const {
         return nslots_;
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 class MNewDeclEnvObject : public MNullaryInstruction
 {
     CompilerRootObject templateObj_;
 
     MNewDeclEnvObject(HandleObject templateObj)
       : MNullaryInstruction(),
@@ -7764,16 +7882,20 @@ class MNewDenseArrayPar : public MBinary
 
     MDefinition *length() const {
         return getOperand(1);
     }
 
     JSObject *templateObject() const {
         return templateObject_;
     }
+
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 // A resume point contains the information needed to reconstruct the interpreter
 // state from a position in the JIT. See the big comment near resumeAfter() in
 // IonBuilder.cpp.
 class MResumePoint : public MNode, public InlineForwardListNode<MResumePoint>
 {
   public:
@@ -8276,16 +8398,20 @@ class MAsmJSCall MOZ_FINAL : public MIns
     size_t dynamicCalleeOperandIndex() const {
         JS_ASSERT(callee_.which() == Callee::Dynamic);
         JS_ASSERT(numArgs_ == numOperands_ - 1);
         return numArgs_;
     }
     size_t spIncrement() const {
         return spIncrement_;
     }
+
+    bool possiblyCalls() const {
+        return true;
+    }
 };
 
 // The asm.js version doesn't use the bail mechanism: instead it throws and
 // exception by jumping to the given label.
 class MAsmJSCheckOverRecursed : public MNullaryInstruction
 {
     Label *onError_;
     MAsmJSCheckOverRecursed(Label *onError) : onError_(onError) {}