Bug 832364 - Generate optimized paths for element accesses on native objects, r=jandem.

This commit is contained in:
Brian Hackett 2013-01-26 13:21:27 -07:00
parent d5d782111a
commit b6034da246
11 changed files with 242 additions and 68 deletions

View File

@ -2934,14 +2934,31 @@ class OutOfLineStoreElementHole : public OutOfLineCodeBase<CodeGenerator>
}
};
bool
CodeGenerator::emitStoreHoleCheck(Register elements, const LAllocation *index, LSnapshot *snapshot)
{
Assembler::Condition cond;
if (index->isConstant())
cond = masm.testMagic(Assembler::Equal, Address(elements, ToInt32(index) * sizeof(js::Value)));
else
cond = masm.testMagic(Assembler::Equal, BaseIndex(elements, ToRegister(index), TimesEight));
return bailoutIf(cond, snapshot);
}
bool
CodeGenerator::visitStoreElementT(LStoreElementT *store)
{
Register elements = ToRegister(store->elements());
const LAllocation *index = store->index();
if (store->mir()->needsBarrier())
emitPreBarrier(ToRegister(store->elements()), store->index(), store->mir()->elementType());
emitPreBarrier(elements, index, store->mir()->elementType());
if (store->mir()->needsHoleCheck() && !emitStoreHoleCheck(elements, index, store->snapshot()))
return false;
storeElementTyped(store->value(), store->mir()->value()->type(), store->mir()->elementType(),
ToRegister(store->elements()), store->index());
elements, index);
return true;
}
@ -2950,9 +2967,13 @@ CodeGenerator::visitStoreElementV(LStoreElementV *lir)
{
const ValueOperand value = ToValue(lir, LStoreElementV::Value);
Register elements = ToRegister(lir->elements());
const LAllocation *index = lir->index();
if (lir->mir()->needsBarrier())
emitPreBarrier(elements, lir->index(), MIRType_Value);
emitPreBarrier(elements, index, MIRType_Value);
if (lir->mir()->needsHoleCheck() && !emitStoreHoleCheck(elements, index, lir->snapshot()))
return false;
if (lir->index()->isConstant())
masm.storeValue(value, Address(elements, ToInt32(lir->index()) * sizeof(js::Value)));

View File

@ -253,6 +253,9 @@ class CodeGenerator : public CodeGeneratorSpecific
// be tested in the first place.)
void testObjectTruthy(Register objreg, Label *ifTruthy, Label *ifFalsy, Register scratch,
OutOfLineTestObject *ool);
// Bailout if an element about to be written to is a hole.
bool emitStoreHoleCheck(Register elements, const LAllocation *index, LSnapshot *snapshot);
};
} // namespace ion

View File

@ -4324,7 +4324,7 @@ IonBuilder::jsop_initelem_array()
current->add(elements);
// Store the value.
MStoreElement *store = MStoreElement::New(elements, id, value);
MStoreElement *store = MStoreElement::New(elements, id, value, /* needsHoleCheck = */ false);
current->add(store);
// Update the length.
@ -5214,7 +5214,7 @@ IonBuilder::jsop_getelem()
{
RootedScript script(cx, this->script());
if (oracle->elementReadIsDenseArray(script, pc))
if (oracle->elementReadIsDenseNative(script, pc))
return jsop_getelem_dense();
int arrayType = TypedArray::TYPE_MAX;
@ -5266,14 +5266,17 @@ IonBuilder::jsop_getelem()
bool
IonBuilder::jsop_getelem_dense()
{
if (oracle->arrayPrototypeHasIndexedProperty())
return abort("GETELEM Array proto has indexed properties");
RootedScript scriptRoot(cx, script());
types::StackTypeSet *barrier = oracle->propertyReadBarrier(scriptRoot, pc);
types::StackTypeSet *types = oracle->propertyRead(script(), pc);
bool needsHoleCheck = !oracle->elementReadIsPacked(script(), pc);
bool maybeUndefined = types->hasType(types::Type::UndefinedType());
// Reads which are on holes in the object do not have to bail out if
// undefined values have been observed at this access site and the access
// cannot hit another indexed property on the object or its prototypes.
bool readOutOfBounds =
types->hasType(types::Type::UndefinedType()) &&
!oracle->elementReadHasExtraIndexedProperty(script(), pc);
MDefinition *id = current->pop();
MDefinition *obj = current->pop();
@ -5310,7 +5313,7 @@ IonBuilder::jsop_getelem_dense()
MInstruction *load;
if (!maybeUndefined) {
if (!readOutOfBounds) {
// This load should not return undefined, so likely we're reading
// in-bounds elements, and the array is packed or its holes are not
// read. This is the best case: we can separate the bounds check for
@ -5483,7 +5486,7 @@ IonBuilder::jsop_setelem()
RootedScript script(cx, this->script());
if (oracle->propertyWriteCanSpecialize(script, pc)) {
if (oracle->elementWriteIsDenseArray(script, pc))
if (oracle->elementWriteIsDenseNative(script, pc))
return jsop_setelem_dense();
int arrayType = TypedArray::TYPE_MAX;
@ -5511,12 +5514,13 @@ IonBuilder::jsop_setelem()
bool
IonBuilder::jsop_setelem_dense()
{
if (oracle->arrayPrototypeHasIndexedProperty())
return abort("SETELEM Array proto has indexed properties");
MIRType elementType = oracle->elementWrite(script(), pc);
bool packed = oracle->elementWriteIsPacked(script(), pc);
// Writes which are on holes in the object do not have to bail out if they
// cannot hit another indexed property on the object or its prototypes.
bool writeOutOfBounds = !oracle->elementWriteHasExtraIndexedProperty(script(), pc);
MDefinition *value = current->pop();
MDefinition *id = current->pop();
MDefinition *obj = current->pop();
@ -5534,7 +5538,7 @@ IonBuilder::jsop_setelem_dense()
// indexes in the past. Otherwise, use MStoreElement so that we can hoist
// the initialized length and bounds check.
MStoreElementCommon *store;
if (oracle->setElementHasWrittenHoles(script(), pc)) {
if (oracle->setElementHasWrittenHoles(script(), pc) && writeOutOfBounds) {
MStoreElementHole *ins = MStoreElementHole::New(obj, elements, id, value);
store = ins;
@ -5549,7 +5553,9 @@ IonBuilder::jsop_setelem_dense()
id = addBoundsCheck(id, initLength);
MStoreElement *ins = MStoreElement::New(elements, id, value);
bool needsHoleCheck = !packed && !writeOutOfBounds;
MStoreElement *ins = MStoreElement::New(elements, id, value, needsHoleCheck);
store = ins;
current->add(ins);
@ -6794,7 +6800,7 @@ bool
IonBuilder::jsop_in()
{
RootedScript scriptRoot(cx, script());
if (oracle->inObjectIsDenseArray(scriptRoot, pc))
if (oracle->inObjectIsDenseNativeWithoutExtraIndexedProperties(scriptRoot, pc))
return jsop_in_dense();
MDefinition *obj = current->pop();
@ -6810,9 +6816,6 @@ IonBuilder::jsop_in()
bool
IonBuilder::jsop_in_dense()
{
if (oracle->arrayPrototypeHasIndexedProperty())
return abort("JSOP_IN Array proto has indexed properties");
bool needsHoleCheck = !oracle->inArrayIsPacked(script(), pc);
MDefinition *obj = current->pop();

View File

@ -1655,6 +1655,8 @@ LIRGenerator::visitStoreElement(MStoreElement *ins)
case MIRType_Value:
{
LInstruction *lir = new LStoreElementV(elements, index);
if (ins->fallible() && !assignSnapshot(lir))
return false;
if (!useBox(lir, LStoreElementV::Value, ins->value()))
return false;
return add(lir, ins);
@ -1663,7 +1665,10 @@ LIRGenerator::visitStoreElement(MStoreElement *ins)
default:
{
const LAllocation value = useRegisterOrNonDoubleConstant(ins->value());
return add(new LStoreElementT(elements, index, value), ins);
LInstruction *lir = new LStoreElementT(elements, index, value);
if (ins->fallible() && !assignSnapshot(lir))
return false;
return add(lir, ins);
}
}
}

View File

@ -222,7 +222,8 @@ IonBuilder::inlineArray(uint32_t argc, bool constructing)
id = MConstant::New(Int32Value(i));
current->add(id);
MStoreElement *store = MStoreElement::New(elements, id, argv[i + 1]);
MStoreElement *store = MStoreElement::New(elements, id, argv[i + 1],
/* needsHoleCheck = */ false);
current->add(store);
}

View File

@ -3852,10 +3852,13 @@ class MStoreElement
public MStoreElementCommon,
public SingleObjectPolicy
{
MStoreElement(MDefinition *elements, MDefinition *index, MDefinition *value) {
bool needsHoleCheck_;
MStoreElement(MDefinition *elements, MDefinition *index, MDefinition *value, bool needsHoleCheck) {
initOperand(0, elements);
initOperand(1, index);
initOperand(2, value);
needsHoleCheck_ = needsHoleCheck;
JS_ASSERT(elements->type() == MIRType_Elements);
JS_ASSERT(index->type() == MIRType_Int32);
}
@ -3863,8 +3866,9 @@ class MStoreElement
public:
INSTRUCTION_HEADER(StoreElement)
static MStoreElement *New(MDefinition *elements, MDefinition *index, MDefinition *value) {
return new MStoreElement(elements, index, value);
static MStoreElement *New(MDefinition *elements, MDefinition *index, MDefinition *value,
bool needsHoleCheck) {
return new MStoreElement(elements, index, value, needsHoleCheck);
}
MDefinition *elements() const {
return getOperand(0);
@ -3881,6 +3885,12 @@ class MStoreElement
AliasSet getAliasSet() const {
return AliasSet::Store(AliasSet::Element);
}
bool needsHoleCheck() const {
return needsHoleCheck_;
}
bool fallible() const {
return needsHoleCheck();
}
};
// Like MStoreElement, but supports indexes >= initialized length. The downside

View File

@ -263,9 +263,9 @@ TypeInferenceOracle::propertyReadAccessGetter(UnrootedScript script, jsbytecode
}
bool
TypeInferenceOracle::inObjectIsDenseArray(HandleScript script, jsbytecode *pc)
TypeInferenceOracle::inObjectIsDenseNativeWithoutExtraIndexedProperties(HandleScript script, jsbytecode *pc)
{
// Check whether the object is a dense array and index is int32 or double.
// Check whether the object is a native and index is int32 or double.
StackTypeSet *id = script->analysis()->poppedTypes(pc, 1);
StackTypeSet *obj = script->analysis()->poppedTypes(pc, 0);
@ -273,7 +273,11 @@ TypeInferenceOracle::inObjectIsDenseArray(HandleScript script, jsbytecode *pc)
if (idType != JSVAL_TYPE_INT32 && idType != JSVAL_TYPE_DOUBLE)
return false;
return obj->getKnownClass() == &ArrayClass;
Class *clasp = obj->getKnownClass();
if (!clasp || !clasp->isNative())
return false;
return !types::TypeCanHaveExtraIndexedProperties(cx, obj);
}
bool
@ -284,7 +288,7 @@ TypeInferenceOracle::inArrayIsPacked(UnrootedScript script, jsbytecode *pc)
}
bool
TypeInferenceOracle::elementReadIsDenseArray(RawScript script, jsbytecode *pc)
TypeInferenceOracle::elementReadIsDenseNative(RawScript script, jsbytecode *pc)
{
// Check whether the object is a dense array and index is int32 or double.
StackTypeSet *obj = script->analysis()->poppedTypes(pc, 1);
@ -294,11 +298,8 @@ TypeInferenceOracle::elementReadIsDenseArray(RawScript script, jsbytecode *pc)
if (idType != JSVAL_TYPE_INT32 && idType != JSVAL_TYPE_DOUBLE)
return false;
if (obj->getKnownClass() != &ArrayClass)
return false;
return !obj->hasObjectFlags(cx, types::OBJECT_FLAG_SPARSE_INDEXES |
types::OBJECT_FLAG_LENGTH_OVERFLOW);
Class *clasp = obj->getKnownClass();
return clasp && clasp->isNative();
}
bool
@ -358,6 +359,13 @@ TypeInferenceOracle::elementReadIsString(UnrootedScript script, jsbytecode *pc)
return true;
}
bool
TypeInferenceOracle::elementReadHasExtraIndexedProperty(UnrootedScript script, jsbytecode *pc)
{
StackTypeSet *obj = script->analysis()->poppedTypes(pc, 1);
return types::TypeCanHaveExtraIndexedProperties(cx, obj);
}
bool
TypeInferenceOracle::elementReadIsPacked(UnrootedScript script, jsbytecode *pc)
{
@ -386,7 +394,7 @@ TypeInferenceOracle::elementReadGeneric(UnrootedScript script, jsbytecode *pc, b
}
bool
TypeInferenceOracle::elementWriteIsDenseArray(HandleScript script, jsbytecode *pc)
TypeInferenceOracle::elementWriteIsDenseNative(HandleScript script, jsbytecode *pc)
{
// Check whether the object is a dense array and index is int32 or double.
StackTypeSet *obj = script->analysis()->poppedTypes(pc, 2);
@ -396,11 +404,8 @@ TypeInferenceOracle::elementWriteIsDenseArray(HandleScript script, jsbytecode *p
if (idType != JSVAL_TYPE_INT32 && idType != JSVAL_TYPE_DOUBLE)
return false;
if (obj->getKnownClass() != &ArrayClass)
return false;
return !obj->hasObjectFlags(cx, types::OBJECT_FLAG_SPARSE_INDEXES |
types::OBJECT_FLAG_LENGTH_OVERFLOW);
Class *clasp = obj->getKnownClass();
return clasp && clasp->isNative();
}
bool
@ -421,6 +426,17 @@ TypeInferenceOracle::elementWriteIsTypedArray(RawScript script, jsbytecode *pc,
return true;
}
bool
TypeInferenceOracle::elementWriteHasExtraIndexedProperty(UnrootedScript script, jsbytecode *pc)
{
StackTypeSet *obj = script->analysis()->poppedTypes(pc, 2);
if (obj->hasObjectFlags(cx, types::OBJECT_FLAG_LENGTH_OVERFLOW))
return true;
return types::TypeCanHaveExtraIndexedProperties(cx, obj);
}
bool
TypeInferenceOracle::elementWriteIsPacked(UnrootedScript script, jsbytecode *pc)
{
@ -446,6 +462,9 @@ TypeInferenceOracle::elementWrite(UnrootedScript script, jsbytecode *pc)
return MIRType_None;
if (TypeObject *object = objTypes->getTypeObject(i)) {
if (object->unknownProperties())
return MIRType_None;
types::HeapTypeSet *elementTypes = object->getProperty(cx, JSID_VOID, false);
if (!elementTypes)
return MIRType_None;
@ -464,13 +483,6 @@ TypeInferenceOracle::elementWrite(UnrootedScript script, jsbytecode *pc)
return elementType;
}
bool
TypeInferenceOracle::arrayPrototypeHasIndexedProperty()
{
RootedScript scriptRoot(cx, script());
return ArrayPrototypeHasIndexedProperty(cx, scriptRoot);
}
bool
TypeInferenceOracle::canInlineCalls()
{

View File

@ -75,13 +75,13 @@ class TypeOracle
*barrier = NULL;
return NULL;
}
virtual bool inObjectIsDenseArray(HandleScript script, jsbytecode *pc) {
virtual bool inObjectIsDenseNativeWithoutExtraIndexedProperties(HandleScript script, jsbytecode *pc) {
return false;
}
virtual bool inArrayIsPacked(UnrootedScript script, jsbytecode *pc) {
return false;
}
virtual bool elementReadIsDenseArray(RawScript script, jsbytecode *pc) {
virtual bool elementReadIsDenseNative(RawScript script, jsbytecode *pc) {
return false;
}
virtual bool elementReadIsTypedArray(HandleScript script, jsbytecode *pc, int *arrayType) {
@ -90,6 +90,9 @@ class TypeOracle
virtual bool elementReadIsString(UnrootedScript script, jsbytecode *pc) {
return false;
}
virtual bool elementReadHasExtraIndexedProperty(UnrootedScript, jsbytecode *pc) {
return false;
}
virtual bool elementReadIsPacked(UnrootedScript script, jsbytecode *pc) {
return false;
}
@ -100,12 +103,15 @@ class TypeOracle
virtual bool setElementHasWrittenHoles(UnrootedScript script, jsbytecode *pc) {
return true;
}
virtual bool elementWriteIsDenseArray(HandleScript script, jsbytecode *pc) {
virtual bool elementWriteIsDenseNative(HandleScript script, jsbytecode *pc) {
return false;
}
virtual bool elementWriteIsTypedArray(RawScript script, jsbytecode *pc, int *arrayType) {
return false;
}
virtual bool elementWriteHasExtraIndexedProperty(UnrootedScript script, jsbytecode *pc) {
return false;
}
virtual bool elementWriteIsPacked(UnrootedScript script, jsbytecode *pc) {
return false;
}
@ -121,9 +127,6 @@ class TypeOracle
virtual MIRType elementWrite(UnrootedScript script, jsbytecode *pc) {
return MIRType_None;
}
virtual bool arrayPrototypeHasIndexedProperty() {
return true;
}
virtual bool canInlineCalls() {
return false;
}
@ -229,22 +232,23 @@ class TypeInferenceOracle : public TypeOracle
types::StackTypeSet *getCallTarget(UnrootedScript caller, uint32_t argc, jsbytecode *pc);
types::StackTypeSet *getCallArg(UnrootedScript caller, uint32_t argc, uint32_t arg, jsbytecode *pc);
types::StackTypeSet *getCallReturn(UnrootedScript caller, jsbytecode *pc);
bool inObjectIsDenseArray(HandleScript script, jsbytecode *pc);
bool inObjectIsDenseNativeWithoutExtraIndexedProperties(HandleScript script, jsbytecode *pc);
bool inArrayIsPacked(UnrootedScript script, jsbytecode *pc);
bool elementReadIsDenseArray(RawScript script, jsbytecode *pc);
bool elementReadIsDenseNative(RawScript script, jsbytecode *pc);
bool elementReadIsTypedArray(HandleScript script, jsbytecode *pc, int *atype);
bool elementReadIsString(UnrootedScript script, jsbytecode *pc);
bool elementReadHasExtraIndexedProperty(UnrootedScript, jsbytecode *pc);
bool elementReadIsPacked(UnrootedScript script, jsbytecode *pc);
void elementReadGeneric(UnrootedScript script, jsbytecode *pc, bool *cacheable, bool *monitorResult);
bool elementWriteIsDenseArray(HandleScript script, jsbytecode *pc);
bool elementWriteIsDenseNative(HandleScript script, jsbytecode *pc);
bool elementWriteIsTypedArray(RawScript script, jsbytecode *pc, int *arrayType);
bool elementWriteHasExtraIndexedProperty(UnrootedScript script, jsbytecode *pc);
bool elementWriteIsPacked(UnrootedScript script, jsbytecode *pc);
bool setElementHasWrittenHoles(UnrootedScript script, jsbytecode *pc);
bool propertyWriteCanSpecialize(UnrootedScript script, jsbytecode *pc);
bool propertyWriteNeedsBarrier(UnrootedScript script, jsbytecode *pc, RawId id);
bool elementWriteNeedsBarrier(UnrootedScript script, jsbytecode *pc);
MIRType elementWrite(UnrootedScript script, jsbytecode *pc);
bool arrayPrototypeHasIndexedProperty();
bool canInlineCalls();
bool canInlineCall(HandleScript caller, jsbytecode *pc);
bool canEnterInlinedFunction(JSFunction *callee);

View File

@ -0,0 +1,44 @@
function testOverwritingSparseHole()
{
var x = [];
for (var i = 0; i < 50; i++)
x[i] = i;
var hit = false;
Object.defineProperty(x, 40, {set: function() { hit = true; }});
for (var i = 0; i < 50; i++)
x[i] = 10;
assertEq(hit, true);
}
testOverwritingSparseHole();
function testReadingSparseHole()
{
var x = [];
for (var i = 5; i < 50; i++)
x[i] = i;
var hit = false;
Object.defineProperty(x, 40, {get: function() { hit = true; return 5.5; }});
var res = 0;
for (var i = 0; i < 50; i++) {
res += x[i];
if (i == 10)
res = 0;
}
assertEq(res, 1135.5);
assertEq(hit, true);
}
testReadingSparseHole();
function testInSparseHole()
{
var x = [];
for (var i = 5; i < 50; i++)
x[i] = i;
Object.defineProperty(x, 40, {get: function() {}});
var res = 0;
for (var i = 0; i < 50; i++)
res += (i in x) ? 1 : 0;
assertEq(res, 45);
}
testInSparseHole();

View File

@ -2037,6 +2037,37 @@ StackTypeSet::isDOMClass()
return true;
}
JSObject *
StackTypeSet::getCommonPrototype()
{
if (unknownObject())
return NULL;
JSObject *proto = NULL;
unsigned count = getObjectCount();
for (unsigned i = 0; i < count; i++) {
TaggedProto nproto;
if (RawObject object = getSingleObject(i))
nproto = object->getProto();
else if (TypeObject *object = getTypeObject(i))
nproto = object->proto.get();
else
continue;
if (proto) {
if (nproto != proto)
return NULL;
} else {
if (!nproto.isObject())
return NULL;
proto = nproto.toObject();
}
}
return proto;
}
JSObject *
StackTypeSet::getSingleton()
{
@ -2483,27 +2514,60 @@ types::UseNewTypeForInitializer(JSContext *cx, JSScript *script, jsbytecode *pc,
return !script->analysis()->getCode(pc).inLoop;
}
static inline bool
ClassCanHaveExtraProperties(Class *clasp)
{
JS_ASSERT(clasp->resolve);
return clasp->resolve != JS_ResolveStub || clasp->ops.lookupGeneric || clasp->ops.getGeneric;
}
static inline bool
PrototypeHasIndexedProperty(JSContext *cx, JSObject *obj)
{
do {
TypeObject *type = obj->getType(cx);
if (ClassCanHaveExtraProperties(type->clasp))
return true;
if (type->unknownProperties())
return true;
HeapTypeSet *indexTypes = type->getProperty(cx, JSID_VOID, false);
if (!indexTypes || indexTypes->isOwnProperty(cx, type, true) || indexTypes->knownNonEmpty(cx))
return true;
obj = obj->getProto();
} while (obj);
return false;
}
bool
types::ArrayPrototypeHasIndexedProperty(JSContext *cx, HandleScript script)
{
if (!cx->typeInferenceEnabled() || !script->compileAndGo)
return true;
RootedObject proto(cx, script->global().getOrCreateArrayPrototype(cx));
JSObject *proto = script->global().getOrCreateArrayPrototype(cx);
if (!proto)
return true;
do {
TypeObject *type = proto->getType(cx);
if (type->unknownProperties())
return true;
HeapTypeSet *indexTypes = type->getProperty(cx, JSID_VOID, false);
if (!indexTypes || indexTypes->isOwnProperty(cx, type, true) || indexTypes->knownNonEmpty(cx))
return true;
proto = proto->getProto();
} while (proto);
return PrototypeHasIndexedProperty(cx, proto);
}
return false;
bool
types::TypeCanHaveExtraIndexedProperties(JSContext *cx, StackTypeSet *types)
{
Class *clasp = types->getKnownClass();
if (!clasp || ClassCanHaveExtraProperties(clasp))
return true;
if (types->hasObjectFlags(cx, types::OBJECT_FLAG_SPARSE_INDEXES))
return true;
JSObject *proto = types->getCommonPrototype();
if (!proto)
return true;
return PrototypeHasIndexedProperty(cx, proto);
}
bool

View File

@ -592,6 +592,9 @@ class StackTypeSet : public TypeSet
/* Get the class shared by all objects in this set, or NULL. */
Class *getKnownClass();
/* Get the prototype shared by all objects in this set, or NULL. */
JSObject *getCommonPrototype();
/* Get the typed array type of all objects in this set, or TypedArray::TYPE_MAX. */
int getTypedArrayType();
@ -1101,6 +1104,10 @@ UseNewTypeForInitializer(JSContext *cx, JSScript *script, jsbytecode *pc, JSProt
bool
ArrayPrototypeHasIndexedProperty(JSContext *cx, HandleScript script);
/* Whether obj or any of its prototypes have an indexed property. */
bool
TypeCanHaveExtraIndexedProperties(JSContext *cx, StackTypeSet *types);
/*
* Type information about a callsite. this is separated from the bytecode
* information itself so we can handle higher order functions not called