[INFER] Fast path for object equality, track equality hooks in type objects, bug 619592.
authorBrian Hackett <bhackett1024@gmail.com>
Sat, 19 Mar 2011 16:53:07 -0700
changeset 74817 526876bb3ff8368eb6f713041bff0f57b114c61b
parent 74816 39ec057f7b172cae8e6860a5d46c893ff76e4b25
child 74818 5ce2f7a9028647c9ab03a4fecf3255f358c22b32
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
bugs619592
milestone2.0b13pre
[INFER] Fast path for object equality, track equality hooks in type objects, bug 619592.
js/src/jsapi.cpp
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsinferinlines.h
js/src/jsobj.cpp
js/src/jsobjinlines.h
js/src/methodjit/Compiler.h
js/src/methodjit/FastArithmetic.cpp
js/src/methodjit/FastOps.cpp
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3074,16 +3074,18 @@ JS_NewGlobalObject(JSContext *cx, JSClas
     JS_ASSERT(clasp->flags & JSCLASS_IS_GLOBAL);
     JSObject *obj = NewNonFunction<WithProto::Given>(cx, Valueify(clasp), NULL, NULL);
     if (!obj)
         return NULL;
 
     TypeObject *type = cx->newTypeObject("Global", NULL);
     if (!type || !obj->setTypeAndUniqueShape(cx, type))
         return NULL;
+    if (Valueify(clasp)->ext.equality)
+        type->hasSpecialEquality = true;
 
     obj->syncSpecialEquality();
 
     /* Construct a regexp statics object for this global object. */
     JSObject *res = regexp_statics_construct(cx, obj);
     if (!res ||
         !js_SetReservedSlot(cx, obj, JSRESERVED_GLOBAL_REGEXP_STATICS, ObjectValue(*res)) ||
         !js_SetReservedSlot(cx, obj, JSRESERVED_GLOBAL_FLAGS, Int32Value(0))) {
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -538,17 +538,17 @@ TypeSet::addBaseSubset(JSContext *cx, Ty
 class TypeConstraintCondensed : public TypeConstraint
 {
 public:
     TypeConstraintCondensed(JSScript *script)
         : TypeConstraint("condensed", script)
     {}
 
     void newType(JSContext *cx, TypeSet *source, jstype type);
-    void arrayNotPacked(JSContext *cx, bool notDense);
+    void newObjectState(JSContext *cx);
 
     bool condensed() { return true; }
 };
 
 void
 TypeSet::addCondensed(JSContext *cx, JSScript *script)
 {
     TypeConstraintCondensed *constraint =
@@ -1215,17 +1215,17 @@ TypeConstraintCondensed::newType(JSConte
          */
         return;
     }
 
     AnalyzeScriptTypes(cx, script);
 }
 
 void
-TypeConstraintCondensed::arrayNotPacked(JSContext *cx, bool notDense)
+TypeConstraintCondensed::newObjectState(JSContext *cx)
 {
     if (script->types)
         return;
     AnalyzeScriptTypes(cx, script);
 }
 
 /* Constraint which marks all types as pushed by some bytecode. */
 class TypeConstraintPushAll : public TypeConstraint
@@ -1380,127 +1380,117 @@ TypeSet::getKnownTypeTag(JSContext *cx, 
 static inline ObjectKind
 CombineObjectKind(TypeObject *object, ObjectKind kind)
 {
     /*
      * All type objects with unknown properties are considered interchangeable
      * with one another, as they can be freely exchanged in type sets to handle
      * objects whose __proto__ has been changed.
      */
-    if (object->unknownProperties)
+    if (object->unknownProperties || object->hasSpecialEquality || kind == OBJECT_UNKNOWN)
         return OBJECT_UNKNOWN;
 
     ObjectKind nkind;
     if (object->isFunction)
         nkind = object->asFunction()->script ? OBJECT_SCRIPTED_FUNCTION : OBJECT_NATIVE_FUNCTION;
     else if (object->isPackedArray)
         nkind = OBJECT_PACKED_ARRAY;
     else if (object->isDenseArray)
         nkind = OBJECT_DENSE_ARRAY;
     else
-        nkind = OBJECT_UNKNOWN;
-
-    if (kind == OBJECT_UNKNOWN || nkind == OBJECT_UNKNOWN)
-        return OBJECT_UNKNOWN;
+        nkind = OBJECT_NO_SPECIAL_EQUALITY;
 
     if (kind == nkind || kind == OBJECT_NONE)
         return nkind;
 
     if ((kind == OBJECT_PACKED_ARRAY && nkind == OBJECT_DENSE_ARRAY) ||
         (kind == OBJECT_DENSE_ARRAY && nkind == OBJECT_PACKED_ARRAY)) {
         return OBJECT_DENSE_ARRAY;
     }
 
-    return OBJECT_UNKNOWN;
+    return OBJECT_NO_SPECIAL_EQUALITY;
 }
 
-/* Constraint which triggers recompilation if an array becomes not-packed or not-dense. */
-class TypeConstraintFreezeArray : public TypeConstraint
+/* Constraint which triggers recompilation if an object changes state. */
+class TypeConstraintFreezeObjectKind : public TypeConstraint
 {
 public:
+    TypeObject *object;
+
     /*
-     * Array kind being specialized on by the FreezeObjectConstraint.  This may have
-     * become OBJECT_UNKNOWN due to subsequent type changes and recompilation.
+     * Kind being specialized by the parent FreezeObjectConstraint. This may
+     * have already changed since this constraint was created.
      */
     ObjectKind *pkind;
 
-    TypeConstraintFreezeArray(ObjectKind *pkind, JSScript *script)
-        : TypeConstraint("freezeArray", script), pkind(pkind)
-    {
-        JS_ASSERT(*pkind == OBJECT_PACKED_ARRAY || *pkind == OBJECT_DENSE_ARRAY);
-    }
+    TypeConstraintFreezeObjectKind(TypeObject *object, ObjectKind *pkind, JSScript *script)
+        : TypeConstraint("freezeObjectKind", script), object(object), pkind(pkind)
+    {}
 
     void newType(JSContext *cx, TypeSet *source, jstype type) {}
 
-    void arrayNotPacked(JSContext *cx, bool notDense)
+    void newObjectState(JSContext *cx)
     {
-        if (*pkind == OBJECT_UNKNOWN) {
-            /* Despecialized the kind we were interested in due to recompilation. */
-            return;
+        ObjectKind nkind = CombineObjectKind(object, *pkind);
+        if (nkind != *pkind) {
+            *pkind = nkind;
+            cx->compartment->types.addPendingRecompile(cx, script);
         }
-
-        JS_ASSERT(*pkind == OBJECT_PACKED_ARRAY || *pkind == OBJECT_DENSE_ARRAY);
-
-        if (!notDense && *pkind == OBJECT_DENSE_ARRAY) {
-            /* Marking an array as not packed, but we were already accounting for this. */
-            return;
-        }
-
-        cx->compartment->types.addPendingRecompile(cx, script);
     }
 };
 
 /*
  * Constraint which triggers recompilation if objects of a different kind are
  * added to a type set.
  */
-class TypeConstraintFreezeObjectKind : public TypeConstraint
+class TypeConstraintFreezeObjectKindSet : public TypeConstraint
 {
 public:
     ObjectKind kind;
 
-    TypeConstraintFreezeObjectKind(ObjectKind kind, JSScript *script)
-        : TypeConstraint("freezeObjectKind", script), kind(kind)
-    {}
+    TypeConstraintFreezeObjectKindSet(ObjectKind kind, JSScript *script)
+        : TypeConstraint("freezeObjectKindSet", script), kind(kind)
+    {
+        JS_ASSERT(kind != OBJECT_NONE);
+    }
 
     void newType(JSContext *cx, TypeSet *source, jstype type)
     {
         if (kind == OBJECT_UNKNOWN) {
             /* Despecialized the kind we were interested in due to recompilation. */
             return;
         }
 
         if (type == TYPE_UNKNOWN) {
             kind = OBJECT_UNKNOWN;
         } else if (TypeIsObject(type)) {
             TypeObject *object = (TypeObject *) type;
             ObjectKind nkind = CombineObjectKind(object, kind);
 
-            if (nkind != OBJECT_UNKNOWN &&
-                (kind == OBJECT_PACKED_ARRAY || kind == OBJECT_DENSE_ARRAY)) {
-                /*
-                 * Arrays can become not-packed or not-dense dynamically.
-                 * Add a constraint on the element type of the object to pick
-                 * up such changes.
-                 */
-                TypeSet *elementTypes = object->getProperty(cx, JSID_VOID, false);
-                if (!elementTypes)
-                    return;
-                elementTypes->add(cx,
-                    ArenaNew<TypeConstraintFreezeArray>(cx->compartment->types.pool, &kind, script), false);
-            }
+            /*
+             * Add a constraint on the element type of the object to pick up
+             * changes in the object's array-ness or any unknown properties.
+             */
+            TypeSet *elementTypes = object->getProperty(cx, JSID_VOID, false);
+            if (!elementTypes)
+                return;
+            elementTypes->add(cx,
+                ArenaNew<TypeConstraintFreezeObjectKind>(cx->compartment->types.pool,
+                                                         object, &kind, script), false);
 
             if (nkind == kind) {
                 /* New object with the same kind we are interested in. */
                 return;
             }
             kind = nkind;
-
-            cx->compartment->types.addPendingRecompile(cx, script);
+        } else {
+            return;
         }
+
+        cx->compartment->types.addPendingRecompile(cx, script);
     }
 };
 
 ObjectKind
 TypeSet::getKnownObjectKind(JSContext *cx, JSScript *script)
 {
     ObjectKind kind = OBJECT_NONE;
 
@@ -1508,24 +1498,26 @@ TypeSet::getKnownObjectKind(JSContext *c
         unsigned objectCapacity = HashSetCapacity(objectCount);
         for (unsigned i = 0; i < objectCapacity; i++) {
             TypeObject *object = objectSet[i];
             if (object)
                 kind = CombineObjectKind(object, kind);
         }
     } else if (objectCount == 1) {
         kind = CombineObjectKind((TypeObject *) objectSet, kind);
+    } else {
+        return OBJECT_UNKNOWN;
     }
 
     if (kind != OBJECT_UNKNOWN) {
         /*
          * Watch for new objects of different kind, and re-traverse existing types
          * in this set to add any needed FreezeArray constraints.
          */
-        add(cx, ArenaNew<TypeConstraintFreezeObjectKind>(cx->compartment->types.pool, kind, script));
+        add(cx, ArenaNew<TypeConstraintFreezeObjectKindSet>(cx->compartment->types.pool, kind, script));
     }
 
     return kind;
 }
 
 /* Constraint which triggers recompilation if any type is added to a type set. */
 class TypeConstraintFreezeNonEmpty : public TypeConstraint
 {
@@ -2478,50 +2470,65 @@ TypeObject::addProperty(JSContext *cx, j
         storeToInstances(cx, base);
 
     /* Pull in this property from all prototypes up the chain. */
     getFromPrototypes(cx, base);
 
     return true;
 }
 
+static inline void
+ObjectStateChange(JSContext *cx, TypeObject *object, bool markingUnknown)
+{
+    /* All constraints listening to state changes are on the element types. */
+    TypeSet *elementTypes = object->getProperty(cx, JSID_VOID, false);
+    if (!elementTypes)
+        return;
+    if (markingUnknown) {
+        /* Mark as unknown after getting the element types, to avoid assert. */
+        object->unknownProperties = true;
+    }
+    TypeConstraint *constraint = elementTypes->constraintList;
+    while (constraint) {
+        constraint->newObjectState(cx);
+        constraint = constraint->next;
+    }
+}
+
 void
 TypeObject::markNotPacked(JSContext *cx, bool notDense)
 {
     JS_ASSERT(cx->compartment->types.inferenceDepth);
 
     if (notDense) {
         if (!isDenseArray)
             return;
         isDenseArray = false;
     } else if (!isPackedArray) {
         return;
     }
     isPackedArray = false;
 
     InferSpew(ISpewOps, "%s: %s", notDense ? "NonDenseArray" : "NonPackedArray", name());
 
-    /* All constraints listening to changes in packed/dense status are on the element types. */
-    TypeSet *elementTypes = getProperty(cx, JSID_VOID, false);
-    if (!elementTypes)
-        return;
-    TypeConstraint *constraint = elementTypes->constraintList;
-    while (constraint) {
-        constraint->arrayNotPacked(cx, notDense);
-        constraint = constraint->next;
-    }
+    ObjectStateChange(cx, this, false);
 }
 
 void
 TypeObject::markUnknown(JSContext *cx)
 {
     JS_ASSERT(!unknownProperties);
 
-    markNotPacked(cx, true);
-    unknownProperties = true;
+    InferSpew(ISpewOps, "UnknownProperties: %s", name());
+
+    isDenseArray = false;
+    isPackedArray = false;
+    hasSpecialEquality = true;
+
+    ObjectStateChange(cx, this, true);
 
     /* Mark existing instances as unknown. */
 
     TypeObject *instance = instanceList;
     while (instance) {
         if (!instance->unknownProperties)
             instance->markUnknown(cx);
         instance = instance->instanceNext;
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -169,22 +169,21 @@ public:
         this->kind_ = kind;
 #endif
     }
 
     /* Register a new type for the set this constraint is listening to. */
     virtual void newType(JSContext *cx, TypeSet *source, jstype type) = 0;
 
     /*
-     * Mark the object containing the set this constraint is listening to
-     * as not a packed array and, possibly, not a dense array.
-     * This is only used for constraints attached to the index type set
-     * (JSID_VOID) of a TypeObject.
+     * For constraints attached to the index type set of an object (JSID_VOID),
+     * mark a change in one of the object's dynamic properties (isDenseArray,
+     * isPackedArray, or unknownProperties).
      */
-    virtual void arrayNotPacked(JSContext *cx, bool notDense) {}
+    virtual void newObjectState(JSContext *cx) {}
 
     /*
      * Whether this is an input type constraint condensed from the original
      * constraints generated during analysis of the associated script.
      * If this type set changes then the script will be reanalyzed/recompiled
      * should the type set change at all in the future.
      */
     virtual bool condensed() { return false; }
@@ -203,25 +202,28 @@ public:
  *                    NONE
  *       ___________ /  | \______________
  *      /               |                \
  * PACKED_ARRAY  SCRIPTED_FUNCTION  NATIVE_FUNCTION
  *      |               |                 |
  * DENSE_ARRAY          |                 |
  *      \____________   |   _____________/
  *                   \  |  /
+ *             NO_SPECIAL_EQUALITY
+ *                      |
  *                   UNKNOWN
  */
 enum ObjectKind {
     OBJECT_NONE,
     OBJECT_UNKNOWN,
     OBJECT_PACKED_ARRAY,
     OBJECT_DENSE_ARRAY,
     OBJECT_SCRIPTED_FUNCTION,
-    OBJECT_NATIVE_FUNCTION
+    OBJECT_NATIVE_FUNCTION,
+    OBJECT_NO_SPECIAL_EQUALITY
 };
 
 /* Coarse flags for the contents of a type set. */
 enum {
     TYPE_FLAG_UNDEFINED = 1 << TYPE_UNDEFINED,
     TYPE_FLAG_NULL      = 1 << TYPE_NULL,
     TYPE_FLAG_BOOLEAN   = 1 << TYPE_BOOLEAN,
     TYPE_FLAG_INT32     = 1 << TYPE_INT32,
@@ -307,16 +309,19 @@ struct TypeSet
      */
 
     /* Get any type tag which all values in this set must have. */
     JSValueType getKnownTypeTag(JSContext *cx, JSScript *script);
 
     /* Get information about the kinds of objects in this type set. */
     ObjectKind getKnownObjectKind(JSContext *cx, JSScript *script);
 
+    /* Whether any objects in this type set have unknown properties. */
+    bool hasUnknownProperties(JSContext *cx, JSScript *script);
+
     /* Get whether this type set is non-empty. */
     bool knownNonEmpty(JSContext *cx, JSScript *script);
 
     /* Mark all current and future types in this set as pushed by script/pc. */
     void pushAllTypes(JSContext *cx, JSScript *script, const jsbytecode *pc);
 
     /*
      * Clone (possibly NULL) source onto target; if any new types are added to
@@ -425,16 +430,19 @@ struct TypeObject
     bool unknownProperties;
 
     /* Whether all objects this represents are dense arrays. */
     bool isDenseArray;
 
     /* Whether all objects this represents are packed arrays (implies isDenseArray). */
     bool isPackedArray;
 
+    /* Whether any objects this represents have an equality hook. */
+    bool hasSpecialEquality;
+
     TypeObject() {}
 
     /* Make an object with the specified name. */
     inline TypeObject(jsid id, JSObject *proto);
 
     /* Coerce this object to a function. */
     TypeFunction* asFunction()
     {
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -1192,17 +1192,17 @@ TypeObject::name()
 #endif
 }
 
 inline TypeObject::TypeObject(jsid name, JSObject *proto)
     : proto(proto), emptyShapes(NULL), isFunction(false), marked(false),
       initializerObject(false), initializerArray(false), initializerOffset(0),
       contribution(0), propertySet(NULL), propertyCount(0),
       instanceList(NULL), instanceNext(NULL), next(NULL), unknownProperties(false),
-      isDenseArray(false), isPackedArray(false)
+      isDenseArray(false), isPackedArray(false), hasSpecialEquality(false)
 {
 #ifdef DEBUG
     this->name_ = name;
 #endif
 
     InferSpew(ISpewOps, "newObject: %s", this->name());
 
     if (proto) {
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3954,16 +3954,23 @@ DefineConstructorAndPrototype(JSContext 
 
     if (clasp == &js_ArrayClass && !proto->makeDenseArraySlow(cx))
         return NULL;
 
     TypeObject *type = proto->getNewType(cx);
     if (!type)
         return NULL;
 
+    /* Mark types with a special equality hook as having unknown properties. */
+    if (clasp->ext.equality &&
+        (!cx->markTypeObjectUnknownProperties(type) ||
+         !cx->markTypeObjectUnknownProperties(proto->getType()))) {
+        return NULL;
+    }
+
     proto->syncSpecialEquality();
 
     /* After this point, control must exit via label bad or out. */
     AutoObjectRooter tvr(cx, proto);
 
     JSObject *ctor;
     bool named = false;
     if (!constructor) {
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -117,18 +117,20 @@ JSObject::unbrand(JSContext *cx)
     }
     setGeneric();
     return true;
 }
 
 inline void
 JSObject::syncSpecialEquality()
 {
-    if (clasp->ext.equality)
+    if (clasp->ext.equality) {
         flags |= JSObject::HAS_EQUALITY;
+        JS_ASSERT(getType()->hasSpecialEquality);
+    }
 }
 
 inline void
 JSObject::finalize(JSContext *cx)
 {
     /* Cope with stillborn objects that have no map. */
     if (!map)
         return;
@@ -752,16 +754,17 @@ JSObject::clearType(JSContext *cx)
 inline void
 JSObject::setType(js::types::TypeObject *newType)
 {
 #ifdef DEBUG
     JS_ASSERT(newType);
     for (JSObject *obj = newType->proto; obj; obj = obj->getProto())
         JS_ASSERT(obj != this);
 #endif
+    JS_ASSERT_IF(hasSpecialEquality(), newType->hasSpecialEquality);
     type = newType;
 }
 
 inline bool
 JSObject::setTypeAndUniqueShape(JSContext *cx, js::types::TypeObject *newType)
 {
     setType(newType);
 
@@ -809,19 +812,16 @@ JSObject::init(JSContext *cx, js::Class 
      * or setSharedNonNativeMap after calling init. To defend this requirement
      * we set map to null in DEBUG builds, and set objShape to a value we then
      * assert obj->shape() never returns.
      */
     map = NULL;
     objShape = JSObjectMap::INVALID_SHAPE;
 #endif
 
-    setType(type);
-    setParent(parent);
-
     privateData = priv;
     slots = fixedSlots();
 
     /*
      * Fill the fixed slots with undefined if needed.  This object must
      * already have its capacity filled in, as by js_NewGCObject.
      */
     JS_ASSERT(capacity == numFixedSlots());
@@ -829,16 +829,19 @@ JSObject::init(JSContext *cx, js::Class 
     if (useHoles) {
         initializedLength = 0;
         flags = PACKED_ARRAY;
     } else {
         ClearValueRange(slots, capacity, false);
         newType = NULL;
         flags = 0;
     }
+
+    setType(type);
+    setParent(parent);
 }
 
 inline void
 JSObject::finish(JSContext *cx)
 {
 #ifdef DEBUG
     if (isNative())
         JS_LOCK_RUNTIME_VOID(cx->runtime, cx->runtime->liveObjectProps -= propertyCount());
--- a/js/src/methodjit/Compiler.h
+++ b/js/src/methodjit/Compiler.h
@@ -600,16 +600,38 @@ class Compiler : public BaseCompiler
     bool jsop_getelem(bool isCall);
     void jsop_getelem_dense(bool isPacked);
     bool isCacheableBaseAndIndex(FrameEntry *obj, FrameEntry *id);
     void jsop_stricteq(JSOp op);
     bool jsop_equality(JSOp op, BoolStub stub, jsbytecode *target, JSOp fused);
     bool jsop_equality_int_string(JSOp op, BoolStub stub, jsbytecode *target, JSOp fused);
     void jsop_pos();
 
+    static inline Assembler::Condition
+    GetCompareCondition(JSOp op, JSOp fused)
+    {
+        bool ifeq = fused == JSOP_IFEQ;
+        switch (op) {
+          case JSOP_GT:
+            return ifeq ? Assembler::LessThanOrEqual : Assembler::GreaterThan;
+          case JSOP_GE:
+            return ifeq ? Assembler::LessThan : Assembler::GreaterThanOrEqual;
+          case JSOP_LT:
+            return ifeq ? Assembler::GreaterThanOrEqual : Assembler::LessThan;
+          case JSOP_LE:
+            return ifeq ? Assembler::GreaterThan : Assembler::LessThanOrEqual;
+          case JSOP_EQ:
+            return ifeq ? Assembler::NotEqual : Assembler::Equal;
+          case JSOP_NE:
+            return ifeq ? Assembler::Equal : Assembler::NotEqual;
+          default:
+            JS_NOT_REACHED("unrecognized op");
+            return Assembler::Equal;
+        }
+    }
    
     void prepareStubCall(Uses uses);
     Call emitStubCall(void *ptr);
 };
 
 // Given a stub call, emits the call into the inline assembly path. If
 // debug mode is on, adds the appropriate instrumentation for recompilation.
 #define INLINE_STUBCALL(stub)                                               \
--- a/js/src/methodjit/FastArithmetic.cpp
+++ b/js/src/methodjit/FastArithmetic.cpp
@@ -1471,35 +1471,16 @@ ReverseCompareOp(JSOp op)
       case JSOP_LE:
         return JSOP_GE;
       default:
         JS_NOT_REACHED("unrecognized op");
         return op;
     }
 }
 
-static inline Assembler::Condition
-GetCompareCondition(JSOp op, JSOp fused)
-{
-    bool ifeq = fused == JSOP_IFEQ;
-    switch (op) {
-      case JSOP_GT:
-        return ifeq ? Assembler::LessThanOrEqual : Assembler::GreaterThan;
-      case JSOP_GE:
-        return ifeq ? Assembler::LessThan : Assembler::GreaterThanOrEqual;
-      case JSOP_LT:
-        return ifeq ? Assembler::GreaterThanOrEqual : Assembler::LessThan;
-      case JSOP_LE:
-        return ifeq ? Assembler::GreaterThan : Assembler::LessThanOrEqual;
-      default:
-        JS_NOT_REACHED("unrecognized op");
-        return Assembler::Equal;
-    }
-}
-
 bool
 mjit::Compiler::jsop_relational_int(JSOp op, jsbytecode *target, JSOp fused)
 {
     FrameEntry *rhs = frame.peek(-1);
     FrameEntry *lhs = frame.peek(-2);
 
     /* Reverse N cmp A comparisons.  The left side must be in a register. */
     if (lhs->isConstant()) {
--- a/js/src/methodjit/FastOps.cpp
+++ b/js/src/methodjit/FastOps.cpp
@@ -558,16 +558,54 @@ mjit::Compiler::jsop_equality(JSOp op, B
             j.linkTo(masm.label(), &masm);
             masm.move(Imm32(op == JSOP_EQ), reg);
             j3.linkTo(masm.label(), &masm);
             frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, reg);
         }
         return true;
     }
 
+    if (cx->typeInferenceEnabled() &&
+        lhs->isType(JSVAL_TYPE_OBJECT) && rhs->isType(JSVAL_TYPE_OBJECT)) {
+        /*
+         * Handle equality between two objects. We have to ensure there is no
+         * special equality operator on either object, if that passes then
+         * this is a pointer comparison.
+         */
+        types::TypeSet *lhsTypes = frame.getTypeSet(lhs);
+        types::TypeSet *rhsTypes = frame.getTypeSet(rhs);
+        types::ObjectKind lhsKind =
+            lhsTypes ? lhsTypes->getKnownObjectKind(cx, script) : types::OBJECT_UNKNOWN;
+        types::ObjectKind rhsKind =
+            rhsTypes ? rhsTypes->getKnownObjectKind(cx, script) : types::OBJECT_UNKNOWN;
+
+        if (lhsKind != types::OBJECT_UNKNOWN && rhsKind != types::OBJECT_UNKNOWN) {
+            /* :TODO: Merge with jsop_relational_int? */
+            JS_ASSERT_IF(!target, fused != JSOP_IFEQ);
+            Assembler::Condition cond = GetCompareCondition(op, fused);
+            if (target) {
+                fixDoubleTypes(Uses(2));
+                if (!frame.syncForBranch(target, Uses(2)))
+                    return false;
+                Jump fast = masm.branchPtr(cond, frame.tempRegForData(lhs), frame.tempRegForData(rhs));
+                frame.popn(2);
+                return jumpAndTrace(fast, target);
+            } else {
+                RegisterID lreg = frame.tempRegForData(lhs);
+                RegisterID rreg = frame.tempRegForData(rhs);
+                RegisterID result = frame.allocReg();
+                masm.branchValue(cond, lreg, rreg, result);
+
+                frame.popn(2);
+                frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, result);
+                return true;
+            }
+        }
+    }
+
     return emitStubCmpOp(stub, target, fused);
 }
 
 bool
 mjit::Compiler::jsop_relational(JSOp op, BoolStub stub, jsbytecode *target, JSOp fused)
 {
     FrameEntry *rhs = frame.peek(-1);
     FrameEntry *lhs = frame.peek(-2);