--- a/js/src/builtin/Array.cpp
+++ b/js/src/builtin/Array.cpp
@@ -419,19 +419,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
@@ -1057,18 +1054,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/auto-regress/bug1500255.js
@@ -0,0 +1,9 @@
+
+setJitCompilerOption("offthread-compilation.enable", 0);
+setJitCompilerOption("ion.warmup.trigger", 100);
+
+Array.prototype.__proto__ = function() {};
+var LENGTH = 1024;
+var big = [];
+for (var i = 0; i < LENGTH; i++)
+ big[i] = i;
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
@@ -1868,16 +1868,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
@@ -3425,16 +3425,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
@@ -4039,16 +4042,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;
+ }
+ NativeObject* nobj = &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;
+ }
+ ArrayObject* aobj = &nobj->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
@@ -1726,16 +1726,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)) {
@@ -2827,27 +2856,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();
+
+ allocator.discardStack(masm);
+ 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