mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-21 01:05:45 +00:00
Bug 1494537: Add CacheIR stub for out-of-initialized-length-bounds assignments to arrays. r=tcampbell
This commit is contained in:
parent
cd852e3dfb
commit
54485b65bc
@ -412,9 +412,6 @@ js::GetElementsWithAdder(JSContext* cx, HandleObject obj, HandleObject receiver,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ObjectMayHaveExtraIndexedProperties(JSObject* obj);
|
||||
|
||||
static inline bool
|
||||
IsPackedArrayOrNoExtraIndexedProperties(JSObject* obj, uint64_t length)
|
||||
{
|
||||
@ -1050,8 +1047,8 @@ ObjectMayHaveExtraIndexedOwnProperties(JSObject* obj)
|
||||
* 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());
|
||||
|
||||
|
@ -196,6 +196,9 @@ 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
|
||||
{
|
||||
/*
|
||||
|
125
js/src/jit-test/tests/cacheir/bug1494537.js
Normal file
125
js/src/jit-test/tests/cacheir/bug1494537.js
Normal file
@ -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();
|
@ -1879,6 +1879,33 @@ BaselineCacheIRCompiler::emitCallProxySetByValue()
|
||||
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()
|
||||
{
|
||||
|
@ -3450,6 +3450,9 @@ SetPropIRGenerator::tryAttachStub()
|
||||
if (tryAttachSetTypedElement(obj, objId, index, indexId, rhsValId)) {
|
||||
return true;
|
||||
}
|
||||
if (tryAttachAddOrUpdateSparseElement(obj, objId, index, indexId, rhsValId)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
@ -4066,6 +4069,89 @@ SetPropIRGenerator::tryAttachSetDenseElementHole(HandleObject obj, ObjOperandId
|
||||
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,
|
||||
|
@ -201,6 +201,7 @@ extern const char* const CacheKindNames[];
|
||||
_(GuardClass) /* Guard an object class, per GuardClassKind */ \
|
||||
_(GuardAnyClass) /* Guard an arbitrary class for an object */ \
|
||||
_(GuardCompartment) \
|
||||
_(GuardIsExtensible) \
|
||||
_(GuardIsNativeFunction) \
|
||||
_(GuardIsNativeObject) \
|
||||
_(GuardIsProxy) \
|
||||
@ -222,6 +223,9 @@ extern const char* const CacheKindNames[];
|
||||
_(GuardHasGetterSetter) \
|
||||
_(GuardGroupHasUnanalyzedNewScript) \
|
||||
_(GuardIndexIsNonNegative) \
|
||||
_(GuardIndexGreaterThanDenseCapacity) \
|
||||
_(GuardIndexGreaterThanArrayLength) \
|
||||
_(GuardIndexIsValidUpdateOrAdd) \
|
||||
_(GuardIndexGreaterThanDenseInitLength) \
|
||||
_(GuardTagNotEqual) \
|
||||
_(GuardXrayExpandoShapeAndDefaultProto) \
|
||||
@ -267,6 +271,7 @@ extern const char* const CacheKindNames[];
|
||||
_(CallSetArrayLength) \
|
||||
_(CallProxySet) \
|
||||
_(CallProxySetByValue) \
|
||||
_(CallAddOrUpdateSparseElementHelper) \
|
||||
_(CallInt32ToString) \
|
||||
_(CallNumberToString) \
|
||||
\
|
||||
@ -765,6 +770,9 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter
|
||||
// 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);
|
||||
}
|
||||
@ -811,6 +819,18 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter
|
||||
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);
|
||||
@ -1041,6 +1061,12 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter
|
||||
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);
|
||||
@ -1753,6 +1779,10 @@ class MOZ_RAII SetPropIRGenerator : public IRGenerator
|
||||
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,
|
||||
|
@ -1734,6 +1734,35 @@ CacheIRCompiler::emitGuardClass()
|
||||
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()
|
||||
{
|
||||
@ -2836,7 +2865,7 @@ CacheIRCompiler::emitGuardIndexGreaterThanDenseInitLength()
|
||||
// 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);
|
||||
@ -2846,6 +2875,89 @@ CacheIRCompiler::emitGuardIndexGreaterThanDenseInitLength()
|
||||
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()
|
||||
{
|
||||
|
@ -32,6 +32,7 @@ namespace jit {
|
||||
_(GuardType) \
|
||||
_(GuardClass) \
|
||||
_(GuardGroupHasUnanalyzedNewScript) \
|
||||
_(GuardIsExtensible) \
|
||||
_(GuardIsNativeFunction) \
|
||||
_(GuardFunctionPrototype) \
|
||||
_(GuardIsNativeObject) \
|
||||
@ -46,6 +47,9 @@ namespace jit {
|
||||
_(GuardAndGetNumberFromString) \
|
||||
_(GuardAndGetIndexFromString) \
|
||||
_(GuardIndexIsNonNegative) \
|
||||
_(GuardIndexGreaterThanDenseCapacity) \
|
||||
_(GuardIndexGreaterThanArrayLength) \
|
||||
_(GuardIndexIsValidUpdateOrAdd) \
|
||||
_(GuardIndexGreaterThanDenseInitLength) \
|
||||
_(GuardTagNotEqual) \
|
||||
_(GuardXrayExpandoShapeAndDefaultProto)\
|
||||
|
@ -2265,6 +2265,28 @@ IonCacheIRCompiler::emitCallProxySetByValue()
|
||||
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()
|
||||
{
|
||||
|
@ -2072,5 +2072,10 @@ typedef bool (*NativeGetElementFn)(JSContext*, HandleNativeObject, HandleValue,
|
||||
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
|
||||
|
@ -982,6 +982,8 @@ extern const VMFunction ProxyHasOwnInfo;
|
||||
|
||||
extern const VMFunction NativeGetElementInfo;
|
||||
|
||||
extern const VMFunction AddOrUpdateSparseElementHelperInfo;
|
||||
|
||||
// TailCall VMFunctions
|
||||
extern const VMFunction DoConcatStringObjectInfo;
|
||||
|
||||
|
@ -2107,6 +2107,48 @@ DefineNonexistentProperty(JSContext* cx, HandleNativeObject obj, HandleId id,
|
||||
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]] *****************************************************************************/
|
||||
|
||||
|
@ -1622,6 +1622,10 @@ 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
|
||||
|
@ -1159,8 +1159,10 @@ class Shape : public gc::TenuredCell
|
||||
void fixupGetterSetterForBarrier(JSTracer* trc);
|
||||
void updateBaseShapeAfterMovingGC();
|
||||
|
||||
#ifdef DEBUG
|
||||
// For JIT usage.
|
||||
static inline size_t offsetOfBaseShape() { return offsetof(Shape, base_); }
|
||||
|
||||
#ifdef DEBUG
|
||||
static inline size_t offsetOfImmutableFlags() { return offsetof(Shape, immutableFlags); }
|
||||
static inline uint32_t fixedSlotsMask() { return FIXED_SLOTS_MASK; }
|
||||
#endif
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
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
|
||||
@ -58,6 +60,8 @@ class ShapedObject : public JSObject
|
||||
// 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");
|
||||
|
Loading…
Reference in New Issue
Block a user