Bug 1494537: Add CacheIR stub for out-of-initialized-length-bounds assignments to arrays. r=tcampbell
authorKannan Vijayan <kvijayan@mozilla.com>
Wed, 17 Oct 2018 14:48:25 -0400
changeset 500253 7a7d5508f873579944aee993dbf0007a5503f94f
parent 500252 200b836d3aa5a1375edebcec36f8afa3c0cb24c0
child 500254 3a58f94c3f5ad656c0c96abf87d0522c3066690e
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstcampbell
bugs1494537
milestone64.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 1494537: Add CacheIR stub for out-of-initialized-length-bounds assignments to arrays. r=tcampbell
js/src/builtin/Array.cpp
js/src/builtin/Array.h
js/src/jit-test/tests/cacheir/bug1494537.js
js/src/jit/BaselineCacheIRCompiler.cpp
js/src/jit/CacheIR.cpp
js/src/jit/CacheIR.h
js/src/jit/CacheIRCompiler.cpp
js/src/jit/CacheIRCompiler.h
js/src/jit/IonCacheIRCompiler.cpp
js/src/jit/VMFunctions.cpp
js/src/jit/VMFunctions.h
js/src/vm/NativeObject.cpp
js/src/vm/NativeObject.h
js/src/vm/Shape.h
js/src/vm/ShapedObject.h
--- a/js/src/builtin/Array.cpp
+++ b/js/src/builtin/Array.cpp
@@ -407,19 +407,16 @@ js::GetElementsWithAdder(JSContext* cx, 
         if (!adder->append(cx, val)) {
             return false;
         }
     }
 
     return true;
 }
 
-static bool
-ObjectMayHaveExtraIndexedProperties(JSObject* obj);
-
 static inline bool
 IsPackedArrayOrNoExtraIndexedProperties(JSObject* obj, uint64_t length)
 {
     return (IsPackedArray(obj) && obj->as<ArrayObject>().length() == length) ||
            !ObjectMayHaveExtraIndexedProperties(obj);
 }
 
 static bool
@@ -1045,18 +1042,18 @@ ObjectMayHaveExtraIndexedOwnProperties(J
                              obj->getClass(), INT_TO_JSID(0), obj);
 }
 
 /*
  * Whether obj may have indexed properties anywhere besides its dense
  * elements. This includes other indexed properties in its shape hierarchy, and
  * indexed properties or elements along its prototype chain.
  */
-static bool
-ObjectMayHaveExtraIndexedProperties(JSObject* obj)
+bool
+js::ObjectMayHaveExtraIndexedProperties(JSObject* obj)
 {
     MOZ_ASSERT_IF(obj->hasDynamicPrototype(), !obj->isNative());
 
     if (ObjectMayHaveExtraIndexedOwnProperties(obj)) {
         return true;
     }
 
     do {
--- a/js/src/builtin/Array.h
+++ b/js/src/builtin/Array.h
@@ -191,16 +191,19 @@ ArrayConstructor(JSContext* cx, unsigned
 
 // Like Array constructor, but doesn't perform GetPrototypeFromConstructor.
 extern bool
 array_construct(JSContext* cx, unsigned argc, Value* vp);
 
 extern bool
 IsCrossRealmArrayConstructor(JSContext* cx, const Value& v, bool* result);
 
+extern bool
+ObjectMayHaveExtraIndexedProperties(JSObject* obj);
+
 class MOZ_NON_TEMPORARY_CLASS ArraySpeciesLookup final
 {
     /*
      * An ArraySpeciesLookup holds the following:
      *
      *  Array.prototype (arrayProto_)
      *      To ensure that the incoming array has the standard proto.
      *
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/cacheir/bug1494537.js
@@ -0,0 +1,125 @@
+setJitCompilerOption("ion.forceinlineCaches", 1);
+
+let offsets = [213, 559, 255, 515, 30, 507, 252, 329, 487, 7];
+
+function update_index(i, j) {
+    var offset = offsets[j % offsets.length];
+    return i + offset;
+}
+
+function compute_index(initial, count) {
+    for (var i = 0; i < count; i++) {
+        initial = update_index(initial, i);
+    }
+    return initial;
+}
+
+// This is written so that the IC added in the bug activates.
+function mutate_array(array, count, epsilon = 0) {
+    var index = 0;
+    for (var i = 0; i < count; i++) {
+        index = update_index(index, i);
+        array[index] = i + epsilon;
+    }
+    return array[offsets[0]+offsets[1]] === (1 + epsilon) &&
+           array[10] === undefined;
+}
+
+// Monomorphizing mutate_array to ensure we get the IC chains we want
+function create_variant(variant) {
+    var source = mutate_array.toString().replace("mutate_array", "mutate_array_"+variant);
+    return source;
+}
+
+function test_basic() {
+    eval(create_variant("basic"));
+    var x = [];
+
+    var count = 100;
+    assertEq(mutate_array_basic(x, count), true);
+    var end = compute_index(0, count);
+    assertEq(x[end], count - 1);
+    assertEq(x[end - 1], undefined);
+}
+
+// Ensure the IC respects frozen.
+function test_frozen() {
+    eval(create_variant("frozen"));
+    var x = [];
+    Object.freeze(x);
+
+    var count = 100;
+    assertEq(mutate_array_frozen(x, count), false);
+    assertEq(x.length, 0);
+
+    var end = compute_index(0, count);
+
+    var y = [];
+    assertEq(mutate_array_frozen(y, count), true);
+    assertEq(y[end], count - 1);
+    Object.freeze(y);
+
+    // After a mutated array is frozen, can't subsequently modify elements
+    assertEq(mutate_array_frozen(x, count, 10), false);
+    assertEq(y[end], count - 1);
+}
+
+// Let's make sure updates to the array happen as expected.
+function test_update() {
+    eval(create_variant("update"));
+
+    var x = [];
+    var count = 100;
+    assertEq(mutate_array_update(x, count), true);
+    var end = compute_index(0, count);
+    assertEq(x[end], count - 1);
+    assertEq(x[end - 1], undefined);
+
+    var epsilon = 2;
+    mutate_array_update(x, 200, epsilon);
+    assertEq(x[end], count -1 + epsilon)
+}
+
+// Elements may be non-writable, let us not write them.
+function test_nonwritable() {
+    eval(create_variant("nonwritable"));
+    var x = [];
+    var count = 100;
+    var index = compute_index(0, 10);
+    Object.defineProperty(x, index, {value: -10, writable: false});
+    mutate_array_nonwritable(x, count);
+    assertEq(x[index], -10);
+}
+
+// Random indices can get setters, let's make sure we honour those.
+function test_setter() {
+    eval(create_variant("setter"));
+    var x = [];
+    var count = 100;
+    var index = compute_index(0, 80);
+    var sigil = 0;
+    Object.defineProperty(x, index, {set(newVal) {sigil++; }});
+    mutate_array_setter(x, count);
+    assertEq(sigil, 1);
+    assertEq(x[index], undefined);
+}
+
+// Ensure indexes on the prototype don't break things;
+//
+function test_proto_indices() {
+    eval(create_variant("proto_indices"));
+    var x = [];
+    var count = 100;
+    var index = compute_index(0, 80);
+    x.__proto__[index] = "hello";
+    mutate_array_proto_indices(x, count);
+    assertEq(x.__proto__[index], "hello");
+    assertEq(x[index], 79);
+}
+
+test_basic();
+test_frozen();
+test_update();
+test_nonwritable();
+test_setter();
+test_proto_indices();
--- a/js/src/jit/BaselineCacheIRCompiler.cpp
+++ b/js/src/jit/BaselineCacheIRCompiler.cpp
@@ -1875,16 +1875,43 @@ BaselineCacheIRCompiler::emitCallProxySe
         return false;
     }
 
     stubFrame.leave(masm);
     return true;
 }
 
 bool
+BaselineCacheIRCompiler::emitCallAddOrUpdateSparseElementHelper()
+{
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    Register id = allocator.useRegister(masm, reader.int32OperandId());
+    ValueOperand val = allocator.useValueRegister(masm, reader.valOperandId());
+    bool strict = reader.readBool();
+    AutoScratchRegister scratch(allocator, masm);
+
+    allocator.discardStack(masm);
+
+    AutoStubFrame stubFrame(*this);
+    stubFrame.enter(masm, scratch);
+
+    masm.Push(Imm32(strict));
+    masm.Push(val);
+    masm.Push(id);
+    masm.Push(obj);
+
+    if (!callVM(masm, AddOrUpdateSparseElementHelperInfo)) {
+        return false;
+    }
+    stubFrame.leave(masm);
+    return true;
+}
+
+
+bool
 BaselineCacheIRCompiler::emitMegamorphicSetElement()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     ValueOperand idVal = allocator.useValueRegister(masm, reader.valOperandId());
     ValueOperand val = allocator.useValueRegister(masm, reader.valOperandId());
     bool strict = reader.readBool();
 
     allocator.discardStack(masm);
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -3445,16 +3445,19 @@ SetPropIRGenerator::tryAttachStub()
                 return true;
             }
             if (tryAttachSetDenseElementHole(obj, objId, index, indexId, rhsValId)) {
                 return true;
             }
             if (tryAttachSetTypedElement(obj, objId, index, indexId, rhsValId)) {
                 return true;
             }
+            if (tryAttachAddOrUpdateSparseElement(obj, objId, index, indexId, rhsValId)) {
+                return true;
+            }
             return false;
         }
         return false;
     }
     return false;
 }
 
 static void
@@ -4061,16 +4064,99 @@ SetPropIRGenerator::tryAttachSetDenseEle
 
     // Type inference uses JSID_VOID for the element types.
     typeCheckInfo_.set(nobj->group(), JSID_VOID);
 
     trackAttached(isAdd ? "AddDenseElement" : "StoreDenseElementHole");
     return true;
 }
 
+// Add an IC for adding or updating a sparse array element.
+bool
+SetPropIRGenerator::tryAttachAddOrUpdateSparseElement(HandleObject obj, ObjOperandId objId,
+                                                      uint32_t index, Int32OperandId indexId,
+                                                      ValOperandId rhsId)
+{
+    JSOp op = JSOp(*pc_);
+    MOZ_ASSERT(IsPropertySetOp(op) || IsPropertyInitOp(op));
+
+    if (op != JSOP_SETELEM && op != JSOP_STRICTSETELEM) {
+        return false;
+    }
+
+    if (!obj->isNative()) {
+        return false;
+    }
+    RootedNativeObject nobj(cx_, &obj->as<NativeObject>());
+
+    // We cannot attach a stub to a non-extensible object
+    if (!nobj->isExtensible()) {
+        return false;
+    }
+
+    // Stub doesn't handle negative indices.
+    if (index > INT_MAX) {
+        return false;
+    }
+
+    // We also need to be past the end of the dense capacity, to ensure sparse.
+    if (index < nobj->getDenseInitializedLength()) {
+        return false;
+    }
+
+    // Only handle Array objects in this stub.
+    if (!nobj->is<ArrayObject>()) {
+        return false;
+    }
+    RootedArrayObject aobj(cx_, &obj->as<ArrayObject>());
+
+    // Don't attach if we're adding to an array with non-writable length.
+    bool isAdd = (index >= aobj->length());
+    if (isAdd && !aobj->lengthIsWritable()) {
+        return false;
+    }
+
+    // Indexed properties on the prototype chain aren't handled by the helper.
+    if (ObjectMayHaveExtraIndexedProperties(aobj->staticPrototype())) {
+        return false;
+    }
+
+    // Ensure we are still talking about an array class.
+    writer.guardClass(objId, GuardClassKind::Array);
+
+    // The helper we are going to call only applies to non-dense elements.
+    writer.guardIndexGreaterThanDenseInitLength(objId, indexId);
+
+    // Guard extensible: We may be trying to add a new element, and so we'd best
+    // be able to do so safely.
+    writer.guardIsExtensible(objId);
+
+    // Ensures we are able to efficiently able to map to an integral jsid.
+    writer.guardIndexIsNonNegative(indexId);
+
+    // Shape guard the prototype chain to avoid shadowing indexes from appearing.
+    // Dense elements may appear on the prototype chain (and prototypes may
+    // have a different notion of which elements are dense), but they can
+    // only be data properties, so our specialized Set handler is ok to bind
+    // to them.
+    ShapeGuardProtoChain(writer, obj, objId);
+
+    // Ensure that if we're adding an element to the object, the object's
+    // length is writable.
+    writer.guardIndexIsValidUpdateOrAdd(objId, indexId);
+
+    writer.callAddOrUpdateSparseElementHelper(objId, indexId, rhsId,
+                                              /* strict = */op == JSOP_STRICTSETELEM);
+    writer.returnFromIC();
+
+    trackAttached("AddOrUpdateSparseElement");
+    return true;
+}
+
+
 bool
 SetPropIRGenerator::tryAttachSetTypedElement(HandleObject obj, ObjOperandId objId,
                                              uint32_t index, Int32OperandId indexId,
                                              ValOperandId rhsId)
 {
     if (!obj->is<TypedArrayObject>() && !IsPrimitiveArrayTypedObject(obj)) {
         return false;
     }
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -196,16 +196,17 @@ extern const char* const CacheKindNames[
     _(GuardIsInt32Index)                  \
     _(GuardType)                          \
     _(GuardShape)                         \
     _(GuardGroup)                         \
     _(GuardProto)                         \
     _(GuardClass)                         /* Guard an object class, per GuardClassKind */ \
     _(GuardAnyClass)                      /* Guard an arbitrary class for an object */ \
     _(GuardCompartment)                   \
+    _(GuardIsExtensible)                  \
     _(GuardIsNativeFunction)              \
     _(GuardIsNativeObject)                \
     _(GuardIsProxy)                       \
     _(GuardHasProxyHandler)               \
     _(GuardNotDOMProxy)                   \
     _(GuardSpecificObject)                \
     _(GuardSpecificAtom)                  \
     _(GuardSpecificSymbol)                \
@@ -217,16 +218,19 @@ extern const char* const CacheKindNames[
     _(GuardNoUnboxedExpando)              \
     _(GuardAndLoadUnboxedExpando)         \
     _(GuardAndGetIndexFromString)         \
     _(GuardAndGetNumberFromString)        \
     _(GuardAndGetIterator)                \
     _(GuardHasGetterSetter)               \
     _(GuardGroupHasUnanalyzedNewScript)   \
     _(GuardIndexIsNonNegative)            \
+    _(GuardIndexGreaterThanDenseCapacity) \
+    _(GuardIndexGreaterThanArrayLength)   \
+    _(GuardIndexIsValidUpdateOrAdd)       \
     _(GuardIndexGreaterThanDenseInitLength) \
     _(GuardTagNotEqual)                   \
     _(GuardXrayExpandoShapeAndDefaultProto) \
     _(GuardFunctionPrototype)             \
     _(GuardNoAllocationMetadataBuilder)   \
     _(GuardObjectGroupNotPretenured)      \
     _(LoadStackValue)                     \
     _(LoadObject)                         \
@@ -262,16 +266,17 @@ extern const char* const CacheKindNames[
     _(ArrayPush)                          \
     _(ArrayJoinResult)                    \
     _(StoreTypedElement)                  \
     _(CallNativeSetter)                   \
     _(CallScriptedSetter)                 \
     _(CallSetArrayLength)                 \
     _(CallProxySet)                       \
     _(CallProxySetByValue)                \
+    _(CallAddOrUpdateSparseElementHelper) \
     _(CallInt32ToString)                  \
     _(CallNumberToString)                 \
                                           \
     /* The *Result ops load a value into the cache's result register. */ \
     _(LoadFixedSlotResult)                \
     _(LoadDynamicSlotResult)              \
     _(LoadUnboxedPropertyResult)          \
     _(LoadTypedObjectResult)              \
@@ -760,16 +765,19 @@ class MOZ_RAII CacheIRWriter : public JS
     void guardCompartment(ObjOperandId obj, JSObject* global, JS::Compartment* compartment) {
         assertSameCompartment(global);
         writeOpWithOperandId(CacheOp::GuardCompartment, obj);
         // Add a reference to a global in the compartment to keep it alive.
         addStubField(uintptr_t(global), StubField::Type::JSObject);
         // Use RawWord, because compartments never move and it can't be GCed.
         addStubField(uintptr_t(compartment), StubField::Type::RawWord);
     }
+    void guardIsExtensible(ObjOperandId obj) {
+        writeOpWithOperandId(CacheOp::GuardIsExtensible, obj);
+    }
     void guardNoDetachedTypedObjects() {
         writeOp(CacheOp::GuardNoDetachedTypedObjects);
     }
     void guardFrameHasNoArgumentsObject() {
         writeOp(CacheOp::GuardFrameHasNoArgumentsObject);
     }
 
     Int32OperandId guardAndGetIndexFromString(StringOperandId str) {
@@ -806,16 +814,28 @@ class MOZ_RAII CacheIRWriter : public JS
 
     void guardIndexIsNonNegative(Int32OperandId index) {
         writeOpWithOperandId(CacheOp::GuardIndexIsNonNegative, index);
     }
     void guardIndexGreaterThanDenseInitLength(ObjOperandId obj, Int32OperandId index) {
         writeOpWithOperandId(CacheOp::GuardIndexGreaterThanDenseInitLength, obj);
         writeOperandId(index);
     }
+    void guardIndexGreaterThanDenseCapacity(ObjOperandId obj, Int32OperandId index) {
+        writeOpWithOperandId(CacheOp::GuardIndexGreaterThanDenseCapacity, obj);
+        writeOperandId(index);
+    }
+    void guardIndexGreaterThanArrayLength(ObjOperandId obj, Int32OperandId index) {
+        writeOpWithOperandId(CacheOp::GuardIndexGreaterThanArrayLength, obj);
+        writeOperandId(index);
+    }
+    void guardIndexIsValidUpdateOrAdd(ObjOperandId obj, Int32OperandId index) {
+        writeOpWithOperandId(CacheOp::GuardIndexIsValidUpdateOrAdd, obj);
+        writeOperandId(index);
+    }
     void guardTagNotEqual(ValueTagOperandId lhs, ValueTagOperandId rhs) {
         writeOpWithOperandId(CacheOp::GuardTagNotEqual, lhs);
         writeOperandId(rhs);
     }
 
     void loadFrameCalleeResult() {
         writeOp(CacheOp::LoadFrameCalleeResult);
     }
@@ -1036,16 +1056,22 @@ class MOZ_RAII CacheIRWriter : public JS
         buffer_.writeByte(uint32_t(strict));
     }
     void callProxySetByValue(ObjOperandId obj, ValOperandId id, ValOperandId rhs, bool strict) {
         writeOpWithOperandId(CacheOp::CallProxySetByValue, obj);
         writeOperandId(id);
         writeOperandId(rhs);
         buffer_.writeByte(uint32_t(strict));
     }
+    void callAddOrUpdateSparseElementHelper(ObjOperandId obj, Int32OperandId id, ValOperandId rhs, bool strict) {
+        writeOpWithOperandId(CacheOp::CallAddOrUpdateSparseElementHelper, obj);
+        writeOperandId(id);
+        writeOperandId(rhs);
+        buffer_.writeByte(uint32_t(strict));
+    }
     StringOperandId callInt32ToString(Int32OperandId id) {
         StringOperandId res(nextOperandId_++);
         writeOpWithOperandId(CacheOp::CallInt32ToString, id);
         writeOperandId(res);
         return res;
     }
     StringOperandId callNumberToString(ValOperandId id) {
         StringOperandId res(nextOperandId_++);
@@ -1748,16 +1774,20 @@ class MOZ_RAII SetPropIRGenerator : publ
     bool tryAttachSetDenseElement(HandleObject obj, ObjOperandId objId, uint32_t index,
                                   Int32OperandId indexId, ValOperandId rhsId);
     bool tryAttachSetTypedElement(HandleObject obj, ObjOperandId objId, uint32_t index,
                                   Int32OperandId indexId, ValOperandId rhsId);
 
     bool tryAttachSetDenseElementHole(HandleObject obj, ObjOperandId objId, uint32_t index,
                                       Int32OperandId indexId, ValOperandId rhsId);
 
+    bool tryAttachAddOrUpdateSparseElement(HandleObject obj, ObjOperandId objId, uint32_t index,
+                                           Int32OperandId indexId, ValOperandId rhsId);
+
+
     bool tryAttachGenericProxy(HandleObject obj, ObjOperandId objId, HandleId id,
                                ValOperandId rhsId, bool handleDOMProxies);
     bool tryAttachDOMProxyShadowed(HandleObject obj, ObjOperandId objId, HandleId id,
                                    ValOperandId rhsId);
     bool tryAttachDOMProxyUnshadowed(HandleObject obj, ObjOperandId objId, HandleId id,
                                      ValOperandId rhsId);
     bool tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objId, HandleId id,
                                   ValOperandId rhsId);
--- a/js/src/jit/CacheIRCompiler.cpp
+++ b/js/src/jit/CacheIRCompiler.cpp
@@ -1730,16 +1730,45 @@ CacheIRCompiler::emitGuardClass()
         masm.branchTestObjClassNoSpectreMitigations(Assembler::NotEqual, obj, clasp, scratch,
                                                     failure->label());
     }
 
     return true;
 }
 
 bool
+CacheIRCompiler::emitGuardIsExtensible()
+{
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    AutoScratchRegister scratch(allocator, masm);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure)) {
+        return false;
+    }
+
+    Address shape(obj, ShapedObject::offsetOfShape());
+    masm.loadPtr(shape, scratch);
+
+    Address baseShape(scratch, Shape::offsetOfBaseShape());
+    masm.loadPtr(baseShape, scratch);
+
+    Address baseShapeFlags(scratch, BaseShape::offsetOfFlags());
+    masm.loadPtr(baseShapeFlags, scratch);
+
+    masm.and32(Imm32(js::BaseShape::NOT_EXTENSIBLE), scratch);
+
+    // Spectre-style checks are not needed here because we do not
+    // interpret data based on this check.
+    masm.branch32(Assembler::Equal, scratch, Imm32(js::BaseShape::NOT_EXTENSIBLE),
+                  failure->label());
+    return true;
+}
+
+bool
 CacheIRCompiler::emitGuardIsNativeFunction()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     JSNative nativeFunc = reinterpret_cast<JSNative>(reader.pointer());
     AutoScratchRegister scratch(allocator, masm);
 
     FailurePath* failure;
     if (!addFailurePath(&failure)) {
@@ -2831,27 +2860,110 @@ CacheIRCompiler::emitGuardIndexGreaterTh
     FailurePath* failure;
     if (!addFailurePath(&failure)) {
         return false;
     }
 
     // Load obj->elements.
     masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
 
-    // Ensure index >= capacity.
+    // Ensure index >= initLength.
     Label outOfBounds;
     Address capacity(scratch, ObjectElements::offsetOfInitializedLength());
     masm.spectreBoundsCheck32(index, capacity, scratch2, &outOfBounds);
     masm.jump(failure->label());
     masm.bind(&outOfBounds);
 
     return true;
 }
 
 bool
+CacheIRCompiler::emitGuardIndexGreaterThanDenseCapacity()
+{
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    Register index = allocator.useRegister(masm, reader.int32OperandId());
+    AutoScratchRegister scratch(allocator, masm);
+    AutoScratchRegister scratch2(allocator, masm);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure)) {
+        return false;
+    }
+
+    // Load obj->elements.
+    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
+
+    // Ensure index >= capacity.
+    Label outOfBounds;
+    Address capacity(scratch, ObjectElements::offsetOfCapacity());
+    masm.spectreBoundsCheck32(index, capacity, scratch2, &outOfBounds);
+    masm.jump(failure->label());
+    masm.bind(&outOfBounds);
+
+    return true;
+}
+
+bool
+CacheIRCompiler::emitGuardIndexGreaterThanArrayLength()
+{
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    Register index = allocator.useRegister(masm, reader.int32OperandId());
+    AutoScratchRegister scratch(allocator, masm);
+    AutoScratchRegister scratch2(allocator, masm);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure)) {
+        return false;
+    }
+
+    // Load obj->elements.
+    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
+
+    // Ensure index >= length;
+    Label outOfBounds;
+    Address length(scratch, ObjectElements::offsetOfLength());
+    masm.spectreBoundsCheck32(index, length, scratch2, &outOfBounds);
+    masm.jump(failure->label());
+    masm.bind(&outOfBounds);
+    return true;
+}
+
+bool
+CacheIRCompiler::emitGuardIndexIsValidUpdateOrAdd()
+{
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    Register index = allocator.useRegister(masm, reader.int32OperandId());
+    AutoScratchRegister scratch(allocator, masm);
+    AutoScratchRegister scratch2(allocator, masm);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure)) {
+        return false;
+    }
+
+    // Load obj->elements.
+    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
+
+    Label success;
+
+    // If length is writable, branch to &success.  All indices are writable.
+    Address flags(scratch, ObjectElements::offsetOfFlags());
+    masm.branchTest32(Assembler::Zero, flags,
+                      Imm32(ObjectElements::Flags::NONWRITABLE_ARRAY_LENGTH),
+                      &success);
+
+    // Otherwise, ensure index is in bounds.
+    Address length(scratch, ObjectElements::offsetOfLength());
+    masm.spectreBoundsCheck32(index, length, scratch2,
+                              /* failure = */ failure->label());
+    masm.bind(&success);
+    return true;
+}
+
+bool
 CacheIRCompiler::emitGuardTagNotEqual()
 {
     Register lhs = allocator.useRegister(masm, reader.valueTagOperandId());
     Register rhs = allocator.useRegister(masm, reader.valueTagOperandId());
 
     FailurePath* failure;
     if (!addFailurePath(&failure)) {
         return false;
--- a/js/src/jit/CacheIRCompiler.h
+++ b/js/src/jit/CacheIRCompiler.h
@@ -27,30 +27,34 @@ namespace jit {
     _(GuardIsString)                      \
     _(GuardIsSymbol)                      \
     _(GuardIsNumber)                      \
     _(GuardIsInt32)                       \
     _(GuardIsInt32Index)                  \
     _(GuardType)                          \
     _(GuardClass)                         \
     _(GuardGroupHasUnanalyzedNewScript)   \
+    _(GuardIsExtensible)                  \
     _(GuardIsNativeFunction)              \
     _(GuardFunctionPrototype)             \
     _(GuardIsNativeObject)                \
     _(GuardIsProxy)                       \
     _(GuardNotDOMProxy)                   \
     _(GuardSpecificInt32Immediate)        \
     _(GuardMagicValue)                    \
     _(GuardNoUnboxedExpando)              \
     _(GuardAndLoadUnboxedExpando)         \
     _(GuardNoDetachedTypedObjects)        \
     _(GuardNoDenseElements)               \
     _(GuardAndGetNumberFromString)        \
     _(GuardAndGetIndexFromString)         \
     _(GuardIndexIsNonNegative)            \
+    _(GuardIndexGreaterThanDenseCapacity) \
+    _(GuardIndexGreaterThanArrayLength)   \
+    _(GuardIndexIsValidUpdateOrAdd)       \
     _(GuardIndexGreaterThanDenseInitLength) \
     _(GuardTagNotEqual)                   \
     _(GuardXrayExpandoShapeAndDefaultProto)\
     _(GuardNoAllocationMetadataBuilder)   \
     _(GuardObjectGroupNotPretenured)      \
     _(LoadObject)                         \
     _(LoadProto)                          \
     _(LoadEnclosingEnvironment)           \
--- a/js/src/jit/IonCacheIRCompiler.cpp
+++ b/js/src/jit/IonCacheIRCompiler.cpp
@@ -2261,16 +2261,38 @@ IonCacheIRCompiler::emitCallProxySetByVa
     masm.Push(val);
     masm.Push(idVal);
     masm.Push(obj);
 
     return callVM(masm, ProxySetPropertyByValueInfo);
 }
 
 bool
+IonCacheIRCompiler::emitCallAddOrUpdateSparseElementHelper()
+{
+    AutoSaveLiveRegisters save(*this);
+
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    Register id = allocator.useRegister(masm, reader.int32OperandId());
+    ValueOperand val = allocator.useValueRegister(masm, reader.valOperandId());
+    bool strict = reader.readBool();
+
+    Label done;
+    prepareVMCall(masm, save);
+
+    masm.Push(Imm32(strict));
+    masm.Push(val);
+    masm.Push(id);
+    masm.Push(obj);
+
+    return callVM(masm, AddOrUpdateSparseElementHelperInfo);
+}
+
+
+bool
 IonCacheIRCompiler::emitMegamorphicSetElement()
 {
     AutoSaveLiveRegisters save(*this);
 
     Register obj = allocator.useRegister(masm, reader.objOperandId());
     ConstantOrRegister idVal = allocator.useConstantOrRegister(masm, reader.valOperandId());
     ConstantOrRegister val = allocator.useConstantOrRegister(masm, reader.valOperandId());
     bool strict = reader.readBool();
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -2067,10 +2067,15 @@ const VMFunction ProxyHasInfo = Function
 typedef bool (*ProxyHasOwnFn)(JSContext*, HandleObject, HandleValue, MutableHandleValue);
 const VMFunction ProxyHasOwnInfo = FunctionInfo<ProxyHasOwnFn>(ProxyHasOwn, "ProxyHasOwn");
 
 typedef bool (*NativeGetElementFn)(JSContext*, HandleNativeObject, HandleValue, int32_t,
                                    MutableHandleValue);
 const VMFunction NativeGetElementInfo =
     FunctionInfo<NativeGetElementFn>(NativeGetElement, "NativeGetProperty");
 
+typedef bool (*AddOrUpdateSparseElementHelperFn)(JSContext* cx, HandleArrayObject obj,
+                                                 int32_t int_id, HandleValue v, bool strict);
+const VMFunction AddOrUpdateSparseElementHelperInfo =
+    FunctionInfo<AddOrUpdateSparseElementHelperFn>(AddOrUpdateSparseElementHelper, "AddOrUpdateSparseElementHelper");
+
 } // namespace jit
 } // namespace js
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -977,15 +977,17 @@ extern const VMFunction ProxyGetProperty
 extern const VMFunction ProxyGetPropertyByValueInfo;
 extern const VMFunction ProxySetPropertyInfo;
 extern const VMFunction ProxySetPropertyByValueInfo;
 extern const VMFunction ProxyHasInfo;
 extern const VMFunction ProxyHasOwnInfo;
 
 extern const VMFunction NativeGetElementInfo;
 
+extern const VMFunction AddOrUpdateSparseElementHelperInfo;
+
 // TailCall VMFunctions
 extern const VMFunction DoConcatStringObjectInfo;
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_VMFunctions_h */
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -2102,16 +2102,58 @@ DefineNonexistentProperty(JSContext* cx,
         if (!AddDataProperty(cx, obj, id, v)) {
             return false;
         }
     }
 
     return result.succeed();
 }
 
+bool
+js::AddOrUpdateSparseElementHelper(JSContext* cx, HandleArrayObject obj, int32_t int_id,
+                                   HandleValue v, bool strict)
+{
+    MOZ_ASSERT(INT_FITS_IN_JSID(int_id));
+    RootedId id(cx, INT_TO_JSID(int_id));
+
+    // This helper doesn't handle the case where the index may be in the dense elements
+    MOZ_ASSERT(int_id >= 0);
+    MOZ_ASSERT(uint32_t(int_id) >= obj->getDenseInitializedLength());
+
+    // First decide if this is an add or an update. Because the IC guards have
+    // already ensured this exists exterior to the dense array range, and the
+    // prototype checks have ensured there are no indexes on the prototype, we
+    // can use the shape lineage to find the element if it exists:
+    RootedShape shape(cx, obj->lastProperty()->search(cx, id));
+
+    // If we didn't find the shape, we're on the add path: delegate to
+    // AddSparseElement:
+    if (shape == nullptr) {
+        Rooted<PropertyDescriptor> desc(cx);
+        desc.setDataDescriptor(v, JSPROP_ENUMERATE);
+        desc.assertComplete();
+
+        return AddOrChangeProperty<IsAddOrChange::Add>(cx, obj, id, desc);
+    }
+
+    // At this point we're updating a property: See SetExistingProperty
+    if (shape->writable() && shape->isDataProperty()) {
+        // While all JSID_INT properties use a single TI entry,
+        // nothing yet has inspected the updated value so we *must* use setSlotWithType().
+        obj->setSlotWithType(cx, shape, v, /* overwriting = */ true);
+        return true;
+    }
+
+    // We don't know exactly what this object looks like, hit the slowpath.
+    RootedValue receiver(cx, ObjectValue(*obj));
+    JS::ObjectOpResult result;
+    return SetProperty(cx, obj, id, v, receiver, result) &&
+           result.checkStrictErrorOrWarning(cx, obj, id, strict);
+}
+
 
 /*** [[HasProperty]] *****************************************************************************/
 
 // ES6 draft rev31 9.1.7.1 OrdinaryHasProperty
 bool
 js::NativeHasProperty(JSContext* cx, HandleNativeObject obj, HandleId id, bool* foundp)
 {
     RootedNativeObject pobj(cx, obj);
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -1617,16 +1617,20 @@ NativeGetElement(JSContext* cx, HandleNa
 bool
 SetPropertyByDefining(JSContext* cx, HandleId id, HandleValue v, HandleValue receiver,
                       ObjectOpResult& result);
 
 bool
 SetPropertyOnProto(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
                    HandleValue receiver, ObjectOpResult& result);
 
+bool
+AddOrUpdateSparseElementHelper(JSContext* cx, HandleArrayObject obj, int32_t int_id,
+                               HandleValue v, bool strict);
+
 /*
  * Indicates whether an assignment operation is qualified (`x.y = 0`) or
  * unqualified (`y = 0`). In strict mode, the latter is an error if no such
  * variable already exists.
  *
  * Used as an argument to NativeSetProperty.
  */
 enum QualifiedBool {
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -1154,18 +1154,20 @@ class Shape : public gc::TenuredCell
 
     MOZ_ALWAYS_INLINE Shape* search(JSContext* cx, jsid id);
     MOZ_ALWAYS_INLINE Shape* searchLinear(jsid id);
 
     void fixupAfterMovingGC();
     void fixupGetterSetterForBarrier(JSTracer* trc);
     void updateBaseShapeAfterMovingGC();
 
+    // For JIT usage.
+    static inline size_t offsetOfBaseShape() { return offsetof(Shape, base_); }
+
 #ifdef DEBUG
-    // For JIT usage.
     static inline size_t offsetOfImmutableFlags() { return offsetof(Shape, immutableFlags); }
     static inline uint32_t fixedSlotsMask() { return FIXED_SLOTS_MASK; }
 #endif
 
   private:
     void fixupDictionaryShapeAfterMovingGC();
     void fixupShapeTreeAfterMovingGC();
 
--- a/js/src/vm/ShapedObject.h
+++ b/js/src/vm/ShapedObject.h
@@ -6,16 +6,18 @@
 
 #ifndef vm_ShapedObject_h
 #define vm_ShapedObject_h
 
 #include "vm/JSObject.h"
 
 namespace js {
 
+namespace jit { class CacheIRCompiler; }
+
 /*
  * Shaped objects are a variant of JSObject that use a GCPtrShape for their
  * |shapeOrExpando_| field. All objects that point to a js::Shape as their
  * |shapeOrExpando_| field should use this as their subclass.
  *
  * NOTE: shape()->getObjectClass() must equal getClass().
  */
 class ShapedObject : public JSObject
@@ -53,16 +55,18 @@ class ShapedObject : public JSObject
     static JSObject* fromShapeFieldPointer(uintptr_t p) {
         return reinterpret_cast<JSObject*>(p - ShapedObject::offsetOfShape());
     }
 
   private:
     // See JSObject::offsetOfGroup() comment.
     friend class js::jit::MacroAssembler;
 
+    friend class js::jit::CacheIRCompiler;
+
     static constexpr size_t offsetOfShape() {
         static_assert(offsetOfShapeOrExpando() == offsetof(shadow::Object, shape),
                       "shadow shape must match actual shape");
         return offsetOfShapeOrExpando();
     }
 };
 
 } // namespace js