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 128283 f10884c6a91e01699b19aff130f58b7b2649252c
parent 128282 1348d28d70fcbceeaaf366c36962ecab1d79ffa9
child 128284 d946c9ecdea6e72f3788aaa53640b5c3fdfeb98c
push id26245
push userjandemooij@gmail.com
push dateWed, 10 Apr 2013 12:38:42 +0000
treeherdermozilla-inbound@f10884c6a91e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdvander
bugs858022
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 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"))