Bug 918584 - Part 1: Gently refactor SetPropertyIC. (r=jandem)

This commit is contained in:
Shu-yu Guo 2013-10-10 20:02:31 -07:00
parent 68e167ff30
commit 2c3e0c476e
2 changed files with 156 additions and 105 deletions

View File

@ -1225,7 +1225,7 @@ GetPropertyIC::tryAttachNative(JSContext *cx, IonScript *ion, HandleObject obj,
switch (type) { switch (type) {
case CanAttachReadSlot: case CanAttachReadSlot:
GenerateReadSlot(cx, ion, masm, attacher, obj, holder, GenerateReadSlot(cx, ion, masm, attacher, obj, holder,
shape, object(), output()); shape, object(), output());
attachKind = idempotent() ? "idempotent reading" attachKind = idempotent() ? "idempotent reading"
: "non idempotent reading"; : "non idempotent reading";
break; break;
@ -1937,83 +1937,89 @@ IonCache::destroy()
{ {
} }
bool static void
SetPropertyIC::attachNativeExisting(JSContext *cx, IonScript *ion, HandleObject obj, GenerateSetSlot(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher,
HandleShape shape, bool checkTypeset) JSObject *obj, Shape *shape, Register object, ConstantOrRegister value,
bool needsTypeBarrier, bool checkTypeset)
{ {
JS_ASSERT(obj->isNative()); JS_ASSERT(obj->isNative());
MacroAssembler masm(cx);
RepatchStubAppender attacher(*this);
Label failures, barrierFailure; Label failures, barrierFailure;
masm.branchPtr(Assembler::NotEqual, masm.branchPtr(Assembler::NotEqual,
Address(object(), JSObject::offsetOfShape()), Address(object, JSObject::offsetOfShape()),
ImmGCPtr(obj->lastProperty()), &failures); ImmGCPtr(obj->lastProperty()), &failures);
// Guard that the incoming value is in the type set for the property // Guard that the incoming value is in the type set for the property
// if a type barrier is required. // if a type barrier is required.
if (needsTypeBarrier()) { if (needsTypeBarrier) {
// We can't do anything that would change the HeapTypeSet, so // We can't do anything that would change the HeapTypeSet, so
// just guard that it's already there. // just guard that it's already there.
// Obtain and guard on the TypeObject of the object. // Obtain and guard on the TypeObject of the object.
types::TypeObject *type = obj->getType(cx); types::TypeObject *type = obj->type();
masm.branchPtr(Assembler::NotEqual, masm.branchPtr(Assembler::NotEqual,
Address(object(), JSObject::offsetOfType()), Address(object, JSObject::offsetOfType()),
ImmGCPtr(type), &failures); ImmGCPtr(type), &failures);
if (checkTypeset) { if (checkTypeset) {
TypedOrValueRegister valReg = value().reg(); TypedOrValueRegister valReg = value.reg();
types::HeapTypeSet *propTypes = type->maybeGetProperty(NameToId(name())); types::HeapTypeSet *propTypes = type->maybeGetProperty(shape->propid());
JS_ASSERT(propTypes); JS_ASSERT(propTypes);
JS_ASSERT(!propTypes->unknown()); JS_ASSERT(!propTypes->unknown());
Register scratchReg = object(); Register scratchReg = object;
masm.push(scratchReg); masm.push(scratchReg);
masm.guardTypeSet(valReg, propTypes, scratchReg, &barrierFailure); masm.guardTypeSet(valReg, propTypes, scratchReg, &barrierFailure);
masm.pop(object()); masm.pop(object);
} }
} }
if (obj->isFixedSlot(shape->slot())) { if (obj->isFixedSlot(shape->slot())) {
Address addr(object(), JSObject::getFixedSlotOffset(shape->slot())); Address addr(object, JSObject::getFixedSlotOffset(shape->slot()));
if (cx->zone()->needsBarrier()) if (cx->zone()->needsBarrier())
masm.callPreBarrier(addr, MIRType_Value); masm.callPreBarrier(addr, MIRType_Value);
masm.storeConstantOrRegister(value(), addr); masm.storeConstantOrRegister(value, addr);
} else { } else {
Register slotsReg = object(); Register slotsReg = object;
masm.loadPtr(Address(object(), JSObject::offsetOfSlots()), slotsReg); masm.loadPtr(Address(object, JSObject::offsetOfSlots()), slotsReg);
Address addr(slotsReg, obj->dynamicSlotIndex(shape->slot()) * sizeof(Value)); Address addr(slotsReg, obj->dynamicSlotIndex(shape->slot()) * sizeof(Value));
if (cx->zone()->needsBarrier()) if (cx->zone()->needsBarrier())
masm.callPreBarrier(addr, MIRType_Value); masm.callPreBarrier(addr, MIRType_Value);
masm.storeConstantOrRegister(value(), addr); masm.storeConstantOrRegister(value, addr);
} }
attacher.jumpRejoin(masm); attacher.jumpRejoin(masm);
if (barrierFailure.used()) { if (barrierFailure.used()) {
masm.bind(&barrierFailure); masm.bind(&barrierFailure);
masm.pop(object()); masm.pop(object);
} }
masm.bind(&failures); masm.bind(&failures);
attacher.jumpNextStub(masm); attacher.jumpNextStub(masm);
}
bool
SetPropertyIC::attachSetSlot(JSContext *cx, IonScript *ion, HandleObject obj,
HandleShape shape, bool checkTypeset)
{
MacroAssembler masm(cx);
RepatchStubAppender attacher(*this);
GenerateSetSlot(cx, masm, attacher, obj, shape, object(), value(), needsTypeBarrier(),
checkTypeset);
return linkAndAttachStub(cx, masm, attacher, ion, "setting"); return linkAndAttachStub(cx, masm, attacher, ion, "setting");
} }
static bool static bool
IsCacheableSetPropCallNative(HandleObject obj, HandleObject holder, HandleShape shape) IsCacheableSetPropCallNative(HandleObject obj, HandleObject holder, HandleShape shape)
{ {
if (!obj->isNative()) JS_ASSERT(obj->isNative());
return false;
if (!shape || !IsCacheableProtoChain(obj, holder)) if (!shape || !IsCacheableProtoChain(obj, holder))
return false; return false;
@ -2024,11 +2030,9 @@ IsCacheableSetPropCallNative(HandleObject obj, HandleObject holder, HandleShape
} }
static bool static bool
IsCacheableSetPropCallPropertyOp(HandleObject obj, HandleObject holder, IsCacheableSetPropCallPropertyOp(HandleObject obj, HandleObject holder, HandleShape shape)
HandleShape shape)
{ {
if (!obj->isNative()) JS_ASSERT(obj->isNative());
return false;
if (!shape) if (!shape)
return false; return false;
@ -2483,7 +2487,7 @@ SetPropertyIC::attachDOMProxyUnshadowed(JSContext *cx, IonScript *ion, HandleObj
} }
bool bool
SetPropertyIC::attachSetterCall(JSContext *cx, IonScript *ion, SetPropertyIC::attachCallSetter(JSContext *cx, IonScript *ion,
HandleObject obj, HandleObject holder, HandleShape shape, HandleObject obj, HandleObject holder, HandleShape shape,
void *returnAddr) void *returnAddr)
{ {
@ -2518,30 +2522,26 @@ SetPropertyIC::attachSetterCall(JSContext *cx, IonScript *ion,
return linkAndAttachStub(cx, masm, attacher, ion, "setter call"); return linkAndAttachStub(cx, masm, attacher, ion, "setter call");
} }
bool static void
SetPropertyIC::attachNativeAdding(JSContext *cx, IonScript *ion, JSObject *obj, GenerateAddSlot(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher,
HandleShape oldShape, HandleShape newShape, JSObject *obj, Shape *oldShape, Register object, ConstantOrRegister value)
HandleShape propShape)
{ {
JS_ASSERT(obj->isNative()); JS_ASSERT(obj->isNative());
MacroAssembler masm(cx);
RepatchStubAppender attacher(*this);
Label failures; Label failures;
/* Guard the type of the object */ // Guard the type of the object
masm.branchPtr(Assembler::NotEqual, Address(object(), JSObject::offsetOfType()), masm.branchPtr(Assembler::NotEqual, Address(object, JSObject::offsetOfType()),
ImmGCPtr(obj->type()), &failures); ImmGCPtr(obj->type()), &failures);
/* Guard shapes along prototype chain. */ // Guard shapes along prototype chain.
masm.branchTestObjShape(Assembler::NotEqual, object(), oldShape, &failures); masm.branchTestObjShape(Assembler::NotEqual, object, oldShape, &failures);
Label protoFailures; Label protoFailures;
masm.push(object()); // save object reg because we clobber it masm.push(object); // save object reg because we clobber it
JSObject *proto = obj->getProto(); JSObject *proto = obj->getProto();
Register protoReg = object(); Register protoReg = object;
while (proto) { while (proto) {
Shape *protoShape = proto->lastProperty(); Shape *protoShape = proto->lastProperty();
@ -2555,48 +2555,58 @@ SetPropertyIC::attachNativeAdding(JSContext *cx, IonScript *ion, JSObject *obj,
proto = proto->getProto(); proto = proto->getProto();
} }
masm.pop(object()); // restore object reg masm.pop(object); // restore object reg
/* Changing object shape. Write the object's new shape. */ // Changing object shape. Write the object's new shape.
Address shapeAddr(object(), JSObject::offsetOfShape()); Shape *newShape = obj->lastProperty();
Address shapeAddr(object, JSObject::offsetOfShape());
if (cx->zone()->needsBarrier()) if (cx->zone()->needsBarrier())
masm.callPreBarrier(shapeAddr, MIRType_Shape); masm.callPreBarrier(shapeAddr, MIRType_Shape);
masm.storePtr(ImmGCPtr(newShape), shapeAddr); masm.storePtr(ImmGCPtr(newShape), shapeAddr);
/* Set the value on the object. */ // Set the value on the object. Since this is an add, obj->lastProperty()
if (obj->isFixedSlot(propShape->slot())) { // must be the shape of the property we are adding.
Address addr(object(), JSObject::getFixedSlotOffset(propShape->slot())); if (obj->isFixedSlot(newShape->slot())) {
masm.storeConstantOrRegister(value(), addr); Address addr(object, JSObject::getFixedSlotOffset(newShape->slot()));
masm.storeConstantOrRegister(value, addr);
} else { } else {
Register slotsReg = object(); Register slotsReg = object;
masm.loadPtr(Address(object(), JSObject::offsetOfSlots()), slotsReg); masm.loadPtr(Address(object, JSObject::offsetOfSlots()), slotsReg);
Address addr(slotsReg, obj->dynamicSlotIndex(propShape->slot()) * sizeof(Value)); Address addr(slotsReg, obj->dynamicSlotIndex(newShape->slot()) * sizeof(Value));
masm.storeConstantOrRegister(value(), addr); masm.storeConstantOrRegister(value, addr);
} }
/* Success. */ // Success.
attacher.jumpRejoin(masm); attacher.jumpRejoin(masm);
/* Failure. */ // Failure.
masm.bind(&protoFailures); masm.bind(&protoFailures);
masm.pop(object()); masm.pop(object);
masm.bind(&failures); masm.bind(&failures);
attacher.jumpNextStub(masm); attacher.jumpNextStub(masm);
}
bool
SetPropertyIC::attachAddSlot(JSContext *cx, IonScript *ion, JSObject *obj, HandleShape oldShape)
{
MacroAssembler masm(cx);
RepatchStubAppender attacher(*this);
GenerateAddSlot(cx, masm, attacher, obj, oldShape, object(), value());
return linkAndAttachStub(cx, masm, attacher, ion, "adding"); return linkAndAttachStub(cx, masm, attacher, ion, "adding");
} }
static bool static bool
IsPropertySetInlineable(JSContext *cx, const SetPropertyIC &cache, HandleObject obj, IsPropertySetInlineable(HandleObject obj, HandleId id, MutableHandleShape pshape,
HandleId id, MutableHandleShape pshape, bool *checkTypeset) ConstantOrRegister val, bool needsTypeBarrier, bool *checkTypeset)
{ {
if (!obj->isNative()) JS_ASSERT(obj->isNative());
return false;
pshape.set(obj->nativeLookup(cx, id)); // Do a pure non-proto chain climbing lookup. See note in
// CanAttachNativeGetProp.
pshape.set(obj->nativeLookupPure(id));
if (!pshape) if (!pshape)
return false; return false;
@ -2611,17 +2621,16 @@ IsPropertySetInlineable(JSContext *cx, const SetPropertyIC &cache, HandleObject
return false; return false;
bool shouldCheck = false; bool shouldCheck = false;
types::TypeObject *type = obj->getType(cx); types::TypeObject *type = obj->type();
if (cache.needsTypeBarrier() && !type->unknownProperties()) { if (needsTypeBarrier && !type->unknownProperties()) {
types::HeapTypeSet *propTypes = type->maybeGetProperty(id); types::HeapTypeSet *propTypes = type->maybeGetProperty(id);
if (!propTypes) if (!propTypes)
return false; return false;
if (!propTypes->unknown()) { if (!propTypes->unknown()) {
shouldCheck = true; shouldCheck = true;
ConstantOrRegister val = cache.value();
if (val.constant()) { if (val.constant()) {
// If the input is a constant, then don't bother if the barrier will always fail. // If the input is a constant, then don't bother if the barrier will always fail.
if (!propTypes->hasType(types::GetValueType(cache.value().value()))) if (!propTypes->hasType(types::GetValueType(val.value())))
return false; return false;
shouldCheck = false; shouldCheck = false;
} else { } else {
@ -2646,20 +2655,22 @@ IsPropertySetInlineable(JSContext *cx, const SetPropertyIC &cache, HandleObject
} }
static bool static bool
IsPropertyAddInlineable(JSContext *cx, HandleObject obj, HandleId id, uint32_t oldSlots, IsPropertyAddInlineable(HandleObject obj, HandleId id, uint32_t oldSlots, HandleShape oldShape)
MutableHandleShape pShape)
{ {
if (!obj->isNative()) JS_ASSERT(obj->isNative());
// If the shape of the object did not change, then this was not an add.
if (obj->lastProperty() == oldShape)
return false; return false;
// This is not a Add, the property exists. Shape *shape = obj->nativeLookupPure(id);
if (pShape.get())
return false;
RootedShape shape(cx, obj->nativeLookup(cx, id));
if (!shape || shape->inDictionary() || !shape->hasSlot() || !shape->hasDefaultSetter()) if (!shape || shape->inDictionary() || !shape->hasSlot() || !shape->hasDefaultSetter())
return false; return false;
// If we have a shape at this point and the object's shape changed, then
// the shape must be the one we just added.
JS_ASSERT(shape == obj->lastProperty());
// If object has a non-default resolve hook, don't inline // If object has a non-default resolve hook, don't inline
if (obj->getClass()->resolve != JS_ResolveStub) if (obj->getClass()->resolve != JS_ResolveStub)
return false; return false;
@ -2672,21 +2683,22 @@ IsPropertyAddInlineable(JSContext *cx, HandleObject obj, HandleId id, uint32_t o
if (!obj->nonProxyIsExtensible() || !shape->writable()) if (!obj->nonProxyIsExtensible() || !shape->writable())
return false; return false;
// walk up the object prototype chain and ensure that all prototypes // Walk up the object prototype chain and ensure that all prototypes
// are native, and that all prototypes have no getter or setter // are native, and that all prototypes have no getter or setter
// defined on the property // defined on the property
for (JSObject *proto = obj->getProto(); proto; proto = proto->getProto()) { for (JSObject *proto = obj->getProto(); proto; proto = proto->getProto()) {
// if prototype is non-native, don't optimize // If prototype is non-native, don't optimize
if (!proto->isNative()) if (!proto->isNative())
return false; return false;
// if prototype defines this property in a non-plain way, don't optimize // If prototype defines this property in a non-plain way, don't optimize
Shape *protoShape = proto->nativeLookup(cx, id); Shape *protoShape = proto->nativeLookupPure(id);
if (protoShape && !protoShape->hasDefaultSetter()) if (protoShape && !protoShape->hasDefaultSetter())
return false; return false;
// Otherise, if there's no such property, watch out for a resolve hook that would need // Otherwise, if there's no such property, watch out for a resolve
// to be invoked and thus prevent inlining of property addition. // hook that would need to be invoked and thus prevent inlining of
// property addition.
if (proto->getClass()->resolve != JS_ResolveStub) if (proto->getClass()->resolve != JS_ResolveStub)
return false; return false;
} }
@ -2697,10 +2709,40 @@ IsPropertyAddInlineable(JSContext *cx, HandleObject obj, HandleId id, uint32_t o
if (obj->numDynamicSlots() != oldSlots) if (obj->numDynamicSlots() != oldSlots)
return false; return false;
pShape.set(shape);
return true; return true;
} }
static SetPropertyIC::NativeSetPropCacheability
CanAttachNativeSetProp(HandleObject obj, HandleId id, ConstantOrRegister val,
bool needsTypeBarrier, MutableHandleObject holder,
MutableHandleShape shape, bool *checkTypeset)
{
if (!obj->isNative())
return SetPropertyIC::CanAttachNone;
// See if the property exists on the object.
if (IsPropertySetInlineable(obj, id, shape, val, needsTypeBarrier, checkTypeset))
return SetPropertyIC::CanAttachSetSlot;
// If we couldn't find the property on the object itself, do a full, but
// still pure lookup for setters.
if (!LookupPropertyPure(obj, id, holder.address(), shape.address()))
return SetPropertyIC::CanAttachNone;
// If the object doesn't have the property, we don't know if we can attach
// a stub to add the property until we do the VM call to add.
if (!shape)
return SetPropertyIC::MaybeCanAttachAddSlot;
if (IsCacheableSetPropCallPropertyOp(obj, holder, shape) ||
IsCacheableSetPropCallNative(obj, holder, shape))
{
return SetPropertyIC::CanAttachCallSetter;
}
return SetPropertyIC::CanAttachNone;
}
bool bool
SetPropertyIC::update(JSContext *cx, size_t cacheIndex, HandleObject obj, SetPropertyIC::update(JSContext *cx, size_t cacheIndex, HandleObject obj,
HandleValue value) HandleValue value)
@ -2713,12 +2755,11 @@ SetPropertyIC::update(JSContext *cx, size_t cacheIndex, HandleObject obj,
SetPropertyIC &cache = ion->getCache(cacheIndex).toSetProperty(); SetPropertyIC &cache = ion->getCache(cacheIndex).toSetProperty();
RootedPropertyName name(cx, cache.name()); RootedPropertyName name(cx, cache.name());
RootedId id(cx, AtomToId(name)); RootedId id(cx, AtomToId(name));
RootedShape shape(cx);
RootedObject holder(cx);
// Stop generating new stubs once we hit the stub count limit, see // Stop generating new stubs once we hit the stub count limit, see
// GetPropertyCache. // GetPropertyCache.
bool inlinable = cache.canAttachStub() && !obj->watched(); bool inlinable = cache.canAttachStub() && !obj->watched();
NativeSetPropCacheability canCache = CanAttachNone;
bool addedSetterStub = false; bool addedSetterStub = false;
if (inlinable) { if (inlinable) {
if (!addedSetterStub && obj->is<ProxyObject>()) { if (!addedSetterStub && obj->is<ProxyObject>()) {
@ -2746,24 +2787,29 @@ SetPropertyIC::update(JSContext *cx, size_t cacheIndex, HandleObject obj,
addedSetterStub = true; addedSetterStub = true;
} }
} }
// Make sure the object de-lazifies its type. We do this here so that
// the parallel IC can share code that assumes that native objects all
// have a type object.
if (obj->isNative() && !obj->getType(cx))
return false;
RootedShape shape(cx); RootedShape shape(cx);
RootedObject holder(cx);
bool checkTypeset; bool checkTypeset;
if (!addedSetterStub && IsPropertySetInlineable(cx, cache, obj, id, &shape, &checkTypeset)) { canCache = CanAttachNativeSetProp(obj, id, cache.value(), cache.needsTypeBarrier(),
if (!cache.attachNativeExisting(cx, ion, obj, shape, checkTypeset)) &holder, &shape, &checkTypeset);
if (!addedSetterStub && canCache == CanAttachSetSlot) {
if (!cache.attachSetSlot(cx, ion, obj, shape, checkTypeset))
return false; return false;
addedSetterStub = true; addedSetterStub = true;
} else if (!addedSetterStub) { }
RootedObject holder(cx);
if (!JSObject::lookupProperty(cx, obj, name, &holder, &shape))
return false;
if (IsCacheableSetPropCallPropertyOp(obj, holder, shape) || if (!addedSetterStub && canCache == CanAttachCallSetter) {
IsCacheableSetPropCallNative(obj, holder, shape)) if (!cache.attachCallSetter(cx, ion, obj, holder, shape, returnAddr))
{ return false;
if (!cache.attachSetterCall(cx, ion, obj, holder, shape, returnAddr)) addedSetterStub = true;
return false;
addedSetterStub = true;
}
} }
} }
@ -2775,12 +2821,11 @@ SetPropertyIC::update(JSContext *cx, size_t cacheIndex, HandleObject obj,
return false; return false;
// The property did not exist before, now we can try to inline the property add. // The property did not exist before, now we can try to inline the property add.
if (inlinable && !addedSetterStub && !cache.needsTypeBarrier() && if (!addedSetterStub && canCache == MaybeCanAttachAddSlot &&
obj->lastProperty() != oldShape && !cache.needsTypeBarrier() &&
IsPropertyAddInlineable(cx, obj, id, oldSlots, &shape)) IsPropertyAddInlineable(obj, id, oldSlots, oldShape))
{ {
RootedShape newShape(cx, obj->lastProperty()); if (!cache.attachAddSlot(cx, ion, obj, oldShape))
if (!cache.attachNativeAdding(cx, ion, obj, oldShape, newShape, shape))
return false; return false;
} }

View File

@ -678,12 +678,18 @@ class SetPropertyIC : public RepatchIonCache
return hasGenericProxyStub_; return hasGenericProxyStub_;
} }
bool attachNativeExisting(JSContext *cx, IonScript *ion, HandleObject obj, enum NativeSetPropCacheability {
HandleShape shape, bool checkTypeset); CanAttachNone,
bool attachSetterCall(JSContext *cx, IonScript *ion, HandleObject obj, CanAttachSetSlot,
MaybeCanAttachAddSlot,
CanAttachCallSetter
};
bool attachSetSlot(JSContext *cx, IonScript *ion, HandleObject obj, HandleShape shape,
bool checkTypeset);
bool attachCallSetter(JSContext *cx, IonScript *ion, HandleObject obj,
HandleObject holder, HandleShape shape, void *returnAddr); HandleObject holder, HandleShape shape, void *returnAddr);
bool attachNativeAdding(JSContext *cx, IonScript *ion, JSObject *obj, HandleShape oldshape, bool attachAddSlot(JSContext *cx, IonScript *ion, JSObject *obj, HandleShape oldShape);
HandleShape newshape, HandleShape propshape);
bool attachGenericProxy(JSContext *cx, IonScript *ion, void *returnAddr); bool attachGenericProxy(JSContext *cx, IonScript *ion, void *returnAddr);
bool attachDOMProxyShadowed(JSContext *cx, IonScript *ion, HandleObject obj, bool attachDOMProxyShadowed(JSContext *cx, IonScript *ion, HandleObject obj,
void *returnAddr); void *returnAddr);
@ -772,7 +778,7 @@ class GetElementIC : public RepatchIonCache
static bool static bool
update(JSContext *cx, size_t cacheIndex, HandleObject obj, HandleValue idval, update(JSContext *cx, size_t cacheIndex, HandleObject obj, HandleValue idval,
MutableHandleValue vp); MutableHandleValue vp);
void incFailedUpdates() { void incFailedUpdates() {
failedUpdates_++; failedUpdates_++;