Bug 862103 - Various benchmark performance fixes.
authorBrian Hackett <bhackett1024@gmail.com>
Mon, 15 Apr 2013 17:12:51 -0600
changeset 129598 d746d516bf55420ebc686f206164973e2d791913
parent 129597 cb36ad241f80bfceabfef7c03e97e18000e8fe8a
child 129599 ae6a2cf914f7f4b964c9dc265cf3c11e149fb49e
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
bugs862103
milestone23.0a1
Bug 862103 - Various benchmark performance fixes.
js/src/ion/BaselineInspector.cpp
js/src/ion/BaselineInspector.h
js/src/ion/IonBuilder.cpp
js/src/ion/IonBuilder.h
js/src/ion/LOpcodes.h
js/src/ion/MCallOptimize.cpp
js/src/ion/MIR.cpp
js/src/ion/MIR.h
js/src/ion/MOpcodes.h
js/src/ion/ParallelArrayAnalysis.cpp
js/src/ion/arm/CodeGenerator-arm.cpp
js/src/ion/arm/CodeGenerator-arm.h
js/src/ion/arm/LIR-arm.h
js/src/ion/arm/Lowering-arm.cpp
js/src/ion/arm/Lowering-arm.h
js/src/ion/shared/CodeGenerator-x86-shared.cpp
js/src/ion/shared/CodeGenerator-x86-shared.h
js/src/ion/shared/LIR-x86-shared.h
js/src/ion/shared/Lowering-x86-shared.cpp
js/src/ion/shared/Lowering-x86-shared.h
js/src/jsinfer.cpp
--- a/js/src/ion/BaselineInspector.cpp
+++ b/js/src/ion/BaselineInspector.cpp
@@ -54,34 +54,62 @@ BaselineInspector::maybeMonomorphicShape
         if (next->toSetProp_Fallback()->hadUnoptimizableAccess())
             return NULL;
         return stub->toSetProp_Native()->shape();
     }
 
     return NULL;
 }
 
-MIRType
-BaselineInspector::expectedResultType(jsbytecode *pc)
+ICStub::Kind
+BaselineInspector::monomorphicStubKind(jsbytecode *pc)
 {
-    // Look at the IC entries for this op to guess what type it will produce,
-    // returning MIRType_None otherwise.
-
     if (!hasBaselineScript())
-        return MIRType_None;
+        return ICStub::INVALID;
 
     const ICEntry &entry = icEntryFromPC(pc);
 
     ICStub *stub = entry.firstStub();
     ICStub *next = stub->next();
 
     if (!next || !next->isFallback())
-        return MIRType_None;
+        return ICStub::INVALID;
+
+    return stub->kind();
+}
+
+bool
+BaselineInspector::dimorphicStubKind(jsbytecode *pc, ICStub::Kind *pfirst, ICStub::Kind *psecond)
+{
+    if (!hasBaselineScript())
+        return false;
+
+    const ICEntry &entry = icEntryFromPC(pc);
+
+    ICStub *stub = entry.firstStub();
+    ICStub *next = stub->next();
+    ICStub *after = next ? next->next() : NULL;
 
-    switch (stub->kind()) {
+    if (!after || !after->isFallback())
+        return false;
+
+    *pfirst = stub->kind();
+    *psecond = next->kind();
+    return true;
+}
+
+MIRType
+BaselineInspector::expectedResultType(jsbytecode *pc)
+{
+    // Look at the IC entries for this op to guess what type it will produce,
+    // returning MIRType_None otherwise.
+
+    ICStub::Kind kind = monomorphicStubKind(pc);
+
+    switch (kind) {
       case ICStub::BinaryArith_Int32:
       case ICStub::BinaryArith_BooleanWithInt32:
       case ICStub::UnaryArith_Int32:
         return MIRType_Int32;
       case ICStub::BinaryArith_Double:
       case ICStub::BinaryArith_DoubleWithInt32:
       case ICStub::UnaryArith_Double:
         return MIRType_Double;
--- a/js/src/ion/BaselineInspector.h
+++ b/js/src/ion/BaselineInspector.h
@@ -91,16 +91,19 @@ class BaselineInspector
 
   public:
     RawShape maybeMonomorphicShapeForPropertyOp(jsbytecode *pc);
 
     SetElemICInspector setElemICInspector(jsbytecode *pc) {
         return makeICInspector<SetElemICInspector>(pc, ICStub::SetElem_Fallback);
     }
 
+    ICStub::Kind monomorphicStubKind(jsbytecode *pc);
+    bool dimorphicStubKind(jsbytecode *pc, ICStub::Kind *pfirst, ICStub::Kind *psecond);
+
     MIRType expectedResultType(jsbytecode *pc);
 };
 
 } // namespace ion
 } // namespace js
 
 #endif
 
--- a/js/src/ion/IonBuilder.cpp
+++ b/js/src/ion/IonBuilder.cpp
@@ -2667,17 +2667,17 @@ IonBuilder::processCondSwitchCase(CFGSta
         return ControlStatus_Error;
 
     // Terminate the last case condition block by emitting the code
     // corresponding to JSOP_CASE bytecode.
     if (bodyBlock != caseBlock) {
         MDefinition *caseOperand = current->pop();
         MDefinition *switchOperand = current->peek(-1);
         MCompare *cmpResult = MCompare::New(switchOperand, caseOperand, JSOP_STRICTEQ);
-        cmpResult->infer(cx);
+        cmpResult->infer(cx, inspector, pc);
         JS_ASSERT(!cmpResult->isEffectful());
         current->add(cmpResult);
         current->end(MTest::New(cmpResult, bodyBlock, caseBlock));
 
         // Add last case as predecessor of the body if the body is aliasing
         // the previous case body.
         if (!bodyIsNew && !bodyBlock->addPredecessorPopN(current, 1))
             return ControlStatus_Error;
@@ -4771,17 +4771,17 @@ IonBuilder::jsop_compare(JSOp op)
 {
     MDefinition *right = current->pop();
     MDefinition *left = current->pop();
 
     MCompare *ins = MCompare::New(left, right, op);
     current->add(ins);
     current->push(ins);
 
-    ins->infer(cx);
+    ins->infer(cx, inspector, pc);
 
     if (ins->isEffectful() && !resumeAfter(ins))
         return false;
     return true;
 }
 
 JSObject *
 IonBuilder::getNewArrayTemplateObject(uint32_t count)
@@ -4944,17 +4944,17 @@ IonBuilder::jsop_initprop(HandleProperty
     RootedShape shape(cx);
     RootedId id(cx, NameToId(name));
     bool res = LookupPropertyWithFlags(cx, templateObject, id,
                                        0, &holder, &shape);
     if (!res)
         return false;
 
     if (!shape || holder != templateObject ||
-        propertyWriteNeedsTypeBarrier(obj, name, &value))
+        propertyWriteNeedsTypeBarrier(&obj, name, &value))
     {
         // JSOP_NEWINIT becomes an MNewObject without preconfigured properties.
         MInitProp *init = MInitProp::New(obj, name, value);
         current->add(init);
         return resumeAfter(init);
     }
 
     bool needsBarrier = true;
@@ -6069,17 +6069,17 @@ IonBuilder::tryAddWriteBarrier(types::St
         if (!property)
             return false;
 
         if (TypeSetIncludes(property, (*pvalue)->type(), (*pvalue)->resultTypeSet()))
             return false;
 
         // This freeze is not required for correctness, but ensures that we
         // will recompile if the property types change and the barrier can
-        // be removed.
+        // potentially be removed.
         property->addFreeze(cx);
 
         if (aggregateProperty) {
             if (!aggregateProperty->isSubset(property) || !property->isSubset(aggregateProperty))
                 return false;
         } else {
             aggregateProperty = property;
         }
@@ -6118,51 +6118,99 @@ IonBuilder::tryAddWriteBarrier(types::St
 
     MInstruction *ins = MTypeBarrier::New(*pvalue, types, Bailout_Normal);
     current->add(ins);
     *pvalue = ins;
     return true;
 }
 
 bool
-IonBuilder::propertyWriteNeedsTypeBarrier(MDefinition *obj, PropertyName *name, MDefinition **pvalue)
+IonBuilder::propertyWriteNeedsTypeBarrier(MDefinition **pobj, PropertyName *name, MDefinition **pvalue)
 {
     // If any value being written is not reflected in the type information for
     // objects which obj could represent, a type barrier is needed when writing
     // the value. As for propertyReadNeedsTypeBarrier, this only applies for
     // properties that are accounted for by type information, i.e. normal data
     // properties and elements.
 
-    types::StackTypeSet *types = obj->resultTypeSet();
+    types::StackTypeSet *types = (*pobj)->resultTypeSet();
     if (!types || types->unknownObject())
         return true;
 
     jsid id = name ? types::IdToTypeId(NameToId(name)) : JSID_VOID;
 
+    // If all of the objects being written to have property types which already
+    // reflect the value, no barrier at all is needed. Additionally, if all
+    // objects being written to have the same types for the property, and those
+    // types do *not* reflect the value, add a type barrier for the value.
+
+    bool success = true;
     for (size_t i = 0; i < types->getObjectCount(); i++) {
         types::TypeObject *object = types->getTypeObject(i);
         if (!object) {
             JSObject *singleton = types->getSingleObject(i);
             if (!singleton)
                 continue;
             object = singleton->getType(cx);
-            if (!object)
-                return true;
+            if (!object) {
+                success = false;
+                break;
+            }
         }
 
         if (object->unknownProperties())
             continue;
 
         types::HeapTypeSet *property = object->getProperty(cx, id, false);
+        if (!property) {
+            success = false;
+            break;
+        }
+        if (!TypeSetIncludes(property, (*pvalue)->type(), (*pvalue)->resultTypeSet())) {
+            success = tryAddWriteBarrier(types, id, pvalue);
+            break;
+        }
+    }
+
+    if (success)
+        return false;
+
+    // If all of the objects except one have property types which reflect the
+    // value, and the remaining object has no types at all for the property,
+    // add a guard that the object does not have that remaining object's type.
+
+    if (types->getObjectCount() <= 1)
+        return true;
+
+    types::TypeObject *excluded = NULL;
+    for (size_t i = 0; i < types->getObjectCount(); i++) {
+        types::TypeObject *object = types->getTypeObject(i);
+        if (!object) {
+            if (types->getSingleObject(i))
+                return true;
+            continue;
+        }
+        if (object->unknownProperties())
+            continue;
+
+        types::HeapTypeSet *property = object->getProperty(cx, id, false);
         if (!property)
             return true;
-        if (!TypeSetIncludes(property, (*pvalue)->type(), (*pvalue)->resultTypeSet()))
-            return !tryAddWriteBarrier(types, id, pvalue);
-    }
-
+
+        if (TypeSetIncludes(property, (*pvalue)->type(), (*pvalue)->resultTypeSet()))
+            continue;
+
+        if (!property->empty() || excluded)
+            return true;
+        excluded = object;
+    }
+
+    JS_ASSERT(excluded);
+
+    *pobj = addTypeGuard(*pobj, excluded, /* bailOnEquality = */ true, Bailout_Normal);
     return false;
 }
 
 bool
 IonBuilder::jsop_getelem()
 {
     MDefinition *obj = current->peek(-2);
     MDefinition *index = current->peek(-1);
@@ -6447,29 +6495,29 @@ IonBuilder::jsop_getelem_string()
 
 bool
 IonBuilder::jsop_setelem()
 {
     MDefinition *value = current->peek(-1);
     MDefinition *index = current->peek(-2);
     MDefinition *object = current->peek(-3);
 
-    if (!propertyWriteNeedsTypeBarrier(object, NULL, &value)) {
+    int arrayType = TypedArray::TYPE_MAX;
+    if (elementAccessIsTypedArray(object, index, &arrayType))
+        return jsop_setelem_typed(arrayType);
+
+    if (!propertyWriteNeedsTypeBarrier(&object, NULL, &value)) {
         if (elementAccessIsDenseNative(object, index)) {
             types::StackTypeSet::DoubleConversion conversion =
                 object->resultTypeSet()->convertDoubleElements(cx);
             if (conversion != types::StackTypeSet::AmbiguousDoubleConversion)
                 return jsop_setelem_dense(conversion);
         }
     }
 
-    int arrayType = TypedArray::TYPE_MAX;
-    if (elementAccessIsTypedArray(object, index, &arrayType))
-        return jsop_setelem_typed(arrayType);
-
     if (object->type() == MIRType_Magic)
         return jsop_arguments_setelem();
 
     if (script()->argumentsHasVarBinding() && object->mightBeType(MIRType_Magic))
         return abort("Type is not definitely lazy arguments.");
 
     current->pop();
     current->pop();
@@ -6933,17 +6981,17 @@ IonBuilder::TestCommonPropFunc(JSContext
     // good enough here, even in the proxy case, because we have ensured there
     // are no lookup hooks for this property.
     MInstruction *wrapper = MConstant::New(ObjectValue(*foundProto));
     current->add(wrapper);
     wrapper = addShapeGuard(wrapper, foundProto->lastProperty(), Bailout_ShapeGuard);
 
     // Pass the guard back so it can be an operand.
     if (isGetter) {
-        JS_ASSERT(wrapper->isGuardShape());
+        JS_ASSERT(wrapper->isGuardShapeOrType());
         *guardOut = wrapper;
     }
 
     // Now we have to freeze all the property typesets to ensure there isn't a
     // lower shadowing getter or setter installed in the future.
     types::TypeObject *curType;
     for (unsigned i = 0; i < types->getObjectCount(); i++) {
         curType = types->getTypeObject(i);
@@ -7511,17 +7559,17 @@ IonBuilder::jsop_setprop(HandlePropertyN
         MCall *call = makeCallHelper(setter, callInfo, false);
         if (!call)
             return false;
 
         current->push(value);
         return resumeAfter(call);
     }
 
-    if (propertyWriteNeedsTypeBarrier(obj, name, &value)) {
+    if (propertyWriteNeedsTypeBarrier(&obj, name, &value)) {
         MInstruction *ins = MCallSetProperty::New(obj, value, name, script()->strict);
         current->add(ins);
         current->push(value);
         return resumeAfter(ins);
     }
 
     if (types::HeapTypeSet *propTypes = GetDefiniteSlot(cx, objTypes, name)) {
         MStoreFixedSlot *fixed = MStoreFixedSlot::New(obj, propTypes->definiteSlot(), value);
@@ -7951,26 +7999,40 @@ IonBuilder::addBoundsCheck(MDefinition *
         check->setNotMovable();
 
     return check;
 }
 
 MInstruction *
 IonBuilder::addShapeGuard(MDefinition *obj, const RawShape shape, BailoutKind bailoutKind)
 {
-    MGuardShape *guard = MGuardShape::New(obj, shape, bailoutKind);
+    MGuardShapeOrType *guard = MGuardShapeOrType::New(obj, shape, NULL, false, bailoutKind);
     current->add(guard);
 
     // If a shape guard failed in the past, don't optimize shape guard.
     if (failedShapeGuard_)
         guard->setNotMovable();
 
     return guard;
 }
 
+MInstruction *
+IonBuilder::addTypeGuard(MDefinition *obj, types::TypeObject *typeObject,
+                         bool bailOnEquality, BailoutKind bailoutKind)
+{
+    MGuardShapeOrType *guard = MGuardShapeOrType::New(obj, NULL, typeObject,
+                                                      bailOnEquality, bailoutKind);
+    current->add(guard);
+
+    // For now, never move type guards.
+    guard->setNotMovable();
+
+    return guard;
+}
+
 types::StackTypeSet *
 IonBuilder::cloneTypeSet(types::StackTypeSet *types)
 {
     if (!js_IonOptions.parallelCompilation)
         return types;
 
     // Clone a type set so that it can be stored into the MIR and accessed
     // during off thread compilation. This is necessary because main thread
--- a/js/src/ion/IonBuilder.h
+++ b/js/src/ion/IonBuilder.h
@@ -306,16 +306,18 @@ class IonBuilder : public MIRGenerator
     MInstruction *createDeclEnvObject(MDefinition *callee, MDefinition *scopeObj);
     MInstruction *createCallObject(MDefinition *callee, MDefinition *scopeObj);
 
     MDefinition *walkScopeChain(unsigned hops);
 
     MInstruction *addConvertElementsToDoubles(MDefinition *elements);
     MInstruction *addBoundsCheck(MDefinition *index, MDefinition *length);
     MInstruction *addShapeGuard(MDefinition *obj, const RawShape shape, BailoutKind bailoutKind);
+    MInstruction *addTypeGuard(MDefinition *obj, types::TypeObject *typeObject,
+                               bool bailOnEquality, BailoutKind bailoutKind);
 
     JSObject *getNewArrayTemplateObject(uint32_t count);
 
     bool invalidatedIdempotentCache();
 
     bool loadSlot(MDefinition *obj, HandleShape shape, MIRType rvalType,
                   bool barrier, types::StackTypeSet *types);
     bool storeSlot(MDefinition *obj, RawShape shape, MDefinition *value, bool needsBarrier);
@@ -418,17 +420,17 @@ class IonBuilder : public MIRGenerator
     bool elementAccessIsPacked(MDefinition *obj);
     bool elementAccessHasExtraIndexedProperty(MDefinition *obj);
     MIRType denseNativeElementType(MDefinition *obj);
     bool propertyReadNeedsTypeBarrier(types::TypeObject *object, PropertyName *name,
                                       types::StackTypeSet *observed);
     bool propertyReadNeedsTypeBarrier(MDefinition *obj, PropertyName *name,
                                       types::StackTypeSet *observed);
     bool propertyReadIsIdempotent(MDefinition *obj, PropertyName *name);
-    bool propertyWriteNeedsTypeBarrier(MDefinition *obj, PropertyName *name, MDefinition **pvalue);
+    bool propertyWriteNeedsTypeBarrier(MDefinition **pobj, PropertyName *name, MDefinition **pvalue);
     bool tryAddWriteBarrier(types::StackTypeSet *objTypes, jsid id, MDefinition **pvalue);
 
     // Native inlining helpers.
     types::StackTypeSet *getInlineReturnTypeSet();
     MIRType getInlineReturnType();
 
     // Array natives.
     InliningStatus inlineArray(CallInfo &callInfo);
--- a/js/src/ion/LOpcodes.h
+++ b/js/src/ion/LOpcodes.h
@@ -125,17 +125,17 @@
     _(ImplicitThis)                 \
     _(Slots)                        \
     _(Elements)                     \
     _(ConvertElementsToDoubles)     \
     _(LoadSlotV)                    \
     _(LoadSlotT)                    \
     _(StoreSlotV)                   \
     _(StoreSlotT)                   \
-    _(GuardShape)                   \
+    _(GuardShapeOrType)             \
     _(GuardClass)                   \
     _(ParWriteGuard)                \
     _(ParDump)                      \
     _(TypeBarrier)                  \
     _(MonitorTypes)                 \
     _(InitializedLength)            \
     _(SetInitializedLength)         \
     _(BoundsCheck)                  \
--- a/js/src/ion/MCallOptimize.cpp
+++ b/js/src/ion/MCallOptimize.cpp
@@ -315,18 +315,21 @@ IonBuilder::inlineArrayPopShift(CallInfo
 }
 
 IonBuilder::InliningStatus
 IonBuilder::inlineArrayPush(CallInfo &callInfo)
 {
     if (callInfo.argc() != 1 || callInfo.constructing())
         return InliningStatus_NotInlined;
 
+    MDefinition *obj = callInfo.thisArg();
     MDefinition *value = callInfo.getArg(0);
-    if (propertyWriteNeedsTypeBarrier(callInfo.thisArg(), NULL, &value) || value != callInfo.getArg(0))
+    if (propertyWriteNeedsTypeBarrier(&obj, NULL, &value))
+        return InliningStatus_NotInlined;
+    if (obj != callInfo.thisArg() || value != callInfo.getArg(0))
         return InliningStatus_NotInlined;
 
     if (getInlineReturnType() != MIRType_Int32)
         return InliningStatus_NotInlined;
     if (callInfo.thisArg()->type() != MIRType_Object)
         return InliningStatus_NotInlined;
 
     types::StackTypeSet *thisTypes = callInfo.thisArg()->resultTypeSet();
@@ -929,17 +932,17 @@ IonBuilder::inlineUnsafeSetElement(CallI
         uint32_t arri = base + 0;
         uint32_t idxi = base + 1;
         uint32_t elemi = base + 2;
 
         MDefinition *obj = callInfo.getArg(arri);
         MDefinition *id = callInfo.getArg(idxi);
         MDefinition *elem = callInfo.getArg(elemi);
 
-        if (propertyWriteNeedsTypeBarrier(obj, NULL, &elem))
+        if (propertyWriteNeedsTypeBarrier(&obj, NULL, &elem))
             return InliningStatus_NotInlined;
 
         int arrayType;
         if (!elementAccessIsDenseNative(obj, id) &&
             !elementAccessIsTypedArray(obj, id, &arrayType))
         {
             return InliningStatus_NotInlined;
         }
--- a/js/src/ion/MIR.cpp
+++ b/js/src/ion/MIR.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=4 sw=4 et tw=99:
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "BaselineInspector.h"
 #include "IonBuilder.h"
 #include "LICM.h" // For LinearSum
 #include "MIR.h"
 #include "MIRGraph.h"
 #include "EdgeCaseAnalysis.h"
 #include "RangeAnalysis.h"
 #include "IonSpewer.h"
 #include "jsnum.h"
@@ -1377,18 +1378,26 @@ MCompare::inputType()
       case Compare_Value:
         return MIRType_Value;
       default:
         JS_NOT_REACHED("No known conversion");
         return MIRType_None;
     }
 }
 
+// Whether a baseline stub kind is suitable for a double comparison that
+// converts its operands to doubles.
+static bool
+CanUseDoubleCompare(ICStub::Kind kind)
+{
+    return kind == ICStub::Compare_Double || kind == ICStub::Compare_NumberWithUndefined;
+}
+
 void
-MCompare::infer(JSContext *cx)
+MCompare::infer(JSContext *cx, BaselineInspector *inspector, jsbytecode *pc)
 {
     JS_ASSERT(operandMightEmulateUndefined());
 
     if (!MaybeEmulatesUndefined(cx, getOperand(0)) && !MaybeEmulatesUndefined(cx, getOperand(1)))
         markNoOperandEmulatesUndefined();
 
     MIRType lhs = getOperand(0)->type();
     MIRType rhs = getOperand(1)->type();
@@ -1484,16 +1493,39 @@ MCompare::infer(JSContext *cx)
         return;
     }
 
     // Determine if we can do the compare based on a quick value check.
     if (!relationalEq && CanDoValueBitwiseCmp(cx, getOperand(0), getOperand(1), looseEq)) {
         compareType_ = Compare_Value;
         return;
     }
+
+    // Type information is not good enough to pick out a particular type of
+    // comparison we can do here. Try to specialize based on any baseline
+    // caches that have been generated for the opcode. These will cause the
+    // instruction's type policy to insert fallible unboxes to the appropriate
+    // input types.
+
+    if (!strictEq) {
+        ICStub::Kind kind = inspector->monomorphicStubKind(pc);
+
+        if (CanUseDoubleCompare(kind)) {
+            compareType_ = Compare_Double;
+            return;
+        }
+
+        ICStub::Kind first, second;
+        if (inspector->dimorphicStubKind(pc, &first, &second)) {
+            if (CanUseDoubleCompare(first) && CanUseDoubleCompare(second)) {
+                compareType_ = Compare_Double;
+                return;
+            }
+        }
+    }
 }
 
 MBitNot *
 MBitNot::New(MDefinition *input)
 {
     return new MBitNot(input);
 }
 
--- a/js/src/ion/MIR.h
+++ b/js/src/ion/MIR.h
@@ -23,16 +23,17 @@
 #include "IonMacroAssembler.h"
 #include "Bailouts.h"
 #include "FixedList.h"
 #include "CompilerRoot.h"
 
 namespace js {
 namespace ion {
 
+class BaselineInspector;
 class ValueNumberData;
 class Range;
 
 static const inline
 MIRType MIRTypeFromValue(const js::Value &vp)
 {
     if (vp.isDouble())
         return MIRType_Double;
@@ -1736,17 +1737,17 @@ class MCompare
     INSTRUCTION_HEADER(Compare)
     static MCompare *New(MDefinition *left, MDefinition *right, JSOp op);
     static MCompare *NewAsmJS(MDefinition *left, MDefinition *right, JSOp op, CompareType compareType);
 
     bool tryFold(bool *result);
     bool evaluateConstantOperands(bool *result);
     MDefinition *foldsTo(bool useValueNumbers);
 
-    void infer(JSContext *cx);
+    void infer(JSContext *cx, BaselineInspector *inspector, jsbytecode *pc);
     CompareType compareType() const {
         return compareType_;
     }
     void setCompareType(CompareType type) {
         compareType_ = type;
     }
     MIRType inputType();
 
@@ -5429,59 +5430,77 @@ class MBindNameCache
     RawScript script() const {
         return script_;
     }
     jsbytecode *pc() const {
         return pc_;
     }
 };
 
-// Guard on an object's shape.
-class MGuardShape
+// Guard on an object's shape or type, either inclusively or exclusively.
+class MGuardShapeOrType
   : public MUnaryInstruction,
     public SingleObjectPolicy
 {
     CompilerRootShape shape_;
+    CompilerRoot<types::TypeObject*> typeObject_;
+    bool bailOnEquality_;
     BailoutKind bailoutKind_;
 
-    MGuardShape(MDefinition *obj, RawShape shape, BailoutKind bailoutKind)
+    MGuardShapeOrType(MDefinition *obj, Shape *shape, types::TypeObject *typeObject,
+                      bool bailOnEquality, BailoutKind bailoutKind)
       : MUnaryInstruction(obj),
         shape_(shape),
+        typeObject_(typeObject),
+        bailOnEquality_(bailOnEquality),
         bailoutKind_(bailoutKind)
     {
+        // Exactly one of the shape or type object to guard on must be specified.
+        JS_ASSERT(!!shape != !!typeObject);
         setGuard();
         setMovable();
         setResultType(MIRType_Object);
     }
 
   public:
-    INSTRUCTION_HEADER(GuardShape)
-
-    static MGuardShape *New(MDefinition *obj, RawShape shape, BailoutKind bailoutKind) {
-        return new MGuardShape(obj, shape, bailoutKind);
+    INSTRUCTION_HEADER(GuardShapeOrType)
+
+    static MGuardShapeOrType *New(MDefinition *obj, Shape *shape, types::TypeObject *typeObject,
+                                  bool bailOnEquality, BailoutKind bailoutKind) {
+        return new MGuardShapeOrType(obj, shape, typeObject, bailOnEquality, bailoutKind);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
     MDefinition *obj() const {
         return getOperand(0);
     }
     const RawShape shape() const {
         return shape_;
     }
+    types::TypeObject *typeObject() const {
+        return typeObject_;
+    }
+    bool bailOnEquality() const {
+        return bailOnEquality_;
+    }
     BailoutKind bailoutKind() const {
         return bailoutKind_;
     }
     bool congruentTo(MDefinition * const &ins) const {
-        if (!ins->isGuardShape())
+        if (!ins->isGuardShapeOrType())
+            return false;
+        if (shape() != ins->toGuardShapeOrType()->shape())
             return false;
-        if (shape() != ins->toGuardShape()->shape())
+        if (typeObject() != ins->toGuardShapeOrType()->typeObject())
             return false;
-        if (bailoutKind() != ins->toGuardShape()->bailoutKind())
+        if (bailOnEquality() != ins->toGuardShapeOrType()->bailOnEquality())
+            return false;
+        if (bailoutKind() != ins->toGuardShapeOrType()->bailoutKind())
             return false;
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::ObjectFields);
     }
 };
 
--- a/js/src/ion/MOpcodes.h
+++ b/js/src/ion/MOpcodes.h
@@ -97,17 +97,17 @@ namespace ion {
     _(LoadSlot)                                                             \
     _(StoreSlot)                                                            \
     _(FunctionEnvironment)                                                  \
     _(TypeBarrier)                                                          \
     _(MonitorTypes)                                                         \
     _(GetPropertyCache)                                                     \
     _(GetElementCache)                                                      \
     _(BindNameCache)                                                        \
-    _(GuardShape)                                                           \
+    _(GuardShapeOrType)                                                     \
     _(GuardClass)                                                           \
     _(ArrayLength)                                                          \
     _(TypedArrayLength)                                                     \
     _(TypedArrayElements)                                                   \
     _(InitializedLength)                                                    \
     _(SetInitializedLength)                                                 \
     _(Not)                                                                  \
     _(BoundsCheck)                                                          \
--- a/js/src/ion/ParallelArrayAnalysis.cpp
+++ b/js/src/ion/ParallelArrayAnalysis.cpp
@@ -186,17 +186,17 @@ class ParallelArrayVisitor : public MIns
     SAFE_OP(LoadSlot)
     WRITE_GUARDED_OP(StoreSlot, slots)
     SAFE_OP(FunctionEnvironment) // just a load of func env ptr
     SAFE_OP(TypeBarrier) // causes a bailout if the type is not found: a-ok with us
     SAFE_OP(MonitorTypes) // causes a bailout if the type is not found: a-ok with us
     SAFE_OP(GetPropertyCache)
     UNSAFE_OP(GetElementCache)
     UNSAFE_OP(BindNameCache)
-    SAFE_OP(GuardShape)
+    SAFE_OP(GuardShapeOrType)
     SAFE_OP(GuardClass)
     SAFE_OP(ArrayLength)
     SAFE_OP(TypedArrayLength)
     SAFE_OP(TypedArrayElements)
     SAFE_OP(InitializedLength)
     WRITE_GUARDED_OP(SetInitializedLength, elements)
     SAFE_OP(Not)
     SAFE_OP(BoundsCheck)
--- a/js/src/ion/arm/CodeGenerator-arm.cpp
+++ b/js/src/ion/arm/CodeGenerator-arm.cpp
@@ -1537,24 +1537,32 @@ CodeGeneratorARM::storeElementTyped(cons
         if (value->isConstant())
             masm.storePayload(*value->toConstant(), elements, indexReg);
         else
             masm.storePayload(ToRegister(value), elements, indexReg);
     }
 }
 
 bool
-CodeGeneratorARM::visitGuardShape(LGuardShape *guard)
+CodeGeneratorARM::visitGuardShapeOrType(LGuardShapeOrType *guard)
 {
     Register obj = ToRegister(guard->input());
     Register tmp = ToRegister(guard->tempInt());
-    masm.ma_ldr(DTRAddr(obj, DtrOffImm(JSObject::offsetOfShape())), tmp);
-    masm.ma_cmp(tmp, ImmGCPtr(guard->mir()->shape()));
 
-    if (!bailoutIf(Assembler::NotEqual, guard->snapshot()))
+    if (guard->mir()->shape()) {
+        masm.ma_ldr(DTRAddr(obj, DtrOffImm(JSObject::offsetOfShape())), tmp);
+        masm.ma_cmp(tmp, ImmGCPtr(guard->mir()->shape()));
+    } else {
+        masm.ma_ldr(DTRAddr(obj, DtrOffImm(JSObject::offsetOfType())), tmp);
+        masm.ma_cmp(tmp, ImmGCPtr(guard->mir()->typeObject()));
+    }
+
+    Assembler::Condition cond =
+        guard->mir()->bailOnEquality() ? Assembler::Equal : Assembler::NotEqual;
+    if (!bailoutIf(cond, guard->snapshot()))
         return false;
     return true;
 }
 
 bool
 CodeGeneratorARM::visitGuardClass(LGuardClass *guard)
 {
     Register obj = ToRegister(guard->input());
--- a/js/src/ion/arm/CodeGenerator-arm.h
+++ b/js/src/ion/arm/CodeGenerator-arm.h
@@ -130,17 +130,17 @@ class CodeGeneratorARM : public CodeGene
     bool visitDouble(LDouble *ins);
 
     bool visitLoadSlotV(LLoadSlotV *load);
     bool visitLoadSlotT(LLoadSlotT *load);
     bool visitStoreSlotT(LStoreSlotT *load);
 
     bool visitLoadElementT(LLoadElementT *load);
 
-    bool visitGuardShape(LGuardShape *guard);
+    bool visitGuardShapeOrType(LGuardShapeOrType *guard);
     bool visitGuardClass(LGuardClass *guard);
     bool visitImplicitThis(LImplicitThis *lir);
 
     bool visitInterruptCheck(LInterruptCheck *lir);
 
     bool visitNegI(LNegI *lir);
     bool visitNegD(LNegD *lir);
     bool visitAsmJSLoadHeap(LAsmJSLoadHeap *ins);
--- a/js/src/ion/arm/LIR-arm.h
+++ b/js/src/ion/arm/LIR-arm.h
@@ -259,27 +259,27 @@ class LTableSwitchV : public LInstructio
         return getTemp(1)->output();
     }
     const LAllocation *tempPointer() {
         return NULL;
     }
 };
 
 // Guard against an object's shape.
-class LGuardShape : public LInstructionHelper<0, 1, 1>
+class LGuardShapeOrType : public LInstructionHelper<0, 1, 1>
 {
   public:
-    LIR_HEADER(GuardShape);
+    LIR_HEADER(GuardShapeOrType);
 
-    LGuardShape(const LAllocation &in, const LDefinition &temp) {
+    LGuardShapeOrType(const LAllocation &in, const LDefinition &temp) {
         setOperand(0, in);
         setTemp(0, temp);
     }
-    const MGuardShape *mir() const {
-        return mir_->toGuardShape();
+    const MGuardShapeOrType *mir() const {
+        return mir_->toGuardShapeOrType();
     }
     const LAllocation *tempInt() {
         return getTemp(0)->output();
     }
 };
 
 class LInterruptCheck : public LInstructionHelper<0, 0, 0>
 {
--- a/js/src/ion/arm/Lowering-arm.cpp
+++ b/js/src/ion/arm/Lowering-arm.cpp
@@ -298,22 +298,22 @@ LIRGeneratorARM::newLTableSwitchV(MTable
 
 LGetPropertyCacheT *
 LIRGeneratorARM::newLGetPropertyCacheT(MGetPropertyCache *ins)
 {
     return new LGetPropertyCacheT(useRegister(ins->object()), LDefinition::BogusTemp());
 }
 
 bool
-LIRGeneratorARM::visitGuardShape(MGuardShape *ins)
+LIRGeneratorARM::visitGuardShapeOrType(MGuardShapeOrType *ins)
 {
     JS_ASSERT(ins->obj()->type() == MIRType_Object);
 
     LDefinition tempObj = temp(LDefinition::OBJECT);
-    LGuardShape *guard = new LGuardShape(useRegister(ins->obj()), tempObj);
+    LGuardShapeOrType *guard = new LGuardShapeOrType(useRegister(ins->obj()), tempObj);
     if (!assignSnapshot(guard, ins->bailoutKind()))
         return false;
     if (!add(guard, ins))
         return false;
     return redefine(ins, ins->obj());
 }
 
 bool
--- a/js/src/ion/arm/Lowering-arm.h
+++ b/js/src/ion/arm/Lowering-arm.h
@@ -58,17 +58,17 @@ class LIRGeneratorARM : public LIRGenera
     LGetPropertyCacheT *newLGetPropertyCacheT(MGetPropertyCache *ins);
 
   public:
     bool visitConstant(MConstant *ins);
     bool visitBox(MBox *box);
     bool visitUnbox(MUnbox *unbox);
     bool visitReturn(MReturn *ret);
     bool lowerPhi(MPhi *phi);
-    bool visitGuardShape(MGuardShape *ins);
+    bool visitGuardShapeOrType(MGuardShapeOrType *ins);
     bool visitStoreTypedArrayElement(MStoreTypedArrayElement *ins);
     bool visitStoreTypedArrayElementHole(MStoreTypedArrayElementHole *ins);
     bool visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble *ins);
     bool visitAsmJSStoreHeap(MAsmJSStoreHeap *ins);
     bool visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr *ins);
     bool visitInterruptCheck(MInterruptCheck *ins);
 };
 
--- a/js/src/ion/shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/ion/shared/CodeGenerator-x86-shared.cpp
@@ -1298,21 +1298,27 @@ CodeGeneratorX86Shared::visitRound(LRoun
             return false;
     }
 
     masm.bind(&end);
     return true;
 }
 
 bool
-CodeGeneratorX86Shared::visitGuardShape(LGuardShape *guard)
+CodeGeneratorX86Shared::visitGuardShapeOrType(LGuardShapeOrType *guard)
 {
     Register obj = ToRegister(guard->input());
-    masm.cmpPtr(Operand(obj, JSObject::offsetOfShape()), ImmGCPtr(guard->mir()->shape()));
-    if (!bailoutIf(Assembler::NotEqual, guard->snapshot()))
+    if (guard->mir()->shape())
+        masm.cmpPtr(Operand(obj, JSObject::offsetOfShape()), ImmGCPtr(guard->mir()->shape()));
+    else
+        masm.cmpPtr(Operand(obj, JSObject::offsetOfType()), ImmGCPtr(guard->mir()->typeObject()));
+
+    Assembler::Condition cond =
+        guard->mir()->bailOnEquality() ? Assembler::Equal : Assembler::NotEqual;
+    if (!bailoutIf(cond, guard->snapshot()))
         return false;
     return true;
 }
 
 bool
 CodeGeneratorX86Shared::visitGuardClass(LGuardClass *guard)
 {
     Register obj = ToRegister(guard->input());
--- a/js/src/ion/shared/CodeGenerator-x86-shared.h
+++ b/js/src/ion/shared/CodeGenerator-x86-shared.h
@@ -99,17 +99,17 @@ class CodeGeneratorX86Shared : public Co
     virtual bool visitCompareAndBranch(LCompareAndBranch *comp);
     virtual bool visitCompareD(LCompareD *comp);
     virtual bool visitCompareDAndBranch(LCompareDAndBranch *comp);
     virtual bool visitNotI(LNotI *comp);
     virtual bool visitNotD(LNotD *comp);
     virtual bool visitMathD(LMathD *math);
     virtual bool visitFloor(LFloor *lir);
     virtual bool visitRound(LRound *lir);
-    virtual bool visitGuardShape(LGuardShape *guard);
+    virtual bool visitGuardShapeOrType(LGuardShapeOrType *guard);
     virtual bool visitGuardClass(LGuardClass *guard);
     virtual bool visitTruncateDToInt32(LTruncateDToInt32 *ins);
     virtual bool visitEffectiveAddress(LEffectiveAddress *ins);
     virtual bool visitAsmJSDivOrMod(LAsmJSDivOrMod *ins);
     virtual bool visitAsmJSPassStackArg(LAsmJSPassStackArg *ins);
 
     bool visitNegI(LNegI *lir);
     bool visitNegD(LNegD *lir);
--- a/js/src/ion/shared/LIR-x86-shared.h
+++ b/js/src/ion/shared/LIR-x86-shared.h
@@ -170,26 +170,26 @@ class LTableSwitchV : public LInstructio
         return getTemp(1)->output();
     }
     const LAllocation *tempPointer() {
         return getTemp(2)->output();
     }
 };
 
 // Guard against an object's shape.
-class LGuardShape : public LInstructionHelper<0, 1, 0>
+class LGuardShapeOrType : public LInstructionHelper<0, 1, 0>
 {
   public:
-    LIR_HEADER(GuardShape)
+    LIR_HEADER(GuardShapeOrType)
 
-    LGuardShape(const LAllocation &in) {
+    LGuardShapeOrType(const LAllocation &in) {
         setOperand(0, in);
     }
-    const MGuardShape *mir() const {
-        return mir_->toGuardShape();
+    const MGuardShapeOrType *mir() const {
+        return mir_->toGuardShapeOrType();
     }
 };
 
 class LInterruptCheck : public LInstructionHelper<0, 0, 0>
 {
   public:
     LIR_HEADER(InterruptCheck)
 };
--- a/js/src/ion/shared/Lowering-x86-shared.cpp
+++ b/js/src/ion/shared/Lowering-x86-shared.cpp
@@ -32,21 +32,21 @@ LIRGeneratorX86Shared::visitInterruptChe
     if (!add(lir, ins))
         return false;
     if (!assignSafepoint(lir, ins))
         return false;
     return true;
 }
 
 bool
-LIRGeneratorX86Shared::visitGuardShape(MGuardShape *ins)
+LIRGeneratorX86Shared::visitGuardShapeOrType(MGuardShapeOrType *ins)
 {
     JS_ASSERT(ins->obj()->type() == MIRType_Object);
 
-    LGuardShape *guard = new LGuardShape(useRegister(ins->obj()));
+    LGuardShapeOrType *guard = new LGuardShapeOrType(useRegister(ins->obj()));
     if (!assignSnapshot(guard, ins->bailoutKind()))
         return false;
     if (!add(guard, ins))
         return false;
     return redefine(ins, ins->obj());
 }
 
 bool
--- a/js/src/ion/shared/Lowering-x86-shared.h
+++ b/js/src/ion/shared/Lowering-x86-shared.h
@@ -20,17 +20,17 @@ class LIRGeneratorX86Shared : public LIR
       : LIRGeneratorShared(gen, graph, lirGraph)
     {}
 
     LTableSwitch *newLTableSwitch(const LAllocation &in, const LDefinition &inputCopy,
                                   MTableSwitch *ins);
     LTableSwitchV *newLTableSwitchV(MTableSwitch *ins);
 
     bool visitInterruptCheck(MInterruptCheck *ins);
-    bool visitGuardShape(MGuardShape *ins);
+    bool visitGuardShapeOrType(MGuardShapeOrType *ins);
     bool visitPowHalf(MPowHalf *ins);
     bool visitConstant(MConstant *ins);
     bool visitAsmJSNeg(MAsmJSNeg *ins);
     bool visitAsmJSUDiv(MAsmJSUDiv *ins);
     bool visitAsmJSUMod(MAsmJSUMod *ins);
     bool lowerMulI(MMul *mul, MDefinition *lhs, MDefinition *rhs);
     bool lowerDivI(MDiv *div);
     bool lowerModI(MMod *mod);
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -5160,42 +5160,42 @@ AnalyzeNewScriptProperties(JSContext *cx
          * 'this' value cannot escape and be accessed except through such uses.
          */
         if (op != JSOP_THIS)
             continue;
 
         SSAValue thisv = SSAValue::PushedValue(offset, 0);
         SSAUseChain *uses = analysis->useChain(thisv);
 
+        /*
+         * If offset >= the offset at the top of the pending stack, we either
+         * encountered the end of a compound inline assignment or a 'this' was
+         * immediately popped and used. In either case, handle the use.
+         */
+        if (!pendingPoppedThis.empty() &&
+            offset >= pendingPoppedThis.back()->offset) {
+            lastThisPopped = pendingPoppedThis[0]->offset;
+            if (!AnalyzePoppedThis(cx, &pendingPoppedThis, type, fun, state))
+                return false;
+        }
+
         JS_ASSERT(uses);
         if (uses->next || !uses->popped) {
             /* 'this' value popped in more than one place. */
             entirelyAnalyzed = false;
             break;
         }
 
         /* Only handle 'this' values popped in unconditional code. */
         Bytecode *poppedCode = analysis->maybeCode(uses->offset);
         if (!poppedCode || !poppedCode->unconditional) {
             entirelyAnalyzed = false;
             break;
         }
 
-        /*
-         * If offset >= the offset at the top of the pending stack, we either
-         * encountered the end of a compound inline assignment or a 'this' was
-         * immediately popped and used. In either case, handle the use.
-         */
-        if (!pendingPoppedThis.empty() &&
-            offset >= pendingPoppedThis.back()->offset) {
-            lastThisPopped = pendingPoppedThis[0]->offset;
-            if (!AnalyzePoppedThis(cx, &pendingPoppedThis, type, fun, state))
-                return false;
-        }
-
         if (!pendingPoppedThis.append(uses)) {
             entirelyAnalyzed = false;
             break;
         }
     }
 
     /*
      * There is an invariant that all definite properties come before