Bug 858022 - Fix baseline compiler crashes on hardware without SSE2. r=dvander
authorJan de Mooij <jdemooij@mozilla.com>
Wed, 10 Apr 2013 14:33:57 +0200
changeset 128312 f10884c6a91e01699b19aff130f58b7b2649252c
parent 128311 1348d28d70fcbceeaaf366c36962ecab1d79ffa9
child 128313 d946c9ecdea6e72f3788aaa53640b5c3fdfeb98c
push idunknown
push userunknown
push dateunknown
reviewersdvander
bugs858022
milestone23.0a1
Bug 858022 - Fix baseline compiler crashes on hardware without SSE2. r=dvander
js/src/assembler/assembler/MacroAssemblerX86Common.cpp
js/src/assembler/assembler/MacroAssemblerX86Common.h
js/src/ion/BaselineIC.cpp
js/src/ion/BaselineJIT.h
js/src/ion/Ion.cpp
js/src/ion/shared/Assembler-x86-shared.h
js/src/ion/x86/Assembler-x86.h
js/src/ion/x86/Trampoline-x86.cpp
js/src/jit-test/jit_test.py
js/src/jit-test/tests/asm.js/testCall.js
js/src/jsapi.cpp
js/src/jscntxt.h
js/src/jsinfer.cpp
js/src/shell/js.cpp
--- a/js/src/assembler/assembler/MacroAssemblerX86Common.cpp
+++ b/js/src/assembler/assembler/MacroAssemblerX86Common.cpp
@@ -9,10 +9,14 @@
 /* SSE checks only make sense on Intel platforms. */
 #if WTF_CPU_X86 || WTF_CPU_X86_64
 
 #include "MacroAssemblerX86Common.h"
 
 using namespace JSC;
 MacroAssemblerX86Common::SSECheckState MacroAssemblerX86Common::s_sseCheckState = NotCheckedSSE;
 
+#ifdef DEBUG
+bool MacroAssemblerX86Common::s_floatingPointDisabled = false;
+#endif
+
 #endif /* WTF_CPU_X86 || WTF_CPU_X86_64 */
 
--- a/js/src/assembler/assembler/MacroAssemblerX86Common.h
+++ b/js/src/assembler/assembler/MacroAssemblerX86Common.h
@@ -1369,16 +1369,25 @@ private:
              "movl %ecx, (%esi);"
              "movl %edx, (%edi);"
              :
              : "S" (&flags_ecx), "D" (&flags_edx)
              : "%eax", "%ecx", "%edx"
              );
 #endif
 #endif
+
+#ifdef DEBUG
+        if (s_floatingPointDisabled) {
+            // Disable SSE2.
+            s_sseCheckState = HasSSE;
+            return;
+        }
+#endif
+
         static const int SSEFeatureBit = 1 << 25;
         static const int SSE2FeatureBit = 1 << 26;
         static const int SSE3FeatureBit = 1 << 0;
         static const int SSSE3FeatureBit = 1 << 9;
         static const int SSE41FeatureBit = 1 << 19;
         static const int SSE42FeatureBit = 1 << 20;
         if (flags_ecx & SSE42FeatureBit)
             s_sseCheckState = HasSSE4_2;
@@ -1402,16 +1411,20 @@ private:
     // All X86 Macs are guaranteed to support at least SSE2
     static bool isSSEPresent()
     {
         return true;
     }
 
     static bool isSSE2Present()
     {
+#ifdef DEBUG
+        if (s_floatingPointDisabled)
+            return false;
+#endif
         return true;
     }
 
 #else // OS(MAC_OS_X)
 
     static bool isSSEPresent()
     {
         if (s_sseCheckState == NotCheckedSSE) {
@@ -1484,15 +1497,24 @@ private:
         if (s_sseCheckState == NotCheckedSSE) {
             setSSECheckState();
         }
         // Only check once.
         ASSERT(s_sseCheckState != NotCheckedSSE);
 
         return s_sseCheckState >= HasSSE4_2;
     }
+
+#ifdef DEBUG
+    static bool s_floatingPointDisabled;
+
+  public:
+    static void SetFloatingPointDisabled() {
+        s_floatingPointDisabled = true;
+    }
+#endif
 };
 
 } // namespace JSC
 
 #endif // ENABLE(ASSEMBLER)
 
 #endif // MacroAssemblerX86Common_h
--- a/js/src/ion/BaselineIC.cpp
+++ b/js/src/ion/BaselineIC.cpp
@@ -1657,16 +1657,19 @@ DoCompareFallback(JSContext *cx, Baselin
         ICStub *int32Stub = compiler.getStub(compiler.getStubSpace(script));
         if (!int32Stub)
             return false;
 
         stub->addNewStub(int32Stub);
         return true;
     }
 
+    if (!cx->runtime->jitSupportsFloatingPoint && (lhs.isNumber() || rhs.isNumber()))
+        return true;
+
     if (lhs.isNumber() && rhs.isNumber()) {
         IonSpew(IonSpew_BaselineIC, "  Generating %s(Number, Number) stub", js_CodeName[op]);
 
         // Unlink int32 stubs, it's faster to always use the double stub.
         stub->unlinkStubsWithKind(cx, ICStub::Compare_Int32);
 
         ICCompare_Double::Compiler compiler(cx, op);
         ICStub *doubleStub = compiler.getStub(compiler.getStubSpace(script));
@@ -2065,17 +2068,17 @@ DoToBoolFallback(JSContext *cx, Baseline
         ICStub *int32Stub = compiler.getStub(compiler.getStubSpace(script));
         if (!int32Stub)
             return false;
 
         stub->addNewStub(int32Stub);
         return true;
     }
 
-    if (arg.isDouble()) {
+    if (arg.isDouble() && cx->runtime->jitSupportsFloatingPoint) {
         IonSpew(IonSpew_BaselineIC, "  Generating ToBool(Double) stub.");
         ICToBool_Double::Compiler compiler(cx);
         ICStub *doubleStub = compiler.getStub(compiler.getStubSpace(script));
         if (!doubleStub)
             return false;
 
         stub->addNewStub(doubleStub);
         return true;
@@ -2451,16 +2454,19 @@ DoBinaryArithFallback(JSContext *cx, Bas
 
     // Handle only int32 or double.
     if (!lhs.isNumber() || !rhs.isNumber())
         return true;
 
     JS_ASSERT(ret.isNumber());
 
     if (lhs.isDouble() || rhs.isDouble() || ret.isDouble()) {
+        if (!cx->runtime->jitSupportsFloatingPoint)
+            return true;
+
         switch (op) {
           case JSOP_ADD:
           case JSOP_SUB:
           case JSOP_MUL:
           case JSOP_DIV:
           case JSOP_MOD: {
             // Unlink int32 stubs, it's faster to always use the double stub.
             stub->unlinkStubsWithKind(cx, ICStub::BinaryArith_Int32);
@@ -2907,17 +2913,20 @@ DoUnaryArithFallback(JSContext *cx, Base
         ICUnaryArith_Int32::Compiler compiler(cx, op);
         ICStub *int32Stub = compiler.getStub(compiler.getStubSpace(script));
         if (!int32Stub)
             return false;
         stub->addNewStub(int32Stub);
         return true;
     }
 
-    if (val.isNumber() && res.isNumber() && op == JSOP_NEG) {
+    if (val.isNumber() && res.isNumber() &&
+        op == JSOP_NEG &&
+        cx->runtime->jitSupportsFloatingPoint)
+    {
         IonSpew(IonSpew_BaselineIC, "  Generating %s(Number => Number) stub", js_CodeName[op]);
         // Unlink int32 stubs, the double stub handles both cases and TI specializes for both.
         stub->unlinkStubsWithKind(cx, ICStub::UnaryArith_Int32);
 
         ICUnaryArith_Double::Compiler compiler(cx, op);
         ICStub *doubleStub = compiler.getStub(compiler.getStubSpace(script));
         if (!doubleStub)
             return false;
@@ -3251,16 +3260,25 @@ static bool TryAttachNativeGetElemStub(J
     if (!newStub)
         return false;
 
     stub->addNewStub(newStub);
     return true;
 }
 
 static bool
+TypedArrayRequiresFloatingPoint(JSObject *obj)
+{
+    uint32_t type = TypedArray::type(obj);
+    return (type == TypedArray::TYPE_UINT32 ||
+            type == TypedArray::TYPE_FLOAT32 ||
+            type == TypedArray::TYPE_FLOAT64);
+}
+
+static bool
 TryAttachGetElemStub(JSContext *cx, HandleScript script, ICGetElem_Fallback *stub,
                      HandleValue lhs, HandleValue rhs, HandleValue res)
 {
     // Check for String[i] => Char accesses.
     if (lhs.isString() && rhs.isInt32() && res.isString() &&
         !stub->hasStub(ICStub::GetElem_String))
     {
         IonSpew(IonSpew_BaselineIC, "  Generating GetElem(String[Int32]) stub");
@@ -3298,16 +3316,19 @@ TryAttachGetElemStub(JSContext *cx, Hand
                 return false;
         }
     }
 
     // Check for TypedArray[int] => Number accesses.
     if (obj->isTypedArray() && rhs.isInt32() && res.isNumber() &&
         !TypedArrayGetElemStubExists(stub, obj))
     {
+        if (!cx->runtime->jitSupportsFloatingPoint && TypedArrayRequiresFloatingPoint(obj))
+            return true;
+
         IonSpew(IonSpew_BaselineIC, "  Generating GetElem(TypedArray[Int32]) stub");
         ICGetElem_TypedArray::Compiler compiler(cx, obj->lastProperty(), TypedArray::type(obj));
         ICStub *typedArrayStub = compiler.getStub(compiler.getStubSpace(script));
         if (!typedArrayStub)
             return false;
 
         stub->addNewStub(typedArrayStub);
         return true;
@@ -3823,16 +3844,19 @@ DoSetElemFallback(JSContext *cx, Baselin
                 stub->addNewStub(denseStub);
             }
         }
 
         return true;
     }
 
     if (obj->isTypedArray() && index.isInt32() && rhs.isNumber()) {
+        if (!cx->runtime->jitSupportsFloatingPoint && TypedArrayRequiresFloatingPoint(obj))
+            return true;
+
         uint32_t len = TypedArray::length(obj);
         int32_t idx = index.toInt32();
         bool expectOutOfBounds = (idx < 0) || (static_cast<uint32_t>(idx) >= len);
 
         if (!TypedArraySetElemStubExists(stub, obj, expectOutOfBounds)) {
             // Remove any existing TypedArraySetElemStub that doesn't handle out-of-bounds
             if (expectOutOfBounds)
                 RemoveExistingTypedArraySetElemStub(cx, stub, obj);
@@ -3975,19 +3999,24 @@ ICSetElem_Dense::Compiler::generateStubC
 
     // It's safe to overwrite R0 now.
     Address valueAddr(BaselineStackReg, ICStackValueOffset);
     masm.loadValue(valueAddr, R0);
     EmitPreBarrier(masm, element, MIRType_Value);
     masm.storeValue(R0, element);
     EmitReturnFromIC(masm);
 
-    // Convert to double and jump back.
+    // Convert to double and jump back. Note that double arrays are only
+    // created by IonMonkey, so if we have no floating-point support
+    // Ion is disabled and there should be no double arrays.
     masm.bind(&convertDoubles);
-    masm.convertInt32ValueToDouble(valueAddr, R0.scratchReg(), &convertDoublesDone);
+    if (cx->runtime->jitSupportsFloatingPoint)
+        masm.convertInt32ValueToDouble(valueAddr, R0.scratchReg(), &convertDoublesDone);
+    else
+        masm.breakpoint();
     masm.jump(&convertDoublesDone);
 
     // Failure case - fail but first unstow R0 and R1
     masm.bind(&failureUnstow);
     EmitUnstowICValues(masm, 2);
 
     // Failure case - jump to next stub
     masm.bind(&failure);
@@ -4138,19 +4167,24 @@ ICSetElemDenseAddCompiler::generateStubC
     // Write the value.  No need for write barrier since we're not overwriting an old value.
     // It's safe to overwrite R0 now.
     BaseIndex element(scratchReg, key, TimesEight);
     Address valueAddr(BaselineStackReg, ICStackValueOffset);
     masm.loadValue(valueAddr, R0);
     masm.storeValue(R0, element);
     EmitReturnFromIC(masm);
 
-    // Convert to double and jump back.
+    // Convert to double and jump back. Note that double arrays are only
+    // created by IonMonkey, so if we have no floating-point support
+    // Ion is disabled and there should be no double arrays.
     masm.bind(&convertDoubles);
-    masm.convertInt32ValueToDouble(valueAddr, R0.scratchReg(), &convertDoublesDone);
+    if (cx->runtime->jitSupportsFloatingPoint)
+        masm.convertInt32ValueToDouble(valueAddr, R0.scratchReg(), &convertDoublesDone);
+    else
+        masm.breakpoint();
     masm.jump(&convertDoublesDone);
 
     // Failure case - fail but first unstow R0 and R1
     masm.bind(&failureUnstow);
     EmitUnstowICValues(masm, 2);
 
     // Failure case - jump to next stub
     masm.bind(&failure);
@@ -4213,38 +4247,46 @@ ICSetElem_TypedArray::Compiler::generate
         Label clamped;
         masm.bind(&clamped);
         masm.storeToTypedIntArray(type_, secondScratch, dest);
         EmitReturnFromIC(masm);
 
         // If the value is a double, clamp to uint8 and jump back.
         // Else, jump to failure.
         masm.bind(&notInt32);
-        masm.branchTestDouble(Assembler::NotEqual, value, &failure);
-        masm.unboxDouble(value, FloatReg0);
-        masm.clampDoubleToUint8(FloatReg0, secondScratch);
-        masm.jump(&clamped);
+        if (cx->runtime->jitSupportsFloatingPoint) {
+            masm.branchTestDouble(Assembler::NotEqual, value, &failure);
+            masm.unboxDouble(value, FloatReg0);
+            masm.clampDoubleToUint8(FloatReg0, secondScratch);
+            masm.jump(&clamped);
+        } else {
+            masm.jump(&failure);
+        }
     } else {
         Label notInt32;
         masm.branchTestInt32(Assembler::NotEqual, value, &notInt32);
         masm.unboxInt32(value, secondScratch);
 
         Label isInt32;
         masm.bind(&isInt32);
         masm.storeToTypedIntArray(type_, secondScratch, dest);
         EmitReturnFromIC(masm);
 
         // If the value is a double, truncate and jump back.
         // Else, jump to failure.
         Label failureRestoreRegs;
         masm.bind(&notInt32);
-        masm.branchTestDouble(Assembler::NotEqual, value, &failure);
-        masm.unboxDouble(value, FloatReg0);
-        masm.branchTruncateDouble(FloatReg0, secondScratch, &failureRestoreRegs);
-        masm.jump(&isInt32);
+        if (cx->runtime->jitSupportsFloatingPoint) {
+            masm.branchTestDouble(Assembler::NotEqual, value, &failure);
+            masm.unboxDouble(value, FloatReg0);
+            masm.branchTruncateDouble(FloatReg0, secondScratch, &failureRestoreRegs);
+            masm.jump(&isInt32);
+        } else {
+            masm.jump(&failure);
+        }
 
         // Writing to secondScratch may have clobbered R0 or R1, restore them
         // first.
         masm.bind(&failureRestoreRegs);
         masm.tagValue(JSVAL_TYPE_OBJECT, obj, R0);
         masm.tagValue(JSVAL_TYPE_INT32, key, R1);
     }
 
@@ -6506,16 +6548,28 @@ ICCall_Native::Compiler::generateStubCod
     masm.bind(&exception);
     masm.handleException();
 
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
 }
 
+static JSBool
+DoubleValueToInt32ForSwitch(Value *v)
+{
+    double d = v->toDouble();
+    int32_t truncated = int32_t(d);
+    if (d != double(truncated))
+        return false;
+
+    v->setInt32(truncated);
+    return true;
+}
+
 bool
 ICTableSwitch::Compiler::generateStubCode(MacroAssembler &masm)
 {
     Label isInt32, notInt32, outOfRange;
     Register scratch = R1.scratchReg();
 
     masm.branchTestInt32(Assembler::NotEqual, R0, &notInt32);
 
@@ -6532,20 +6586,37 @@ ICTableSwitch::Compiler::generateStubCod
     masm.loadPtr(BaseIndex(scratch, key, ScalePointer), scratch);
 
     EmitChangeICReturnAddress(masm, scratch);
     EmitReturnFromIC(masm);
 
     masm.bind(&notInt32);
 
     masm.branchTestDouble(Assembler::NotEqual, R0, &outOfRange);
-    masm.unboxDouble(R0, FloatReg0);
-
-    // N.B. -0 === 0, so convert -0 to a 0 int32.
-    masm.convertDoubleToInt32(FloatReg0, key, &outOfRange, /* negativeZeroCheck = */ false);
+    if (cx->runtime->jitSupportsFloatingPoint) {
+        masm.unboxDouble(R0, FloatReg0);
+
+        // N.B. -0 === 0, so convert -0 to a 0 int32.
+        masm.convertDoubleToInt32(FloatReg0, key, &outOfRange, /* negativeZeroCheck = */ false);
+    } else {
+        // Pass pointer to double value.
+        masm.pushValue(R0);
+        masm.movePtr(StackPointer, R0.scratchReg());
+
+        masm.setupUnalignedABICall(1, scratch);
+        masm.passABIArg(R0.scratchReg());
+        masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, DoubleValueToInt32ForSwitch));
+
+        // If the function returns |true|, the value has been converted to
+        // int32.
+        masm.mov(ReturnReg, scratch);
+        masm.popValue(R0);
+        masm.branchTest32(Assembler::Zero, scratch, scratch, &outOfRange);
+        masm.unboxInt32(R0, key);
+    }
     masm.jump(&isInt32);
 
     masm.bind(&outOfRange);
 
     masm.loadPtr(Address(BaselineStubReg, offsetof(ICTableSwitch, defaultTarget_)), scratch);
 
     EmitChangeICReturnAddress(masm, scratch);
     EmitReturnFromIC(masm);
--- a/js/src/ion/BaselineJIT.h
+++ b/js/src/ion/BaselineJIT.h
@@ -244,17 +244,18 @@ struct BaselineScript
 
     void toggleSPS(bool enable);
 
     static size_t offsetOfFlags() {
         return offsetof(BaselineScript, flags_);
     }
 };
 
-inline bool IsBaselineEnabled(JSContext *cx)
+inline bool
+IsBaselineEnabled(JSContext *cx)
 {
     return cx->hasOption(JSOPTION_BASELINE);
 }
 
 MethodStatus
 CanEnterBaselineJIT(JSContext *cx, JSScript *scriptArg, StackFrame *fp, bool newType);
 
 IonExecStatus
--- a/js/src/ion/Ion.cpp
+++ b/js/src/ion/Ion.cpp
@@ -193,47 +193,50 @@ IonRuntime::initialize(JSContext *cx)
     execAlloc_ = cx->runtime->getExecAlloc(cx);
     if (!execAlloc_)
         return false;
 
     functionWrappers_ = cx->new_<VMWrapperMap>(cx);
     if (!functionWrappers_ || !functionWrappers_->init())
         return false;
 
-    if (!bailoutTables_.reserve(FrameSizeClass::ClassLimit().classId()))
-        return false;
-
-    for (uint32_t id = 0;; id++) {
-        FrameSizeClass class_ = FrameSizeClass::FromClass(id);
-        if (class_ == FrameSizeClass::ClassLimit())
-            break;
-        bailoutTables_.infallibleAppend((IonCode *)NULL);
-        bailoutTables_[id] = generateBailoutTable(cx, id);
-        if (!bailoutTables_[id])
+    if (cx->runtime->jitSupportsFloatingPoint) {
+        // Initialize some Ion-only stubs that require floating-point support.
+        if (!bailoutTables_.reserve(FrameSizeClass::ClassLimit().classId()))
+            return false;
+
+        for (uint32_t id = 0;; id++) {
+            FrameSizeClass class_ = FrameSizeClass::FromClass(id);
+            if (class_ == FrameSizeClass::ClassLimit())
+                break;
+            bailoutTables_.infallibleAppend((IonCode *)NULL);
+            bailoutTables_[id] = generateBailoutTable(cx, id);
+            if (!bailoutTables_[id])
+                return false;
+        }
+
+        bailoutHandler_ = generateBailoutHandler(cx);
+        if (!bailoutHandler_)
+            return false;
+
+        invalidator_ = generateInvalidator(cx);
+        if (!invalidator_)
             return false;
     }
 
-    bailoutHandler_ = generateBailoutHandler(cx);
-    if (!bailoutHandler_)
-        return false;
-
     argumentsRectifier_ = generateArgumentsRectifier(cx, SequentialExecution, &argumentsRectifierReturnAddr_);
     if (!argumentsRectifier_)
         return false;
 
 #ifdef JS_THREADSAFE
     parallelArgumentsRectifier_ = generateArgumentsRectifier(cx, ParallelExecution, NULL);
     if (!parallelArgumentsRectifier_)
         return false;
 #endif
 
-    invalidator_ = generateInvalidator(cx);
-    if (!invalidator_)
-        return false;
-
     enterJIT_ = generateEnterJIT(cx, EnterJitOptimized);
     if (!enterJIT_)
         return false;
 
     enterBaselineJIT_ = generateEnterJIT(cx, EnterJitBaseline);
     if (!enterBaselineJIT_)
         return false;
 
--- a/js/src/ion/shared/Assembler-x86-shared.h
+++ b/js/src/ion/shared/Assembler-x86-shared.h
@@ -273,100 +273,109 @@ class AssemblerX86Shared
             masm.movl_i32m(imm32.value, dest.disp(), dest.base(), dest.index(), dest.scale());
             break;
           default:
             JS_NOT_REACHED("unexpected operand kind");
         }
     }
 
     void movsd(const FloatRegister &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         masm.movsd_rr(src.code(), dest.code());
     }
     void movsd(const Operand &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         switch (src.kind()) {
           case Operand::FPREG:
             masm.movsd_rr(src.fpu(), dest.code());
             break;
           case Operand::REG_DISP:
             masm.movsd_mr(src.disp(), src.base(), dest.code());
             break;
           case Operand::SCALE:
             masm.movsd_mr(src.disp(), src.base(), src.index(), src.scale(), dest.code());
             break;
           default:
             JS_NOT_REACHED("unexpected operand kind");
         }
     }
     void movsd(const FloatRegister &src, const Operand &dest) {
+        JS_ASSERT(HasSSE2());
         switch (dest.kind()) {
           case Operand::FPREG:
             masm.movsd_rr(src.code(), dest.fpu());
             break;
           case Operand::REG_DISP:
             masm.movsd_rm(src.code(), dest.disp(), dest.base());
             break;
           case Operand::SCALE:
             masm.movsd_rm(src.code(), dest.disp(), dest.base(), dest.index(), dest.scale());
             break;
           default:
             JS_NOT_REACHED("unexpected operand kind");
         }
     }
     void movss(const Operand &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         switch (src.kind()) {
           case Operand::REG_DISP:
             masm.movss_mr(src.disp(), src.base(), dest.code());
             break;
           case Operand::SCALE:
             masm.movss_mr(src.disp(), src.base(), src.index(), src.scale(), dest.code());
             break;
           default:
             JS_NOT_REACHED("unexpected operand kind");
         }
     }
     void movss(const FloatRegister &src, const Operand &dest) {
+        JS_ASSERT(HasSSE2());
         switch (dest.kind()) {
           case Operand::REG_DISP:
             masm.movss_rm(src.code(), dest.disp(), dest.base());
             break;
           case Operand::SCALE:
             masm.movss_rm(src.code(), dest.disp(), dest.base(), dest.index(), dest.scale());
             break;
           default:
             JS_NOT_REACHED("unexpected operand kind");
         }
     }
     void movdqa(const Operand &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         switch (src.kind()) {
           case Operand::REG_DISP:
             masm.movdqa_mr(src.disp(), src.base(), dest.code());
             break;
           case Operand::SCALE:
             masm.movdqa_mr(src.disp(), src.base(), src.index(), src.scale(), dest.code());
             break;
           default:
             JS_NOT_REACHED("unexpected operand kind");
         }
     }
     void movdqa(const FloatRegister &src, const Operand &dest) {
+        JS_ASSERT(HasSSE2());
         switch (dest.kind()) {
           case Operand::REG_DISP:
             masm.movdqa_rm(src.code(), dest.disp(), dest.base());
             break;
           case Operand::SCALE:
             masm.movdqa_rm(src.code(), dest.disp(), dest.base(), dest.index(), dest.scale());
             break;
           default:
             JS_NOT_REACHED("unexpected operand kind");
         }
     }
     void cvtss2sd(const FloatRegister &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         masm.cvtss2sd_rr(src.code(), dest.code());
     }
     void cvtsd2ss(const FloatRegister &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         masm.cvtsd2ss_rr(src.code(), dest.code());
     }
     void movzbl(const Operand &src, const Register &dest) {
         switch (src.kind()) {
           case Operand::REG_DISP:
             masm.movzbl_mr(src.disp(), src.base(), dest.code());
             break;
           case Operand::SCALE:
@@ -650,16 +659,19 @@ class AssemblerX86Shared
             JS_NOT_REACHED("unexpected operand kind");
         }
     }
 
     void breakpoint() {
         masm.int3();
     }
 
+    static bool HasSSE2() {
+        return JSC::MacroAssembler::getSSEState() >= JSC::MacroAssembler::HasSSE2;
+    }
     static bool HasSSE41() {
         return JSC::MacroAssembler::getSSEState() >= JSC::MacroAssembler::HasSSE4_1;
     }
 
     // The below cmpl methods switch the lhs and rhs when it invokes the
     // macroassembler to conform with intel standard.  When calling this
     // function put the left operand on the left as you would expect.
     void cmpl(const Register &lhs, const Register &rhs) {
@@ -1047,87 +1059,103 @@ class AssemblerX86Shared
     void idiv(Register divisor) {
         masm.idivl_r(divisor.code());
     }
     void udiv(Register divisor) {
         masm.divl_r(divisor.code());
     }
 
     void unpcklps(const FloatRegister &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         masm.unpcklps_rr(src.code(), dest.code());
     }
     void pinsrd(const Register &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         masm.pinsrd_rr(src.code(), dest.code());
     }
     void pinsrd(const Operand &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         switch (src.kind()) {
           case Operand::REG:
             masm.pinsrd_rr(src.reg(), dest.code());
             break;
           case Operand::REG_DISP:
             masm.pinsrd_mr(src.disp(), src.base(), dest.code());
             break;
           default:
             JS_NOT_REACHED("unexpected operand kind");
         }
     }
     void psrldq(Imm32 shift, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         masm.psrldq_rr(dest.code(), shift.value);
     }
     void psllq(Imm32 shift, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         masm.psllq_rr(dest.code(), shift.value);
     }
     void psrlq(Imm32 shift, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         masm.psrlq_rr(dest.code(), shift.value);
     }
 
     void cvtsi2sd(const Operand &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         switch (src.kind()) {
           case Operand::REG:
             masm.cvtsi2sd_rr(src.reg(), dest.code());
             break;
           case Operand::REG_DISP:
             masm.cvtsi2sd_mr(src.disp(), src.base(), dest.code());
             break;
           case Operand::SCALE:
             masm.cvtsi2sd_mr(src.disp(), src.base(), src.index(), src.scale(), dest.code());
             break;
           default:
             JS_NOT_REACHED("unexpected operand kind");
         }
     }
     void cvttsd2si(const FloatRegister &src, const Register &dest) {
+        JS_ASSERT(HasSSE2());
         masm.cvttsd2si_rr(src.code(), dest.code());
     }
     void cvtsi2sd(const Register &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         masm.cvtsi2sd_rr(src.code(), dest.code());
     }
     void movmskpd(const FloatRegister &src, const Register &dest) {
+        JS_ASSERT(HasSSE2());
         masm.movmskpd_rr(src.code(), dest.code());
     }
     void ptest(const FloatRegister &lhs, const FloatRegister &rhs) {
         JS_ASSERT(HasSSE41());
         masm.ptest_rr(rhs.code(), lhs.code());
     }
     void ucomisd(const FloatRegister &lhs, const FloatRegister &rhs) {
+        JS_ASSERT(HasSSE2());
         masm.ucomisd_rr(rhs.code(), lhs.code());
     }
     void pcmpeqw(const FloatRegister &lhs, const FloatRegister &rhs) {
+        JS_ASSERT(HasSSE2());
         masm.pcmpeqw_rr(rhs.code(), lhs.code());
     }    
     void movd(const Register &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         masm.movd_rr(src.code(), dest.code());
     }
     void movd(const FloatRegister &src, const Register &dest) {
+        JS_ASSERT(HasSSE2());
         masm.movd_rr(src.code(), dest.code());
     }
     void addsd(const FloatRegister &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         masm.addsd_rr(src.code(), dest.code());
     }
     void addsd(const Operand &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         switch (src.kind()) {
           case Operand::FPREG:
             masm.addsd_rr(src.fpu(), dest.code());
             break;
           case Operand::REG_DISP:
             masm.addsd_mr(src.disp(), src.base(), dest.code());
             break;
 #ifdef JS_CPU_X86
@@ -1135,75 +1163,86 @@ class AssemblerX86Shared
             masm.addsd_mr(src.address(), dest.code());
             break;
 #endif
           default:
             JS_NOT_REACHED("unexpected operand kind");
         }
     }
     void subsd(const FloatRegister &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         masm.subsd_rr(src.code(), dest.code());
     }
     void subsd(const Operand &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         switch (src.kind()) {
           case Operand::FPREG:
             masm.subsd_rr(src.fpu(), dest.code());
             break;
           case Operand::REG_DISP:
             masm.subsd_mr(src.disp(), src.base(), dest.code());
             break;
           default:
             JS_NOT_REACHED("unexpected operand kind");
         }
     }
     void mulsd(const FloatRegister &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         masm.mulsd_rr(src.code(), dest.code());
     }
     void mulsd(const Operand &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         switch (src.kind()) {
           case Operand::FPREG:
             masm.mulsd_rr(src.fpu(), dest.code());
             break;
           case Operand::REG_DISP:
             masm.mulsd_mr(src.disp(), src.base(), dest.code());
             break;
           default:
             JS_NOT_REACHED("unexpected operand kind");
         }
     }
     void divsd(const FloatRegister &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         masm.divsd_rr(src.code(), dest.code());
     }
     void divsd(const Operand &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         switch (src.kind()) {
           case Operand::FPREG:
             masm.divsd_rr(src.fpu(), dest.code());
             break;
           case Operand::REG_DISP:
             masm.divsd_mr(src.disp(), src.base(), dest.code());
             break;
           default:
             JS_NOT_REACHED("unexpected operand kind");
         }
     }
     void xorpd(const FloatRegister &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         masm.xorpd_rr(src.code(), dest.code());
     }
     void orpd(const FloatRegister &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         masm.orpd_rr(src.code(), dest.code());
     }
     void andpd(const FloatRegister &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         masm.andpd_rr(src.code(), dest.code());
     }
     void sqrtsd(const FloatRegister &src, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         masm.sqrtsd_rr(src.code(), dest.code());
     }
     void roundsd(const FloatRegister &src, const FloatRegister &dest,
                  JSC::X86Assembler::RoundingMode mode)
     {
+        JS_ASSERT(HasSSE41());
         masm.roundsd_rr(src.code(), dest.code(), mode);
     }
     void fstp(const Operand &src) {
          switch (src.kind()) {
            case Operand::REG_DISP:
              masm.fstp_m(src.disp(), src.base());
              break;
            default:
--- a/js/src/ion/x86/Assembler-x86.h
+++ b/js/src/ion/x86/Assembler-x86.h
@@ -427,42 +427,45 @@ class Assembler : public AssemblerX86Sha
                 addPendingJump(jmp, target, reloc);
                 jmp = next;
             } while (more);
         }
         label->reset();
     }
 
     void movsd(const double *dp, const FloatRegister &dest) {
+        JS_ASSERT(HasSSE2());
         masm.movsd_mr((const void *)dp, dest.code());
     }
 
     // Move a 32-bit immediate into a register where the immediate can be
     // patched.
     CodeOffsetLabel movlWithPatch(Imm32 imm, Register dest) {
         masm.movl_i32r(imm.value, dest.code());
         return masm.currentOffset();
     }
 
     // Load from *addr where addr can be patched.
     CodeOffsetLabel movlWithPatch(void *addr, Register dest) {
         masm.movl_mr(addr, dest.code());
         return masm.currentOffset();
     }
     CodeOffsetLabel movsdWithPatch(void *addr, FloatRegister dest) {
+        JS_ASSERT(HasSSE2());
         masm.movsd_mr(addr, dest.code());
         return masm.currentOffset();
     }
 
     // Store to *addr where addr can be patched
     CodeOffsetLabel movlWithPatch(Register src, void *addr) {
         masm.movl_rm(src.code(), addr);
         return masm.currentOffset();
     }
     CodeOffsetLabel movsdWithPatch(FloatRegister dest, void *addr) {
+        JS_ASSERT(HasSSE2());
         masm.movsd_rm(dest.code(), addr);
         return masm.currentOffset();
     }
 
     // Load from *(base + disp32) where disp32 can be patched.
     CodeOffsetLabel movxblWithPatch(Address src, Register dest) {
         masm.movxbl_mr_disp32(src.offset, src.base.code(), dest.code());
         return masm.currentOffset();
@@ -479,20 +482,22 @@ class Assembler : public AssemblerX86Sha
         masm.movzwl_mr_disp32(src.offset, src.base.code(), dest.code());
         return masm.currentOffset();
     }
     CodeOffsetLabel movlWithPatch(Address src, Register dest) {
         masm.movl_mr_disp32(src.offset, src.base.code(), dest.code());
         return masm.currentOffset();
     }
     CodeOffsetLabel movssWithPatch(Address src, FloatRegister dest) {
+        JS_ASSERT(HasSSE2());
         masm.movss_mr_disp32(src.offset, src.base.code(), dest.code());
         return masm.currentOffset();
     }
     CodeOffsetLabel movsdWithPatch(Address src, FloatRegister dest) {
+        JS_ASSERT(HasSSE2());
         masm.movsd_mr_disp32(src.offset, src.base.code(), dest.code());
         return masm.currentOffset();
     }
 
     // Store to *(base + disp32) where disp32 can be patched.
     CodeOffsetLabel movbWithPatch(Register src, Address dest) {
         masm.movb_rm_disp32(src.code(), dest.offset, dest.base.code());
         return masm.currentOffset();
@@ -501,20 +506,22 @@ class Assembler : public AssemblerX86Sha
         masm.movw_rm_disp32(src.code(), dest.offset, dest.base.code());
         return masm.currentOffset();
     }
     CodeOffsetLabel movlWithPatch(Register src, Address dest) {
         masm.movl_rm_disp32(src.code(), dest.offset, dest.base.code());
         return masm.currentOffset();
     }
     CodeOffsetLabel movssWithPatch(FloatRegister src, Address dest) {
+        JS_ASSERT(HasSSE2());
         masm.movss_rm_disp32(src.code(), dest.offset, dest.base.code());
         return masm.currentOffset();
     }
     CodeOffsetLabel movsdWithPatch(FloatRegister src, Address dest) {
+        JS_ASSERT(HasSSE2());
         masm.movsd_rm_disp32(src.code(), dest.offset, dest.base.code());
         return masm.currentOffset();
     }
 
     // Load from *(addr + index*scale) where addr can be patched.
     CodeOffsetLabel movlWithPatch(void *addr, Register index, Scale scale, Register dest) {
         masm.movl_mr(addr, index.code(), scale, dest.code());
         return masm.currentOffset();
--- a/js/src/ion/x86/Trampoline-x86.cpp
+++ b/js/src/ion/x86/Trampoline-x86.cpp
@@ -660,18 +660,24 @@ IonRuntime::generateVMWrapper(JSContext 
     return wrapper;
 }
 
 IonCode *
 IonRuntime::generatePreBarrier(JSContext *cx, MIRType type)
 {
     MacroAssembler masm;
 
-    RegisterSet save = RegisterSet(GeneralRegisterSet(Registers::VolatileMask),
-                                   FloatRegisterSet(FloatRegisters::VolatileMask));
+    RegisterSet save;
+    if (cx->runtime->jitSupportsFloatingPoint) {
+        save = RegisterSet(GeneralRegisterSet(Registers::VolatileMask),
+                           FloatRegisterSet(FloatRegisters::VolatileMask));
+    } else {
+        save = RegisterSet(GeneralRegisterSet(Registers::VolatileMask),
+                           FloatRegisterSet());
+    }
     masm.PushRegsInMask(save);
 
     JS_ASSERT(PreBarrierReg == edx);
     masm.movl(ImmWord(cx->runtime), ecx);
 
     masm.setupUnalignedABICall(2, eax);
     masm.passABIArg(ecx);
     masm.passABIArg(edx);
--- a/js/src/jit-test/jit_test.py
+++ b/js/src/jit-test/jit_test.py
@@ -142,17 +142,17 @@ def main(argv):
     if options.tbpl:
         # Running all bits would take forever. Instead, we test a few interesting combinations.
         flags = [
                       ['--no-baseline', '--no-jm'],
                       ['--ion-eager'], # implies --baseline-eager
                       ['--no-baseline'],
                       ['--no-baseline', '--ion-eager'],
                       ['--baseline-eager'],
-                      ['--baseline-eager', '--no-ti'],
+                      ['--baseline-eager', '--no-ti', '--no-fpu'],
                       # Below, equivalents the old shell flags: ,m,am,amd,n,mn,amn,amdn,mdn
                       ['--no-baseline', '--no-ion', '--no-jm', '--no-ti'],
                       ['--no-baseline', '--no-ion', '--no-ti'],
                       ['--no-baseline', '--no-ion', '--no-ti', '--always-mjit', '--debugjit'],
                       ['--no-baseline', '--no-ion', '--no-jm'],
                       ['--no-baseline', '--no-ion'],
                       ['--no-baseline', '--no-ion', '--always-mjit'],
                       ['--no-baseline', '--no-ion', '--always-mjit', '--debugjit'],
--- a/js/src/jit-test/tests/asm.js/testCall.js
+++ b/js/src/jit-test/tests/asm.js/testCall.js
@@ -41,9 +41,9 @@ assertEq(asmLink(asmCompile(USE_ASM+"fun
 
 var rec = asmLink(asmCompile(USE_ASM+"function rec() { rec() } return rec"));
 assertThrowsInstanceOf(rec, InternalError);
 
 var rec = asmLink(asmCompile(USE_ASM+"function rec(i) { i=i|0; if (!i) return 0; return (rec((i-1)|0)+1)|0 } return rec"));
 assertEq(rec(100), 100);
 assertEq(rec(1000), 1000);
 assertThrowsInstanceOf(function() rec(100000000000), InternalError);
-assertEq(rec(10000), 10000);
+assertEq(rec(2000), 2000);
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -681,16 +681,34 @@ JS::isGCEnabled()
     return !TlsPerThreadData.get()->suppressGC;
 }
 #else
 JS_FRIEND_API(bool) JS::isGCEnabled() { return true; }
 #endif
 
 static const JSSecurityCallbacks NullSecurityCallbacks = { };
 
+static bool
+JitSupportsFloatingPoint()
+{
+#if defined(JS_METHODJIT) || defined(JS_ION)
+    if (!JSC::MacroAssembler().supportsFloatingPoint())
+        return false;
+
+#if defined(JS_ION) && WTF_ARM_ARCH_VERSION == 6
+    if (!js::ion::hasVFP())
+        return false;
+#endif
+
+    return true;
+#else
+    return false;
+#endif
+}
+
 PerThreadData::PerThreadData(JSRuntime *runtime)
   : PerThreadDataFriendFields(),
     runtime_(runtime),
     ionTop(NULL),
     ionJSContext(NULL),
     ionStackLimit(0),
 #ifdef JS_THREADSAFE
     ionStackLimitLock_(NULL),
@@ -888,16 +906,17 @@ JSRuntime::JSRuntime(JSUseHelperThreads 
     wrapObjectCallback(TransparentObjectWrapper),
     sameCompartmentWrapObjectCallback(NULL),
     preWrapObjectCallback(NULL),
     preserveWrapperCallback(NULL),
 #ifdef DEBUG
     noGCOrAllocationCheck(0),
 #endif
     jitHardening(false),
+    jitSupportsFloatingPoint(false),
     ionPcScriptCache(NULL),
     threadPool(this),
     ctypesActivityCallback(NULL),
     parallelWarmup(0),
     ionReturnOverride_(MagicValue(JS_ARG_POISON)),
     useHelperThreads_(useHelperThreads),
     requestedHelperThreadCount(-1),
 #ifdef DEBUG
@@ -985,16 +1004,18 @@ JSRuntime::init(uint32_t maxbytes)
     if (useHelperThreads() && !sourceCompressorThread.init())
         return false;
 #endif
 
     if (!evalCache.init())
         return false;
 
     nativeStackBase = GetNativeStackBase();
+
+    jitSupportsFloatingPoint = JitSupportsFloatingPoint();
     return true;
 }
 
 JSRuntime::~JSRuntime()
 {
 #ifdef JS_THREADSAFE
     clearOwnerThread();
 #endif
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -1299,16 +1299,18 @@ struct JSRuntime : private JS::shadow::R
     js::ScriptDataTable scriptDataTable;
 
 #ifdef DEBUG
     size_t              noGCOrAllocationCheck;
 #endif
 
     bool                jitHardening;
 
+    bool                jitSupportsFloatingPoint;
+
     // Used to reset stack limit after a signaled interrupt (i.e. ionStackLimit_ = -1)
     // has been noticed by Ion/Baseline.
     void resetIonStackLimit() {
         mainThread.setIonStackLimit(mainThread.nativeStackLimit);
     }
 
     // Cache for ion::GetPcScript().
     js::ion::PcScriptCache *ionPcScriptCache;
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -2377,53 +2377,28 @@ class TypeConstraintFreezeStack : public
         AddPendingRecompile(cx, script, NULL);
     }
 };
 
 /////////////////////////////////////////////////////////////////////
 // TypeCompartment
 /////////////////////////////////////////////////////////////////////
 
-static inline bool
-TypeInferenceSupported()
-{
-#ifdef JS_METHODJIT
-    // JM+TI will generate FPU instructions with TI enabled. As a workaround,
-    // we disable TI to prevent this on platforms which do not have FPU
-    // support.
-    JSC::MacroAssembler masm;
-    if (!masm.supportsFloatingPoint())
-        return false;
-#endif
-
-#if WTF_ARM_ARCH_VERSION == 6
-#ifdef  JS_ION
-    return js::ion::hasVFP();
-#else
-    // If building for ARMv6 targets, we can't be guaranteed an FPU,
-    // so we hardcode TI off for consistency (see bug 793740).
-    return false;
-#endif
-#endif
-
-    return true;
-}
-
 TypeCompartment::TypeCompartment()
 {
     PodZero(this);
     compiledInfo.outputIndex = RecompileInfo::NoCompilerRunning;
 }
 
 void
 TypeZone::init(JSContext *cx)
 {
     if (!cx ||
         !cx->hasOption(JSOPTION_TYPE_INFERENCE) ||
-        !TypeInferenceSupported())
+        !cx->runtime->jitSupportsFloatingPoint)
     {
         return;
     }
 
     inferenceEnabled = true;
 }
 
 TypeObject *
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -5290,16 +5290,18 @@ main(int argc, char **argv, char **envp)
                                "Compile scripts off thread (default: off)")
 #endif
         || !op.addBoolOption('\0', "baseline", "Enable baseline compiler (default)")
         || !op.addBoolOption('\0', "no-baseline", "Disable baseline compiler")
         || !op.addBoolOption('\0', "baseline-eager", "Always baseline-compile methods")
         || !op.addIntOption('\0', "baseline-uses-before-compile", "COUNT",
                             "Wait for COUNT calls or iterations before baseline-compiling "
                             "(default: 10)", -1)
+        || !op.addBoolOption('\0', "no-fpu", "Pretend CPU does not support floating-point operations "
+                             "to test JIT codegen (no-op on platforms other than x86).")
 #ifdef JSGC_GENERATIONAL
         || !op.addBoolOption('\0', "ggc", "Enable Generational GC")
 #endif
     )
     {
         return EXIT_FAILURE;
     }
 
@@ -5324,16 +5326,21 @@ main(int argc, char **argv, char **envp)
     /*
      * Process OOM options as early as possible so that we can observe as many
      * allocations as possible.
      */
     if (op.getIntOption('A') >= 0)
         OOM_maxAllocations = op.getIntOption('A');
     if (op.getBoolOption('O'))
         OOM_printAllocationCount = true;
+
+#if defined(JS_CPU_X86)
+    if (op.getBoolOption("no-fpu"))
+        JSC::MacroAssembler::SetFloatingPointDisabled();
+#endif
 #endif
 
     /* Use the same parameters as the browser in xpcjsruntime.cpp. */
     rt = JS_NewRuntime(32L * 1024L * 1024L, JS_USE_HELPER_THREADS);
     if (!rt)
         return 1;
     gTimeoutFunc = NullValue();
     if (!JS_AddNamedValueRootRT(rt, &gTimeoutFunc, "gTimeoutFunc"))