Bug 1445235 part 6 - Use spectreBoundsCheck32 for more stores in JIT code. r=nbp
authorJan de Mooij <jdemooij@mozilla.com>
Wed, 28 Mar 2018 16:09:04 +0200
changeset 410481 03f1b458b9861280f68f555206397014367a4ad1
parent 410480 0ba5e089f9a0ee441405690395d6058eef2f5b6c
child 410482 a0d9c1cc3c97aa04d85c14f838cd9bf90e4dd18b
push id33729
push userrgurzau@mozilla.com
push dateWed, 28 Mar 2018 21:55:49 +0000
treeherdermozilla-central@6aa3b57955fe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnbp
bugs1445235
milestone61.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 1445235 part 6 - Use spectreBoundsCheck32 for more stores in JIT code. r=nbp
js/src/jit/BaselineCacheIRCompiler.cpp
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/IonCacheIRCompiler.cpp
js/src/jit/Lowering.cpp
js/src/jit/shared/LIR-shared.h
--- a/js/src/jit/BaselineCacheIRCompiler.cpp
+++ b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -1375,19 +1375,21 @@ BaselineCacheIRCompiler::emitStoreDenseE
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
     // Load obj->elements in scratch.
     masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
 
-    // Bounds check.
+    // Bounds check. Unfortunately we don't have more registers available on
+    // x86, so use InvalidReg and emit slightly slower code on x86.
+    Register spectreTemp = InvalidReg;
     Address initLength(scratch, ObjectElements::offsetOfInitializedLength());
-    masm.branch32(Assembler::BelowOrEqual, initLength, index, failure->label());
+    masm.spectreBoundsCheck32(index, initLength, spectreTemp, failure->label());
 
     // Hole check.
     BaseObjectElementIndex element(scratch, index);
     masm.branchTestMagic(Assembler::Equal, element, failure->label());
 
     // Perform a single test to see if we either need to convert double
     // elements, clone the copy on write elements in the object or fail
     // due to a frozen element.
@@ -1466,28 +1468,40 @@ BaselineCacheIRCompiler::emitStoreDenseE
     Address elementsFlags(scratch, ObjectElements::offsetOfFlags());
 
     // Check for copy-on-write or frozen elements.
     masm.branchTest32(Assembler::NonZero, elementsFlags,
                       Imm32(ObjectElements::COPY_ON_WRITE |
                             ObjectElements::FROZEN),
                       failure->label());
 
+    // We don't have enough registers on x86 so use InvalidReg. This will emit
+    // slightly less efficient code on x86.
+    Register spectreTemp = InvalidReg;
+
     if (handleAdd) {
-        // Fail if index > initLength.
-        masm.branch32(Assembler::Below, initLength, index, failure->label());
+        // Bounds check.
+        Label capacityOk, outOfBounds;
+        masm.spectreBoundsCheck32(index, initLength, spectreTemp, &outOfBounds);
+        masm.jump(&capacityOk);
+
+        // If we're out-of-bounds, only handle the index == initLength case.
+        masm.bind(&outOfBounds);
+        masm.branch32(Assembler::NotEqual, initLength, index, failure->label());
 
         // If index < capacity, we can add a dense element inline. If not we
         // need to allocate more elements.
-        Label capacityOk;
+        Label allocElement;
         Address capacity(scratch, ObjectElements::offsetOfCapacity());
-        masm.branch32(Assembler::Above, capacity, index, &capacityOk);
+        masm.spectreBoundsCheck32(index, capacity, spectreTemp, &allocElement);
+        masm.jump(&capacityOk);
 
         // Check for non-writable array length. We only have to do this if
         // index >= capacity.
+        masm.bind(&allocElement);
         masm.branchTest32(Assembler::NonZero, elementsFlags,
                           Imm32(ObjectElements::NONWRITABLE_ARRAY_LENGTH),
                           failure->label());
 
         LiveRegisterSet save(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs());
         save.takeUnchecked(scratch);
         masm.PushRegsInMask(save);
 
@@ -1505,17 +1519,17 @@ BaselineCacheIRCompiler::emitStoreDenseE
         masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
 
         masm.bind(&capacityOk);
 
         // We increment initLength after the callTypeUpdateIC call, to ensure
         // the type update code doesn't read uninitialized memory.
     } else {
         // Fail if index >= initLength.
-        masm.branch32(Assembler::BelowOrEqual, initLength, index, failure->label());
+        masm.spectreBoundsCheck32(index, initLength, spectreTemp, failure->label());
     }
 
     // Check if we have to convert a double element.
     Label noConversion;
     masm.branchTest32(Assembler::Zero, elementsFlags,
                       Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS),
                       &noConversion);
 
@@ -1590,39 +1604,41 @@ BaselineCacheIRCompiler::emitArrayPush()
     AutoScratchRegister scratchLength(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
     // Load obj->elements in scratch.
     masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
-    masm.load32(Address(scratch, ObjectElements::offsetOfLength()), scratchLength);
-
-    BaseObjectElementIndex element(scratch, scratchLength);
-    Address initLength(scratch, ObjectElements::offsetOfInitializedLength());
+
+    Address elementsInitLength(scratch, ObjectElements::offsetOfInitializedLength());
+    Address elementsLength(scratch, ObjectElements::offsetOfLength());
     Address elementsFlags(scratch, ObjectElements::offsetOfFlags());
 
     // Check for copy-on-write or frozen elements.
     masm.branchTest32(Assembler::NonZero, elementsFlags,
                       Imm32(ObjectElements::COPY_ON_WRITE |
                             ObjectElements::FROZEN),
                       failure->label());
 
     // Fail if length != initLength.
-    masm.branch32(Assembler::NotEqual, initLength, scratchLength, failure->label());
+    masm.load32(elementsInitLength, scratchLength);
+    masm.branch32(Assembler::NotEqual, elementsLength, scratchLength, failure->label());
 
     // If scratchLength < capacity, we can add a dense element inline. If not we
     // need to allocate more elements.
-    Label capacityOk;
+    Label capacityOk, allocElement;
     Address capacity(scratch, ObjectElements::offsetOfCapacity());
-    masm.branch32(Assembler::Above, capacity, scratchLength, &capacityOk);
+    masm.spectreBoundsCheck32(scratchLength, capacity, InvalidReg, &allocElement);
+    masm.jump(&capacityOk);
 
     // Check for non-writable array length. We only have to do this if
     // index >= capacity.
+    masm.bind(&allocElement);
     masm.branchTest32(Assembler::NonZero, elementsFlags,
                       Imm32(ObjectElements::NONWRITABLE_ARRAY_LENGTH),
                       failure->label());
 
     LiveRegisterSet save(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs());
     save.takeUnchecked(scratch);
     masm.PushRegsInMask(save);
 
@@ -1667,22 +1683,22 @@ BaselineCacheIRCompiler::emitArrayPush()
     saveRegs.add(val);
     if (!callTypeUpdateIC(obj, val, scratch, saveRegs))
         return false;
 
     // Reload obj->elements as callTypeUpdateIC used the scratch register.
     masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
 
     // Increment initLength and length.
-    Address length(scratch, ObjectElements::offsetOfLength());
-    masm.add32(Imm32(1), initLength);
-    masm.load32(length, scratchLength);
-    masm.add32(Imm32(1), length);
+    masm.add32(Imm32(1), elementsInitLength);
+    masm.load32(elementsLength, scratchLength);
+    masm.add32(Imm32(1), elementsLength);
 
     // Store the value.
+    BaseObjectElementIndex element(scratch, scratchLength);
     masm.storeValue(val, element);
     emitPostBarrierElement(obj, val, scratch, scratchLength);
 
     // Return value is new length.
     masm.add32(Imm32(1), scratchLength);
     masm.tagValue(JSVAL_TYPE_INT32, scratchLength, val);
 
     return true;
@@ -1703,17 +1719,21 @@ BaselineCacheIRCompiler::emitStoreTypedE
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
     // Bounds check.
     Label done;
     LoadTypedThingLength(masm, layout, obj, scratch1);
-    masm.branch32(Assembler::BelowOrEqual, scratch1, index, handleOOB ? &done : failure->label());
+
+    // Unfortunately we don't have more registers available on x86, so use
+    // InvalidReg and emit slightly slower code on x86.
+    Register spectreTemp = InvalidReg;
+    masm.spectreBoundsCheck32(index, scratch1, spectreTemp, handleOOB ? &done : failure->label());
 
     // Load the elements vector.
     LoadTypedThingData(masm, layout, obj, scratch1);
 
     BaseIndex dest(scratch1, index, ScaleFromElemWidth(Scalar::byteSize(type)));
 
     // Use ICStubReg as second scratch register. TODO: consider doing the RHS
     // type check/conversion as a separate IR instruction so we can simplify
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -9222,19 +9222,20 @@ CodeGenerator::emitStoreElementHoleT(T* 
                   "emitStoreElementHoleT called with unexpected argument type");
 
     OutOfLineStoreElementHole* ool =
         new(alloc()) OutOfLineStoreElementHole(lir, current->mir()->strict());
     addOutOfLineCode(ool, lir->mir());
 
     Register elements = ToRegister(lir->elements());
     Register index = ToRegister(lir->index());
+    Register spectreTemp = ToTempRegisterOrInvalid(lir->spectreTemp());
 
     Address initLength(elements, ObjectElements::offsetOfInitializedLength());
-    masm.branch32(Assembler::BelowOrEqual, initLength, index, ool->entry());
+    masm.spectreBoundsCheck32(index, initLength, spectreTemp, ool->entry());
 
     if (lir->mir()->needsBarrier())
         emitPreBarrier(elements, lir->index(), 0);
 
     masm.bind(ool->rejoinStore());
     emitStoreElementTyped(lir->value(), lir->mir()->value()->type(), lir->mir()->elementType(),
                           elements, lir->index(), 0);
 
@@ -9255,19 +9256,20 @@ CodeGenerator::emitStoreElementHoleV(T* 
 
     OutOfLineStoreElementHole* ool =
         new(alloc()) OutOfLineStoreElementHole(lir, current->mir()->strict());
     addOutOfLineCode(ool, lir->mir());
 
     Register elements = ToRegister(lir->elements());
     Register index = ToRegister(lir->index());
     const ValueOperand value = ToValue(lir, T::Value);
+    Register spectreTemp = ToTempRegisterOrInvalid(lir->spectreTemp());
 
     Address initLength(elements, ObjectElements::offsetOfInitializedLength());
-    masm.branch32(Assembler::BelowOrEqual, initLength, index, ool->entry());
+    masm.spectreBoundsCheck32(index, initLength, spectreTemp, ool->entry());
 
     if (lir->mir()->needsBarrier())
         emitPreBarrier(elements, lir->index(), 0);
 
     masm.bind(ool->rejoinStore());
     masm.storeValue(value, BaseIndex(elements, index, TimesEight));
 
     masm.bind(ool->rejoin());
@@ -9355,70 +9357,77 @@ static const VMFunction SetDenseElementI
 void
 CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool)
 {
     Register object, elements;
     LInstruction* ins = ool->ins();
     const LAllocation* index;
     MIRType valueType;
     ConstantOrRegister value;
+    Register spectreTemp;
 
     if (ins->isStoreElementHoleV()) {
         LStoreElementHoleV* store = ins->toStoreElementHoleV();
         object = ToRegister(store->object());
         elements = ToRegister(store->elements());
         index = store->index();
         valueType = store->mir()->value()->type();
         value = TypedOrValueRegister(ToValue(store, LStoreElementHoleV::Value));
+        spectreTemp = ToTempRegisterOrInvalid(store->spectreTemp());
     } else if (ins->isFallibleStoreElementV()) {
         LFallibleStoreElementV* store = ins->toFallibleStoreElementV();
         object = ToRegister(store->object());
         elements = ToRegister(store->elements());
         index = store->index();
         valueType = store->mir()->value()->type();
         value = TypedOrValueRegister(ToValue(store, LFallibleStoreElementV::Value));
+        spectreTemp = ToTempRegisterOrInvalid(store->spectreTemp());
     } else if (ins->isStoreElementHoleT()) {
         LStoreElementHoleT* store = ins->toStoreElementHoleT();
         object = ToRegister(store->object());
         elements = ToRegister(store->elements());
         index = store->index();
         valueType = store->mir()->value()->type();
         if (store->value()->isConstant())
             value = ConstantOrRegister(store->value()->toConstant()->toJSValue());
         else
             value = TypedOrValueRegister(valueType, ToAnyRegister(store->value()));
+        spectreTemp = ToTempRegisterOrInvalid(store->spectreTemp());
     } else { // ins->isFallibleStoreElementT()
         LFallibleStoreElementT* store = ins->toFallibleStoreElementT();
         object = ToRegister(store->object());
         elements = ToRegister(store->elements());
         index = store->index();
         valueType = store->mir()->value()->type();
         if (store->value()->isConstant())
             value = ConstantOrRegister(store->value()->toConstant()->toJSValue());
         else
             value = TypedOrValueRegister(valueType, ToAnyRegister(store->value()));
+        spectreTemp = ToTempRegisterOrInvalid(store->spectreTemp());
     }
 
     Register indexReg = ToRegister(index);
 
     // If index == initializedLength, try to bump the initialized length inline.
     // If index > initializedLength, call a stub. Note that this relies on the
     // condition flags sticking from the incoming branch.
+    // Also note: this branch does not need Spectre mitigations, doing that for
+    // the capacity check below is sufficient.
     Label callStub;
 #if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
     // Had to reimplement for MIPS because there are no flags.
     Address initLength(elements, ObjectElements::offsetOfInitializedLength());
     masm.branch32(Assembler::NotEqual, initLength, indexReg, &callStub);
 #else
     masm.j(Assembler::NotEqual, &callStub);
 #endif
 
     // Check array capacity.
-    masm.branch32(Assembler::BelowOrEqual, Address(elements, ObjectElements::offsetOfCapacity()),
-                  indexReg, &callStub);
+    masm.spectreBoundsCheck32(indexReg, Address(elements, ObjectElements::offsetOfCapacity()),
+                              spectreTemp, &callStub);
 
     // Update initialized length. The capacity guard above ensures this won't overflow,
     // due to MAX_DENSE_ELEMENTS_COUNT.
     masm.add32(Imm32(1), indexReg);
     masm.store32(indexReg, Address(elements, ObjectElements::offsetOfInitializedLength()));
 
     // Update length if length < initializedLength.
     Label dontUpdate;
@@ -9553,22 +9562,23 @@ CodeGenerator::emitArrayPopShift(LInstru
     } else {
         MOZ_ASSERT(mir->mode() == MArrayPopShift::Shift);
         ool = oolCallVM(ArrayShiftDenseInfo, lir, ArgList(obj), StoreValueTo(out));
     }
 
     // VM call if a write barrier is necessary.
     masm.branchTestNeedsIncrementalBarrier(Assembler::NonZero, ool->entry());
 
-    // Load elements and length, and VM call if length != initializedLength.
+    // Load elements and initializedLength, and VM call if
+    // length != initializedLength.
     masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), elementsTemp);
-    masm.load32(Address(elementsTemp, ObjectElements::offsetOfLength()), lengthTemp);
-
-    Address initLength(elementsTemp, ObjectElements::offsetOfInitializedLength());
-    masm.branch32(Assembler::NotEqual, initLength, lengthTemp, ool->entry());
+    masm.load32(Address(elementsTemp, ObjectElements::offsetOfInitializedLength()), lengthTemp);
+
+    Address lengthAddr(elementsTemp, ObjectElements::offsetOfLength());
+    masm.branch32(Assembler::NotEqual, lengthAddr, lengthTemp, ool->entry());
 
     // Test for length != 0. On zero length either take a VM call or generate
     // an undefined value, depending on whether the call is known to produce
     // undefined.
     Label done;
     if (mir->maybeUndefined()) {
         Label notEmpty;
         masm.branchTest32(Assembler::NonZero, lengthTemp, lengthTemp, &notEmpty);
@@ -9650,31 +9660,32 @@ CodeGenerator::visitArrayPopShiftT(LArra
 }
 
 typedef bool (*ArrayPushDenseFn)(JSContext*, HandleArrayObject, HandleValue, uint32_t*);
 static const VMFunction ArrayPushDenseInfo =
     FunctionInfo<ArrayPushDenseFn>(jit::ArrayPushDense, "ArrayPushDense");
 
 void
 CodeGenerator::emitArrayPush(LInstruction* lir, Register obj,
-                             const ConstantOrRegister& value, Register elementsTemp, Register length)
+                             const ConstantOrRegister& value, Register elementsTemp, Register length,
+                             Register spectreTemp)
 {
     OutOfLineCode* ool = oolCallVM(ArrayPushDenseInfo, lir, ArgList(obj, value), StoreRegisterTo(length));
 
     // Load elements and length.
     masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), elementsTemp);
     masm.load32(Address(elementsTemp, ObjectElements::offsetOfLength()), length);
 
     // Guard length == initializedLength.
     Address initLength(elementsTemp, ObjectElements::offsetOfInitializedLength());
     masm.branch32(Assembler::NotEqual, initLength, length, ool->entry());
 
     // Guard length < capacity.
     Address capacity(elementsTemp, ObjectElements::offsetOfCapacity());
-    masm.branch32(Assembler::BelowOrEqual, capacity, length, ool->entry());
+    masm.spectreBoundsCheck32(length, capacity, spectreTemp, ool->entry());
 
     // Do the store.
     masm.storeConstantOrRegister(value, BaseIndex(elementsTemp, length, TimesEight));
 
     masm.add32(Imm32(1), length);
 
     // Update length and initialized length.
     masm.store32(length, Address(elementsTemp, ObjectElements::offsetOfLength()));
@@ -9685,31 +9696,33 @@ CodeGenerator::emitArrayPush(LInstructio
 
 void
 CodeGenerator::visitArrayPushV(LArrayPushV* lir)
 {
     Register obj = ToRegister(lir->object());
     Register elementsTemp = ToRegister(lir->temp());
     Register length = ToRegister(lir->output());
     ConstantOrRegister value = TypedOrValueRegister(ToValue(lir, LArrayPushV::Value));
-    emitArrayPush(lir, obj, value, elementsTemp, length);
+    Register spectreTemp = ToTempRegisterOrInvalid(lir->spectreTemp());
+    emitArrayPush(lir, obj, value, elementsTemp, length, spectreTemp);
 }
 
 void
 CodeGenerator::visitArrayPushT(LArrayPushT* lir)
 {
     Register obj = ToRegister(lir->object());
     Register elementsTemp = ToRegister(lir->temp());
     Register length = ToRegister(lir->output());
     ConstantOrRegister value;
     if (lir->value()->isConstant())
         value = ConstantOrRegister(lir->value()->toConstant()->toJSValue());
     else
         value = TypedOrValueRegister(lir->mir()->value()->type(), ToAnyRegister(lir->value()));
-    emitArrayPush(lir, obj, value, elementsTemp, length);
+    Register spectreTemp = ToTempRegisterOrInvalid(lir->spectreTemp());
+    emitArrayPush(lir, obj, value, elementsTemp, length, spectreTemp);
 }
 
 typedef JSObject* (*ArraySliceDenseFn)(JSContext*, HandleObject, int32_t, int32_t, HandleObject);
 static const VMFunction ArraySliceDenseInfo =
     FunctionInfo<ArraySliceDenseFn>(array_slice_dense, "array_slice_dense");
 
 void
 CodeGenerator::visitArraySlice(LArraySlice* lir)
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -158,17 +158,18 @@ class CodeGenerator final : public CodeG
     template<typename T> void emitLoadElementT(LLoadElementT* lir, const T& source);
 
     template <typename T> void emitStoreElementHoleT(T* lir);
     template <typename T> void emitStoreElementHoleV(T* lir);
 
     void emitArrayPopShift(LInstruction* lir, const MArrayPopShift* mir, Register obj,
                            Register elementsTemp, Register lengthTemp, TypedOrValueRegister out);
     void emitArrayPush(LInstruction* lir, Register obj,
-                       const ConstantOrRegister& value, Register elementsTemp, Register length);
+                       const ConstantOrRegister& value, Register elementsTemp, Register length,
+                       Register spectreTemp);
 
     void emitRest(LInstruction* lir, Register array, Register numActuals,
                   Register temp0, Register temp1, unsigned numFormals,
                   JSObject* templateObject, bool saveAndRestore, Register resultreg);
     void emitInstanceOf(LInstruction* ins, JSObject* prototypeObject);
 
     enum CallableOrConstructor {
         Callable,
--- a/js/src/jit/IonCacheIRCompiler.cpp
+++ b/js/src/jit/IonCacheIRCompiler.cpp
@@ -1896,125 +1896,134 @@ EmitStoreDenseElement(MacroAssembler& ma
 
 bool
 IonCacheIRCompiler::emitStoreDenseElement()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     Register index = allocator.useRegister(masm, reader.int32OperandId());
     ConstantOrRegister val = allocator.useConstantOrRegister(masm, reader.valOperandId());
 
-    AutoScratchRegister scratch(allocator, masm);
+    AutoScratchRegister scratch1(allocator, masm);
+    AutoScratchRegister scratch2(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
     EmitCheckPropertyTypes(masm, typeCheckInfo_, obj, val, *liveRegs_, failure->label());
 
     // Load obj->elements in scratch.
-    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
+    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch1);
 
     // Bounds check.
-    Address initLength(scratch, ObjectElements::offsetOfInitializedLength());
-    masm.branch32(Assembler::BelowOrEqual, initLength, index, failure->label());
+    Address initLength(scratch1, ObjectElements::offsetOfInitializedLength());
+    masm.spectreBoundsCheck32(index, initLength, scratch2, failure->label());
 
     // Hole check.
-    BaseObjectElementIndex element(scratch, index);
+    BaseObjectElementIndex element(scratch1, index);
     masm.branchTestMagic(Assembler::Equal, element, failure->label());
 
     EmitPreBarrier(masm, element, MIRType::Value);
-    EmitStoreDenseElement(masm, val, scratch, element);
+    EmitStoreDenseElement(masm, val, scratch1, element);
     if (needsPostBarrier())
-        emitPostBarrierElement(obj, val, scratch, index);
+        emitPostBarrierElement(obj, val, scratch1, index);
     return true;
 }
 
 bool
 IonCacheIRCompiler::emitStoreDenseElementHole()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     Register index = allocator.useRegister(masm, reader.int32OperandId());
     ConstantOrRegister val = allocator.useConstantOrRegister(masm, reader.valOperandId());
 
     // handleAdd boolean is only relevant for Baseline. Ion ICs can always
     // handle adds as we don't have to set any flags on the fallback stub to
     // track this.
     reader.readBool();
 
-    AutoScratchRegister scratch(allocator, masm);
+    AutoScratchRegister scratch1(allocator, masm);
+    AutoScratchRegister scratch2(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
     EmitCheckPropertyTypes(masm, typeCheckInfo_, obj, val, *liveRegs_, failure->label());
 
-    // Load obj->elements in scratch.
-    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
-
-    Address initLength(scratch, ObjectElements::offsetOfInitializedLength());
-    BaseObjectElementIndex element(scratch, index);
-
-    Label inBounds, doStore;
-    masm.branch32(Assembler::Above, initLength, index, &inBounds);
+    // Load obj->elements in scratch1.
+    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch1);
+
+    Address initLength(scratch1, ObjectElements::offsetOfInitializedLength());
+    BaseObjectElementIndex element(scratch1, index);
+
+    Label inBounds, outOfBounds;
+    Register spectreTemp = scratch2;
+    masm.spectreBoundsCheck32(index, initLength, spectreTemp, &outOfBounds);
+    masm.jump(&inBounds);
+
+    masm.bind(&outOfBounds);
     masm.branch32(Assembler::NotEqual, initLength, index, failure->label());
 
     // If index < capacity, we can add a dense element inline. If not we
     // need to allocate more elements.
-    Label capacityOk;
-    Address capacity(scratch, ObjectElements::offsetOfCapacity());
-    masm.branch32(Assembler::Above, capacity, index, &capacityOk);
+    Label capacityOk, allocElement;
+    Address capacity(scratch1, ObjectElements::offsetOfCapacity());
+    masm.spectreBoundsCheck32(index, capacity, spectreTemp, &allocElement);
+    masm.jump(&capacityOk);
 
     // Check for non-writable array length. We only have to do this if
     // index >= capacity.
-    Address elementsFlags(scratch, ObjectElements::offsetOfFlags());
+    masm.bind(&allocElement);
+    Address elementsFlags(scratch1, ObjectElements::offsetOfFlags());
     masm.branchTest32(Assembler::NonZero, elementsFlags,
                       Imm32(ObjectElements::NONWRITABLE_ARRAY_LENGTH),
                       failure->label());
 
     LiveRegisterSet save(GeneralRegisterSet::Volatile(), liveVolatileFloatRegs());
-    save.takeUnchecked(scratch);
+    save.takeUnchecked(scratch1);
     masm.PushRegsInMask(save);
 
-    masm.setupUnalignedABICall(scratch);
-    masm.loadJSContext(scratch);
-    masm.passABIArg(scratch);
+    masm.setupUnalignedABICall(scratch1);
+    masm.loadJSContext(scratch1);
+    masm.passABIArg(scratch1);
     masm.passABIArg(obj);
     masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, NativeObject::addDenseElementDontReportOOM));
-    masm.mov(ReturnReg, scratch);
+    masm.mov(ReturnReg, scratch1);
 
     masm.PopRegsInMask(save);
-    masm.branchIfFalseBool(scratch, failure->label());
+    masm.branchIfFalseBool(scratch1, failure->label());
 
     // Load the reallocated elements pointer.
-    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
+    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch1);
 
     masm.bind(&capacityOk);
 
     // Increment initLength.
     masm.add32(Imm32(1), initLength);
 
     // If length is now <= index, increment length too.
     Label skipIncrementLength;
-    Address length(scratch, ObjectElements::offsetOfLength());
+    Address length(scratch1, ObjectElements::offsetOfLength());
     masm.branch32(Assembler::Above, length, index, &skipIncrementLength);
     masm.add32(Imm32(1), length);
     masm.bind(&skipIncrementLength);
 
     // Skip EmitPreBarrier as the memory is uninitialized.
+    Label doStore;
     masm.jump(&doStore);
 
     masm.bind(&inBounds);
 
     EmitPreBarrier(masm, element, MIRType::Value);
 
     masm.bind(&doStore);
-    EmitStoreDenseElement(masm, val, scratch, element);
+    EmitStoreDenseElement(masm, val, scratch1, element);
     if (needsPostBarrier())
-        emitPostBarrierElement(obj, val, scratch, index);
+        emitPostBarrierElement(obj, val, scratch1, index);
     return true;
 }
 
 bool
 IonCacheIRCompiler::emitArrayPush()
 {
     MOZ_ASSERT_UNREACHABLE("emitArrayPush not supported for IonCaches.");
     return false;
@@ -2027,29 +2036,26 @@ IonCacheIRCompiler::emitStoreTypedElemen
     Register index = allocator.useRegister(masm, reader.int32OperandId());
     ConstantOrRegister val = allocator.useConstantOrRegister(masm, reader.valOperandId());
 
     TypedThingLayout layout = reader.typedThingLayout();
     Scalar::Type arrayType = reader.scalarType();
     bool handleOOB = reader.readBool();
 
     AutoScratchRegister scratch1(allocator, masm);
-
-    Maybe<AutoScratchRegister> scratch2;
-    if (arrayType != Scalar::Float32 && arrayType != Scalar::Float64)
-        scratch2.emplace(allocator, masm);
+    AutoScratchRegister scratch2(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
     // Bounds check.
     Label done;
     LoadTypedThingLength(masm, layout, obj, scratch1);
-    masm.branch32(Assembler::BelowOrEqual, scratch1, index, handleOOB ? &done : failure->label());
+    masm.spectreBoundsCheck32(index, scratch1, scratch2, handleOOB ? &done : failure->label());
 
     // Load the elements vector.
     LoadTypedThingData(masm, layout, obj, scratch1);
 
     BaseIndex dest(scratch1, index, ScaleFromElemWidth(Scalar::byteSize(arrayType)));
 
     FloatRegister maybeTempDouble = ic_->asSetPropertyIC()->maybeTempDouble();
     FloatRegister maybeTempFloat32 = ic_->asSetPropertyIC()->maybeTempFloat32();
@@ -2061,17 +2067,17 @@ IonCacheIRCompiler::emitStoreTypedElemen
         if (!masm.convertConstantOrRegisterToFloat(cx_, val, tempFloat, failure->label()))
             return false;
         masm.storeToTypedFloatArray(arrayType, tempFloat, dest);
     } else if (arrayType == Scalar::Float64) {
         if (!masm.convertConstantOrRegisterToDouble(cx_, val, maybeTempDouble, failure->label()))
             return false;
         masm.storeToTypedFloatArray(arrayType, maybeTempDouble, dest);
     } else {
-        Register valueToStore = scratch2.ref();
+        Register valueToStore = scratch2;
         if (arrayType == Scalar::Uint8Clamped) {
             if (!masm.clampConstantOrRegisterToUint8(cx_, val, maybeTempDouble, valueToStore,
                                                      failure->label()))
             {
                 return false;
             }
         } else {
             if (!masm.truncateConstantOrRegisterToInt32(cx_, val, maybeTempDouble, valueToStore,
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -3414,36 +3414,51 @@ LIRGenerator::visitStoreElement(MStoreEl
         if (ins->fallible())
             assignSnapshot(lir, Bailout_Hole);
         add(lir, ins);
         break;
       }
     }
 }
 
+static bool
+BoundsCheckNeedsSpectreTemp()
+{
+    // On x86, spectreBoundsCheck32 can emit better code if it has a scratch
+    // register and index masking is enabled.
+#ifdef JS_CODEGEN_X86
+    return JitOptions.spectreIndexMasking;
+#else
+    return false;
+#endif
+}
+
 void
 LIRGenerator::visitStoreElementHole(MStoreElementHole* ins)
 {
     MOZ_ASSERT(ins->elements()->type() == MIRType::Elements);
     MOZ_ASSERT(ins->index()->type() == MIRType::Int32);
 
     const LUse object = useRegister(ins->object());
     const LUse elements = useRegister(ins->elements());
     const LAllocation index = useRegister(ins->index());
 
+    LDefinition spectreTemp = BoundsCheckNeedsSpectreTemp() ? temp() : LDefinition::BogusTemp();
+
     LInstruction* lir;
     switch (ins->value()->type()) {
       case MIRType::Value:
-        lir = new(alloc()) LStoreElementHoleV(object, elements, index, useBox(ins->value()));
+        lir = new(alloc()) LStoreElementHoleV(object, elements, index, useBox(ins->value()),
+                                              spectreTemp);
         break;
 
       default:
       {
         const LAllocation value = useRegisterOrNonDoubleConstant(ins->value());
-        lir = new(alloc()) LStoreElementHoleT(object, elements, index, value);
+        lir = new(alloc()) LStoreElementHoleT(object, elements, index, value, spectreTemp);
         break;
       }
     }
 
     add(lir, ins);
     assignSafepoint(lir, ins);
 }
 
@@ -3452,24 +3467,28 @@ LIRGenerator::visitFallibleStoreElement(
 {
     MOZ_ASSERT(ins->elements()->type() == MIRType::Elements);
     MOZ_ASSERT(ins->index()->type() == MIRType::Int32);
 
     const LUse object = useRegister(ins->object());
     const LUse elements = useRegister(ins->elements());
     const LAllocation index = useRegister(ins->index());
 
+    LDefinition spectreTemp = BoundsCheckNeedsSpectreTemp() ? temp() : LDefinition::BogusTemp();
+
     LInstruction* lir;
     switch (ins->value()->type()) {
       case MIRType::Value:
-        lir = new(alloc()) LFallibleStoreElementV(object, elements, index, useBox(ins->value()));
+        lir = new(alloc()) LFallibleStoreElementV(object, elements, index, useBox(ins->value()),
+                                                  spectreTemp);
         break;
       default:
         const LAllocation value = useRegisterOrNonDoubleConstant(ins->value());
-        lir = new(alloc()) LFallibleStoreElementT(object, elements, index, value);
+        lir = new(alloc()) LFallibleStoreElementT(object, elements, index, value,
+                                                  spectreTemp);
         break;
     }
 
     add(lir, ins);
     assignSafepoint(lir, ins);
 }
 
 
@@ -3558,29 +3577,32 @@ LIRGenerator::visitArrayPopShift(MArrayP
 
 void
 LIRGenerator::visitArrayPush(MArrayPush* ins)
 {
     MOZ_ASSERT(ins->type() == MIRType::Int32);
 
     LUse object = useRegister(ins->object());
 
+    LDefinition spectreTemp = BoundsCheckNeedsSpectreTemp() ? temp() : LDefinition::BogusTemp();
+
     switch (ins->value()->type()) {
       case MIRType::Value:
       {
-        LArrayPushV* lir = new(alloc()) LArrayPushV(object, useBox(ins->value()), temp());
+        LArrayPushV* lir = new(alloc()) LArrayPushV(object, useBox(ins->value()), temp(),
+                                                    spectreTemp);
         define(lir, ins);
         assignSafepoint(lir, ins);
         break;
       }
 
       default:
       {
         const LAllocation value = useRegisterOrNonDoubleConstant(ins->value());
-        LArrayPushT* lir = new(alloc()) LArrayPushT(object, value, temp());
+        LArrayPushT* lir = new(alloc()) LArrayPushT(object, value, temp(), spectreTemp);
         define(lir, ins);
         assignSafepoint(lir, ins);
         break;
       }
     }
 }
 
 void
@@ -3793,17 +3815,17 @@ LIRGenerator::visitStoreTypedArrayElemen
     LAllocation index = useRegister(ins->index());
     // For byte arrays, the value has to be in a byte register on x86.
     LAllocation value;
     if (ins->isByteWrite())
         value = useByteOpRegisterOrNonDoubleConstant(ins->value());
     else
         value = useRegisterOrNonDoubleConstant(ins->value());
 
-    LDefinition spectreTemp = JitOptions.spectreIndexMasking ? temp() : LDefinition::BogusTemp();
+    LDefinition spectreTemp = BoundsCheckNeedsSpectreTemp() ? temp() : LDefinition::BogusTemp();
     auto* lir =
         new(alloc()) LStoreTypedArrayElementHole(elements, length, index, value, spectreTemp);
     add(lir, ins);
 }
 
 void
 LIRGenerator::visitLoadFixedSlot(MLoadFixedSlot* ins)
 {
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -6435,61 +6435,68 @@ class LStoreElementT : public LInstructi
         return getOperand(1);
     }
     const LAllocation* value() {
         return getOperand(2);
     }
 };
 
 // Like LStoreElementV, but supports indexes >= initialized length.
-class LStoreElementHoleV : public LInstructionHelper<0, 3 + BOX_PIECES, 0>
+class LStoreElementHoleV : public LInstructionHelper<0, 3 + BOX_PIECES, 1>
 {
   public:
     LIR_HEADER(StoreElementHoleV)
 
     LStoreElementHoleV(const LAllocation& object, const LAllocation& elements,
-                       const LAllocation& index, const LBoxAllocation& value)
+                       const LAllocation& index, const LBoxAllocation& value,
+                       const LDefinition& spectreTemp)
       : LInstructionHelper(classOpcode)
     {
         setOperand(0, object);
         setOperand(1, elements);
         setOperand(2, index);
         setBoxOperand(Value, value);
+        setTemp(0, spectreTemp);
     }
 
     static const size_t Value = 3;
 
     const MStoreElementHole* mir() const {
         return mir_->toStoreElementHole();
     }
     const LAllocation* object() {
         return getOperand(0);
     }
     const LAllocation* elements() {
         return getOperand(1);
     }
     const LAllocation* index() {
         return getOperand(2);
     }
+    const LDefinition* spectreTemp() {
+        return getTemp(0);
+    }
 };
 
 // Like LStoreElementT, but supports indexes >= initialized length.
-class LStoreElementHoleT : public LInstructionHelper<0, 4, 0>
+class LStoreElementHoleT : public LInstructionHelper<0, 4, 1>
 {
   public:
     LIR_HEADER(StoreElementHoleT)
 
     LStoreElementHoleT(const LAllocation& object, const LAllocation& elements,
-                       const LAllocation& index, const LAllocation& value)
+                       const LAllocation& index, const LAllocation& value,
+                       const LDefinition& spectreTemp)
       : LInstructionHelper(classOpcode)
     {
         setOperand(0, object);
         setOperand(1, elements);
         setOperand(2, index);
         setOperand(3, value);
+        setTemp(0, spectreTemp);
     }
 
     const MStoreElementHole* mir() const {
         return mir_->toStoreElementHole();
     }
     const LAllocation* object() {
         return getOperand(0);
     }
@@ -6497,64 +6504,74 @@ class LStoreElementHoleT : public LInstr
         return getOperand(1);
     }
     const LAllocation* index() {
         return getOperand(2);
     }
     const LAllocation* value() {
         return getOperand(3);
     }
+    const LDefinition* spectreTemp() {
+        return getTemp(0);
+    }
 };
 
 // Like LStoreElementV, but can just ignore assignment (for eg. frozen objects)
-class LFallibleStoreElementV : public LInstructionHelper<0, 3 + BOX_PIECES, 0>
+class LFallibleStoreElementV : public LInstructionHelper<0, 3 + BOX_PIECES, 1>
 {
   public:
     LIR_HEADER(FallibleStoreElementV)
 
     LFallibleStoreElementV(const LAllocation& object, const LAllocation& elements,
-                           const LAllocation& index, const LBoxAllocation& value)
+                           const LAllocation& index, const LBoxAllocation& value,
+                           const LDefinition& spectreTemp)
       : LInstructionHelper(classOpcode)
     {
         setOperand(0, object);
         setOperand(1, elements);
         setOperand(2, index);
         setBoxOperand(Value, value);
+        setTemp(0, spectreTemp);
     }
 
     static const size_t Value = 3;
 
     const MFallibleStoreElement* mir() const {
         return mir_->toFallibleStoreElement();
     }
     const LAllocation* object() {
         return getOperand(0);
     }
     const LAllocation* elements() {
         return getOperand(1);
     }
     const LAllocation* index() {
         return getOperand(2);
     }
+    const LDefinition* spectreTemp() {
+        return getTemp(0);
+    }
 };
 
 // Like LStoreElementT, but can just ignore assignment (for eg. frozen objects)
-class LFallibleStoreElementT : public LInstructionHelper<0, 4, 0>
+class LFallibleStoreElementT : public LInstructionHelper<0, 4, 1>
 {
   public:
     LIR_HEADER(FallibleStoreElementT)
 
     LFallibleStoreElementT(const LAllocation& object, const LAllocation& elements,
-                           const LAllocation& index, const LAllocation& value)
+                           const LAllocation& index, const LAllocation& value,
+                           const LDefinition& spectreTemp)
       : LInstructionHelper(classOpcode)
     {
         setOperand(0, object);
         setOperand(1, elements);
         setOperand(2, index);
         setOperand(3, value);
+        setTemp(0, spectreTemp);
     }
 
     const MFallibleStoreElement* mir() const {
         return mir_->toFallibleStoreElement();
     }
     const LAllocation* object() {
         return getOperand(0);
     }
@@ -6562,16 +6579,19 @@ class LFallibleStoreElementT : public LI
         return getOperand(1);
     }
     const LAllocation* index() {
         return getOperand(2);
     }
     const LAllocation* value() {
         return getOperand(3);
     }
+    const LDefinition* spectreTemp() {
+        return getTemp(0);
+    }
 };
 
 class LStoreUnboxedPointer : public LInstructionHelper<0, 3, 0>
 {
   public:
     LIR_HEADER(StoreUnboxedPointer)
 
     LStoreUnboxedPointer(LAllocation elements, LAllocation index, LAllocation value)
@@ -6676,67 +6696,77 @@ class LArrayPopShiftT : public LInstruct
     const LDefinition* temp0() {
         return getTemp(0);
     }
     const LDefinition* temp1() {
         return getTemp(1);
     }
 };
 
-class LArrayPushV : public LInstructionHelper<1, 1 + BOX_PIECES, 1>
+class LArrayPushV : public LInstructionHelper<1, 1 + BOX_PIECES, 2>
 {
   public:
     LIR_HEADER(ArrayPushV)
 
-    LArrayPushV(const LAllocation& object, const LBoxAllocation& value, const LDefinition& temp)
+    LArrayPushV(const LAllocation& object, const LBoxAllocation& value, const LDefinition& temp,
+                const LDefinition& spectreTemp)
       : LInstructionHelper(classOpcode)
     {
         setOperand(0, object);
         setBoxOperand(Value, value);
         setTemp(0, temp);
+        setTemp(1, spectreTemp);
     }
 
     static const size_t Value = 1;
 
     const MArrayPush* mir() const {
         return mir_->toArrayPush();
     }
     const LAllocation* object() {
         return getOperand(0);
     }
     const LDefinition* temp() {
         return getTemp(0);
     }
-};
-
-class LArrayPushT : public LInstructionHelper<1, 2, 1>
+    const LDefinition* spectreTemp() {
+        return getTemp(1);
+    }
+};
+
+class LArrayPushT : public LInstructionHelper<1, 2, 2>
 {
   public:
     LIR_HEADER(ArrayPushT)
 
-    LArrayPushT(const LAllocation& object, const LAllocation& value, const LDefinition& temp)
+    LArrayPushT(const LAllocation& object, const LAllocation& value, const LDefinition& temp,
+                const LDefinition& spectreTemp)
       : LInstructionHelper(classOpcode)
     {
         setOperand(0, object);
         setOperand(1, value);
         setTemp(0, temp);
+        setTemp(1, spectreTemp);
     }
 
     const MArrayPush* mir() const {
         return mir_->toArrayPush();
     }
     const LAllocation* object() {
         return getOperand(0);
     }
     const LAllocation* value() {
         return getOperand(1);
     }
     const LDefinition* temp() {
         return getTemp(0);
     }
+    const LDefinition* spectreTemp() {
+        return getTemp(1);
+    }
 };
 
 class LArraySlice : public LCallInstructionHelper<1, 3, 2>
 {
   public:
     LIR_HEADER(ArraySlice)
 
     LArraySlice(const LAllocation& obj, const LAllocation& begin, const LAllocation& end,