Bug 833898 - Allow converting mixed arrays of ints and doubles to uniform doubles, r=jandem.
authorBrian Hackett <bhackett1024@gmail.com>
Tue, 29 Jan 2013 16:20:03 -0700
changeset 120310 d7dd65663469a6a5b301778d1daf38fcef30d8ae
parent 120309 3bc1aa6145edd8f6feb76b97ccd91e86fffc4419
child 120311 527874a5324b07f652dc11c817af10001446008c
push id24246
push userryanvm@gmail.com
push dateWed, 30 Jan 2013 13:05:37 +0000
treeherdermozilla-central@5f9775715519 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs833898
milestone21.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 833898 - Allow converting mixed arrays of ints and doubles to uniform doubles, r=jandem.
js/src/ion/CodeGenerator.cpp
js/src/ion/CodeGenerator.h
js/src/ion/IonAnalysis.cpp
js/src/ion/IonBuilder.cpp
js/src/ion/IonBuilder.h
js/src/ion/IonMacroAssembler.cpp
js/src/ion/LIR-Common.h
js/src/ion/LOpcodes.h
js/src/ion/Lowering.cpp
js/src/ion/Lowering.h
js/src/ion/MCallOptimize.cpp
js/src/ion/MIR.h
js/src/ion/MOpcodes.h
js/src/ion/TypeOracle.cpp
js/src/ion/TypeOracle.h
js/src/ion/VMFunctions.h
js/src/ion/arm/CodeGenerator-arm.cpp
js/src/ion/x64/Assembler-x64.h
js/src/ion/x64/CodeGenerator-x64.cpp
js/src/ion/x86/Assembler-x86.h
js/src/ion/x86/CodeGenerator-x86.cpp
js/src/jit-test/tests/ion/doubleArrays.js
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/jstypedarrayinlines.h
js/src/methodjit/BaseAssembler.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/Compiler.h
js/src/methodjit/FastBuiltins.cpp
js/src/methodjit/FastOps.cpp
js/src/vm/ObjectImpl.cpp
js/src/vm/ObjectImpl.h
--- a/js/src/ion/CodeGenerator.cpp
+++ b/js/src/ion/CodeGenerator.cpp
@@ -730,16 +730,36 @@ CodeGenerator::visitStoreSlotV(LStoreSlo
 bool
 CodeGenerator::visitElements(LElements *lir)
 {
     Address elements(ToRegister(lir->object()), JSObject::offsetOfElements());
     masm.loadPtr(elements, ToRegister(lir->output()));
     return true;
 }
 
+typedef bool (*ConvertElementsToDoublesFn)(JSContext *, uintptr_t);
+static const VMFunction ConvertElementsToDoublesInfo =
+    FunctionInfo<ConvertElementsToDoublesFn>(ObjectElements::ConvertElementsToDoubles);
+
+bool
+CodeGenerator::visitConvertElementsToDoubles(LConvertElementsToDoubles *lir)
+{
+    Register elements = ToRegister(lir->elements());
+
+    OutOfLineCode *ool = oolCallVM(ConvertElementsToDoublesInfo, lir,
+                                   (ArgList(), elements), StoreNothing());
+    if (!ool)
+        return false;
+
+    Address convertedAddress(elements, ObjectElements::offsetOfConvertDoubleElements());
+    masm.branch32(Assembler::Equal, convertedAddress, Imm32(0), ool->entry());
+    masm.bind(ool->rejoin());
+    return true;
+}
+
 bool
 CodeGenerator::visitFunctionEnvironment(LFunctionEnvironment *lir)
 {
     Address environment(ToRegister(lir->function()), JSFunction::offsetOfEnvironment());
     masm.loadPtr(environment, ToRegister(lir->output()));
     return true;
 }
 
--- a/js/src/ion/CodeGenerator.h
+++ b/js/src/ion/CodeGenerator.h
@@ -71,16 +71,17 @@ class CodeGenerator : public CodeGenerat
     bool visitRegExp(LRegExp *lir);
     bool visitRegExpTest(LRegExpTest *lir);
     bool visitLambda(LLambda *lir);
     bool visitLambdaForSingleton(LLambdaForSingleton *lir);
     bool visitPointer(LPointer *lir);
     bool visitSlots(LSlots *lir);
     bool visitStoreSlotV(LStoreSlotV *store);
     bool visitElements(LElements *lir);
+    bool visitConvertElementsToDoubles(LConvertElementsToDoubles *lir);
     bool visitTypeBarrier(LTypeBarrier *lir);
     bool visitMonitorTypes(LMonitorTypes *lir);
     bool visitCallNative(LCallNative *call);
     bool emitCallInvokeFunction(LInstruction *call, Register callereg,
                                 uint32_t argc, uint32_t unusedStack);
     bool visitCallGeneric(LCallGeneric *call);
     bool visitCallKnown(LCallKnown *call);
     bool emitCallInvokeFunction(LApplyArgsGeneric *apply, Register extraStackSize);
--- a/js/src/ion/IonAnalysis.cpp
+++ b/js/src/ion/IonAnalysis.cpp
@@ -1356,16 +1356,21 @@ ion::EliminateRedundantChecks(MIRGraph &
             bool eliminated = false;
 
             if (iter->isBoundsCheck()) {
                 if (!TryEliminateBoundsCheck(checks, index, iter->toBoundsCheck(), &eliminated))
                     return false;
             } else if (iter->isTypeBarrier()) {
                 if (!TryEliminateTypeBarrier(iter->toTypeBarrier(), &eliminated))
                     return false;
+            } else if (iter->isConvertElementsToDoubles()) {
+                // Now that code motion passes have finished, replace any
+                // ConvertElementsToDoubles with the actual elements.
+                MConvertElementsToDoubles *ins = iter->toConvertElementsToDoubles();
+                ins->replaceAllUsesWith(ins->elements());
             }
 
             if (eliminated)
                 iter = block->discardDefAt(iter);
             else
                 iter++;
         }
         index++;
--- a/js/src/ion/IonBuilder.cpp
+++ b/js/src/ion/IonBuilder.cpp
@@ -4371,16 +4371,19 @@ bool
 IonBuilder::jsop_newarray(uint32_t count)
 {
     JS_ASSERT(script()->compileAndGo);
 
     JSObject *templateObject = getNewArrayTemplateObject(count);
     if (!templateObject)
         return false;
 
+    if (oracle->arrayResultShouldHaveDoubleConversion(script(), pc))
+        templateObject->setShouldConvertDoubleElements();
+
     MNewArray *ins = new MNewArray(count, templateObject, MNewArray::NewArray_Allocating);
 
     current->add(ins);
     current->push(ins);
 
     return true;
 }
 
@@ -4429,16 +4432,22 @@ IonBuilder::jsop_initelem_array()
 
     MConstant *id = MConstant::New(Int32Value(GET_UINT24(pc)));
     current->add(id);
 
     // Get the elements vector.
     MElements *elements = MElements::New(obj);
     current->add(elements);
 
+    if (obj->toNewArray()->templateObject()->shouldConvertDoubleElements()) {
+        MInstruction *valueDouble = MToDouble::New(value);
+        current->add(valueDouble);
+        value = valueDouble;
+    }
+
     // Store the value.
     MStoreElement *store = MStoreElement::New(elements, id, value, /* needsHoleCheck = */ false);
     current->add(store);
 
     // Update the length.
     MSetInitializedLength *initLength = MSetInitializedLength::New(elements, id);
     current->add(initLength);
 
@@ -5411,32 +5420,43 @@ IonBuilder::jsop_getelem_dense()
     }
 
     // Ensure id is an integer.
     MInstruction *idInt32 = MToInt32::New(id);
     current->add(idInt32);
     id = idInt32;
 
     // Get the elements vector.
-    MElements *elements = MElements::New(obj);
+    MInstruction *elements = MElements::New(obj);
     current->add(elements);
 
+    // If we can load the element as a definite double, make sure to check that
+    // the array has been converted to homogenous doubles first.
+    bool loadDouble = !barrier &&
+                      loopDepth_ &&
+                      !readOutOfBounds &&
+                      oracle->elementReadShouldAlwaysLoadDoubles(script(), pc);
+    if (loadDouble) {
+        JS_ASSERT(!needsHoleCheck && knownType == JSVAL_TYPE_DOUBLE);
+        elements = addConvertElementsToDoubles(elements);
+    }
+
     MInitializedLength *initLength = MInitializedLength::New(elements);
     current->add(initLength);
 
     MInstruction *load;
 
     if (!readOutOfBounds) {
         // This load should not return undefined, so likely we're reading
         // in-bounds elements, and the array is packed or its holes are not
         // read. This is the best case: we can separate the bounds check for
         // hoisting.
         id = addBoundsCheck(id, initLength);
 
-        load = MLoadElement::New(elements, id, needsHoleCheck);
+        load = MLoadElement::New(elements, id, needsHoleCheck, loadDouble);
         current->add(load);
     } else {
         // This load may return undefined, so assume that we *can* read holes,
         // or that we can read out-of-bounds accesses. In this case, the bounds
         // check is part of the opcode.
         load = MLoadElementHole::New(elements, id, initLength, needsHoleCheck);
         current->add(load);
 
@@ -5636,16 +5656,23 @@ IonBuilder::jsop_setelem_dense()
     MDefinition *id = current->pop();
     MDefinition *obj = current->pop();
 
     // Ensure id is an integer.
     MInstruction *idInt32 = MToInt32::New(id);
     current->add(idInt32);
     id = idInt32;
 
+    // Ensure the value is a double, if double conversion might be needed.
+    if (oracle->elementWriteNeedsDoubleConversion(script(), pc)) {
+        MInstruction *valueDouble = MToDouble::New(value);
+        current->add(valueDouble);
+        value = valueDouble;
+    }
+
     // Get the elements vector.
     MElements *elements = MElements::New(obj);
     current->add(elements);
 
     // Use MStoreElementHole if this SETELEM has written to out-of-bounds
     // indexes in the past. Otherwise, use MStoreElement so that we can hoist
     // the initialized length and bounds check.
     MStoreElementCommon *store;
@@ -6996,16 +7023,24 @@ IonBuilder::jsop_instanceof()
 
     current->add(ins);
     current->push(ins);
 
     return resumeAfter(ins);
 }
 
 MInstruction *
+IonBuilder::addConvertElementsToDoubles(MDefinition *elements)
+{
+    MInstruction *convert = MConvertElementsToDoubles::New(elements);
+    current->add(convert);
+    return convert;
+}
+
+MInstruction *
 IonBuilder::addBoundsCheck(MDefinition *index, MDefinition *length)
 {
     MInstruction *check = MBoundsCheck::New(index, length);
     current->add(check);
 
     // If a bounds check failed in the past, don't optimize bounds checks.
     if (failedBoundsCheck_)
         check->setNotMovable();
--- a/js/src/ion/IonBuilder.h
+++ b/js/src/ion/IonBuilder.h
@@ -279,16 +279,17 @@ class IonBuilder : public MIRGenerator
     MDefinition *createThisScripted(MDefinition *callee);
     MDefinition *createThisScriptedSingleton(HandleFunction target, MDefinition *callee);
     MDefinition *createThis(HandleFunction target, MDefinition *callee);
     MInstruction *createDeclEnvObject(MDefinition *callee, MDefinition *scopeObj);
     MInstruction *createCallObject(MDefinition *callee, MDefinition *scopeObj);
 
     MDefinition *walkScopeChain(unsigned hops);
 
+    MInstruction *addConvertElementsToDoubles(MDefinition *elements);
     MInstruction *addBoundsCheck(MDefinition *index, MDefinition *length);
     MInstruction *addShapeGuard(MDefinition *obj, const UnrootedShape shape, BailoutKind bailoutKind);
 
     JSObject *getNewArrayTemplateObject(uint32_t count);
 
     bool invalidatedIdempotentCache();
 
     bool loadSlot(MDefinition *obj, HandleShape shape, MIRType rvalType);
--- a/js/src/ion/IonMacroAssembler.cpp
+++ b/js/src/ion/IonMacroAssembler.cpp
@@ -351,16 +351,18 @@ MacroAssembler::initGCThing(const Regist
 
         // Fill in the elements header.
         store32(Imm32(templateObject->getDenseCapacity()),
                 Address(obj, elementsOffset + ObjectElements::offsetOfCapacity()));
         store32(Imm32(templateObject->getDenseInitializedLength()),
                 Address(obj, elementsOffset + ObjectElements::offsetOfInitializedLength()));
         store32(Imm32(templateObject->getArrayLength()),
                 Address(obj, elementsOffset + ObjectElements::offsetOfLength()));
+        store32(Imm32(templateObject->shouldConvertDoubleElements() ? 1 : 0),
+                Address(obj, elementsOffset + ObjectElements::offsetOfConvertDoubleElements()));
     } else {
         storePtr(ImmWord(emptyObjectElements), Address(obj, JSObject::offsetOfElements()));
 
         // Fixed slots of non-array objects are required to be initialized.
         // Use the values currently in the template object.
         size_t nslots = Min(templateObject->numFixedSlots(), templateObject->slotSpan());
         for (unsigned i = 0; i < nslots; i++) {
             storeValue(templateObject->getFixedSlot(i),
--- a/js/src/ion/LIR-Common.h
+++ b/js/src/ion/LIR-Common.h
@@ -2118,16 +2118,31 @@ class LElements : public LInstructionHel
         setOperand(0, object);
     }
 
     const LAllocation *object() {
         return getOperand(0);
     }
 };
 
+// If necessary, convert any int32 elements in a vector into doubles.
+class LConvertElementsToDoubles : public LInstructionHelper<0, 1, 0>
+{
+  public:
+    LIR_HEADER(ConvertElementsToDoubles)
+
+    LConvertElementsToDoubles(const LAllocation &elements) {
+        setOperand(0, elements);
+    }
+
+    const LAllocation *elements() {
+        return getOperand(0);
+    }
+};
+
 // Load a dense array's initialized length from an elements vector.
 class LInitializedLength : public LInstructionHelper<1, 1, 0>
 {
   public:
     LIR_HEADER(InitializedLength)
 
     LInitializedLength(const LAllocation &elements) {
         setOperand(0, elements);
--- a/js/src/ion/LOpcodes.h
+++ b/js/src/ion/LOpcodes.h
@@ -106,16 +106,17 @@
     _(OsrScopeChain)                \
     _(RegExp)                       \
     _(RegExpTest)                   \
     _(Lambda)                       \
     _(LambdaForSingleton)           \
     _(ImplicitThis)                 \
     _(Slots)                        \
     _(Elements)                     \
+    _(ConvertElementsToDoubles)     \
     _(LoadSlotV)                    \
     _(LoadSlotT)                    \
     _(StoreSlotV)                   \
     _(StoreSlotT)                   \
     _(GuardShape)                   \
     _(GuardClass)                   \
     _(TypeBarrier)                  \
     _(MonitorTypes)                 \
--- a/js/src/ion/Lowering.cpp
+++ b/js/src/ion/Lowering.cpp
@@ -1392,16 +1392,23 @@ LIRGenerator::visitElements(MElements *i
 
 bool
 LIRGenerator::visitConstantElements(MConstantElements *ins)
 {
     return define(new LPointer(ins->value(), LPointer::NON_GC_THING), ins);
 }
 
 bool
+LIRGenerator::visitConvertElementsToDoubles(MConvertElementsToDoubles *ins)
+{
+    LInstruction *check = new LConvertElementsToDoubles(useRegister(ins->elements()));
+    return add(check, ins) && assignSafepoint(check, ins);
+}
+
+bool
 LIRGenerator::visitLoadSlot(MLoadSlot *ins)
 {
     switch (ins->type()) {
       case MIRType_Value:
         return defineBox(new LLoadSlotV(useRegister(ins->slots())), ins);
 
       case MIRType_Undefined:
       case MIRType_Null:
--- a/js/src/ion/Lowering.h
+++ b/js/src/ion/Lowering.h
@@ -135,16 +135,17 @@ class LIRGenerator : public LIRGenerator
     bool visitToString(MToString *convert);
     bool visitRegExp(MRegExp *ins);
     bool visitRegExpTest(MRegExpTest *ins);
     bool visitLambda(MLambda *ins);
     bool visitImplicitThis(MImplicitThis *ins);
     bool visitSlots(MSlots *ins);
     bool visitElements(MElements *ins);
     bool visitConstantElements(MConstantElements *ins);
+    bool visitConvertElementsToDoubles(MConvertElementsToDoubles *ins);
     bool visitLoadSlot(MLoadSlot *ins);
     bool visitFunctionEnvironment(MFunctionEnvironment *ins);
     bool visitStoreSlot(MStoreSlot *ins);
     bool visitTypeBarrier(MTypeBarrier *ins);
     bool visitMonitorTypes(MMonitorTypes *ins);
     bool visitArrayLength(MArrayLength *ins);
     bool visitTypedArrayLength(MTypedArrayLength *ins);
     bool visitTypedArrayElements(MTypedArrayElements *ins);
--- a/js/src/ion/MCallOptimize.cpp
+++ b/js/src/ion/MCallOptimize.cpp
@@ -200,16 +200,20 @@ IonBuilder::inlineArray(uint32_t argc, b
     MDefinitionVector argv;
     if (!discardCall(argc, argv, current))
         return InliningStatus_Error;
 
     JSObject *templateObject = getNewArrayTemplateObject(initLength);
     if (!templateObject)
         return InliningStatus_Error;
 
+    bool convertDoubles = oracle->arrayResultShouldHaveDoubleConversion(script(), pc);
+    if (convertDoubles)
+        templateObject->setShouldConvertDoubleElements();
+
     MNewArray *ins = new MNewArray(initLength, templateObject, allocating);
     current->add(ins);
     current->push(ins);
 
     if (argc >= 2) {
         // Get the elements vector.
         MElements *elements = MElements::New(ins);
         current->add(elements);
@@ -217,17 +221,24 @@ IonBuilder::inlineArray(uint32_t argc, b
         // Store all values, no need to initialize the length after each as
         // jsop_initelem_array is doing because we do not expect to bailout
         // because the memory is supposed to be allocated by now.
         MConstant *id = NULL;
         for (uint32_t i = 0; i < initLength; i++) {
             id = MConstant::New(Int32Value(i));
             current->add(id);
 
-            MStoreElement *store = MStoreElement::New(elements, id, argv[i + 1],
+            MDefinition *value = argv[i + 1];
+            if (convertDoubles) {
+                MInstruction *valueDouble = MToDouble::New(value);
+                current->add(valueDouble);
+                value = valueDouble;
+            }
+
+            MStoreElement *store = MStoreElement::New(elements, id, value,
                                                       /* needsHoleCheck = */ false);
             current->add(store);
         }
 
         // Update the length.
         MSetInitializedLength *length = MSetInitializedLength::New(elements, id);
         current->add(length);
 
@@ -308,21 +319,34 @@ IonBuilder::inlineArrayPush(uint32_t arg
                                   types::OBJECT_FLAG_LENGTH_OVERFLOW))
     {
         return InliningStatus_NotInlined;
     }
     RootedScript script(cx, script_);
     if (types::ArrayPrototypeHasIndexedProperty(cx, script))
         return InliningStatus_NotInlined;
 
+    types::StackTypeSet::DoubleConversion conversion = thisTypes->convertDoubleElements(cx);
+    if (conversion == types::StackTypeSet::AmbiguousDoubleConversion)
+        return InliningStatus_NotInlined;
+
     MDefinitionVector argv;
     if (!discardCall(argc, argv, current))
         return InliningStatus_Error;
 
-    MArrayPush *ins = MArrayPush::New(argv[0], argv[1]);
+    MDefinition *value = argv[1];
+    if (conversion == types::StackTypeSet::AlwaysConvertToDoubles ||
+        conversion == types::StackTypeSet::MaybeConvertToDoubles)
+    {
+        MInstruction *valueDouble = MToDouble::New(value);
+        current->add(valueDouble);
+        value = valueDouble;
+    }
+
+    MArrayPush *ins = MArrayPush::New(argv[0], value);
     current->add(ins);
     current->push(ins);
 
     if (!resumeAfter(ins))
         return InliningStatus_Error;
     return InliningStatus_Inlined;
 }
 
--- a/js/src/ion/MIR.h
+++ b/js/src/ion/MIR.h
@@ -3496,16 +3496,53 @@ class MConstantElements : public MNullar
         return ins->isConstantElements() && ins->toConstantElements()->value() == value();
     }
 
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 };
 
+// Passes through an object's elements, after ensuring it is entirely doubles.
+class MConvertElementsToDoubles
+  : public MUnaryInstruction
+{
+    MConvertElementsToDoubles(MDefinition *elements)
+      : MUnaryInstruction(elements)
+    {
+        setGuard();
+        setMovable();
+        setResultType(MIRType_Elements);
+    }
+
+  public:
+    INSTRUCTION_HEADER(ConvertElementsToDoubles)
+
+    static MConvertElementsToDoubles *New(MDefinition *elements) {
+        return new MConvertElementsToDoubles(elements);
+    }
+
+    MDefinition *elements() const {
+        return getOperand(0);
+    }
+    bool congruentTo(MDefinition *const &ins) const {
+        return congruentIfOperandsEqual(ins);
+    }
+    AliasSet getAliasSet() const {
+        // This instruction can read and write to the elements' contents.
+        // However, it is alright to hoist this from loops which explicitly
+        // read or write to the elements: such reads and writes will use double
+        // values and can be reordered freely wrt this conversion, except that
+        // definite double loads must follow the conversion. The latter
+        // property is ensured by chaining this instruction with the elements
+        // themselves, in the same manner as MBoundsCheck.
+        return AliasSet::None();
+    }
+};
+
 // Load a dense array's initialized length from an elements vector.
 class MInitializedLength
   : public MUnaryInstruction
 {
     MInitializedLength(MDefinition *elements)
       : MUnaryInstruction(elements)
     {
         setResultType(MIRType_Int32);
@@ -3788,46 +3825,52 @@ class MBoundsCheckLower
 
 // Load a value from a dense array's element vector and does a hole check if the
 // array is not known to be packed.
 class MLoadElement
   : public MBinaryInstruction,
     public SingleObjectPolicy
 {
     bool needsHoleCheck_;
-
-    MLoadElement(MDefinition *elements, MDefinition *index, bool needsHoleCheck)
+    bool loadDoubles_;
+
+    MLoadElement(MDefinition *elements, MDefinition *index, bool needsHoleCheck, bool loadDoubles)
       : MBinaryInstruction(elements, index),
-        needsHoleCheck_(needsHoleCheck)
+        needsHoleCheck_(needsHoleCheck),
+        loadDoubles_(loadDoubles)
     {
         setResultType(MIRType_Value);
         setMovable();
         JS_ASSERT(elements->type() == MIRType_Elements);
         JS_ASSERT(index->type() == MIRType_Int32);
     }
 
   public:
     INSTRUCTION_HEADER(LoadElement)
 
-    static MLoadElement *New(MDefinition *elements, MDefinition *index, bool needsHoleCheck) {
-        return new MLoadElement(elements, index, needsHoleCheck);
+    static MLoadElement *New(MDefinition *elements, MDefinition *index,
+                             bool needsHoleCheck, bool loadDoubles) {
+        return new MLoadElement(elements, index, needsHoleCheck, loadDoubles);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
     MDefinition *elements() const {
         return getOperand(0);
     }
     MDefinition *index() const {
         return getOperand(1);
     }
     bool needsHoleCheck() const {
         return needsHoleCheck_;
     }
+    bool loadDoubles() const {
+        return loadDoubles_;
+    }
     bool fallible() const {
         return needsHoleCheck();
     }
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::Element);
     }
 };
 
--- a/js/src/ion/MOpcodes.h
+++ b/js/src/ion/MOpcodes.h
@@ -83,16 +83,17 @@ namespace ion {
     _(Nop)                                                                  \
     _(RegExp)                                                               \
     _(RegExpTest)                                                           \
     _(Lambda)                                                               \
     _(ImplicitThis)                                                         \
     _(Slots)                                                                \
     _(Elements)                                                             \
     _(ConstantElements)                                                     \
+    _(ConvertElementsToDoubles)                                             \
     _(LoadSlot)                                                             \
     _(StoreSlot)                                                            \
     _(FunctionEnvironment)                                                  \
     _(TypeBarrier)                                                          \
     _(MonitorTypes)                                                         \
     _(GetPropertyCache)                                                     \
     _(GetElementCache)                                                      \
     _(BindNameCache)                                                        \
--- a/js/src/ion/TypeOracle.cpp
+++ b/js/src/ion/TypeOracle.cpp
@@ -355,16 +355,24 @@ TypeInferenceOracle::elementReadIsString
     StackTypeSet *pushed = script->analysis()->pushedTypes(pc, 0);
     if (pushed->getKnownTypeTag() != JSVAL_TYPE_STRING)
         return false;
 
     return true;
 }
 
 bool
+TypeInferenceOracle::elementReadShouldAlwaysLoadDoubles(UnrootedScript script, jsbytecode *pc)
+{
+    StackTypeSet *types = script->analysis()->poppedTypes(pc, 1);
+    types::StackTypeSet::DoubleConversion conversion = types->convertDoubleElements(cx);
+    return conversion == StackTypeSet::AlwaysConvertToDoubles;
+}
+
+bool
 TypeInferenceOracle::elementReadHasExtraIndexedProperty(UnrootedScript script, jsbytecode *pc)
 {
     StackTypeSet *obj = script->analysis()->poppedTypes(pc, 1);
     return types::TypeCanHaveExtraIndexedProperties(cx, obj);
 }
 
 bool
 TypeInferenceOracle::elementReadIsPacked(UnrootedScript script, jsbytecode *pc)
@@ -400,17 +408,20 @@ TypeInferenceOracle::elementWriteIsDense
     StackTypeSet *obj = script->analysis()->poppedTypes(pc, 2);
     StackTypeSet *id = script->analysis()->poppedTypes(pc, 1);
 
     JSValueType idType = id->getKnownTypeTag();
     if (idType != JSVAL_TYPE_INT32 && idType != JSVAL_TYPE_DOUBLE)
         return false;
 
     Class *clasp = obj->getKnownClass();
-    return clasp && clasp->isNative();
+    if (!clasp || !clasp->isNative())
+        return false;
+
+    return obj->convertDoubleElements(cx) != StackTypeSet::AmbiguousDoubleConversion;
 }
 
 bool
 TypeInferenceOracle::elementWriteIsTypedArray(RawScript script, jsbytecode *pc, int *arrayType)
 {
     // Check whether the object is a dense array and index is int32 or double.
     StackTypeSet *obj = script->analysis()->poppedTypes(pc, 2);
     StackTypeSet *id = script->analysis()->poppedTypes(pc, 1);
@@ -422,16 +433,25 @@ TypeInferenceOracle::elementWriteIsTyped
     *arrayType = obj->getTypedArrayType();
     if (*arrayType == TypedArray::TYPE_MAX)
         return false;
 
     return true;
 }
 
 bool
+TypeInferenceOracle::elementWriteNeedsDoubleConversion(UnrootedScript script, jsbytecode *pc)
+{
+    StackTypeSet *types = script->analysis()->poppedTypes(pc, 2);
+    types::StackTypeSet::DoubleConversion conversion = types->convertDoubleElements(cx);
+    return conversion == StackTypeSet::AlwaysConvertToDoubles ||
+           conversion == StackTypeSet::MaybeConvertToDoubles;
+}
+
+bool
 TypeInferenceOracle::elementWriteHasExtraIndexedProperty(UnrootedScript script, jsbytecode *pc)
 {
     StackTypeSet *obj = script->analysis()->poppedTypes(pc, 2);
 
     if (obj->hasObjectFlags(cx, types::OBJECT_FLAG_LENGTH_OVERFLOW))
         return true;
 
     return types::TypeCanHaveExtraIndexedProperties(cx, obj);
@@ -479,16 +499,24 @@ TypeInferenceOracle::elementWrite(Unroot
                 return MIRType_None;
         }
     }
 
     return elementType;
 }
 
 bool
+TypeInferenceOracle::arrayResultShouldHaveDoubleConversion(UnrootedScript script, jsbytecode *pc)
+{
+    types::StackTypeSet::DoubleConversion conversion =
+        script->analysis()->pushedTypes(pc, 0)->convertDoubleElements(cx);
+    return conversion == types::StackTypeSet::AlwaysConvertToDoubles;
+}
+
+bool
 TypeInferenceOracle::canInlineCalls()
 {
     return script()->analysis()->hasFunctionCalls();
 }
 
 bool
 TypeInferenceOracle::propertyWriteCanSpecialize(UnrootedScript script, jsbytecode *pc)
 {
--- a/js/src/ion/TypeOracle.h
+++ b/js/src/ion/TypeOracle.h
@@ -85,16 +85,19 @@ class TypeOracle
         return false;
     }
     virtual bool elementReadIsTypedArray(HandleScript script, jsbytecode *pc, int *arrayType) {
         return false;
     }
     virtual bool elementReadIsString(UnrootedScript script, jsbytecode *pc) {
         return false;
     }
+    virtual bool elementReadShouldAlwaysLoadDoubles(UnrootedScript script, jsbytecode *pc) {
+        return false;
+    }
     virtual bool elementReadHasExtraIndexedProperty(UnrootedScript, jsbytecode *pc) {
         return false;
     }
     virtual bool elementReadIsPacked(UnrootedScript script, jsbytecode *pc) {
         return false;
     }
     virtual void elementReadGeneric(UnrootedScript script, jsbytecode *pc, bool *cacheable, bool *monitorResult) {
         *cacheable = false;
@@ -104,22 +107,28 @@ class TypeOracle
         return true;
     }
     virtual bool elementWriteIsDenseNative(HandleScript script, jsbytecode *pc) {
         return false;
     }
     virtual bool elementWriteIsTypedArray(RawScript script, jsbytecode *pc, int *arrayType) {
         return false;
     }
+    virtual bool elementWriteNeedsDoubleConversion(UnrootedScript script, jsbytecode *pc) {
+        return false;
+    }
     virtual bool elementWriteHasExtraIndexedProperty(UnrootedScript script, jsbytecode *pc) {
         return false;
     }
     virtual bool elementWriteIsPacked(UnrootedScript script, jsbytecode *pc) {
         return false;
     }
+    virtual bool arrayResultShouldHaveDoubleConversion(UnrootedScript script, jsbytecode *pc) {
+        return false;
+    }
     virtual bool propertyWriteCanSpecialize(UnrootedScript script, jsbytecode *pc) {
         return true;
     }
     virtual bool propertyWriteNeedsBarrier(UnrootedScript script, jsbytecode *pc, RawId id) {
         return true;
     }
     virtual bool elementWriteNeedsBarrier(UnrootedScript script, jsbytecode *pc) {
         return true;
@@ -232,23 +241,26 @@ class TypeInferenceOracle : public TypeO
     types::StackTypeSet *getCallTarget(UnrootedScript caller, uint32_t argc, jsbytecode *pc);
     types::StackTypeSet *getCallArg(UnrootedScript caller, uint32_t argc, uint32_t arg, jsbytecode *pc);
     types::StackTypeSet *getCallReturn(UnrootedScript caller, jsbytecode *pc);
     bool inObjectIsDenseNativeWithoutExtraIndexedProperties(HandleScript script, jsbytecode *pc);
     bool inArrayIsPacked(UnrootedScript script, jsbytecode *pc);
     bool elementReadIsDenseNative(RawScript script, jsbytecode *pc);
     bool elementReadIsTypedArray(HandleScript script, jsbytecode *pc, int *atype);
     bool elementReadIsString(UnrootedScript script, jsbytecode *pc);
+    bool elementReadShouldAlwaysLoadDoubles(UnrootedScript script, jsbytecode *pc);
     bool elementReadHasExtraIndexedProperty(UnrootedScript, jsbytecode *pc);
     bool elementReadIsPacked(UnrootedScript script, jsbytecode *pc);
     void elementReadGeneric(UnrootedScript script, jsbytecode *pc, bool *cacheable, bool *monitorResult);
     bool elementWriteIsDenseNative(HandleScript script, jsbytecode *pc);
     bool elementWriteIsTypedArray(RawScript script, jsbytecode *pc, int *arrayType);
+    bool elementWriteNeedsDoubleConversion(UnrootedScript script, jsbytecode *pc);
     bool elementWriteHasExtraIndexedProperty(UnrootedScript script, jsbytecode *pc);
     bool elementWriteIsPacked(UnrootedScript script, jsbytecode *pc);
+    bool arrayResultShouldHaveDoubleConversion(UnrootedScript script, jsbytecode *pc);
     bool setElementHasWrittenHoles(UnrootedScript script, jsbytecode *pc);
     bool propertyWriteCanSpecialize(UnrootedScript script, jsbytecode *pc);
     bool propertyWriteNeedsBarrier(UnrootedScript script, jsbytecode *pc, RawId id);
     bool elementWriteNeedsBarrier(UnrootedScript script, jsbytecode *pc);
     MIRType elementWrite(UnrootedScript script, jsbytecode *pc);
     bool canInlineCalls();
     bool canInlineCall(HandleScript caller, jsbytecode *pc);
     bool canEnterInlinedFunction(HandleScript caller, jsbytecode *pc, JSFunction *callee);
--- a/js/src/ion/VMFunctions.h
+++ b/js/src/ion/VMFunctions.h
@@ -460,13 +460,14 @@ JSObject *NewStringObject(JSContext *cx,
 bool SPSEnter(JSContext *cx, HandleScript script);
 bool SPSExit(JSContext *cx, HandleScript script);
 
 bool OperatorIn(JSContext *cx, HandleValue key, HandleObject obj, JSBool *out);
 
 bool GetIntrinsicValue(JSContext *cx, HandlePropertyName name, MutableHandleValue rval);
 
 bool CreateThis(JSContext *cx, HandleObject callee, MutableHandleValue rval);
+
 } // namespace ion
 } // namespace js
 
 #endif // jsion_vm_functions_h_
 
--- a/js/src/ion/arm/CodeGenerator-arm.cpp
+++ b/js/src/ion/arm/CodeGenerator-arm.cpp
@@ -1451,21 +1451,29 @@ CodeGeneratorARM::visitStoreSlotT(LStore
     return true;
 }
 
 bool
 CodeGeneratorARM::visitLoadElementT(LLoadElementT *load)
 {
     Register base = ToRegister(load->elements());
     if (load->mir()->type() == MIRType_Double) {
+        FloatRegister fpreg = ToFloatRegister(load->output());
         if (load->index()->isConstant()) {
             Address source(base, ToInt32(load->index()) * sizeof(Value));
-            masm.loadInt32OrDouble(source, ToFloatRegister(load->output()));
+            if (load->mir()->loadDoubles())
+                masm.loadDouble(source, fpreg);
+            else
+                masm.loadInt32OrDouble(source, fpreg);
         } else {
-            masm.loadInt32OrDouble(base, ToRegister(load->index()), ToFloatRegister(load->output()));
+            Register index = ToRegister(load->index());
+            if (load->mir()->loadDoubles())
+                masm.loadDouble(BaseIndex(base, index, TimesEight), fpreg);
+            else
+                masm.loadInt32OrDouble(base, index, fpreg);
         }
     } else {
         if (load->index()->isConstant()) {
             Address source(base, ToInt32(load->index()) * sizeof(Value));
             masm.load32(source, ToRegister(load->output()));
         } else {
             masm.ma_ldr(DTRAddr(base, DtrRegImmShift(ToRegister(load->index()), LSL, 3)),
                         ToRegister(load->output()));
--- a/js/src/ion/x64/Assembler-x64.h
+++ b/js/src/ion/x64/Assembler-x64.h
@@ -184,16 +184,26 @@ class Operand
         index_(index.code())
     { }
     Operand(Register reg, int32_t disp)
       : kind_(REG_DISP),
         base_(reg.code()),
         disp_(disp)
     { }
 
+    Address toAddress() {
+        JS_ASSERT(kind() == REG_DISP);
+        return Address(Register::FromCode(base()), disp());
+    }
+
+    BaseIndex toBaseIndex() {
+        JS_ASSERT(kind() == SCALE);
+        return BaseIndex(Register::FromCode(base()), Register::FromCode(index()), scale(), disp());
+    }
+
     Kind kind() const {
         return kind_;
     }
     Register::Code reg() const {
         JS_ASSERT(kind() == REG);
         return (Registers::Code)base_;
     }
     Registers::Code base() const {
--- a/js/src/ion/x64/CodeGenerator-x64.cpp
+++ b/js/src/ion/x64/CodeGenerator-x64.cpp
@@ -246,17 +246,26 @@ CodeGeneratorX64::visitStoreSlotT(LStore
     storeUnboxedValue(value, valueType, Operand(base, offset), slotType);
     return true;
 }
 
 bool
 CodeGeneratorX64::visitLoadElementT(LLoadElementT *load)
 {
     Operand source = createArrayElementOperand(ToRegister(load->elements()), load->index());
-    loadUnboxedValue(source, load->mir()->type(), load->output());
+
+    if (load->mir()->loadDoubles()) {
+        FloatRegister fpreg = ToFloatRegister(load->output());
+        if (source.kind() == Operand::REG_DISP)
+            masm.loadDouble(source.toAddress(), fpreg);
+        else
+            masm.loadDouble(source.toBaseIndex(), fpreg);
+    } else {
+        loadUnboxedValue(source, load->mir()->type(), load->output());
+    }
 
     JS_ASSERT(!load->mir()->needsHoleCheck());
     return true;
 }
 
 
 void
 CodeGeneratorX64::storeElementTyped(const LAllocation *value, MIRType valueType, MIRType elementType,
--- a/js/src/ion/x86/Assembler-x86.h
+++ b/js/src/ion/x86/Assembler-x86.h
@@ -139,16 +139,26 @@ class Operand
       : kind_(ADDRESS),
         base_(reinterpret_cast<int32_t>(address.addr))
     { }
     explicit Operand(const void *address)
       : kind_(ADDRESS),
         base_(reinterpret_cast<int32_t>(address))
     { }
 
+    Address toAddress() {
+        JS_ASSERT(kind() == REG_DISP);
+        return Address(Register::FromCode(base()), disp());
+    }
+
+    BaseIndex toBaseIndex() {
+        JS_ASSERT(kind() == SCALE);
+        return BaseIndex(Register::FromCode(base()), Register::FromCode(index()), scale(), disp());
+    }
+
     Kind kind() const {
         return kind_;
     }
     Registers::Code reg() const {
         JS_ASSERT(kind() == REG);
         return (Registers::Code)base_;
     }
     Registers::Code base() const {
--- a/js/src/ion/x86/CodeGenerator-x86.cpp
+++ b/js/src/ion/x86/CodeGenerator-x86.cpp
@@ -235,20 +235,29 @@ CodeGeneratorX86::visitLoadElementT(LLoa
     Operand source = createArrayElementOperand(ToRegister(load->elements()), load->index());
 
     if (load->mir()->needsHoleCheck()) {
         Assembler::Condition cond = masm.testMagic(Assembler::Equal, source);
         if (!bailoutIf(cond, load->snapshot()))
             return false;
     }
 
-    if (load->mir()->type() == MIRType_Double)
-        masm.loadInt32OrDouble(source, ToFloatRegister(load->output()));
-    else
+    if (load->mir()->type() == MIRType_Double) {
+        FloatRegister fpreg = ToFloatRegister(load->output());
+        if (load->mir()->loadDoubles()) {
+            if (source.kind() == Operand::REG_DISP)
+                masm.loadDouble(source.toAddress(), fpreg);
+            else
+                masm.loadDouble(source.toBaseIndex(), fpreg);
+        } else {
+            masm.loadInt32OrDouble(source, fpreg);
+        }
+    } else {
         masm.movl(masm.ToPayload(source), ToRegister(load->output()));
+    }
 
     return true;
 }
 
 void
 CodeGeneratorX86::storeElementTyped(const LAllocation *value, MIRType valueType, MIRType elementType,
                                     const Register &elements, const LAllocation *index)
 {
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/doubleArrays.js
@@ -0,0 +1,51 @@
+
+function testPushConvert() {
+  var x = [];
+  for (var i = 0; i < 10; i++)
+    x.push(i + .5);
+  for (var i = 0; i < 5; i++)
+    x.push(i);
+  var res = 0;
+  for (var i = 0; i < x.length; i++)
+    res += x[i];
+  assertEq(res, 60);
+}
+testPushConvert();
+
+function testArrayInitializer() {
+  var x = [.5,1.5,2.5,3];
+  var res = 0;
+  for (var i = 0; i < x.length; i++)
+    res += x[i];
+  assertEq(res, 7.5);
+}
+for (var i = 0; i < 5; i++)
+  testArrayInitializer();
+
+function testArrayConstructor() {
+  var x = Array(.5,1.5,2.5,3);
+  var res = 0;
+  for (var i = 0; i < x.length; i++)
+    res += x[i];
+  assertEq(res, 7.5);
+}
+for (var i = 0; i < 5; i++)
+  testArrayConstructor();
+
+function addInt(a) {
+  // inhibit ion
+  try {
+    a[0] = 10;
+  } catch (e) {}
+}
+
+function testBaseline() {
+  var x = Array(.5,1.5,2.5,3);
+  addInt(x);
+  var res = 0;
+  for (var i = 0; i < x.length; i++)
+    res += x[i];
+  assertEq(res, 17);
+}
+for (var i = 0; i < 5; i++)
+  testBaseline();
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -1991,16 +1991,80 @@ StackTypeSet::filtersType(const StackTyp
             if (type != filteredType && !hasType(type))
                 return false;
         }
     }
 
     return true;
 }
 
+StackTypeSet::DoubleConversion
+StackTypeSet::convertDoubleElements(JSContext *cx)
+{
+    if (unknownObject() || !getObjectCount())
+        return AmbiguousDoubleConversion;
+
+    bool alwaysConvert = true;
+    bool maybeConvert = false;
+    bool dontConvert = false;
+
+    for (unsigned i = 0; i < getObjectCount(); i++) {
+        TypeObject *type = getTypeObject(i);
+        if (!type) {
+            if (JSObject *obj = getSingleObject(i)) {
+                type = obj->getType(cx);
+                if (!type)
+                    return AmbiguousDoubleConversion;
+            } else {
+                continue;
+            }
+        }
+
+        if (type->unknownProperties()) {
+            alwaysConvert = false;
+            continue;
+        }
+
+        HeapTypeSet *types = type->getProperty(cx, JSID_VOID, false);
+        if (!types)
+            return AmbiguousDoubleConversion;
+
+        // We can't convert to double elements for objects which do not have
+        // double in their element types (as the conversion may render the type
+        // information incorrect), nor for non-array objects (as their elements
+        // may point to emptyObjectElements, which cannot be converted).
+        if (!types->hasType(Type::DoubleType()) || type->clasp != &ArrayClass) {
+            dontConvert = true;
+            alwaysConvert = false;
+            continue;
+        }
+
+        // Only bother with converting known packed arrays whose possible
+        // element types are int or double. Other arrays require type tests
+        // when elements are accessed regardless of the conversion.
+        if (types->getKnownTypeTag(cx) == JSVAL_TYPE_DOUBLE &&
+            !HeapTypeSet::HasObjectFlags(cx, type, OBJECT_FLAG_NON_PACKED))
+        {
+            maybeConvert = true;
+        } else {
+            alwaysConvert = false;
+        }
+    }
+
+    JS_ASSERT_IF(alwaysConvert, maybeConvert);
+
+    if (maybeConvert && dontConvert)
+        return AmbiguousDoubleConversion;
+    if (alwaysConvert)
+        return AlwaysConvertToDoubles;
+    if (maybeConvert)
+        return MaybeConvertToDoubles;
+    return DontConvertToDoubles;
+}
+
 bool
 HeapTypeSet::knownSubset(JSContext *cx, TypeSet *other)
 {
     JS_ASSERT(!other->constraintsPurged());
 
     if (!isSubset(other))
         return false;
 
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -632,16 +632,36 @@ class StackTypeSet : public TypeSet
 
     bool knownPrimitiveOrObject() {
         TypeFlags flags = TYPE_FLAG_PRIMITIVE | TYPE_FLAG_ANYOBJECT;
         if (baseFlags() & (~flags & TYPE_FLAG_BASE_MASK))
             return false;
 
         return true;
     }
+
+    enum DoubleConversion {
+        /* All types in the set should use eager double conversion. */
+        AlwaysConvertToDoubles,
+
+        /* Some types in the set should use eager double conversion. */
+        MaybeConvertToDoubles,
+
+        /* No types should use eager double conversion. */
+        DontConvertToDoubles,
+
+        /* Some types should use eager double conversion, others cannot. */
+        AmbiguousDoubleConversion
+    };
+
+    /*
+     * Whether known double optimizations are possible for element accesses on
+     * objects in this type set.
+     */
+    DoubleConversion convertDoubleElements(JSContext *cx);
 };
 
 /*
  * Type set for a property of a TypeObject, or for the return value or property
  * read inputs of a script. In contrast with stack type sets, constraints on
  * these sets are not cleared during analysis purges, and are not implicitly
  * frozen during compilation.
  */
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3180,17 +3180,17 @@ js::DefineNativeProperty(JSContext *cx, 
             attrs == JSPROP_ENUMERATE &&
             (!obj->isIndexed() || !obj->nativeContains(cx, id)))
         {
             uint32_t index = JSID_TO_INT(id);
             JSObject::EnsureDenseResult result = obj->ensureDenseElements(cx, index, 1);
             if (result == JSObject::ED_FAILED)
                 return false;
             if (result == JSObject::ED_OK) {
-                obj->setDenseElement(index, value);
+                obj->setDenseElementMaybeConvertDouble(index, value);
                 if (!CallAddPropertyHookDense(cx, clasp, obj, index, value)) {
                     JSObject::setDenseElementHole(cx, obj, index);
                     return false;
                 }
                 return true;
             }
         }
 
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -584,27 +584,30 @@ class JSObject : public js::ObjectImpl
     void shrinkElements(JSContext *cx, unsigned cap);
     inline void setDynamicElements(js::ObjectElements *header);
 
     inline uint32_t getDenseCapacity();
     inline void setDenseInitializedLength(uint32_t length);
     inline void ensureDenseInitializedLength(JSContext *cx, unsigned index, unsigned extra);
     inline void setDenseElement(unsigned idx, const js::Value &val);
     inline void initDenseElement(unsigned idx, const js::Value &val);
+    inline void setDenseElementMaybeConvertDouble(unsigned idx, const js::Value &val);
     static inline void setDenseElementWithType(JSContext *cx, js::HandleObject obj,
                                                unsigned idx, const js::Value &val);
     static inline void initDenseElementWithType(JSContext *cx, js::HandleObject obj,
                                                 unsigned idx, const js::Value &val);
     static inline void setDenseElementHole(JSContext *cx, js::HandleObject obj, unsigned idx);
     static inline void removeDenseElementForSparseIndex(JSContext *cx, js::HandleObject obj,
                                                         unsigned idx);
     inline void copyDenseElements(unsigned dstStart, const js::Value *src, unsigned count);
     inline void initDenseElements(unsigned dstStart, const js::Value *src, unsigned count);
     inline void moveDenseElements(unsigned dstStart, unsigned srcStart, unsigned count);
     inline void moveDenseElementsUnbarriered(unsigned dstStart, unsigned srcStart, unsigned count);
+    inline bool shouldConvertDoubleElements();
+    inline void setShouldConvertDoubleElements();
 
     /* Packed information for this object's elements. */
     inline void markDenseElementsNotPacked(JSContext *cx);
 
     /*
      * ensureDenseElements ensures that the object can hold at least
      * index + extra elements. It returns ED_OK on success, ED_FAILED on
      * failure to grow the array, ED_SPARSE when the object is too sparse to
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -383,20 +383,17 @@ JSObject::getArrayLength() const
 }
 
 /* static */ inline void
 JSObject::setArrayLength(JSContext *cx, js::HandleObject obj, uint32_t length)
 {
     JS_ASSERT(obj->isArray());
 
     if (length > INT32_MAX) {
-        /*
-         * Mark the type of this object as possibly not a dense array, per the
-         * requirements of OBJECT_FLAG_NON_DENSE_ARRAY.
-         */
+        /* Track objects with overflowing lengths in type information. */
         js::types::MarkTypeObjectFlags(cx, obj,
                                        js::types::OBJECT_FLAG_LENGTH_OVERFLOW);
         jsid lengthId = js::NameToId(cx->names().length);
         js::types::AddTypePropertyId(cx, obj, lengthId,
                                      js::types::Type::DoubleType());
     }
 
     obj->getElementsHeader()->length = length;
@@ -423,16 +420,30 @@ JSObject::setDenseInitializedLength(uint
 inline uint32_t
 JSObject::getDenseCapacity()
 {
     JS_ASSERT(isNative());
     return getElementsHeader()->capacity;
 }
 
 inline bool
+JSObject::shouldConvertDoubleElements()
+{
+    JS_ASSERT(isNative());
+    return getElementsHeader()->convertDoubleElements;
+}
+
+inline void
+JSObject::setShouldConvertDoubleElements()
+{
+    JS_ASSERT(isArray() && !hasEmptyElements());
+    getElementsHeader()->convertDoubleElements = 1;
+}
+
+inline bool
 JSObject::ensureElements(JSContext *cx, uint32_t capacity)
 {
     if (capacity > getDenseCapacity())
         return growElements(cx, capacity);
     return true;
 }
 
 inline void
@@ -446,34 +457,44 @@ JSObject::setDynamicElements(js::ObjectE
 inline void
 JSObject::setDenseElement(unsigned idx, const js::Value &val)
 {
     JS_ASSERT(isNative() && idx < getDenseInitializedLength());
     elements[idx].set(this, js::HeapSlot::Element, idx, val);
 }
 
 inline void
+JSObject::setDenseElementMaybeConvertDouble(unsigned idx, const js::Value &val)
+{
+    if (val.isInt32() && shouldConvertDoubleElements())
+        setDenseElement(idx, js::DoubleValue(val.toInt32()));
+    else
+        setDenseElement(idx, val);
+}
+
+inline void
 JSObject::initDenseElement(unsigned idx, const js::Value &val)
 {
     JS_ASSERT(isNative() && idx < getDenseInitializedLength());
     elements[idx].init(this, js::HeapSlot::Element, idx, val);
 }
 
 /* static */ inline void
 JSObject::setDenseElementWithType(JSContext *cx, js::HandleObject obj, unsigned idx,
                                   const js::Value &val)
 {
     js::types::AddTypePropertyId(cx, obj, JSID_VOID, val);
-    obj->setDenseElement(idx, val);
+    obj->setDenseElementMaybeConvertDouble(idx, val);
 }
 
 /* static */ inline void
 JSObject::initDenseElementWithType(JSContext *cx, js::HandleObject obj, unsigned idx,
                                    const js::Value &val)
 {
+    JS_ASSERT(!obj->shouldConvertDoubleElements());
     js::types::AddTypePropertyId(cx, obj, JSID_VOID, val);
     obj->initDenseElement(idx, val);
 }
 
 /* static */ inline void
 JSObject::setDenseElementHole(JSContext *cx, js::HandleObject obj, unsigned idx)
 {
     js::types::MarkTypeObjectFlags(cx, obj, js::types::OBJECT_FLAG_NON_PACKED);
--- a/js/src/jstypedarrayinlines.h
+++ b/js/src/jstypedarrayinlines.h
@@ -23,17 +23,17 @@ js::ArrayBufferObject::setElementsHeader
     /*
      * Note that |bytes| may not be a multiple of |sizeof(Value)|, so
      * |capacity * sizeof(Value)| may underestimate the size by up to
      * |sizeof(Value) - 1| bytes.
      */
     header->capacity = bytes / sizeof(js::Value);
     header->initializedLength = bytes;
     header->length = 0;
-    header->unused = 0;
+    header->convertDoubleElements = 0;
 }
 
 inline uint32_t
 js::ArrayBufferObject::byteLength() const
 {
     JS_ASSERT(isArrayBuffer());
     return getElementsHeader()->initializedLength;
 }
--- a/js/src/methodjit/BaseAssembler.h
+++ b/js/src/methodjit/BaseAssembler.h
@@ -1407,16 +1407,18 @@ static const JSC::MacroAssembler::Regist
         if (templateObject->isArray()) {
             /* Fill in the elements header. */
             store32(Imm32(templateObject->getDenseCapacity()),
                     Address(result, elementsOffset + ObjectElements::offsetOfCapacity()));
             store32(Imm32(templateObject->getDenseInitializedLength()),
                     Address(result, elementsOffset + ObjectElements::offsetOfInitializedLength()));
             store32(Imm32(templateObject->getArrayLength()),
                     Address(result, elementsOffset + ObjectElements::offsetOfLength()));
+            store32(Imm32(templateObject->shouldConvertDoubleElements() ? 1 : 0),
+                    Address(result, elementsOffset + ObjectElements::offsetOfConvertDoubleElements()));
         } else {
             /*
              * Fixed slots of non-array objects are required to be initialized;
              * Use the values currently in the template object.
              */
             for (unsigned i = 0; i < templateObject->slotSpan(); i++) {
                 storeValue(templateObject->getFixedSlot(i),
                            Address(result, JSObject::getFixedSlotOffset(i)));
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -6975,20 +6975,25 @@ mjit::Compiler::jsop_newinit()
         INLINE_STUBCALL(stub, REJOIN_FALLTHROUGH);
         frame.pushSynced(knownPushedType(0));
 
         frame.extra(frame.peek(-1)).initObject = baseobj;
         return true;
     }
 
     JSObject *templateObject;
-    if (isArray)
+    if (isArray) {
         templateObject = NewDenseUnallocatedArray(cx, count);
-    else
+        types::StackTypeSet::DoubleConversion conversion =
+            script->analysis()->pushedTypes(PC, 0)->convertDoubleElements(cx);
+        if (conversion == types::StackTypeSet::AlwaysConvertToDoubles)
+            templateObject->setShouldConvertDoubleElements();
+    } else {
         templateObject = CopyInitializerObject(cx, baseobj);
+    }
     if (!templateObject)
         return false;
     templateObject->setType(type);
 
     RegisterID result = frame.allocReg();
     Jump emptyFreeList = getNewObject(cx, result, templateObject);
 
     stubcc.linkExit(emptyFreeList, Uses(0));
--- a/js/src/methodjit/Compiler.h
+++ b/js/src/methodjit/Compiler.h
@@ -711,17 +711,17 @@ private:
     bool booleanJumpScript(JSOp op, jsbytecode *target);
     bool jsop_ifneq(JSOp op, jsbytecode *target);
     bool jsop_andor(JSOp op, jsbytecode *target);
     bool jsop_newinit();
     bool jsop_regexp();
     void jsop_initmethod();
     void jsop_initprop();
     void jsop_initelem_array();
-    void jsop_setelem_dense();
+    void jsop_setelem_dense(types::StackTypeSet::DoubleConversion conversion);
 #ifdef JS_METHODJIT_TYPED_ARRAY
     void jsop_setelem_typed(int atype);
     void convertForTypedArray(int atype, ValueRemat *vr, bool *allocated);
 #endif
     bool jsop_setelem(bool popGuaranteed);
     bool jsop_getelem();
     void jsop_getelem_dense(bool isPacked);
     void jsop_getelem_args();
@@ -775,17 +775,18 @@ private:
     CompileStatus compileMathAbsInt(FrameEntry *arg);
     CompileStatus compileMathAbsDouble(FrameEntry *arg);
     CompileStatus compileMathSqrt(FrameEntry *arg);
     CompileStatus compileMathMinMaxDouble(FrameEntry *arg1, FrameEntry *arg2,
                                           Assembler::DoubleCondition cond);
     CompileStatus compileMathMinMaxInt(FrameEntry *arg1, FrameEntry *arg2,
                                        Assembler::Condition cond);
     CompileStatus compileMathPowSimple(FrameEntry *arg1, FrameEntry *arg2);
-    CompileStatus compileArrayPush(FrameEntry *thisv, FrameEntry *arg);
+    CompileStatus compileArrayPush(FrameEntry *thisv, FrameEntry *arg,
+                                   types::StackTypeSet::DoubleConversion conversion);
     CompileStatus compileArrayConcat(types::TypeSet *thisTypes, types::TypeSet *argTypes,
                                      FrameEntry *thisValue, FrameEntry *argValue);
     CompileStatus compileArrayPopShift(FrameEntry *thisv, bool isPacked, bool isArrayPop);
     CompileStatus compileArrayWithLength(uint32_t argc);
     CompileStatus compileArrayWithArgs(uint32_t argc);
 
     enum RoundingMode { Floor, Round };
     CompileStatus compileRound(FrameEntry *arg, RoundingMode mode);
--- a/js/src/methodjit/FastBuiltins.cpp
+++ b/js/src/methodjit/FastBuiltins.cpp
@@ -409,24 +409,31 @@ mjit::Compiler::compileStringFromCode(Fr
     frame.popn(3);
     frame.pushTypedPayload(JSVAL_TYPE_STRING, argReg);
 
     stubcc.rejoin(Changes(1));
     return Compile_Okay;
 }
 
 CompileStatus
-mjit::Compiler::compileArrayPush(FrameEntry *thisValue, FrameEntry *arg)
+mjit::Compiler::compileArrayPush(FrameEntry *thisValue, FrameEntry *arg,
+                                 types::StackTypeSet::DoubleConversion conversion)
 {
     /* This behaves like an assignment this[this.length] = arg; */
 
     /* Filter out silly cases. */
     if (frame.haveSameBacking(thisValue, arg) || thisValue->isConstant())
         return Compile_InlineAbort;
 
+    if (conversion == types::StackTypeSet::AlwaysConvertToDoubles ||
+        conversion == types::StackTypeSet::MaybeConvertToDoubles)
+    {
+        frame.ensureDouble(arg);
+    }
+
     /* Allocate registers. */
     ValueRemat vr;
     frame.pinEntry(arg, vr, /* breakDouble = */ false);
 
     RegisterID objReg = frame.tempRegForData(thisValue);
     frame.pinReg(objReg);
 
     RegisterID slotsReg = frame.allocReg();
@@ -760,16 +767,26 @@ mjit::Compiler::compileArrayWithArgs(uin
 
     JSObject *templateObject = NewDenseUnallocatedArray(cx, argc, type->proto);
     if (!templateObject)
         return Compile_Error;
     templateObject->setType(type);
 
     JS_ASSERT(templateObject->getDenseCapacity() >= argc);
 
+    types::StackTypeSet::DoubleConversion conversion =
+        script->analysis()->pushedTypes(PC, 0)->convertDoubleElements(cx);
+    if (conversion == types::StackTypeSet::AlwaysConvertToDoubles) {
+        templateObject->setShouldConvertDoubleElements();
+        for (unsigned i = 0; i < argc; i++) {
+            FrameEntry *arg = frame.peek(-(int32_t)argc + i);
+            frame.ensureDouble(arg);
+        }
+    }
+
     RegisterID result = frame.allocReg();
     Jump emptyFreeList = getNewObject(cx, result, templateObject);
     stubcc.linkExit(emptyFreeList, Uses(0));
 
     int offset = JSObject::offsetOfFixedElements();
     masm.store32(Imm32(argc),
                  Address(result, offset + ObjectElements::offsetOfInitializedLength()));
 
@@ -999,17 +1016,19 @@ mjit::Compiler::inlineNativeFunction(uin
             /*
              * Constraints propagating properties into the 'this' object are
              * generated by TypeConstraintCall during inference.
              */
             if (thisTypes->getKnownClass() == &ArrayClass &&
                 !thisTypes->hasObjectFlags(cx, types::OBJECT_FLAG_SPARSE_INDEXES |
                                            types::OBJECT_FLAG_LENGTH_OVERFLOW) &&
                 !types::ArrayPrototypeHasIndexedProperty(cx, outerScript)) {
-                return compileArrayPush(thisValue, arg);
+                types::StackTypeSet::DoubleConversion conversion = thisTypes->convertDoubleElements(cx);
+                if (conversion != types::StackTypeSet::AmbiguousDoubleConversion)
+                    return compileArrayPush(thisValue, arg, conversion);
             }
         }
         if (native == js::array_concat && argType == JSVAL_TYPE_OBJECT &&
             thisType == JSVAL_TYPE_OBJECT && type == JSVAL_TYPE_OBJECT &&
             thisTypes->getKnownClass() == &ArrayClass &&
             !thisTypes->hasObjectFlags(cx, types::OBJECT_FLAG_SPARSE_INDEXES |
                                        types::OBJECT_FLAG_LENGTH_OVERFLOW) &&
             argTypes->getKnownClass() == &ArrayClass &&
--- a/js/src/methodjit/FastOps.cpp
+++ b/js/src/methodjit/FastOps.cpp
@@ -853,24 +853,32 @@ IsCacheableSetElem(FrameEntry *obj, Fram
     // obj[id] = obj is allowed.
     if (obj->hasSameBacking(id))
         return false;
 
     return true;
 }
 
 void
-mjit::Compiler::jsop_setelem_dense()
+mjit::Compiler::jsop_setelem_dense(types::StackTypeSet::DoubleConversion conversion)
 {
     FrameEntry *obj = frame.peek(-3);
     FrameEntry *id = frame.peek(-2);
     FrameEntry *value = frame.peek(-1);
 
     frame.forgetMismatchedObject(obj);
 
+    // If the array being written to might need integer elements converted to
+    // doubles, make the conversion before writing.
+    if (conversion == types::StackTypeSet::AlwaysConvertToDoubles ||
+        conversion == types::StackTypeSet::MaybeConvertToDoubles)
+    {
+        frame.ensureDouble(value);
+    }
+
     // We might not know whether this is an object, but if it is an object we
     // know it is a dense array.
     if (!obj->isTypeKnown()) {
         Jump guard = frame.testObject(Assembler::NotEqual, obj);
         stubcc.linkExit(guard, Uses(3));
     }
 
     if (id->isType(JSVAL_TYPE_DOUBLE))
@@ -1335,23 +1343,28 @@ mjit::Compiler::jsop_setelem(bool popGua
         return true;
     }
 
     // If the object is definitely a dense array or a typed array we can generate
     // code directly without using an inline cache.
     if (cx->typeInferenceEnabled()) {
         types::StackTypeSet *types = analysis->poppedTypes(PC, 2);
 
+        types::StackTypeSet::DoubleConversion conversion = types->convertDoubleElements(cx);
         if (types->getKnownClass() == &ArrayClass &&
             !types->hasObjectFlags(cx, types::OBJECT_FLAG_SPARSE_INDEXES |
                                    types::OBJECT_FLAG_LENGTH_OVERFLOW) &&
-            !types::ArrayPrototypeHasIndexedProperty(cx, outerScript))
+            !types::ArrayPrototypeHasIndexedProperty(cx, outerScript) &&
+            conversion != types::StackTypeSet::AmbiguousDoubleConversion &&
+            (conversion == types::StackTypeSet::DontConvertToDoubles ||
+             value->isType(JSVAL_TYPE_DOUBLE) ||
+             popGuaranteed))
         {
             // Inline dense array path.
-            jsop_setelem_dense();
+            jsop_setelem_dense(conversion);
             return true;
         }
 
 #ifdef JS_METHODJIT_TYPED_ARRAY
         int atype = types->getTypedArrayType();
         if ((value->mightBeType(JSVAL_TYPE_INT32) || value->mightBeType(JSVAL_TYPE_DOUBLE)) &&
             atype != TypedArray::TYPE_MAX)
         {
@@ -2510,16 +2523,23 @@ mjit::Compiler::jsop_initprop()
 }
 
 void
 mjit::Compiler::jsop_initelem_array()
 {
     FrameEntry *obj = frame.peek(-2);
     FrameEntry *fe = frame.peek(-1);
 
+    if (cx->typeInferenceEnabled()) {
+        types::StackTypeSet::DoubleConversion conversion =
+            script_->analysis()->poppedTypes(PC, 1)->convertDoubleElements(cx);
+        if (conversion == types::StackTypeSet::AlwaysConvertToDoubles)
+            frame.ensureDouble(fe);
+    }
+
     uint32_t index = GET_UINT24(PC);
 
     RegisterID objReg = frame.copyDataIntoReg(obj);
     masm.loadPtr(Address(objReg, JSObject::offsetOfElements()), objReg);
 
     /* Update the initialized length. */
     masm.store32(Imm32(index + 1), Address(objReg, ObjectElements::offsetOfInitializedLength()));
 
--- a/js/src/vm/ObjectImpl.cpp
+++ b/js/src/vm/ObjectImpl.cpp
@@ -145,16 +145,40 @@ PropDesc::wrapInto(JSContext *cx, Handle
 }
 
 static ObjectElements emptyElementsHeader(0, 0);
 
 /* Objects with no elements share one empty set of elements. */
 HeapSlot *js::emptyObjectElements =
     reinterpret_cast<HeapSlot *>(uintptr_t(&emptyElementsHeader) + sizeof(ObjectElements));
 
+/* static */ bool
+ObjectElements::ConvertElementsToDoubles(JSContext *cx, uintptr_t elementsPtr)
+{
+    /*
+     * This function is infallible, but has a fallible interface so that it can
+     * be called directly from Ion code. Only arrays can have their dense
+     * elements converted to doubles, and arrays never have empty elements.
+     */
+    HeapSlot *elementsHeapPtr = (HeapSlot *) elementsPtr;
+    JS_ASSERT(elementsHeapPtr != emptyObjectElements);
+
+    ObjectElements *header = ObjectElements::fromElements(elementsHeapPtr);
+    JS_ASSERT(!header->convertDoubleElements);
+
+    Value *vp = (Value *) elementsPtr;
+    for (size_t i = 0; i < header->initializedLength; i++) {
+        if (vp[i].isInt32())
+            vp[i].setDouble(vp[i].toInt32());
+    }
+
+    header->convertDoubleElements = 1;
+    return true;
+}
+
 #ifdef DEBUG
 void
 js::ObjectImpl::checkShapeConsistency()
 {
     static int throttle = -1;
     if (throttle < 0) {
         if (const char *var = getenv("JS_CHECK_SHAPE_THROTTLE"))
             throttle = atoi(var);
--- a/js/src/vm/ObjectImpl.h
+++ b/js/src/vm/ObjectImpl.h
@@ -930,44 +930,49 @@ class ObjectElements
      * uninitialized, but values between the initialized length and the proper
      * length are conceptually holes.
      */
     uint32_t initializedLength;
 
     /* 'length' property of array objects, unused for other objects. */
     uint32_t length;
 
-    /* :XXX: bug 586842 store state about sparse slots. */
-    uint32_t unused;
+    /* If non-zero, integer elements should be converted to doubles. */
+    uint32_t convertDoubleElements;
 
     void staticAsserts() {
         MOZ_STATIC_ASSERT(sizeof(ObjectElements) == VALUES_PER_HEADER * sizeof(Value),
                           "Elements size and values-per-Elements mismatch");
     }
 
   public:
 
     ObjectElements(uint32_t capacity, uint32_t length)
-      : capacity(capacity), initializedLength(0), length(length)
+      : capacity(capacity), initializedLength(0), length(length), convertDoubleElements(0)
     {}
 
     HeapSlot *elements() { return (HeapSlot *)(uintptr_t(this) + sizeof(ObjectElements)); }
     static ObjectElements * fromElements(HeapSlot *elems) {
         return (ObjectElements *)(uintptr_t(elems) - sizeof(ObjectElements));
     }
 
     static int offsetOfCapacity() {
         return (int)offsetof(ObjectElements, capacity) - (int)sizeof(ObjectElements);
     }
     static int offsetOfInitializedLength() {
         return (int)offsetof(ObjectElements, initializedLength) - (int)sizeof(ObjectElements);
     }
     static int offsetOfLength() {
         return (int)offsetof(ObjectElements, length) - (int)sizeof(ObjectElements);
     }
+    static int offsetOfConvertDoubleElements() {
+        return (int)offsetof(ObjectElements, convertDoubleElements) - (int)sizeof(ObjectElements);
+    }
+
+    static bool ConvertElementsToDoubles(JSContext *cx, uintptr_t elements);
 
     static const size_t VALUES_PER_HEADER = 2;
 };
 
 /* Shared singleton for objects with no elements. */
 extern HeapSlot *emptyObjectElements;
 
 struct Class;