Bug 861596 - Add optimized ArgumentsObject stub to ion ICs. Topcrash fixed. r=h4writer
authorKannan Vijayan <kvijayan@mozilla.com>
Fri, 03 May 2013 15:31:42 -0400
changeset 141732 c860539f82bbf52e173e256a078258b91dddf4bd
parent 141731 5878a9a7c54083345f54051069c2ed7a1c336230
child 141733 45ec50d84651e8a21e845090385b0033d15a4750
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersh4writer
bugs861596
milestone23.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 861596 - Add optimized ArgumentsObject stub to ion ICs. Topcrash fixed. r=h4writer
js/src/ion/IonCaches.cpp
js/src/ion/IonCaches.h
js/src/ion/IonMacroAssembler.h
js/src/ion/arm/MacroAssembler-arm.cpp
js/src/ion/arm/MacroAssembler-arm.h
js/src/ion/x64/MacroAssembler-x64.h
js/src/ion/x86/MacroAssembler-x86.h
--- a/js/src/ion/IonCaches.cpp
+++ b/js/src/ion/IonCaches.cpp
@@ -393,18 +393,24 @@ IonCache::linkAndAttachStub(JSContext *c
 {
     IonCode *code = NULL;
     LinkStatus status = linkCode(cx, masm, ion, &code);
     if (status != LINK_GOOD)
         return status != LINK_ERROR;
 
     attachStub(masm, attacher, code);
 
-    IonSpew(IonSpew_InlineCaches, "Generated %s %s stub at %p",
-            attachKind, CacheName(kind()), code->raw());
+    if (pc) {
+        IonSpew(IonSpew_InlineCaches, "Cache %p(%s:%d/%d) generated %s %s stub at %p",
+                this, script->filename(), script->lineno, pc - script->code,
+                attachKind, CacheName(kind()), code->raw());
+    } else {
+        IonSpew(IonSpew_InlineCaches, "Cache %p generated %s %s stub at %p",
+                this, attachKind, CacheName(kind()), code->raw());
+    }
     return true;
 }
 
 void
 IonCache::updateBaseAddress(IonCode *code, MacroAssembler &masm)
 {
     fallbackLabel_.repoint(code, &masm);
 }
@@ -539,16 +545,49 @@ IsCacheableNoProperty(JSObject *obj, JSO
     // monitor and invalidate the script.
     if (!output.hasValue())
         return false;
 
     return true;
 }
 
 static bool
+IsOptimizableArgumentsObjectForLength(JSObject *obj)
+{
+    if (!obj->isArguments())
+        return false;
+
+    if (obj->asArguments().hasOverriddenLength())
+        return false;
+
+    return true;
+}
+
+static bool
+IsOptimizableArgumentsObjectForGetElem(JSObject *obj, Value idval)
+{
+    if (!IsOptimizableArgumentsObjectForLength(obj))
+        return false;
+
+    ArgumentsObject &argsObj = obj->asArguments();
+
+    if (argsObj.isAnyElementDeleted())
+        return false;
+
+    if (!idval.isInt32())
+        return false;
+
+    int32_t idint = idval.toInt32();
+    if (idint < 0 || static_cast<uint32_t>(idint) >= argsObj.initialLength())
+        return false;
+
+    return true;
+}
+
+static bool
 IsCacheableGetPropCallNative(JSObject *obj, JSObject *holder, Shape *shape)
 {
     if (!shape || !IsCacheableProtoChain(obj, holder))
         return false;
 
     if (!shape->hasGetterValue() || !shape->getterValue().isObject())
         return false;
 
@@ -1071,16 +1110,71 @@ GetPropertyIC::attachTypedArrayLength(JS
     masm.bind(&failures);
     attacher.jumpNextStub(masm);
 
     JS_ASSERT(!hasTypedArrayLengthStub_);
     hasTypedArrayLengthStub_ = true;
     return linkAndAttachStub(cx, masm, attacher, ion, "typed array length");
 }
 
+bool
+GetPropertyIC::attachArgumentsLength(JSContext *cx, IonScript *ion, JSObject *obj)
+{
+    JS_ASSERT(obj->isArguments());
+    JS_ASSERT(!idempotent());
+
+    Label failures;
+    MacroAssembler masm(cx);
+    RepatchStubAppender attacher(*this);
+
+    Register tmpReg;
+    if (output().hasValue()) {
+        tmpReg = output().valueReg().scratchReg();
+    } else {
+        JS_ASSERT(output().type() == MIRType_Int32);
+        tmpReg = output().typedReg().gpr();
+    }
+    JS_ASSERT(object() != tmpReg);
+
+    Class *clasp = obj->isStrictArguments() ? &StrictArgumentsObjectClass
+                                            : &NormalArgumentsObjectClass;
+
+    Label fail;
+    Label pass;
+    masm.branchTestObjClass(Assembler::NotEqual, object(), tmpReg, clasp, &failures);
+
+    // Get initial ArgsObj length value, test if length has been overridden.
+    masm.unboxInt32(Address(object(), ArgumentsObject::getInitialLengthSlotOffset()), tmpReg);
+    masm.branchTest32(Assembler::NonZero, tmpReg, Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT),
+                      &failures);
+
+    masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), tmpReg);
+
+    // If output is Int32, result is already in right place, otherwise box it into output.
+    if (output().hasValue())
+        masm.tagValue(JSVAL_TYPE_INT32, tmpReg, output().valueReg());
+
+    // Success.
+    attacher.jumpRejoin(masm);
+
+    // Failure.
+    masm.bind(&failures);
+    attacher.jumpNextStub(masm);
+
+    if (obj->isStrictArguments()) {
+        JS_ASSERT(!hasStrictArgumentsLengthStub_);
+        hasStrictArgumentsLengthStub_ = true;
+        return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj length (strict)");
+    }
+
+    JS_ASSERT(!hasNormalArgumentsLengthStub_);
+    hasNormalArgumentsLengthStub_ = true;
+    return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj length (normal)");
+}
+
 static bool
 IsIdempotentAndMaybeHasHooks(IonCache &cache, JSObject *obj)
 {
     // If the cache is idempotent, watch out for resolve hooks or non-native
     // objects on the proto chain. We check this before calling lookupProperty,
     // to make sure no effectful lookup hooks or resolve hooks are called.
     return cache.idempotent() && !obj->hasIdempotentProtoChain();
 }
@@ -1141,16 +1235,17 @@ IsIdempotentAndHasSingletonHolder(IonCac
 static bool
 TryAttachNativeGetPropStub(JSContext *cx, IonScript *ion,
                            GetPropertyIC &cache, HandleObject obj,
                            HandlePropertyName name,
                            const SafepointIndex *safepointIndex,
                            void *returnAddr, bool *isCacheable)
 {
     JS_ASSERT(!*isCacheable);
+    JS_ASSERT(cache.canAttachStub());
 
     RootedObject checkObj(cx, obj);
     if (IsCacheableListBase(obj)) {
         Value expandoVal = obj->getFixedSlot(GetListBaseExpandoSlot());
 
         // Expando objects just hold any extra properties the object has been given by a script,
         // and have no prototype or anything else that will complicate property lookups on them.
         JS_ASSERT_IF(expandoVal.isObject(),
@@ -1181,20 +1276,16 @@ TryAttachNativeGetPropStub(JSContext *cx
         return true;
     }
 
     if (IsIdempotentAndHasSingletonHolder(cache, holder, shape))
         return true;
 
     *isCacheable = true;
 
-    // Falback to the interpreter function.
-    if (!cache.canAttachStub())
-        return true;
-
     if (readSlot)
         return cache.attachReadSlot(cx, ion, obj, holder, shape);
     else if (obj->isArray() && !cache.hasArrayLengthStub() && cx->names().length == name)
         return cache.attachArrayLength(cx, ion, obj);
     return cache.attachCallGetter(cx, ion, obj, holder, shape, safepointIndex, returnAddr);
 }
 
 bool
@@ -1216,35 +1307,45 @@ GetPropertyIC::update(JSContext *cx, siz
     // If the cache is idempotent, we will redo the op in the interpreter.
     if (cache.idempotent())
         adi.disable();
 
     // For now, just stop generating new stubs once we hit the stub count
     // limit. Once we can make calls from within generated stubs, a new call
     // stub will be generated instead and the previous stubs unlinked.
     bool isCacheable = false;
-    if (!TryAttachNativeGetPropStub(cx, ion, cache, obj, name,
-                                    safepointIndex, returnAddr,
-                                    &isCacheable))
-    {
-        return false;
-    }
-
-    if (!isCacheable && cache.canAttachStub() &&
-        !cache.idempotent() && cx->names().length == name)
-    {
-        if (cache.output().type() != MIRType_Value && cache.output().type() != MIRType_Int32) {
-            // The next execution should cause an invalidation because the type
-            // does not fit.
-            isCacheable = false;
-        } else if (obj->isTypedArray() && !cache.hasTypedArrayLengthStub()) {
+    if (cache.canAttachStub()) {
+        if (name == cx->names().length &&
+            IsOptimizableArgumentsObjectForLength(obj) &&
+            (cache.output().type() == MIRType_Value || cache.output().type() == MIRType_Int32) &&
+            !cache.hasArgumentsLengthStub(obj->isStrictArguments()))
+        {
             isCacheable = true;
-            if (!cache.attachTypedArrayLength(cx, ion, obj))
+            if (!cache.attachArgumentsLength(cx, ion, obj))
                 return false;
         }
+
+        if (!isCacheable && !TryAttachNativeGetPropStub(cx, ion, cache, obj, name,
+                                                        safepointIndex, returnAddr,
+                                                        &isCacheable))
+        {
+            return false;
+        }
+
+        if (!isCacheable && !cache.idempotent() && cx->names().length == name) {
+            if (cache.output().type() != MIRType_Value && cache.output().type() != MIRType_Int32) {
+                // The next execution should cause an invalidation because the type
+                // does not fit.
+                isCacheable = false;
+            } else if (obj->isTypedArray() && !cache.hasTypedArrayLengthStub()) {
+                isCacheable = true;
+                if (!cache.attachTypedArrayLength(cx, ion, obj))
+                    return false;
+            }
+        }
     }
 
     if (cache.idempotent() && !isCacheable) {
         // Invalidate the cache if the property was not found, or was found on
         // a non-native object. This ensures:
         // 1) The property read has no observable side-effects.
         // 2) There's no need to dynamically monitor the return type. This would
         //    be complicated since (due to GVN) there can be multiple pc's
@@ -2111,16 +2212,125 @@ GetElementIC::attachTypedArrayElement(JS
     masm.bind(&failures);
 
     attacher.jumpNextStub(masm);
 
     return linkAndAttachStub(cx, masm, attacher, ion, "typed array");
 }
 
 bool
+GetElementIC::attachArgumentsElement(JSContext *cx, IonScript *ion, JSObject *obj)
+{
+    JS_ASSERT(obj->isArguments());
+
+    Label failures;
+    MacroAssembler masm(cx);
+    RepatchStubAppender attacher(*this);
+
+    Register tmpReg = output().scratchReg().gpr();
+    JS_ASSERT(tmpReg != InvalidReg);
+
+    Class *clasp = obj->isStrictArguments() ? &StrictArgumentsObjectClass
+                                            : &NormalArgumentsObjectClass;
+
+    Label fail;
+    Label pass;
+    masm.branchTestObjClass(Assembler::NotEqual, object(), tmpReg, clasp, &failures);
+
+    // Get initial ArgsObj length value, test if length has been overridden.
+    masm.unboxInt32(Address(object(), ArgumentsObject::getInitialLengthSlotOffset()), tmpReg);
+    masm.branchTest32(Assembler::NonZero, tmpReg, Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT),
+                      &failures);
+    masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), tmpReg);
+
+    // Decide to what type index the stub should be optimized
+    Register indexReg;
+    JS_ASSERT(!index().constant());
+
+    // Check index against length.
+    Label failureRestoreIndex;
+    if (index().reg().hasValue()) {
+        ValueOperand val = index().reg().valueReg();
+        masm.branchTestInt32(Assembler::NotEqual, val, &failures);
+        indexReg = val.scratchReg();
+
+        masm.unboxInt32(val, indexReg);
+        masm.branch32(Assembler::AboveOrEqual, indexReg, tmpReg, &failureRestoreIndex);
+    } else {
+        JS_ASSERT(index().reg().type() == MIRType_Int32);
+        indexReg = index().reg().typedReg().gpr();
+        masm.branch32(Assembler::AboveOrEqual, indexReg, tmpReg, &failures);
+    }
+    // Save indexReg because it needs to be clobbered to check deleted bit.
+    Label failurePopIndex;
+    masm.push(indexReg);
+
+    // Check if property was deleted on arguments object.
+    masm.loadPrivate(Address(object(), ArgumentsObject::getDataSlotOffset()), tmpReg);
+    masm.loadPtr(Address(tmpReg, offsetof(ArgumentsData, deletedBits)), tmpReg);
+
+    // In tempReg, calculate index of word containing bit: (idx >> logBitsPerWord)
+    masm.rshiftPtr(Imm32(JS_BITS_PER_WORD_LOG2), indexReg);
+    masm.loadPtr(BaseIndex(tmpReg, indexReg, ScaleFromElemWidth(sizeof(size_t))), tmpReg);
+
+    // Don't bother testing specific bit, if any bit is set in the word, fail.
+    masm.branchPtr(Assembler::NotEqual, tmpReg, ImmWord((size_t)0), &failurePopIndex);
+
+    // Get the address to load from into tmpReg
+    masm.loadPrivate(Address(object(), ArgumentsObject::getDataSlotOffset()), tmpReg);
+    masm.addPtr(Imm32(ArgumentsData::offsetOfArgs()), tmpReg);
+
+    // Restore original index register value, to use for indexing element.
+    masm.pop(indexReg);
+    BaseIndex elemIdx(tmpReg, indexReg, ScaleFromElemWidth(sizeof(Value)));
+
+    // Ensure result is not magic value, and type-check result.
+    masm.branchTestMagic(Assembler::Equal, elemIdx, &failureRestoreIndex);
+
+    if (output().hasTyped()) {
+        JS_ASSERT(!output().typedReg().isFloat());
+        JS_ASSERT(index().reg().type() == MIRType_Boolean ||
+                  index().reg().type() == MIRType_Int32 ||
+                  index().reg().type() == MIRType_String ||
+                  index().reg().type() == MIRType_Object);
+        masm.branchTestMIRType(Assembler::NotEqual, elemIdx, index().reg().type(),
+                               &failureRestoreIndex);
+    }
+
+    masm.loadTypedOrValue(elemIdx, output());
+
+    // indexReg may need to be reconstructed if it was originally a value.
+    if (index().reg().hasValue())
+        masm.tagValue(JSVAL_TYPE_INT32, indexReg, index().reg().valueReg());
+
+    // Success.
+    attacher.jumpRejoin(masm);
+
+    // Restore the object before continuing to the next stub.
+    masm.bind(&failurePopIndex);
+    masm.pop(indexReg);
+    masm.bind(&failureRestoreIndex);
+    if (index().reg().hasValue())
+        masm.tagValue(JSVAL_TYPE_INT32, indexReg, index().reg().valueReg());
+    masm.bind(&failures);
+    attacher.jumpNextStub(masm);
+
+
+    if (obj->isStrictArguments()) {
+        JS_ASSERT(!hasStrictArgumentsStub_);
+        hasStrictArgumentsStub_ = true;
+        return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj element (strict)");
+    }
+
+    JS_ASSERT(!hasNormalArgumentsStub_);
+    hasNormalArgumentsStub_ = true;
+    return linkAndAttachStub(cx, masm, attacher, ion, "ArgsObj element (normal)");
+}
+
+bool
 GetElementIC::update(JSContext *cx, size_t cacheIndex, HandleObject obj,
                      HandleValue idval, MutableHandleValue res)
 {
     IonScript *ion = GetTopIonJSScript(cx)->ionScript();
     GetElementIC &cache = ion->getCache(cacheIndex).toGetElement();
     RootedScript script(cx);
     jsbytecode *pc;
     cache.getScriptedLocation(&script, &pc);
@@ -2138,17 +2348,27 @@ GetElementIC::update(JSContext *cx, size
     AutoDetectInvalidation adi(cx, res.address(), ion);
 
     RootedId id(cx);
     if (!ValueToId<CanGC>(cx, idval, &id))
         return false;
 
     bool attachedStub = false;
     if (cache.canAttachStub()) {
-        if (obj->isNative() && cache.monitoredResult()) {
+        if (IsOptimizableArgumentsObjectForGetElem(obj, idval) &&
+            !cache.hasArgumentsStub(obj->isStrictArguments()) &&
+            !cache.index().constant() &&
+            (cache.index().reg().hasValue() ||
+             cache.index().reg().type() == MIRType_Int32) &&
+            (cache.output().hasValue() || !cache.output().typedReg().isFloat()))
+        {
+            if (!cache.attachArgumentsElement(cx, ion, obj))
+                return false;
+            attachedStub = true;
+        } else if (obj->isNative() && cache.monitoredResult()) {
             uint32_t dummy;
             if (idval.isString() && JSID_IS_ATOM(id) && !JSID_TO_ATOM(id)->isIndex(&dummy)) {
                 RootedPropertyName name(cx, JSID_TO_ATOM(id)->asPropertyName());
                 if (!cache.attachGetProp(cx, ion, obj, idval, name))
                     return false;
                 attachedStub = true;
             }
         } else if (!cache.hasDenseStub() && obj->isNative() && idval.isInt32()) {
--- a/js/src/ion/IonCaches.h
+++ b/js/src/ion/IonCaches.h
@@ -491,16 +491,18 @@ class GetPropertyIC : public RepatchIonC
     RegisterSet liveRegs_;
 
     Register object_;
     PropertyName *name_;
     TypedOrValueRegister output_;
     bool allowGetters_ : 1;
     bool hasArrayLengthStub_ : 1;
     bool hasTypedArrayLengthStub_ : 1;
+    bool hasStrictArgumentsLengthStub_ : 1;
+    bool hasNormalArgumentsLengthStub_ : 1;
 
   public:
     GetPropertyIC(RegisterSet liveRegs,
                   Register object, PropertyName *name,
                   TypedOrValueRegister output,
                   bool allowGetters)
       : liveRegs_(liveRegs),
         object_(object),
@@ -529,24 +531,28 @@ class GetPropertyIC : public RepatchIonC
         return allowGetters_;
     }
     bool hasArrayLengthStub() const {
         return hasArrayLengthStub_;
     }
     bool hasTypedArrayLengthStub() const {
         return hasTypedArrayLengthStub_;
     }
+    bool hasArgumentsLengthStub(bool strict) const {
+        return strict ? hasStrictArgumentsLengthStub_ : hasNormalArgumentsLengthStub_;
+    }
 
     bool attachReadSlot(JSContext *cx, IonScript *ion, JSObject *obj, JSObject *holder,
                         HandleShape shape);
     bool attachCallGetter(JSContext *cx, IonScript *ion, JSObject *obj, JSObject *holder,
                           HandleShape shape,
                           const SafepointIndex *safepointIndex, void *returnAddr);
     bool attachArrayLength(JSContext *cx, IonScript *ion, JSObject *obj);
     bool attachTypedArrayLength(JSContext *cx, IonScript *ion, JSObject *obj);
+    bool attachArgumentsLength(JSContext *cx, IonScript *ion, JSObject *obj);
 
     static bool update(JSContext *cx, size_t cacheIndex, HandleObject obj, MutableHandleValue vp);
 };
 
 class SetPropertyIC : public RepatchIonCache
 {
   protected:
     // Registers live after the cache, excluding output registers. The initial
@@ -603,16 +609,18 @@ class GetElementIC : public RepatchIonCa
 {
   protected:
     Register object_;
     ConstantOrRegister index_;
     TypedOrValueRegister output_;
 
     bool monitoredResult_ : 1;
     bool hasDenseStub_ : 1;
+    bool hasStrictArgumentsStub_ : 1;
+    bool hasNormalArgumentsStub_ : 1;
 
     size_t failedUpdates_;
 
     static const size_t MAX_FAILED_UPDATES;
 
   public:
     GetElementIC(Register object, ConstantOrRegister index,
                  TypedOrValueRegister output, bool monitoredResult)
@@ -639,24 +647,28 @@ class GetElementIC : public RepatchIonCa
         return output_;
     }
     bool monitoredResult() const {
         return monitoredResult_;
     }
     bool hasDenseStub() const {
         return hasDenseStub_;
     }
+    bool hasArgumentsStub(bool strict) const {
+        return strict ? hasStrictArgumentsStub_ : hasNormalArgumentsStub_;
+    }
     void setHasDenseStub() {
         JS_ASSERT(!hasDenseStub());
         hasDenseStub_ = true;
     }
 
     bool attachGetProp(JSContext *cx, IonScript *ion, HandleObject obj, const Value &idval, HandlePropertyName name);
     bool attachDenseElement(JSContext *cx, IonScript *ion, JSObject *obj, const Value &idval);
     bool attachTypedArrayElement(JSContext *cx, IonScript *ion, JSObject *obj, const Value &idval);
+    bool attachArgumentsElement(JSContext *cx, IonScript *ion, JSObject *obj);
 
     static bool
     update(JSContext *cx, size_t cacheIndex, HandleObject obj, HandleValue idval,
                 MutableHandleValue vp);
 
     void incFailedUpdates() {
         failedUpdates_++;
     }
--- a/js/src/ion/IonMacroAssembler.h
+++ b/js/src/ion/IonMacroAssembler.h
@@ -160,16 +160,41 @@ class MacroAssembler : public MacroAssem
     }
     void branchTestObjShape(Condition cond, Register obj, const Shape *shape, Label *label) {
         branchPtr(cond, Address(obj, JSObject::offsetOfShape()), ImmGCPtr(shape), label);
     }
     void branchTestObjShape(Condition cond, Register obj, Register shape, Label *label) {
         branchPtr(cond, Address(obj, JSObject::offsetOfShape()), shape, label);
     }
 
+    template <typename Value>
+    Condition testMIRType(Condition cond, const Value &val, MIRType type) {
+        JS_ASSERT(type == MIRType_Null    || type == MIRType_Undefined  ||
+                  type == MIRType_Boolean || type == MIRType_Int32      ||
+                  type == MIRType_String  || type == MIRType_Object     ||
+                  type == MIRType_Double);
+        switch (type) {
+          case MIRType_Null:        return testNull(cond, val);
+          case MIRType_Undefined:   return testUndefined(cond, val);
+          case MIRType_Boolean:     return testBoolean(cond, val);
+          case MIRType_Int32:       return testInt32(cond, val);
+          case MIRType_String:      return testString(cond, val);
+          case MIRType_Object:      return testObject(cond, val);
+          case MIRType_Double:      return testDouble(cond, val);
+          default:
+            JS_NOT_REACHED("Bad MIRType");
+        }
+    }
+
+    template <typename Value>
+    void branchTestMIRType(Condition cond, const Value &val, MIRType type, Label *label) {
+        cond = testMIRType(cond, val, type);
+        j(cond, label);
+    }
+
     // Branches to |label| if |reg| is false. |reg| should be a C++ bool.
     void branchIfFalseBool(const Register &reg, Label *label) {
         // Note that C++ bool is only 1 byte, so ignore the higher-order bits.
         branchTest32(Assembler::Zero, reg, Imm32(0xFF), label);
     }
 
     void loadObjPrivate(Register obj, uint32_t nfixed, Register dest) {
         loadPtr(Address(obj, JSObject::getPrivateDataOffset(nfixed)), dest);
--- a/js/src/ion/arm/MacroAssembler-arm.cpp
+++ b/js/src/ion/arm/MacroAssembler-arm.cpp
@@ -2295,43 +2295,25 @@ MacroAssemblerARMCompat::testGCThing(Ass
 {
     JS_ASSERT(cond == Equal || cond == NotEqual);
     extractTag(address, ScratchRegister);
     ma_cmp(ScratchRegister, ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET));
     return cond == Equal ? AboveOrEqual : Below;
 }
 
 Assembler::Condition
-MacroAssemblerARMCompat::testGCThing(Assembler::Condition cond, const BaseIndex &address)
-{
-    JS_ASSERT(cond == Equal || cond == NotEqual);
-    extractTag(address, ScratchRegister);
-    ma_cmp(ScratchRegister, ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET));
-    return cond == Equal ? AboveOrEqual : Below;
-}
-
-Assembler::Condition
 MacroAssemblerARMCompat::testMagic(Assembler::Condition cond, const Address &address)
 {
     JS_ASSERT(cond == Equal || cond == NotEqual);
     extractTag(address, ScratchRegister);
     ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_MAGIC));
     return cond;
 }
 
 Assembler::Condition
-MacroAssemblerARMCompat::testMagic(Assembler::Condition cond, const BaseIndex &address)
-{
-    JS_ASSERT(cond == Equal || cond == NotEqual);
-    extractTag(address, ScratchRegister);
-    ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_MAGIC));
-    return cond;
-}
-
-Assembler::Condition
 MacroAssemblerARMCompat::testInt32(Assembler::Condition cond, const Address &address)
 {
     JS_ASSERT(cond == Equal || cond == NotEqual);
     extractTag(address, ScratchRegister);
     ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_INT32));
     return cond;
 }
 
@@ -2355,16 +2337,98 @@ MacroAssemblerARMCompat::testDouble(Cond
 Assembler::Condition
 MacroAssemblerARMCompat::testNumber(Condition cond, const Register &tag)
 {
     JS_ASSERT(cond == Equal || cond == NotEqual);
     ma_cmp(tag, ImmTag(JSVAL_UPPER_INCL_TAG_OF_NUMBER_SET));
     return cond == Equal ? BelowOrEqual : Above;
 }
 
+Assembler::Condition
+MacroAssemblerARMCompat::testUndefined(Condition cond, const BaseIndex &src)
+{
+    JS_ASSERT(cond == Equal || cond == NotEqual);
+    extractTag(src, ScratchRegister);
+    ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_UNDEFINED));
+    return cond;
+}
+
+Assembler::Condition
+MacroAssemblerARMCompat::testNull(Condition cond, const BaseIndex &src)
+{
+    JS_ASSERT(cond == Equal || cond == NotEqual);
+    extractTag(src, ScratchRegister);
+    ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_NULL));
+    return cond;
+}
+
+Assembler::Condition
+MacroAssemblerARMCompat::testBoolean(Condition cond, const BaseIndex &src)
+{
+    JS_ASSERT(cond == Equal || cond == NotEqual);
+    extractTag(src, ScratchRegister);
+    ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_BOOLEAN));
+    return cond;
+}
+
+Assembler::Condition
+MacroAssemblerARMCompat::testString(Condition cond, const BaseIndex &src)
+{
+    JS_ASSERT(cond == Equal || cond == NotEqual);
+    extractTag(src, ScratchRegister);
+    ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_STRING));
+    return cond;
+}
+
+Assembler::Condition
+MacroAssemblerARMCompat::testInt32(Condition cond, const BaseIndex &src)
+{
+    JS_ASSERT(cond == Equal || cond == NotEqual);
+    extractTag(src, ScratchRegister);
+    ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_INT32));
+    return cond;
+}
+
+Assembler::Condition
+MacroAssemblerARMCompat::testObject(Condition cond, const BaseIndex &src)
+{
+    JS_ASSERT(cond == Equal || cond == NotEqual);
+    extractTag(src, ScratchRegister);
+    ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_OBJECT));
+    return cond;
+}
+
+Assembler::Condition
+MacroAssemblerARMCompat::testDouble(Condition cond, const BaseIndex &src)
+{
+    JS_ASSERT(cond == Equal || cond == NotEqual);
+    Assembler::Condition actual = (cond == Equal) ? Below : AboveOrEqual;
+    extractTag(src, ScratchRegister);
+    ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_CLEAR));
+    return actual;
+}
+
+Assembler::Condition
+MacroAssemblerARMCompat::testMagic(Condition cond, const BaseIndex &address)
+{
+    JS_ASSERT(cond == Equal || cond == NotEqual);
+    extractTag(address, ScratchRegister);
+    ma_cmp(ScratchRegister, ImmTag(JSVAL_TAG_MAGIC));
+    return cond;
+}
+
+Assembler::Condition
+MacroAssemblerARMCompat::testGCThing(Condition cond, const BaseIndex &address)
+{
+    JS_ASSERT(cond == Equal || cond == NotEqual);
+    extractTag(address, ScratchRegister);
+    ma_cmp(ScratchRegister, ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET));
+    return cond == Equal ? AboveOrEqual : Below;
+}
+
 void
 MacroAssemblerARMCompat::branchTestValue(Condition cond, const ValueOperand &value, const Value &v,
                                          Label *label)
 {
     // If cond == NotEqual, branch when a.payload != b.payload || a.tag != b.tag.
     // If the payloads are equal, compare the tags. If the payloads are not equal,
     // short circuit true (NotEqual).
     //
--- a/js/src/ion/arm/MacroAssembler-arm.h
+++ b/js/src/ion/arm/MacroAssembler-arm.h
@@ -637,22 +637,30 @@ class MacroAssemblerARMCompat : public M
     Condition testString(Condition cond, const Register &tag);
     Condition testObject(Condition cond, const Register &tag);
     Condition testDouble(Condition cond, const Register &tag);
     Condition testNumber(Condition cond, const Register &tag);
     Condition testMagic(Condition cond, const Register &tag);
     Condition testPrimitive(Condition cond, const Register &tag);
 
     Condition testGCThing(Condition cond, const Address &address);
-    Condition testGCThing(Condition cond, const BaseIndex &address);
     Condition testMagic(Condition cond, const Address &address);
-    Condition testMagic(Condition cond, const BaseIndex &address);
     Condition testInt32(Condition cond, const Address &address);
     Condition testDouble(Condition cond, const Address &address);
 
+    Condition testUndefined(Condition cond, const BaseIndex &src);
+    Condition testNull(Condition cond, const BaseIndex &src);
+    Condition testBoolean(Condition cond, const BaseIndex &src);
+    Condition testString(Condition cond, const BaseIndex &src);
+    Condition testInt32(Condition cond, const BaseIndex &src);
+    Condition testObject(Condition cond, const BaseIndex &src);
+    Condition testDouble(Condition cond, const BaseIndex &src);
+    Condition testMagic(Condition cond, const BaseIndex &src);
+    Condition testGCThing(Condition cond, const BaseIndex &src);
+
     template <typename T>
     void branchTestGCThing(Condition cond, const T &t, Label *label) {
         Condition c = testGCThing(cond, t);
         ma_b(label, c);
     }
     template <typename T>
     void branchTestPrimitive(Condition cond, const T &t, Label *label) {
         Condition c = testPrimitive(cond, t);
--- a/js/src/ion/x64/MacroAssembler-x64.h
+++ b/js/src/ion/x64/MacroAssembler-x64.h
@@ -313,33 +313,63 @@ class MacroAssemblerX64 : public MacroAs
     Condition testGCThing(Condition cond, const ValueOperand &src) {
         splitTag(src, ScratchReg);
         return testGCThing(cond, ScratchReg);
     }
     Condition testGCThing(Condition cond, const Address &src) {
         splitTag(src, ScratchReg);
         return testGCThing(cond, ScratchReg);
     }
-    Condition testGCThing(Condition cond, const BaseIndex &src) {
-        splitTag(src, ScratchReg);
-        return testGCThing(cond, ScratchReg);
-    }
     Condition testMagic(Condition cond, const Address &src) {
         splitTag(src, ScratchReg);
         return testMagic(cond, ScratchReg);
     }
-    Condition testMagic(Condition cond, const BaseIndex &src) {
-        splitTag(src, ScratchReg);
-        return testMagic(cond, ScratchReg);
-    }
     Condition testPrimitive(Condition cond, const ValueOperand &src) {
         splitTag(src, ScratchReg);
         return testPrimitive(cond, ScratchReg);
     }
 
+
+    Condition testUndefined(Condition cond, const BaseIndex &src) {
+        splitTag(src, ScratchReg);
+        return testUndefined(cond, ScratchReg);
+    }
+    Condition testNull(Condition cond, const BaseIndex &src) {
+        splitTag(src, ScratchReg);
+        return testNull(cond, ScratchReg);
+    }
+    Condition testBoolean(Condition cond, const BaseIndex &src) {
+        splitTag(src, ScratchReg);
+        return testBoolean(cond, ScratchReg);
+    }
+    Condition testString(Condition cond, const BaseIndex &src) {
+        splitTag(src, ScratchReg);
+        return testString(cond, ScratchReg);
+    }
+    Condition testInt32(Condition cond, const BaseIndex &src) {
+        splitTag(src, ScratchReg);
+        return testInt32(cond, ScratchReg);
+    }
+    Condition testObject(Condition cond, const BaseIndex &src) {
+        splitTag(src, ScratchReg);
+        return testObject(cond, ScratchReg);
+    }
+    Condition testDouble(Condition cond, const BaseIndex &src) {
+        splitTag(src, ScratchReg);
+        return testDouble(cond, ScratchReg);
+    }
+    Condition testMagic(Condition cond, const BaseIndex &src) {
+        splitTag(src, ScratchReg);
+        return testMagic(cond, ScratchReg);
+    }
+    Condition testGCThing(Condition cond, const BaseIndex &src) {
+        splitTag(src, ScratchReg);
+        return testGCThing(cond, ScratchReg);
+    }
+
     Condition isMagic(Condition cond, const ValueOperand &src, JSWhyMagic why) {
         uint64_t magic = MagicValue(why).asRawBits();
         cmpPtr(src.valueReg(), ImmWord(magic));
         return cond;
     }
 
     void cmpPtr(const Register &lhs, const ImmWord rhs) {
         JS_ASSERT(lhs != ScratchReg);
--- a/js/src/ion/x86/MacroAssembler-x86.h
+++ b/js/src/ion/x86/MacroAssembler-x86.h
@@ -276,31 +276,21 @@ class MacroAssemblerX86 : public MacroAs
         cmpl(tag, ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET));
         return cond == Equal ? AboveOrEqual : Below;
     }
     Condition testGCThing(Condition cond, const Address &address) {
         JS_ASSERT(cond == Equal || cond == NotEqual);
         cmpl(tagOf(address), ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET));
         return cond == Equal ? AboveOrEqual : Below;
     }
-    Condition testGCThing(Condition cond, const BaseIndex &address) {
-        JS_ASSERT(cond == Equal || cond == NotEqual);
-        cmpl(tagOf(address), ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET));
-        return cond == Equal ? AboveOrEqual : Below;
-    }
     Condition testMagic(Condition cond, const Address &address) {
         JS_ASSERT(cond == Equal || cond == NotEqual);
         cmpl(tagOf(address), ImmTag(JSVAL_TAG_MAGIC));
         return cond;
     }
-    Condition testMagic(Condition cond, const BaseIndex &address) {
-        JS_ASSERT(cond == Equal || cond == NotEqual);
-        cmpl(tagOf(address), ImmTag(JSVAL_TAG_MAGIC));
-        return cond;
-    }
     Condition testMagic(Condition cond, const Register &tag) {
         JS_ASSERT(cond == Equal || cond == NotEqual);
         cmpl(tag, ImmTag(JSVAL_TAG_MAGIC));
         return cond;
     }
     Condition testMagic(Condition cond, const Operand &operand) {
         JS_ASSERT(cond == Equal || cond == NotEqual);
         cmpl(ToType(operand), ImmTag(JSVAL_TAG_MAGIC));
@@ -365,16 +355,66 @@ class MacroAssemblerX86 : public MacroAs
     }
     Condition testGCThing(Condition cond, const ValueOperand &value) {
         return testGCThing(cond, value.typeReg());
     }
     Condition testPrimitive(Condition cond, const ValueOperand &value) {
         return testPrimitive(cond, value.typeReg());
     }
 
+
+    Condition testUndefined(Condition cond, const BaseIndex &address) {
+        JS_ASSERT(cond == Equal || cond == NotEqual);
+        cmpl(tagOf(address), ImmTag(JSVAL_TAG_UNDEFINED));
+        return cond;
+    }
+    Condition testNull(Condition cond, const BaseIndex &address) {
+        JS_ASSERT(cond == Equal || cond == NotEqual);
+        cmpl(tagOf(address), ImmTag(JSVAL_TAG_NULL));
+        return cond;
+    }
+    Condition testBoolean(Condition cond, const BaseIndex &address) {
+        JS_ASSERT(cond == Equal || cond == NotEqual);
+        cmpl(tagOf(address), ImmTag(JSVAL_TAG_BOOLEAN));
+        return cond;
+    }
+    Condition testString(Condition cond, const BaseIndex &address) {
+        JS_ASSERT(cond == Equal || cond == NotEqual);
+        cmpl(tagOf(address), ImmTag(JSVAL_TAG_STRING));
+        return cond;
+    }
+    Condition testInt32(Condition cond, const BaseIndex &address) {
+        JS_ASSERT(cond == Equal || cond == NotEqual);
+        cmpl(tagOf(address), ImmTag(JSVAL_TAG_INT32));
+        return cond;
+    }
+    Condition testObject(Condition cond, const BaseIndex &address) {
+        JS_ASSERT(cond == Equal || cond == NotEqual);
+        cmpl(tagOf(address), ImmTag(JSVAL_TAG_OBJECT));
+        return cond;
+    }
+    Condition testDouble(Condition cond, const BaseIndex &address) {
+        JS_ASSERT(cond == Equal || cond == NotEqual);
+        Condition actual = (cond == Equal) ? Below : AboveOrEqual;
+        cmpl(tagOf(address), ImmTag(JSVAL_TAG_CLEAR));
+        return actual;
+    }
+    Condition testMagic(Condition cond, const BaseIndex &address) {
+        JS_ASSERT(cond == Equal || cond == NotEqual);
+        cmpl(tagOf(address), ImmTag(JSVAL_TAG_MAGIC));
+        return cond;
+    }
+    Condition testGCThing(Condition cond, const BaseIndex &address) {
+        JS_ASSERT(cond == Equal || cond == NotEqual);
+        cmpl(tagOf(address), ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET));
+        return cond == Equal ? AboveOrEqual : Below;
+    }
+
+
+
     void branchTestValue(Condition cond, const ValueOperand &value, const Value &v, Label *label);
     void branchTestValue(Condition cond, const Address &valaddr, const ValueOperand &value,
                          Label *label)
     {
         JS_ASSERT(cond == Equal || cond == NotEqual);
         branchPtr(cond, tagOf(valaddr), value.typeReg(), label);
         branchPtr(cond, payloadOf(valaddr), value.payloadReg(), label);
     }