Bug 725966 - Fast path for typeof x == y. r=jandem
☠☠ backed out by fdf520ad7dbc ☠ ☠
authorTom Schuster <evilpies@gmail.com>
Wed, 18 Jul 2012 01:22:15 +0200
changeset 106557 183decadb9acf6825a364dc18685a8e9eb72831a
parent 106556 8144319a6464e3c76b3ec5fca75a65f1a61e114d
child 106558 8dd41661cf09782ae692e36dffc2a3f580e7e003
push id23447
push userdanderson@mozilla.com
push dateTue, 11 Sep 2012 17:34:27 +0000
treeherdermozilla-central@fdfaef738a00 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs725966
milestone16.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 725966 - Fast path for typeof x == y. r=jandem
js/src/ion/CodeGenerator.cpp
js/src/ion/CodeGenerator.h
js/src/ion/LIR-Common.h
js/src/ion/LOpcodes.h
js/src/ion/Lowering.cpp
js/src/ion/arm/MacroAssembler-arm.cpp
js/src/ion/arm/MacroAssembler-arm.h
js/src/ion/x64/MacroAssembler-x64.h
js/src/jit-test/tests/ion/typeof-is.js
--- a/js/src/ion/CodeGenerator.cpp
+++ b/js/src/ion/CodeGenerator.cpp
@@ -1655,17 +1655,17 @@ CodeGenerator::visitBinaryV(LBinaryV *li
         JS_NOT_REACHED("Unexpected binary op");
         return false;
     }
 }
 
 bool
 CodeGenerator::visitCompareS(LCompareS *lir)
 {
-    JSOp op = lir->mir()->jsop();
+    JSOp op = lir->jsop();
     Register left = ToRegister(lir->left());
     Register right = ToRegister(lir->right());
     Register output = ToRegister(lir->output());
 
     typedef bool (*pf)(JSContext *, HandleString, HandleString, JSBool *);
     static const VMFunction stringsEqualInfo = FunctionInfo<pf>(ion::StringsEqual<true>);
     static const VMFunction stringsNotEqualInfo = FunctionInfo<pf>(ion::StringsEqual<false>);
 
@@ -1829,16 +1829,225 @@ CodeGenerator::visitIsNullOrUndefinedAnd
         cond = masm.testNull(cond, value);
     else
         cond = masm.testUndefined(cond, value);
 
     emitBranch(cond, lir->ifTrue(), lir->ifFalse());
     return true;
 }
 
+static JSType
+StringToJSType(JSContext *cx, JSLinearString *str)
+{
+    JSRuntime *rt = cx->runtime;
+
+    if (EqualStrings(str, rt->atomState.typeAtoms[JSTYPE_STRING]))
+        return JSTYPE_STRING;
+    if (EqualStrings(str, rt->atomState.typeAtoms[JSTYPE_NUMBER]))
+        return JSTYPE_NUMBER;
+    if (EqualStrings(str, rt->atomState.typeAtoms[JSTYPE_BOOLEAN]))
+        return JSTYPE_BOOLEAN;
+    if (EqualStrings(str, rt->atomState.typeAtoms[JSTYPE_VOID]))
+        return JSTYPE_VOID;
+    if (EqualStrings(str, rt->atomState.typeAtoms[JSTYPE_OBJECT]))
+        return JSTYPE_OBJECT;
+    if (EqualStrings(str, rt->atomState.typeAtoms[JSTYPE_FUNCTION]))
+        return JSTYPE_FUNCTION;
+    if (EqualStrings(str, rt->atomState.typeAtoms[JSTYPE_XML]))
+        return JSTYPE_XML;
+    return JSTYPE_LIMIT;
+}
+
+
+bool
+CodeGenerator::visitTypeOfIs(LTypeOfIs *lir) 
+{
+    JSOp op = lir->jsop();
+    const ValueOperand value = ToValue(lir, LTypeOfIs::Value);
+    Register output = ToRegister(lir->output());
+
+    JSContext *cx = gen->cx;
+    JSLinearString *str = lir->value().toString()->ensureLinear(cx);
+    if (!str)
+        return false;
+
+    JSType type = StringToJSType(cx, str);
+
+    Assembler::Condition cond = JSOpToCondition(op);
+    if (type == JSTYPE_STRING) {
+        cond = masm.testString(cond, value);
+        emitSet(cond, output);
+    } else if (type == JSTYPE_NUMBER) {
+        cond = masm.testNumber(cond, value);
+        emitSet(cond, output);
+    } else if (type == JSTYPE_BOOLEAN) {
+        cond = masm.testBoolean(cond, value);
+        emitSet(cond, output);
+    } else if (type == JSTYPE_VOID) {
+        cond = masm.testUndefined(cond, value);
+        emitSet(cond, output);
+    } else if (type == JSTYPE_OBJECT) {
+        Register tag = masm.splitTagForTest(value);
+
+        Label isObject, isntObject, done;
+        masm.branchTestNull(Assembler::Equal, tag, &isObject);
+        masm.branchTestObject(Assembler::NotEqual, tag, &isntObject);
+
+        Register object = masm.extractObject(value, output);
+        masm.loadObjClass(object, output);
+
+        // JSFunction is special, because there is no Call hook in Class for them.
+        masm.cmpPtr(output, ImmWord(&FunctionClass));
+        masm.j(Assembler::Equal, &isntObject);
+
+        // The typeof hook could tell us anything, eg. "xml".
+        masm.cmpPtr(Address(output, offsetof(Class, ops.typeOf)), ImmWord(reinterpret_cast<void *>(NULL)));
+        if (!bailoutIf(Assembler::NotEqual, lir->snapshot()))
+            return false;
+
+        // Per ES5 everything which has no [[Call]] is an object.
+        masm.cmpPtr(Address(output, offsetof(Class, call)), ImmWord(reinterpret_cast<void *>(NULL)));
+        masm.j(Assembler::NotEqual, &isntObject);
+
+        masm.bind(&isObject);
+        masm.move32(Imm32(op == JSOP_EQ || op == JSOP_STRICTEQ), output);
+        masm.jump(&done);
+
+        masm.bind(&isntObject);
+        masm.move32(Imm32(op == JSOP_NE || op == JSOP_STRICTNE), output);
+        masm.bind(&done);
+    } else if (type == JSTYPE_FUNCTION) {
+        Register tag = masm.splitTagForTest(value);
+
+        Label isFunction, isntFunction, done;
+        masm.branchTestObject(Assembler::NotEqual, tag, &isntFunction);
+
+        Register object = masm.extractObject(value, output);
+        masm.loadObjClass(object, output);
+
+        masm.cmpPtr(output, ImmWord(&FunctionClass));
+        masm.j(Assembler::Equal, &isFunction);
+
+        masm.cmpPtr(Address(output, offsetof(Class, ops.typeOf)), ImmWord(reinterpret_cast<void *>(NULL)));
+        if (!bailoutIf(Assembler::NotEqual, lir->snapshot()))
+            return false;
+
+        masm.cmpPtr(Address(output, offsetof(Class, call)), ImmWord(reinterpret_cast<void *>(NULL)));
+        masm.j(Assembler::Equal, &isntFunction);
+
+        masm.bind(&isFunction);
+        masm.move32(Imm32(op == JSOP_EQ || op == JSOP_STRICTEQ), output);
+        masm.jump(&done);
+
+        masm.bind(&isntFunction);
+        masm.move32(Imm32(op == JSOP_NE || op == JSOP_STRICTNE), output);
+        masm.bind(&done);
+    } else if (type == JSTYPE_XML) {
+        // Don't even really bother.
+        cond = masm.testObject(Assembler::Equal, value);
+        if (!bailoutIf(cond, lir->snapshot()))
+            return false;
+        masm.move32(Imm32(op == JSOP_NE || op == JSOP_STRICTNE), output);
+    } else {
+        JS_ASSERT(type == JSTYPE_LIMIT);
+        // This happens for code like |typeof x == 'banana'|, because this would never be
+        // equal to any type we can always return false.
+        masm.move32(Imm32(op == JSOP_NE || op == JSOP_STRICTNE), output);
+    }
+    return true;
+}
+
+bool
+CodeGenerator::visitTypeOfIsAndBranch(LTypeOfIsAndBranch *lir)
+{
+    JSOp op = lir->jsop();
+    const ValueOperand value = ToValue(lir, LTypeOfIsAndBranch::Value);
+
+    JSContext *cx = gen->cx;
+    JSLinearString *str = lir->value().toString()->ensureLinear(cx);
+    if (!str)
+        return false;
+
+    JSType type = StringToJSType(cx, str);
+
+    MBasicBlock *ifTrue;
+    MBasicBlock *ifFalse;
+
+    if (op == JSOP_EQ || op == JSOP_STRICTEQ) {
+        ifTrue = lir->ifTrue();
+        ifFalse = lir->ifFalse();
+    } else {
+        // Swap branches.
+        ifTrue = lir->ifFalse();
+        ifFalse = lir->ifTrue();
+    }
+
+    if (type == JSTYPE_STRING) {
+        Assembler::Condition cond = masm.testString(Assembler::Equal, value);
+        emitBranch(cond, ifTrue, ifFalse);
+    } else if (type == JSTYPE_NUMBER) {
+        Assembler::Condition cond = masm.testNumber(Assembler::Equal, value);
+        emitBranch(cond, ifTrue, ifFalse);
+    } else if (type == JSTYPE_BOOLEAN) {
+        Assembler::Condition cond = masm.testBoolean(Assembler::Equal, value);
+        emitBranch(cond, ifTrue, ifFalse);
+    } else if (type == JSTYPE_VOID) {
+        Assembler::Condition cond = masm.testUndefined(Assembler::Equal, value);
+        emitBranch(cond, ifTrue, ifFalse);
+    } else if (type == JSTYPE_OBJECT) {
+        Register tag = masm.splitTagForTest(value);
+        Register temp = ToRegister(lir->temp());
+
+        masm.branchTestNull(Assembler::Equal, tag, ifTrue->lir()->label());
+        masm.branchTestObject(Assembler::NotEqual, tag, ifFalse->lir()->label());
+
+        Register object = masm.extractObject(value, temp);
+        masm.loadObjClass(object, temp);
+
+        masm.cmpPtr(temp, ImmWord(&FunctionClass));
+        masm.j(Assembler::Equal, ifFalse->lir()->label());
+
+        masm.cmpPtr(Address(temp, offsetof(Class, ops.typeOf)), ImmWord(reinterpret_cast<void *>(NULL)));
+        if (!bailoutIf(Assembler::NotEqual, lir->snapshot()))
+            return false;
+
+        masm.cmpPtr(Address(temp, offsetof(Class, call)), ImmWord(reinterpret_cast<void *>(NULL)));
+        emitBranch(Assembler::Equal, ifTrue, ifFalse);
+    } else if (type == JSTYPE_FUNCTION) {
+        Register tag = masm.splitTagForTest(value);
+        Register temp = ToRegister(lir->temp());
+
+        masm.branchTestObject(Assembler::NotEqual, tag, ifFalse->lir()->label());
+
+        Register object = masm.extractObject(value, temp);
+        masm.loadObjClass(object, temp);
+
+        masm.cmpPtr(temp, ImmWord(&FunctionClass));
+        masm.j(Assembler::Equal, ifTrue->lir()->label());
+
+        masm.cmpPtr(Address(temp, offsetof(Class, ops.typeOf)), ImmWord(reinterpret_cast<void *>(NULL)));
+        if (!bailoutIf(Assembler::NotEqual, lir->snapshot()))
+            return false;
+
+        masm.cmpPtr(Address(temp, offsetof(Class, call)), ImmWord(reinterpret_cast<void *>(NULL)));
+        emitBranch(Assembler::NotEqual, ifTrue, ifFalse);
+    } else if (type == JSTYPE_XML) {
+        // Don't even really bother.
+        Assembler::Condition cond = masm.testObject(Assembler::Equal, value);
+        if (!bailoutIf(cond, lir->snapshot()))
+            return false;
+        masm.jump(ifFalse->lir()->label());
+    } else {
+        JS_ASSERT(type == JSTYPE_LIMIT);
+        masm.jump(ifFalse->lir()->label());
+    }
+
+    return true;
+}
+
 bool
 CodeGenerator::visitConcat(LConcat *lir)
 {
     typedef JSString *(*pf)(JSContext *, HandleString, HandleString);
     static const VMFunction js_ConcatStringsInfo = FunctionInfo<pf>(js_ConcatStrings);
 
     pushArg(ToRegister(lir->rhs()));
     pushArg(ToRegister(lir->lhs()));
--- a/js/src/ion/CodeGenerator.h
+++ b/js/src/ion/CodeGenerator.h
@@ -142,16 +142,18 @@ class CodeGenerator : public CodeGenerat
     bool visitStoreFixedSlotT(LStoreFixedSlotT *ins);
     bool visitAbsI(LAbsI *lir);
     bool visitMathFunctionD(LMathFunctionD *ins);
     bool visitBinaryV(LBinaryV *lir);
     bool visitCompareS(LCompareS *lir);
     bool visitCompareV(LCompareV *lir);
     bool visitIsNullOrUndefined(LIsNullOrUndefined *lir);
     bool visitIsNullOrUndefinedAndBranch(LIsNullOrUndefinedAndBranch *lir);
+    bool visitTypeOfIs(LTypeOfIs *lir);
+    bool visitTypeOfIsAndBranch(LTypeOfIsAndBranch *lir);
     bool visitConcat(LConcat *lir);
     bool visitCharCodeAt(LCharCodeAt *lir);
     bool visitFromCharCode(LFromCharCode *lir);
     bool visitFunctionEnvironment(LFunctionEnvironment *lir);
     bool visitCallGetProperty(LCallGetProperty *lir);
     bool visitCallGetElement(LCallGetElement *lir);
     bool visitCallSetElement(LCallSetElement *lir);
     bool visitThrow(LThrow *lir);
--- a/js/src/ion/LIR-Common.h
+++ b/js/src/ion/LIR-Common.h
@@ -857,23 +857,30 @@ class LCompareD : public LInstructionHel
     }
     MCompare *mir() {
         return mir_->toCompare();
     }
 };
 
 class LCompareS : public LInstructionHelper<1, 2, 0>
 {
+    JSOp jsop_;
+
   public:
     LIR_HEADER(CompareS);
-    LCompareS(const LAllocation &left, const LAllocation &right) {
+    LCompareS(JSOp jsop, const LAllocation &left, const LAllocation &right) 
+      : jsop_(jsop)
+    {
         setOperand(0, left);
         setOperand(1, right);
     }
 
+    JSOp jsop() const{
+        return jsop_;
+    }
     const LAllocation *left() {
         return getOperand(0);
     }
     const LAllocation *right() {
         return getOperand(1);
     }
     const LDefinition *output() {
         return getDef(0);
@@ -1061,16 +1068,81 @@ class LIsNullOrUndefinedAndBranch : publ
     MBasicBlock *ifFalse() const {
         return ifFalse_;
     }
     MCompare *mir() {
         return mir_->toCompare();
     }
 };
 
+class LTypeOfIs : public LInstructionHelper<1, BOX_PIECES, 0>
+{
+    JSOp op_;
+    const js::Value v_;
+
+  public:
+    LIR_HEADER(TypeOfIs);
+
+    LTypeOfIs(JSOp op, const js::Value &v) 
+      : op_(op), v_(v)
+    { }
+
+    static const size_t Value = 0;
+
+    JSOp jsop() {
+        return op_;
+    }
+    js::Value value() {
+        return v_;
+    }
+    const LDefinition *output() {
+        return getDef(0);
+    }
+};
+
+class LTypeOfIsAndBranch : public LInstructionHelper<0, BOX_PIECES, 1>
+{
+    JSOp jsop_;
+    const js::Value v_;
+    MBasicBlock *ifTrue_;
+    MBasicBlock *ifFalse_;
+
+  public:
+    LIR_HEADER(TypeOfIsAndBranch);
+
+    LTypeOfIsAndBranch(JSOp jsop, const js::Value &v, const LDefinition &temp,
+                      MBasicBlock *ifTrue, MBasicBlock *ifFalse)
+      : jsop_(jsop),
+        v_(v),
+        ifTrue_(ifTrue),
+        ifFalse_(ifFalse)
+    {
+        setTemp(0, temp);
+    }
+
+    static const size_t Value = 0;
+
+    JSOp jsop() const {
+        return jsop_;
+    }
+    js::Value value() {
+        return v_;
+    }
+    const LDefinition *temp() {
+        return getTemp(0);
+    }    
+    MBasicBlock *ifTrue() const {
+        return ifTrue_;
+    }
+    MBasicBlock *ifFalse() const {
+        return ifFalse_;
+    }
+
+};
+
 // Not operation on an integer.
 class LNotI : public LInstructionHelper<1, 1, 0>
 {
   public:
     LIR_HEADER(NotI);
 
     LNotI(const LAllocation &input) {
         setOperand(0, input);
--- a/js/src/ion/LOpcodes.h
+++ b/js/src/ion/LOpcodes.h
@@ -87,16 +87,18 @@
     _(CompareS)                     \
     _(CompareV)                     \
     _(CompareAndBranch)             \
     _(CompareDAndBranch)            \
     _(CompareB)                     \
     _(CompareBAndBranch)            \
     _(IsNullOrUndefined)            \
     _(IsNullOrUndefinedAndBranch)   \
+    _(TypeOfIs)                     \
+    _(TypeOfIsAndBranch)            \
     _(AbsI)                         \
     _(AbsD)                         \
     _(SqrtD)                        \
     _(MathFunctionD)                \
     _(NotI)                         \
     _(NotD)                         \
     _(NotV)                         \
     _(AddI)                         \
--- a/js/src/ion/Lowering.cpp
+++ b/js/src/ion/Lowering.cpp
@@ -339,16 +339,24 @@ ReorderComparison(JSOp op, MDefinition *
     if (lhs->isConstant()) {
         *rhsp = lhs;
         *lhsp = rhs;
         return js::analyze::ReverseCompareOp(op);
     }
     return op;
 }
 
+inline bool
+IsTypeOfCompare(MDefinition *left, MDefinition *right)
+{
+    if (!left->isTypeOf() || !right->isConstant())
+        return false;
+    return right->toConstant()->value().isString();
+}
+
 bool
 LIRGenerator::visitTest(MTest *test)
 {
     MDefinition *opd = test->getOperand(0);
     MBasicBlock *ifTrue = test->ifTrue();
     MBasicBlock *ifFalse = test->ifFalse();
 
     if (opd->type() == MIRType_Value) {
@@ -396,16 +404,33 @@ LIRGenerator::visitTest(MTest *test)
         // first operand.
         if (IsNullOrUndefined(comp->specialization())) {
             LIsNullOrUndefinedAndBranch *lir = new LIsNullOrUndefinedAndBranch(ifTrue, ifFalse);
             if (!useBox(lir, LIsNullOrUndefinedAndBranch::Value, left))
                 return false;
             return add(lir, comp);
         }
 
+
+        if (comp->specialization() == MIRType_String) {
+            // We can safely reorder, because operand order doesn't matter for LCompareS.
+            JSOp op = ReorderComparison(comp->jsop(), &left, &right);
+            if (IsTypeOfCompare(left, right)) {
+                MTypeOf *typeOf = left->toTypeOf();
+
+                LTypeOfIsAndBranch *lir = new LTypeOfIsAndBranch(op, right->toConstant()->value(), 
+                                                                 temp(), ifTrue, ifFalse);
+                if (!useBox(lir, LTypeOfIsAndBranch::Value, typeOf->input()))
+                    return false;
+                if (!assignSnapshot(lir))
+                    return false;                
+                return add(lir, comp);            
+            }
+        }
+
         if (comp->specialization() == MIRType_Boolean) {
             JS_ASSERT(left->type() == MIRType_Value);
             JS_ASSERT(right->type() == MIRType_Boolean);
 
             LCompareBAndBranch *lir = new LCompareBAndBranch(useRegisterOrConstant(right),
                                                              ifTrue, ifFalse);
             if (!useBox(lir, LCompareBAndBranch::Lhs, left))
                 return false;
@@ -424,63 +449,53 @@ bool
 LIRGenerator::visitInlineFunctionGuard(MInlineFunctionGuard *ins)
 {
     LInlineFunctionGuard *lir =
         new LInlineFunctionGuard(useRegister(ins->input()),
             ins->function(), ins->functionBlock(), ins->fallbackBlock());
     return add(lir);
 }
 
-static inline bool
-CanEmitCompareAtUses(MInstruction *ins)
+inline bool
+CanEmitInstructionAtUses(MInstruction *ins, MDefinition::Opcode op)
 {
     if (!ins->canEmitAtUses())
         return false;
 
-    bool foundTest = false;
+    bool alreadyUsed = false;
     for (MUseIterator iter(ins->usesBegin()); iter != ins->usesEnd(); iter++) {
         MNode *node = iter->node();
         if (!node->isDefinition())
             return false;
-        if (!node->toDefinition()->isTest())
+        if (node->toDefinition()->op() != op)
             return false;
-        if (foundTest)
+        if (alreadyUsed)
             return false;
-        foundTest = true;
+        alreadyUsed = true;
     }
     return true;
 }
 
 bool
 LIRGenerator::visitCompare(MCompare *comp)
 {
     MDefinition *left = comp->lhs();
     MDefinition *right = comp->rhs();
 
     if (comp->specialization() != MIRType_None) {
         // Try to fold the comparison so that we don't have to handle all cases.
         bool result;
         if (comp->tryFold(&result))
             return define(new LInteger(result), comp);
 
-        // Move below the emitAtUses call if we ever implement
-        // LCompareSAndBranch. Doing this now wouldn't be wrong, but doesn't
-        // make sense and avoids confusion.
-        if (comp->specialization() == MIRType_String) {
-            LCompareS *lir = new LCompareS(useRegister(left), useRegister(right));
-            if (!define(lir, comp))
-                return false;
-            return assignSafepoint(lir, comp);
-        }
-
         // Sniff out if the output of this compare is used only for a branching.
         // If it is, then we willl emit an LCompare*AndBranch instruction in place
         // of this compare and any test that uses this compare. Thus, we can
         // ignore this Compare.
-        if (CanEmitCompareAtUses(comp))
+        if (CanEmitInstructionAtUses(comp, MDefinition::Op_Test))
             return emitAtUses(comp);
 
         if (comp->specialization() == MIRType_Int32 || comp->specialization() == MIRType_Object) {
             JSOp op = ReorderComparison(comp->jsop(), &left, &right);
             LAllocation rhs = comp->specialization() == MIRType_Object
                               ? useRegister(right)
                               : useAnyOrConstant(right);
             return define(new LCompare(op, useRegister(left), rhs), comp);
@@ -494,16 +509,36 @@ LIRGenerator::visitCompare(MCompare *com
             JS_ASSERT(right->type() == MIRType_Boolean);
 
             LCompareB *lir = new LCompareB(useRegisterOrConstant(right));
             if (!useBox(lir, LCompareB::Lhs, left))
                 return false;
             return define(lir, comp);
         }
 
+        if (comp->specialization() == MIRType_String) {
+            JSOp op = ReorderComparison(comp->jsop(), &left, &right);
+
+            if (IsTypeOfCompare(left, right)) {
+                MTypeOf *typeOf = left->toTypeOf();
+
+                LTypeOfIs *lir = new LTypeOfIs(op, right->toConstant()->value());
+                if (!useBox(lir, LTypeOfIs::Value, typeOf->input()))
+                    return false;
+                if (!assignSnapshot(lir))
+                    return false;                
+                return define(lir, comp);
+            }
+
+            LCompareS *lir = new LCompareS(op, useRegister(left), useRegister(right));
+            if (!define(lir, comp))
+                return false;
+            return assignSafepoint(lir, comp);
+        }
+
         JS_ASSERT(IsNullOrUndefined(comp->specialization()));
 
         LIsNullOrUndefined *lir = new LIsNullOrUndefined();
         if (!useBox(lir, LIsNullOrUndefined::Value, comp->getOperand(0)))
             return false;
         return define(lir, comp);
     }
 
@@ -549,16 +584,19 @@ LIRGenerator::lowerBitOp(JSOp op, MInstr
 }
 
 bool
 LIRGenerator::visitTypeOf(MTypeOf *ins)
 {
     MDefinition *opd = ins->input();
     JS_ASSERT(opd->type() == MIRType_Value);
 
+    if (CanEmitInstructionAtUses(ins, MDefinition::Op_Compare))
+        return emitAtUses(ins);
+
     LTypeOfV *lir = new LTypeOfV();
     if (!useBox(lir, LTypeOfV::Input, opd))
         return false;
     return define(lir, ins) && assignSafepoint(lir, ins);
 }
 
 bool
 LIRGenerator::visitToId(MToId *ins)
--- a/js/src/ion/arm/MacroAssembler-arm.cpp
+++ b/js/src/ion/arm/MacroAssembler-arm.cpp
@@ -1969,28 +1969,40 @@ MacroAssemblerARMCompat::testInt32(Assem
 
 Assembler::Condition
 MacroAssemblerARMCompat::testBoolean(Assembler::Condition cond, const ValueOperand &value)
 {
     JS_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual);
     ma_cmp(value.typeReg(), ImmType(JSVAL_TYPE_BOOLEAN));
     return cond;
 }
+
 Assembler::Condition
 MacroAssemblerARMCompat::testDouble(Assembler::Condition cond, const ValueOperand &value)
 {
     JS_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual);
     Assembler::Condition actual = (cond == Equal)
         ? Below
         : AboveOrEqual;
     ma_cmp(value.typeReg(), ImmTag(JSVAL_TAG_CLEAR));
     return actual;
 }
 
 Assembler::Condition
+MacroAssemblerARMCompat::testNumber(Assembler::Condition cond, const ValueOperand &value)
+{
+    JS_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual);
+    Assembler::Condition actual = (cond == Equal)
+        ? BelowOrEqual
+        : Above;
+    ma_cmp(value.typeReg(), ImmTag(JSVAL_UPPER_INCL_TAG_OF_NUMBER_SET));
+    return actual;
+}
+
+Assembler::Condition
 MacroAssemblerARMCompat::testNull(Assembler::Condition cond, const ValueOperand &value)
 {
     JS_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual);
     ma_cmp(value.typeReg(), ImmType(JSVAL_TYPE_NULL));
     return cond;
 }
 
 Assembler::Condition
--- a/js/src/ion/arm/MacroAssembler-arm.h
+++ b/js/src/ion/arm/MacroAssembler-arm.h
@@ -526,16 +526,17 @@ class MacroAssemblerARMCompat : public M
     Register splitTagForTest(const ValueOperand &value) {
         return value.typeReg();
     }
 
     // higher level tag testing code
     Condition testInt32(Condition cond, const ValueOperand &value);
     Condition testBoolean(Condition cond, const ValueOperand &value);
     Condition testDouble(Condition cond, const ValueOperand &value);
+    Condition testNumber(Condition cond, const ValueOperand &value);
     Condition testNull(Condition cond, const ValueOperand &value);
     Condition testUndefined(Condition cond, const ValueOperand &value);
     Condition testString(Condition cond, const ValueOperand &value);
     Condition testObject(Condition cond, const ValueOperand &value);
     Condition testMagic(Condition cond, const ValueOperand &value);
 
     Condition testPrimitive(Condition cond, const ValueOperand &value);
 
--- a/js/src/ion/x64/MacroAssembler-x64.h
+++ b/js/src/ion/x64/MacroAssembler-x64.h
@@ -271,16 +271,20 @@ class MacroAssemblerX64 : public MacroAs
     Condition testBoolean(Condition cond, const ValueOperand &src) {
         splitTag(src, ScratchReg);
         return testBoolean(cond, ScratchReg);
     }
     Condition testDouble(Condition cond, const ValueOperand &src) {
         splitTag(src, ScratchReg);
         return testDouble(cond, ScratchReg);
     }
+    Condition testNumber(Condition cond, const ValueOperand &src) {
+        splitTag(src, ScratchReg);
+        return testNumber(cond, ScratchReg);
+    }
     Condition testNull(Condition cond, const ValueOperand &src) {
         splitTag(src, ScratchReg);
         return testNull(cond, ScratchReg);
     }
     Condition testString(Condition cond, const ValueOperand &src) {
         splitTag(src, ScratchReg);
         return testString(cond, ScratchReg);
     }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/typeof-is.js
@@ -0,0 +1,346 @@
+function isObject(x) {
+        return "object" === typeof x;
+}
+function isObject2(x) {
+    if ("object" === typeof x)
+        return true;
+    return false;
+}
+function isBoolean(x) {
+        return "boolean" === typeof x;
+}
+function isBoolean2(x) {
+    if ("boolean" === typeof x)
+        return true;
+    return false;
+}
+function isFunction(x) {
+        return "function" === typeof x;
+}
+function isFunction2(x) {
+    if ("function" === typeof x)
+        return true;
+    return false;
+}
+function isNumber(x) {
+        return "number" === typeof x;
+}
+function isNumber2(x) {
+    if ("number" === typeof x)
+        return true;
+    return false;
+}
+function isString(x) {
+        return "string" === typeof x;
+}
+function isString2(x) {
+    if ("string" === typeof x)
+        return true;
+    return false;
+}
+function isUndefined(x) {
+        return "undefined" === typeof x;
+}
+function isUndefined2(x) {
+    if ("undefined" === typeof x)
+        return true;
+    return false;
+}
+function isBanana(x) {
+        return "Banana" === typeof x;
+}
+function isBanana2(x) {
+    if ("Banana" === typeof x)
+        return true;
+    return false;
+}
+
+for (var i = 0; i < 100; i++) {
+    assertEq(isObject(null), true);
+    assertEq(isObject2(null), true);
+    assertEq(isBoolean(null), false);
+    assertEq(isBoolean2(null), false);
+    assertEq(isFunction(null), false);
+    assertEq(isFunction2(null), false);
+    assertEq(isNumber(null), false);
+    assertEq(isNumber2(null), false);
+    assertEq(isString(null), false);
+    assertEq(isString2(null), false);
+    assertEq(isUndefined(null), false);
+    assertEq(isUndefined2(null), false);
+    assertEq(isBanana(null), false);
+    assertEq(isBanana2(null), false);
+
+    assertEq(isObject({}), true);
+    assertEq(isObject2({}), true);
+    assertEq(isBoolean({}), false);
+    assertEq(isBoolean2({}), false);
+    assertEq(isFunction({}), false);
+    assertEq(isFunction2({}), false);
+    assertEq(isNumber({}), false);
+    assertEq(isNumber2({}), false);
+    assertEq(isString({}), false);
+    assertEq(isString2({}), false);
+    assertEq(isUndefined({}), false);
+    assertEq(isUndefined2({}), false);
+    assertEq(isBanana({}), false);
+    assertEq(isBanana2({}), false);
+
+    assertEq(isObject([]), true);
+    assertEq(isObject2([]), true);
+    assertEq(isBoolean([]), false);
+    assertEq(isBoolean2([]), false);
+    assertEq(isFunction([]), false);
+    assertEq(isFunction2([]), false);
+    assertEq(isNumber([]), false);
+    assertEq(isNumber2([]), false);
+    assertEq(isString([]), false);
+    assertEq(isString2([]), false);
+    assertEq(isUndefined([]), false);
+    assertEq(isUndefined2([]), false);
+    assertEq(isBanana([]), false);
+    assertEq(isBanana2([]), false);
+
+    assertEq(isObject(new Date()), true);
+    assertEq(isObject2(new Date()), true);
+    assertEq(isBoolean(new Date()), false);
+    assertEq(isBoolean2(new Date()), false);
+    assertEq(isFunction(new Date()), false);
+    assertEq(isFunction2(new Date()), false);
+    assertEq(isNumber(new Date()), false);
+    assertEq(isNumber2(new Date()), false);
+    assertEq(isString(new Date()), false);
+    assertEq(isString2(new Date()), false);
+    assertEq(isUndefined(new Date()), false);
+    assertEq(isUndefined2(new Date()), false);
+    assertEq(isBanana(new Date()), false);
+    assertEq(isBanana2(new Date()), false);
+
+    assertEq(isObject(new String()), true);
+    assertEq(isObject2(new String()), true);
+    assertEq(isBoolean(new String()), false);
+    assertEq(isBoolean2(new String()), false);
+    assertEq(isFunction(new String()), false);
+    assertEq(isFunction2(new String()), false);
+    assertEq(isNumber(new String()), false);
+    assertEq(isNumber2(new String()), false);
+    assertEq(isString(new String()), false);
+    assertEq(isString2(new String()), false);
+    assertEq(isUndefined(new String()), false);
+    assertEq(isUndefined2(new String()), false);
+    assertEq(isBanana(new String()), false);
+    assertEq(isBanana2(new String()), false);
+
+    assertEq(isObject(new Boolean()), true);
+    assertEq(isObject2(new Boolean()), true);
+    assertEq(isBoolean(new Boolean()), false);
+    assertEq(isBoolean2(new Boolean()), false);
+    assertEq(isFunction(new Boolean()), false);
+    assertEq(isFunction2(new Boolean()), false);
+    assertEq(isNumber(new Boolean()), false);
+    assertEq(isNumber2(new Boolean()), false);
+    assertEq(isString(new Boolean()), false);
+    assertEq(isString2(new Boolean()), false);
+    assertEq(isUndefined(new Boolean()), false);
+    assertEq(isUndefined2(new Boolean()), false);
+    assertEq(isBanana(new Boolean()), false);
+    assertEq(isBanana2(new Boolean()), false);
+
+    assertEq(isObject(Proxy.create({})), true);
+    assertEq(isObject2(Proxy.create({})), true);
+    assertEq(isBoolean(Proxy.create({})), false);
+    assertEq(isBoolean2(Proxy.create({})), false);
+    assertEq(isFunction(Proxy.create({})), false);
+    assertEq(isFunction2(Proxy.create({})), false);
+    assertEq(isNumber(Proxy.create({})), false);
+    assertEq(isNumber2(Proxy.create({})), false);
+    assertEq(isString(Proxy.create({})), false);
+    assertEq(isString2(Proxy.create({})), false);
+    assertEq(isUndefined(Proxy.create({})), false);
+    assertEq(isUndefined2(Proxy.create({})), false);
+    assertEq(isBanana(Proxy.create({})), false);
+    assertEq(isBanana2(Proxy.create({})), false);
+
+    assertEq(isObject(true), false);
+    assertEq(isObject2(true), false);
+    assertEq(isBoolean(true), true);
+    assertEq(isBoolean2(true), true);
+    assertEq(isFunction(true), false);
+    assertEq(isFunction2(true), false);
+    assertEq(isNumber(true), false);
+    assertEq(isNumber2(true), false);
+    assertEq(isString(true), false);
+    assertEq(isString2(true), false);
+    assertEq(isUndefined(true), false);
+    assertEq(isUndefined2(true), false);
+    assertEq(isBanana(true), false);
+    assertEq(isBanana2(true), false);
+
+    assertEq(isObject(false), false);
+    assertEq(isObject2(false), false);
+    assertEq(isBoolean(false), true);
+    assertEq(isBoolean2(false), true);
+    assertEq(isFunction(false), false);
+    assertEq(isFunction2(false), false);
+    assertEq(isNumber(false), false);
+    assertEq(isNumber2(false), false);
+    assertEq(isString(false), false);
+    assertEq(isString2(false), false);
+    assertEq(isUndefined(false), false);
+    assertEq(isUndefined2(false), false);
+    assertEq(isBanana(false), false);
+    assertEq(isBanana2(false), false);
+
+
+    assertEq(isObject(Function.prototype), false);
+    assertEq(isObject2(Function.prototype), false);
+    assertEq(isBoolean(Function.prototype), false);
+    assertEq(isBoolean2(Function.prototype), false);
+    assertEq(isFunction(Function.prototype), true);
+    assertEq(isFunction2(Function.prototype), true);
+    assertEq(isNumber(Function.prototype), false);
+    assertEq(isNumber2(Function.prototype), false);
+    assertEq(isString(Function.prototype), false);
+    assertEq(isString2(Function.prototype), false);
+    assertEq(isUndefined(Function.prototype), false);
+    assertEq(isUndefined2(Function.prototype), false);
+    assertEq(isBanana(Function.prototype), false);
+    assertEq(isBanana2(Function.prototype), false);
+
+
+    assertEq(isObject(eval), false);
+    assertEq(isObject2(eval), false);
+    assertEq(isBoolean(eval), false);
+    assertEq(isBoolean2(eval), false);
+    assertEq(isFunction(eval), true);
+    assertEq(isFunction2(eval), true);
+    assertEq(isNumber(eval), false);
+    assertEq(isNumber2(eval), false);
+    assertEq(isString(eval), false);
+    assertEq(isString2(eval), false);
+    assertEq(isUndefined(eval), false);
+    assertEq(isUndefined2(eval), false);
+    assertEq(isBanana(eval), false);
+    assertEq(isBanana2(eval), false);
+
+
+    assertEq(isObject((function () {}).bind()), false);
+    assertEq(isObject2((function () {}).bind()), false);
+    assertEq(isBoolean((function () {}).bind()), false);
+    assertEq(isBoolean2((function () {}).bind()), false);
+    assertEq(isFunction((function () {}).bind()), true);
+    assertEq(isFunction2((function () {}).bind()), true);
+    assertEq(isNumber((function () {}).bind()), false);
+    assertEq(isNumber2((function () {}).bind()), false);
+    assertEq(isString((function () {}).bind()), false);
+    assertEq(isString2((function () {}).bind()), false);
+    assertEq(isUndefined((function () {}).bind()), false);
+    assertEq(isUndefined2((function () {}).bind()), false);
+    assertEq(isBanana((function () {}).bind()), false);
+    assertEq(isBanana2((function () {}).bind()), false);
+
+    assertEq(isObject(eval), false);
+    assertEq(isObject2(eval), false);
+    assertEq(isBoolean(eval), false);
+    assertEq(isBoolean2(eval), false);
+    assertEq(isFunction(eval), true);
+    assertEq(isFunction2(eval), true);
+    assertEq(isNumber(eval), false);
+    assertEq(isNumber2(eval), false);
+    assertEq(isString(eval), false);
+    assertEq(isString2(eval), false);
+    assertEq(isUndefined(eval), false);
+    assertEq(isUndefined2(eval), false);
+    assertEq(isBanana(eval), false);
+    assertEq(isBanana2(eval), false);
+
+    assertEq(isObject(Proxy.createFunction({}, function () {}, function () {})), false);
+    assertEq(isObject2(Proxy.createFunction({}, function () {}, function () {})), false);
+    assertEq(isBoolean(Proxy.createFunction({}, function () {}, function () {})), false);
+    assertEq(isBoolean2(Proxy.createFunction({}, function () {}, function () {})), false);
+    assertEq(isFunction(Proxy.createFunction({}, function () {}, function () {})), true);
+    assertEq(isFunction2(Proxy.createFunction({}, function () {}, function () {})), true);
+    assertEq(isNumber(Proxy.createFunction({}, function () {}, function () {})), false);
+    assertEq(isNumber2(Proxy.createFunction({}, function () {}, function () {})), false);
+    assertEq(isString(Proxy.createFunction({}, function () {}, function () {})), false);
+    assertEq(isString2(Proxy.createFunction({}, function () {}, function () {})), false);
+    assertEq(isUndefined(Proxy.createFunction({}, function () {}, function () {})), false);
+    assertEq(isUndefined2(Proxy.createFunction({}, function () {}, function () {})), false);
+    assertEq(isBanana(Proxy.createFunction({}, function () {}, function () {})), false);
+    assertEq(isBanana2(Proxy.createFunction({}, function () {}, function () {})), false);
+
+    assertEq(isObject(1.5), false);
+    assertEq(isObject2(1.5), false);
+    assertEq(isBoolean(1.5), false);
+    assertEq(isBoolean2(1.5), false);
+    assertEq(isFunction(1.5), false);
+    assertEq(isFunction2(1.5), false);
+    assertEq(isNumber(1.5), true);
+    assertEq(isNumber2(1.5), true);
+    assertEq(isString(1.5), false);
+    assertEq(isString2(1.5), false);
+    assertEq(isUndefined(1.5), false);
+    assertEq(isUndefined2(1.5), false);
+    assertEq(isBanana(1.5), false);
+    assertEq(isBanana2(1.5), false);
+
+    assertEq(isObject(30), false);
+    assertEq(isObject2(30), false);
+    assertEq(isBoolean(30), false);
+    assertEq(isBoolean2(30), false);
+    assertEq(isFunction(30), false);
+    assertEq(isFunction2(30), false);
+    assertEq(isNumber(30), true);
+    assertEq(isNumber2(30), true);
+    assertEq(isString(30), false);
+    assertEq(isString2(30), false);
+    assertEq(isUndefined(30), false);
+    assertEq(isUndefined2(30), false);
+    assertEq(isBanana(30), false);
+    assertEq(isBanana2(30), false);
+
+    assertEq(isObject(NaN), false);
+    assertEq(isObject2(NaN), false);
+    assertEq(isBoolean(NaN), false);
+    assertEq(isBoolean2(NaN), false);
+    assertEq(isFunction(NaN), false);
+    assertEq(isFunction2(NaN), false);
+    assertEq(isNumber(NaN), true);
+    assertEq(isNumber2(NaN), true);
+    assertEq(isString(NaN), false);
+    assertEq(isString2(NaN), false);
+    assertEq(isUndefined(NaN), false);
+    assertEq(isUndefined2(NaN), false);
+    assertEq(isBanana(NaN), false);
+    assertEq(isBanana2(NaN), false);
+
+    assertEq(isObject("test"), false);
+    assertEq(isObject2("test"), false);
+    assertEq(isBoolean("test"), false);
+    assertEq(isBoolean2("test"), false);
+    assertEq(isFunction("test"), false);
+    assertEq(isFunction2("test"), false);
+    assertEq(isNumber("test"), false);
+    assertEq(isNumber2("test"), false);
+    assertEq(isString("test"), true);
+    assertEq(isString2("test"), true);
+    assertEq(isUndefined("test"), false);
+    assertEq(isUndefined2("test"), false);
+    assertEq(isBanana("test"), false);
+    assertEq(isBanana2("test"), false);
+
+    assertEq(isObject(undefined), false);
+    assertEq(isObject2(undefined), false);
+    assertEq(isBoolean(undefined), false);
+    assertEq(isBoolean2(undefined), false);
+    assertEq(isFunction(undefined), false);
+    assertEq(isFunction2(undefined), false);
+    assertEq(isNumber(undefined), false);
+    assertEq(isNumber2(undefined), false);
+    assertEq(isString(undefined), false);
+    assertEq(isString2(undefined), false);
+    assertEq(isUndefined(undefined), true);
+    assertEq(isUndefined2(undefined), true);
+    assertEq(isBanana(undefined), false);
+    assertEq(isBanana2(undefined), false);
+}