Generate ICs which see through ListBase proxies, bug 769911. r=peterv,dvander

This commit is contained in:
Brian Hackett 2012-08-06 14:51:33 -06:00
Родитель 0726b1fe02
Коммит 1219ffcd31
8 изменённых файлов: 124 добавлений и 239 удалений

Просмотреть файл

@ -922,4 +922,26 @@ EnableRuntimeProfilingStack(JSRuntime *rt, bool enabled)
rt->spsProfiler.enable(enabled);
}
static void *gListBaseHandlerFamily = NULL;
static uint32_t gListBaseExpandoSlot = 0;
JS_FRIEND_API(void)
SetListBaseInformation(void *listBaseHandlerFamily, uint32_t listBaseExpandoSlot)
{
gListBaseHandlerFamily = listBaseHandlerFamily;
gListBaseExpandoSlot = listBaseExpandoSlot;
}
void *
GetListBaseHandlerFamily()
{
return gListBaseHandlerFamily;
}
uint32_t
GetListBaseExpandoSlot()
{
return gListBaseExpandoSlot;
}
} // namespace js

Просмотреть файл

@ -456,13 +456,6 @@ GetObjectSlot(RawObject obj, size_t slot)
return reinterpret_cast<const shadow::Object *>(obj)->slotRef(slot);
}
inline Shape *
GetObjectShape(RawObject obj)
{
shadow::Shape *shape = reinterpret_cast<const shadow::Object*>(obj)->shape;
return reinterpret_cast<Shape *>(shape);
}
inline const jschar *
GetAtomChars(JSAtom *atom)
{
@ -893,6 +886,13 @@ NukeCrossCompartmentWrappers(JSContext* cx,
const CompartmentFilter& targetFilter,
NukeReferencesToWindow nukeReferencesToWindow);
/* Specify information about ListBase proxies in the DOM, for use by ICs. */
JS_FRIEND_API(void)
SetListBaseInformation(void *listBaseHandlerFamily, uint32_t listBaseExpandoSlot);
void *GetListBaseHandlerFamily();
uint32_t GetListBaseExpandoSlot();
} /* namespace js */
#endif

Просмотреть файл

@ -5023,7 +5023,7 @@ mjit::Compiler::jsop_getprop(PropertyName *name, JSValueType knownType,
*/
pic.canCallHook = pic.forcedTypeBarrier =
!forPrototype &&
JSOp(*PC) == JSOP_GETPROP &&
(JSOp(*PC) == JSOP_GETPROP || JSOp(*PC) == JSOP_LENGTH) &&
analysis->getCode(PC).accessGetter;
/* Guard that the type is an object. */

Просмотреть файл

@ -428,6 +428,10 @@ class NunboxAssembler : public JSC::MacroAssembler
return branch32(cond, tagOf(address), ImmTag(JSVAL_TAG_STRING));
}
Jump testPrivate(Condition cond, Address address, void *ptr) {
return branchPtr(cond, address, ImmPtr(ptr));
}
void compareValue(Address one, Address two, RegisterID T0, RegisterID T1,
Vector<Jump> *mismatches) {
loadValueAsComponents(one, T0, T1);

Просмотреть файл

@ -595,6 +595,23 @@ IsCacheableProtoChain(JSObject *obj, JSObject *holder)
return true;
}
static bool
IsCacheableListBase(JSObject *obj)
{
if (!obj->isProxy())
return false;
BaseProxyHandler *handler = GetProxyHandler(obj);
if (handler->family() != GetListBaseHandlerFamily())
return false;
if (obj->numFixedSlots() <= GetListBaseExpandoSlot())
return false;
return true;
}
template <typename IC>
struct GetPropHelper {
// These fields are set in the constructor and describe a property lookup.
@ -641,6 +658,9 @@ struct GetPropHelper {
JSObject *aobj = obj;
if (obj->isDenseArray())
aobj = obj->getProto();
else if (IsCacheableListBase(obj))
aobj = obj->getProto();
if (!aobj->isNative())
return ic.disable(f, "non-native");
@ -1226,6 +1246,53 @@ class GetPropCompiler : public PICStubCompiler
if (!shapeMismatches.append(shapeGuardJump))
return error();
// Guard on the proxy guts for ListBase accesses, if applicable.
if (IsCacheableListBase(obj)) {
// The shape check above ensures this is a proxy with the correct
// number of fixed slots, but we need further checks to ensure that
//
// (a) the object is a ListBase.
// (b) the object does not have any expando properties, or has an
// expando which does not have the desired property.
//
// If both of these hold, all accesses on non-indexed PropertyName
// properties will go through the object's native prototype chain.
Address handler(pic.objReg, JSObject::getFixedSlotOffset(JSSLOT_PROXY_HANDLER));
Jump handlerGuard = masm.testPrivate(Assembler::NotEqual, handler, GetProxyHandler(obj));
if (!shapeMismatches.append(handlerGuard))
return error();
Address expandoAddress(pic.objReg, JSObject::getFixedSlotOffset(GetListBaseExpandoSlot()));
Value expandoValue = obj->getFixedSlot(GetListBaseExpandoSlot());
JSObject *expando = expandoValue.isObject() ? &expandoValue.toObject() : NULL;
// Expando objects just hold any extra properties the object has
// been given by a script, and have no prototype or anything else
// that will complicate property lookups on them.
JS_ASSERT_IF(expando, expando->isNative() && expando->getProto() == NULL);
if (expando && expando->nativeLookupNoAllocation(name) == NULL) {
Jump expandoGuard = masm.testObject(Assembler::NotEqual, expandoAddress);
if (!shapeMismatches.append(expandoGuard))
return error();
masm.loadPayload(expandoAddress, pic.shapeReg);
pic.shapeRegHasBaseShape = false;
Jump shapeGuard = masm.branchPtr(Assembler::NotEqual,
Address(pic.shapeReg, JSObject::offsetOfShape()),
ImmPtr(expando->lastProperty()));
if (!shapeMismatches.append(expandoGuard))
return error();
} else {
Jump expandoGuard = masm.testUndefined(Assembler::NotEqual, expandoAddress);
if (!shapeMismatches.append(expandoGuard))
return error();
}
}
RegisterID holderReg = pic.objReg;
if (obj != holder) {
if (!GeneratePrototypeGuards(cx, shapeMismatches, masm, obj, holder,
@ -2164,6 +2231,10 @@ GetElementIC::attachGetProp(VMFrame &f, HandleObject obj, HandleValue v, HandleP
JS_ASSERT(v.isString());
JSContext *cx = f.cx;
// don't handle special logic required for property access on proxies.
if (!obj->isNative())
return Lookup_Uncacheable;
GetPropHelper<GetElementIC> getprop(cx, obj, name, *this, f);
LookupStatus status = getprop.lookupAndTest();
if (status != Lookup_Cacheable)

Просмотреть файл

@ -345,6 +345,11 @@ class PunboxAssembler : public JSC::MacroAssembler
return testString(cond, Registers::ValueReg);
}
Jump testPrivate(Condition cond, Address address, void *ptr) {
uint64_t valueBits = PrivateValue(ptr).asRawBits();
return branchPtr(cond, address, ImmPtr((void *) valueBits));
}
void compareValue(Address one, Address two, RegisterID T0, RegisterID T1,
Vector<Jump> *mismatches) {
loadValue(one, T0);

Просмотреть файл

@ -29,8 +29,7 @@ namespace dom {
namespace binding {
enum {
JSPROXYSLOT_PROTOSHAPE = 0,
JSPROXYSLOT_EXPANDO = 1
JSPROXYSLOT_EXPANDO = 0
};
static jsid s_prototype_id = JSID_VOID;
@ -68,6 +67,12 @@ DefineStaticJSVals(JSContext *cx)
int HandlerFamily;
struct SetListBaseInformation
{
SetListBaseInformation() {
js::SetListBaseInformation((void*) &HandlerFamily, js::JSSLOT_PROXY_EXTRA + JSPROXYSLOT_EXPANDO);
}
} gSetListBaseInformation;
JSBool
Throw(JSContext *cx, nsresult rv)
@ -138,26 +143,6 @@ Unwrap(JSContext *cx, jsval v, NoType **ppArg, nsISupports **ppArgRef, jsval *vp
return false;
}
// Because we use proxies for wrapping DOM list objects we don't get the benefits of the property
// cache. To improve performance when using a property that lives on the prototype chain we
// implemented a cheap caching mechanism. Every DOM list proxy object stores a pointer to a shape
// in an extra slot. The first time we access a property on the object that lives on the prototype
// we check if all the DOM properties on the prototype chain are the real DOM properties and in
// that case we store a pointer to the shape of the object's prototype in the extra slot. From
// then on, every time we access a DOM property that lives on the prototype we check that the
// shape of the prototype is still identical to the cached shape and we do a fast lookup of the
// property. If the shape has changed, we recheck all the DOM properties on the prototype chain
// and we update the shape pointer if they are still the real DOM properties. This mechanism
// covers addition/removal of properties, changes in getters/setters, changes in the prototype
// chain, ... It does not cover changes in the values of the properties. For those we store an
// enum value in a reserved slot in every DOM prototype object. The value starts off as USE_CACHE.
// If a property of a DOM prototype object is set to a different value, we set the value to
// CHECK_CACHE. The next time we try to access the value of a property on that DOM prototype
// object we check if all the DOM properties on that DOM prototype object still match the real DOM
// properties. If they do we set the value to USE_CACHE again, if they're not we set the value to
// DONT_USE_CACHE. If the value is USE_CACHE we do the fast lookup.
template<class LC>
typename ListBase<LC>::Properties ListBase<LC>::sProtoProperties[] = {
{ s_VOID_id, NULL, NULL }
@ -203,22 +188,6 @@ ListBase<LC>::getListObject(JSObject *obj)
return getNative(obj);
}
template<class LC>
js::Shape *
ListBase<LC>::getProtoShape(JSObject *obj)
{
JS_ASSERT(objIsList(obj));
return (js::Shape *) js::GetProxyExtra(obj, JSPROXYSLOT_PROTOSHAPE).toPrivate();
}
template<class LC>
void
ListBase<LC>::setProtoShape(JSObject *obj, js::Shape *shape)
{
JS_ASSERT(objIsList(obj));
js::SetProxyExtra(obj, JSPROXYSLOT_PROTOSHAPE, PrivateValue(shape));
}
static JSBool
UnwrapSecurityWrapper(JSContext *cx, JSObject *obj, JSObject *callee, JSObject **unwrapped)
{
@ -346,43 +315,18 @@ interface_hasInstance(JSContext *cx, JSHandleObject obj, const JS::Value *vp, JS
return true;
}
enum {
USE_CACHE = 0,
CHECK_CACHE = 1,
DONT_USE_CACHE = 2
};
static JSBool
InvalidateProtoShape_add(JSContext *cx, JSHandleObject obj, JSHandleId id, JSMutableHandleValue vp);
static JSBool
InvalidateProtoShape_set(JSContext *cx, JSHandleObject obj, JSHandleId id, JSBool strict, JSMutableHandleValue vp);
js::Class sInterfacePrototypeClass = {
"Object",
JSCLASS_HAS_RESERVED_SLOTS(1),
InvalidateProtoShape_add, /* addProperty */
JS_PropertyStub, /* delProperty */
JS_PropertyStub, /* getProperty */
InvalidateProtoShape_set, /* setProperty */
JSCLASS_HAS_RESERVED_SLOTS(0),
JS_PropertyStub, /* addProperty */
JS_PropertyStub, /* delProperty */
JS_PropertyStub, /* getProperty */
JS_StrictPropertyStub, /* setProperty */
JS_EnumerateStub,
JS_ResolveStub,
JS_ConvertStub
};
static JSBool
InvalidateProtoShape_add(JSContext *cx, JSHandleObject obj, JSHandleId id, JSMutableHandleValue vp)
{
if (JSID_IS_STRING(id) && JS_InstanceOf(cx, obj, Jsvalify(&sInterfacePrototypeClass), NULL))
js::SetReservedSlot(obj, 0, PrivateUint32Value(CHECK_CACHE));
return JS_TRUE;
}
static JSBool
InvalidateProtoShape_set(JSContext *cx, JSHandleObject obj, JSHandleId id, JSBool strict, JSMutableHandleValue vp)
{
return InvalidateProtoShape_add(cx, obj, id, vp);
}
template<class LC>
JSObject *
ListBase<LC>::getPrototype(JSContext *cx, JSObject *receiver, bool *enabled)
@ -458,10 +402,6 @@ ListBase<LC>::getPrototype(JSContext *cx, XPCWrappedNativeScope *scope,
NULL, 0))
return NULL;
// This needs to happen after we've set all our own properties on interfacePrototype, to
// overwrite the value set by InvalidateProtoShape_add when we set our own properties.
js::SetReservedSlot(interfacePrototype, 0, PrivateUint32Value(USE_CACHE));
if (!cache.Put(sInterfaceClass.name, interfacePrototype, fallible_t()))
return NULL;
@ -498,7 +438,6 @@ ListBase<LC>::create(JSContext *cx, JSObject *scope, ListType *aList,
return NULL;
NS_ADDREF(aList);
setProtoShape(obj, NULL);
aWrapperCache->SetWrapper(obj);
@ -831,59 +770,6 @@ ListBase<LC>::has(JSContext *cx, JSObject *proxy, jsid id, bool *bp)
return ok;
}
template<class LC>
bool
ListBase<LC>::protoIsClean(JSContext *cx, JSObject *proto, bool *isClean)
{
JSPropertyDescriptor desc;
for (size_t n = 0; n < sProtoPropertiesCount; ++n) {
jsid id = sProtoProperties[n].id;
if (!JS_GetPropertyDescriptorById(cx, proto, id, JSRESOLVE_QUALIFIED, &desc))
return false;
JSStrictPropertyOp setter =
sProtoProperties[n].setter ? sProtoProperties[n].setter : InvalidateProtoShape_set;
if (desc.obj != proto || desc.getter != sProtoProperties[n].getter ||
desc.setter != setter) {
*isClean = false;
return true;
}
}
for (size_t n = 0; n < sProtoMethodsCount; ++n) {
jsid id = sProtoMethods[n].id;
if (!JS_GetPropertyDescriptorById(cx, proto, id, JSRESOLVE_QUALIFIED, &desc))
return false;
if (desc.obj != proto || desc.getter || JSVAL_IS_PRIMITIVE(desc.value) ||
n >= js::GetObjectSlotSpan(proto) || js::GetObjectSlot(proto, n + 1) != desc.value ||
!JS_IsNativeFunction(JSVAL_TO_OBJECT(desc.value), sProtoMethods[n].native)) {
*isClean = false;
return true;
}
}
*isClean = true;
return true;
}
template<class LC>
bool
ListBase<LC>::shouldCacheProtoShape(JSContext *cx, JSObject *proto, bool *shouldCache)
{
bool ok = protoIsClean(cx, proto, shouldCache);
if (!ok || !*shouldCache)
return ok;
js::SetReservedSlot(proto, 0, PrivateUint32Value(USE_CACHE));
JSObject *protoProto = js::GetObjectProto(proto);
if (!protoProto) {
*shouldCache = false;
return true;
}
return Base::shouldCacheProtoShape(cx, protoProto, shouldCache);
}
template<class LC>
bool
ListBase<LC>::resolveNativeName(JSContext *cx, JSObject *proxy, jsid id, JSPropertyDescriptor *desc)
@ -921,64 +807,6 @@ ListBase<LC>::resolveNativeName(JSContext *cx, JSObject *proxy, jsid id, JSPrope
return Base::resolveNativeName(cx, proxy, id, desc);
}
template<class LC>
bool
ListBase<LC>::nativeGet(JSContext *cx, JSObject *proxy_, JSObject *proto, jsid id_, bool *found, Value *vp)
{
JS::RootedObject proxy(cx, proxy_);
JS::RootedId id(cx, id_);
uint32_t cache = js::GetReservedSlot(proto, 0).toPrivateUint32();
if (cache == CHECK_CACHE) {
bool isClean;
if (!protoIsClean(cx, proto, &isClean))
return false;
if (!isClean) {
js::SetReservedSlot(proto, 0, PrivateUint32Value(DONT_USE_CACHE));
return true;
}
js::SetReservedSlot(proto, 0, PrivateUint32Value(USE_CACHE));
}
else if (cache == DONT_USE_CACHE) {
return true;
}
else {
#ifdef DEBUG
bool isClean;
JS_ASSERT(protoIsClean(cx, proto, &isClean) && isClean);
#endif
}
for (size_t n = 0; n < sProtoPropertiesCount; ++n) {
if (sProtoProperties[n].id == id) {
*found = true;
if (!vp)
return true;
return sProtoProperties[n].getter(cx, proxy, id, JSMutableHandleValue::fromMarkedLocation(vp));
}
}
for (size_t n = 0; n < sProtoMethodsCount; ++n) {
if (sProtoMethods[n].id == id) {
*found = true;
if (!vp)
return true;
*vp = js::GetObjectSlot(proto, n + 1);
JS_ASSERT(JS_IsNativeFunction(&vp->toObject(), sProtoMethods[n].native));
return true;
}
}
JSObject *protoProto = js::GetObjectProto(proto);
if (!protoProto) {
*found = false;
return true;
}
return Base::nativeGet(cx, proxy, protoProto, id, found, vp);
}
template<class LC>
bool
ListBase<LC>::getPropertyOnPrototype(JSContext *cx, JSObject *proxy, jsid id, bool *found,
@ -988,33 +816,6 @@ ListBase<LC>::getPropertyOnPrototype(JSContext *cx, JSObject *proxy, jsid id, bo
if (!proto)
return true;
bool hit;
if (getProtoShape(proxy) != js::GetObjectShape(proto)) {
if (!shouldCacheProtoShape(cx, proto, &hit))
return false;
if (hit)
setProtoShape(proxy, js::GetObjectShape(proto));
} else {
hit = true;
}
if (hit) {
if (id == s_length_id) {
if (vp) {
PRUint32 length;
getListObject(proxy)->GetLength(&length);
JS_ASSERT(int32_t(length) >= 0);
vp->setInt32(length);
}
*found = true;
return true;
}
if (!nativeGet(cx, proxy, proto, id, found, vp))
return false;
if (*found)
return true;
}
JSBool hasProp;
if (!JS_HasPropertyById(cx, proto, id, &hasProp))
return false;
@ -1038,7 +839,7 @@ ListBase<LC>::hasPropertyOnPrototype(JSContext *cx, JSObject *proxy, jsid id)
}
JS_ASSERT(objIsList(proxy));
bool found;
bool found = false;
// We ignore an error from getPropertyOnPrototype.
return !getPropertyOnPrototype(cx, proxy, id, &found, NULL) || found;
}
@ -1077,7 +878,7 @@ ListBase<LC>::get(JSContext *cx, JSObject *proxy, JSObject *receiver, jsid id, V
}
}
bool found;
bool found = false;
if (!getPropertyOnPrototype(cx, proxy, id, &found, vp))
return false;

Просмотреть файл

@ -78,21 +78,10 @@ class NoBase {
public:
static JSObject *getPrototype(JSContext *cx, XPCWrappedNativeScope *scope,
JSObject *receiver);
static bool shouldCacheProtoShape(JSContext *cx, JSObject *proto, bool *shouldCache)
{
*shouldCache = true;
return true;
}
static bool resolveNativeName(JSContext *cx, JSObject *proxy, jsid id, JSPropertyDescriptor *desc)
{
return true;
}
static bool nativeGet(JSContext *cx, JSObject *proxy, JSObject *proto, jsid id, bool *found,
JS::Value *vp)
{
*found = false;
return true;
}
static nsISupports* nativeToSupports(nsISupports* aNative)
{
return aNative;
@ -144,9 +133,6 @@ private:
static JSObject *ensureExpandoObject(JSContext *cx, JSObject *obj);
static js::Shape *getProtoShape(JSObject *obj);
static void setProtoShape(JSObject *obj, js::Shape *shape);
static JSBool length_getter(JSContext *cx, JSHandleObject obj, JSHandleId id, JSMutableHandleValue vp);
static inline bool getItemAt(ListType *list, uint32_t i, IndexGetterType &item);
@ -213,12 +199,8 @@ public:
static JSObject *getPrototype(JSContext *cx, XPCWrappedNativeScope *scope,
JSObject *receiver);
static inline bool protoIsClean(JSContext *cx, JSObject *proto, bool *isClean);
static bool shouldCacheProtoShape(JSContext *cx, JSObject *proto, bool *shouldCache);
static bool resolveNativeName(JSContext *cx, JSObject *proxy, jsid id,
JSPropertyDescriptor *desc);
static bool nativeGet(JSContext *cx, JSObject *proxy, JSObject *proto, jsid id, bool *found,
JS::Value *vp);
static ListType *getNative(JSObject *proxy);
static nsISupports* nativeToSupports(ListType* aNative)
{