Bug 1131531 - Ion GetElement IC should handle dense element holes. r=jandem

This commit is contained in:
Tom Schuster 2015-02-23 18:21:51 +01:00
parent 2c54b810d8
commit 8b0b19285c
6 changed files with 217 additions and 2 deletions

View File

@ -0,0 +1,18 @@
function f(obj) {
return typeof obj[15];
}
function test() {
var a = [1, 2];
a.__proto__ = {15: 1337};
var b = [1, 2, 3, 4];
for (var i = 0; i < 1000; i++) {
var r = f(i % 2 ? a : b);
assertEq(r, i % 2 ? "number" : "undefined");
}
}
test();
test();
test();

View File

@ -0,0 +1,28 @@
function f() {
var x = [1, 2, 3];
var y = {};
x.__proto__ = y;
for (var i = 0; i < 200; i++) {
if (i == 100)
y[100000] = 15;
else
assertEq(typeof x[100000], i > 100 ? "number" : "undefined");
}
}
function g() {
var x = [1, 2, 3];
var y = {};
x.__proto__ = y;
for (var i = 0; i < 200; i++) {
if (i == 100)
y[4] = 15;
else
assertEq(typeof x[4], i > 100 ? "number" : "undefined");
}
}
f();
g();

View File

@ -3258,6 +3258,160 @@ GetElementIC::attachDenseElement(JSContext *cx, HandleScript outerScript, IonScr
return linkAndAttachStub(cx, masm, attacher, ion, "dense array");
}
/* static */ bool
GetElementIC::canAttachDenseElementHole(JSObject *obj, const Value &idval, TypedOrValueRegister output)
{
if (!idval.isInt32())
return false;
if (!output.hasValue())
return false;
if (!obj->isNative())
return false;
if (obj->as<NativeObject>().getDenseInitializedLength() == 0)
return false;
while (obj) {
if (obj->isIndexed())
return false;
if (ClassCanHaveExtraProperties(obj->getClass()))
return false;
JSObject *proto = obj->getProto();
if (!proto)
break;
if (!proto->isNative())
return false;
// Make sure objects on the prototype don't have dense elements.
if (proto->as<NativeObject>().getDenseInitializedLength() != 0)
return false;
obj = proto;
}
return true;
}
static bool
GenerateDenseElementHole(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher,
IonScript *ion, JSObject *obj, const Value &idval,
Register object, ConstantOrRegister index, TypedOrValueRegister output)
{
MOZ_ASSERT(GetElementIC::canAttachDenseElementHole(obj, idval, output));
MOZ_ASSERT(obj->lastProperty());
Register scratchReg = output.valueReg().scratchReg();
// Guard on the shape and group, to prevent non-dense elements from appearing.
Label failures;
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
Address(object, JSObject::offsetOfShape()),
ImmGCPtr(obj->lastProperty()), &failures);
if (obj->hasUncacheableProto()) {
masm.loadPtr(Address(object, JSObject::offsetOfGroup()), scratchReg);
Address proto(scratchReg, ObjectGroup::offsetOfProto());
masm.branchPtr(Assembler::NotEqual, proto,
ImmMaybeNurseryPtr(obj->getProto()), &failures);
}
JSObject *pobj = obj->getProto();
while (pobj) {
MOZ_ASSERT(pobj->lastProperty());
masm.movePtr(ImmMaybeNurseryPtr(pobj), scratchReg);
if (pobj->hasUncacheableProto()) {
MOZ_ASSERT(!pobj->isSingleton());
Address groupAddr(scratchReg, JSObject::offsetOfGroup());
masm.branchPtr(Assembler::NotEqual, groupAddr, ImmGCPtr(pobj->group()), &failures);
}
// Make sure the shape matches, to avoid non-dense elements.
masm.branchPtr(Assembler::NotEqual, Address(scratchReg, JSObject::offsetOfShape()),
ImmGCPtr(pobj->lastProperty()), &failures);
// Load elements vector.
masm.loadPtr(Address(scratchReg, NativeObject::offsetOfElements()), scratchReg);
// Also make sure there are no dense elements.
Label hole;
Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength());
masm.branch32(Assembler::NotEqual, initLength, Imm32(0), &failures);
pobj = pobj->getProto();
}
// Ensure the index is an int32 value.
Register indexReg = InvalidReg;
Register elementsReg = InvalidReg;
if (index.reg().hasValue()) {
indexReg = scratchReg;
MOZ_ASSERT(indexReg != InvalidReg);
ValueOperand val = index.reg().valueReg();
masm.branchTestInt32(Assembler::NotEqual, val, &failures);
// Unbox the index.
masm.unboxInt32(val, indexReg);
// Save the object register.
masm.push(object);
elementsReg = object;
} else {
MOZ_ASSERT(!index.reg().typedReg().isFloat());
indexReg = index.reg().typedReg().gpr();
elementsReg = scratchReg;
}
// Load elements vector.
masm.loadPtr(Address(object, NativeObject::offsetOfElements()), elementsReg);
// Guard on the initialized length.
Label hole;
Address initLength(elementsReg, ObjectElements::offsetOfInitializedLength());
masm.branch32(Assembler::BelowOrEqual, initLength, indexReg, &hole);
// Load the value.
Label done;
masm.loadValue(BaseObjectElementIndex(elementsReg, indexReg), output.valueReg());
masm.branchTestMagic(Assembler::NotEqual, output.valueReg(), &done);
// Load undefined for the hole.
masm.bind(&hole);
masm.moveValue(UndefinedValue(), output.valueReg());
masm.bind(&done);
// Restore the object register.
if (elementsReg == object)
masm.pop(object);
attacher.jumpRejoin(masm);
// All failure flows through here.
masm.bind(&failures);
attacher.jumpNextStub(masm);
return true;
}
bool
GetElementIC::attachDenseElementHole(JSContext *cx, HandleScript outerScript, IonScript *ion,
HandleObject obj, const Value &idval)
{
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
RepatchStubAppender attacher(*this);
GenerateDenseElementHole(cx, masm, attacher, ion, obj, idval, object(), index(), output());
return linkAndAttachStub(cx, masm, attacher, ion, "dense hole");
}
/* static */ bool
GetElementIC::canAttachTypedArrayElement(JSObject *obj, const Value &idval,
TypedOrValueRegister output)
@ -3568,6 +3722,13 @@ GetElementIC::update(JSContext *cx, HandleScript outerScript, size_t cacheIndex,
return false;
attachedStub = true;
}
if (!attachedStub && cache.monitoredResult() &&
canAttachDenseElementHole(obj, idval, cache.output()))
{
if (!cache.attachDenseElementHole(cx, outerScript, ion, obj, idval))
return false;
attachedStub = true;
}
if (!attachedStub && canAttachTypedArrayElement(obj, idval, cache.output())) {
if (!cache.attachTypedArrayElement(cx, outerScript, ion, obj, idval))
return false;

View File

@ -842,6 +842,8 @@ class GetElementIC : public RepatchIonCache
static bool canAttachGetProp(JSObject *obj, const Value &idval, jsid id);
static bool canAttachDenseElement(JSObject *obj, const Value &idval);
static bool canAttachDenseElementHole(JSObject *obj, const Value &idval,
TypedOrValueRegister output);
static bool canAttachTypedArrayElement(JSObject *obj, const Value &idval,
TypedOrValueRegister output);
@ -851,6 +853,9 @@ class GetElementIC : public RepatchIonCache
bool attachDenseElement(JSContext *cx, HandleScript outerScript, IonScript *ion,
HandleObject obj, const Value &idval);
bool attachDenseElementHole(JSContext *cx, HandleScript outerScript, IonScript *ion,
HandleObject obj, const Value &idval);
bool attachTypedArrayElement(JSContext *cx, HandleScript outerScript, IonScript *ion,
HandleObject tarr, const Value &idval);

View File

@ -2268,8 +2268,8 @@ TemporaryTypeSet::propertyNeedsBarrier(CompilerConstraintList *constraints, jsid
return false;
}
static inline bool
ClassCanHaveExtraProperties(const Class *clasp)
bool
js::ClassCanHaveExtraProperties(const Class *clasp)
{
return clasp->resolve
|| clasp->ops.lookupProperty

View File

@ -909,6 +909,9 @@ class TypeNewScript
/* Is this a reasonable PC to be doing inlining on? */
inline bool isInlinableCall(jsbytecode *pc);
bool
ClassCanHaveExtraProperties(const Class *clasp);
/*
* Whether Array.prototype, or an object on its proto chain, has an
* indexed property.