Bug 1133423 - Optimize sets of expando properties and expando setter calls on DOM proxies. r=evilpie

This commit is contained in:
Jan de Mooij 2017-03-22 09:08:08 +01:00
parent dc82ea3ccb
commit b86962b1de
3 changed files with 135 additions and 41 deletions

View File

@ -19,45 +19,56 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=965992
// Ensure we are in JIT code and attach IC stubs.
const iterations = 50;
function testFoo(obj, expected, kind) {
function testFoo(obj, kind, expected) {
for (var i = 0; i < iterations; i++) {
is(obj.foo, expected, "Looking up an expando should work - " + kind);
obj.foo = i;
is(obj.foo, (expected === undefined) ? i : expected,
"Looking up an expando should work - " + kind);
}
}
function getPropTests(obj) {
// Start with a plain data property.
obj.foo = "bar";
testFoo(obj, "bar", "plain");
testFoo(obj, "plain");
// Now change it to a scripted getter.
// Now change it to a scripted getter/setter.
var count = 0;
Object.defineProperty(obj, "foo", {get:function() {
var getterSetterVal = 0;
Object.defineProperty(obj, "foo", {configurable: true, get: function() {
is(this, obj, "Getter should have the proxy as |this|");
is(arguments.length, 0, "Shouldn't pass arguments to getters");
count++;
return 123;
}, configurable: true});
testFoo(obj, 123, "scripted getter");
is(count, iterations, "Should have called the getter enough times");
return getterSetterVal;
}, set: function(v) {
is(this, obj, "Setter should have the proxy as |this|");
is(arguments.length, 1, "Should pass 1 argument to setters");
getterSetterVal = v;
count++;
}});
testFoo(obj, "scripted getter/setter");
is(count, iterations * 2, "Should have called the getter/setter enough times");
// Now try a native getter.
Object.defineProperty(obj, "foo", {get: Object.prototype.valueOf, configurable: true});
testFoo(obj, obj, "native getter");
// Now try a native getter/setter.
Object.defineProperty(obj, "foo", {get: Math.abs, set: Math.abs, configurable: true});
testFoo(obj, "native getter/setter", NaN);
}
function getElemTests(obj) {
// Define two expando properties, then test inline caches for obj[prop]
// correctly guard on prop being the same.
var count = 0;
var count = 0, getterSetterVal = 0;
obj.elem1 = 1;
Object.defineProperty(obj, "elem2", {get: function() { count++; return 2; }});
Object.defineProperty(obj, "elem2", {
get: function() { count++; return getterSetterVal; },
set: function(v) { getterSetterVal = v; count++; }
});
for (var i = 0; i < iterations; i++) {
var prop = ((i & 1) == 0) ? "elem1" : "elem2";
is(obj[prop], (i & 1) + 1, "Should return correct property value");
obj[prop] = i;
is(obj[prop], i, "Should return correct property value");
}
is(count, iterations / 2, "Should have called the getter enough times");
is(count, iterations, "Should have called the getter/setter enough times");
}
var directExpando = document.getElementsByTagName("*");

View File

@ -773,6 +773,27 @@ GetPropIRGenerator::tryAttachGenericProxy(HandleObject obj, ObjOperandId objId,
return true;
}
ObjOperandId
IRGenerator::guardDOMProxyExpandoObjectAndShape(JSObject* obj, ObjOperandId objId,
const Value& expandoVal, JSObject* expandoObj)
{
MOZ_ASSERT(IsCacheableDOMProxy(obj));
writer.guardShape(objId, obj->maybeShape());
// Shape determines Class, so now it must be a DOM proxy.
ValOperandId expandoValId;
if (expandoVal.isObject())
expandoValId = writer.loadDOMExpandoValue(objId);
else
expandoValId = writer.loadDOMExpandoValueIgnoreGeneration(objId);
// Guard the expando is an object and shape guard.
ObjOperandId expandoObjId = writer.guardIsObject(expandoValId);
writer.guardShape(expandoObjId, expandoObj->as<NativeObject>().shape());
return expandoObjId;
}
bool
GetPropIRGenerator::tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objId, HandleId id)
{
@ -780,13 +801,12 @@ GetPropIRGenerator::tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objI
RootedValue expandoVal(cx_, GetProxyExtra(obj, GetDOMProxyExpandoSlot()));
RootedObject expandoObj(cx_);
ExpandoAndGeneration* expandoAndGeneration = nullptr;
if (expandoVal.isObject()) {
expandoObj = &expandoVal.toObject();
} else {
MOZ_ASSERT(!expandoVal.isUndefined(),
"How did a missing expando manage to shadow things?");
expandoAndGeneration = static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
auto expandoAndGeneration = static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
MOZ_ASSERT(expandoAndGeneration);
expandoObj = &expandoAndGeneration->expando.toObject();
}
@ -805,20 +825,8 @@ GetPropIRGenerator::tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objI
MOZ_ASSERT(holder == expandoObj);
maybeEmitIdGuard(id);
writer.guardShape(objId, obj->maybeShape());
// Shape determines Class, so now it must be a DOM proxy.
ValOperandId expandoValId;
if (expandoVal.isObject()) {
expandoValId = writer.loadDOMExpandoValue(objId);
} else {
MOZ_ASSERT(expandoAndGeneration);
expandoValId = writer.loadDOMExpandoValueIgnoreGeneration(objId);
}
// Guard the expando is an object and shape guard.
ObjOperandId expandoObjId = writer.guardIsObject(expandoValId);
writer.guardShape(expandoObjId, expandoObj->as<NativeObject>().shape());
ObjOperandId expandoObjId =
guardDOMProxyExpandoObjectAndShape(obj, objId, expandoVal, expandoObj);
if (canCache == CanAttachReadSlot) {
// Load from the expando's slots.
@ -2128,20 +2136,20 @@ LookupShapeForSetSlot(NativeObject* obj, jsid id)
return nullptr;
}
bool
SetPropIRGenerator::tryAttachNativeSetSlot(HandleObject obj, ObjOperandId objId, HandleId id,
ValOperandId rhsId)
static bool
CanAttachNativeSetSlot(JSContext* cx, HandleObject obj, HandleId id,
bool* isTemporarilyUnoptimizable, MutableHandleShape propShape)
{
if (!obj->isNative())
return false;
RootedShape propShape(cx_, LookupShapeForSetSlot(&obj->as<NativeObject>(), id));
propShape.set(LookupShapeForSetSlot(&obj->as<NativeObject>(), id));
if (!propShape)
return false;
RootedObjectGroup group(cx_, JSObject::getGroup(cx_, obj));
ObjectGroup* group = JSObject::getGroup(cx, obj);
if (!group) {
cx_->recoverFromOutOfMemory();
cx->recoverFromOutOfMemory();
return false;
}
@ -2149,12 +2157,23 @@ SetPropIRGenerator::tryAttachNativeSetSlot(HandleObject obj, ObjOperandId objId,
// properties, TI will not mark the property as having been
// overwritten. Don't attach a stub in this case, so that we don't
// execute another write to the property without TI seeing that write.
EnsureTrackPropertyTypes(cx_, obj, id);
EnsureTrackPropertyTypes(cx, obj, id);
if (!PropertyHasBeenMarkedNonConstant(obj, id)) {
*isTemporarilyUnoptimizable_ = true;
*isTemporarilyUnoptimizable = true;
return false;
}
return true;
}
bool
SetPropIRGenerator::tryAttachNativeSetSlot(HandleObject obj, ObjOperandId objId, HandleId id,
ValOperandId rhsId)
{
RootedShape propShape(cx_);
if (!CanAttachNativeSetSlot(cx_, obj, id, isTemporarilyUnoptimizable_, &propShape))
return false;
if (mode_ == ICState::Mode::Megamorphic && cacheKind_ == CacheKind::SetProp) {
writer.megamorphicStoreSlot(objId, JSID_TO_ATOM(id)->asPropertyName(), rhsId,
typeCheckInfo_.needsTypeBarrier());
@ -2802,6 +2821,58 @@ SetPropIRGenerator::tryAttachDOMProxyUnshadowed(HandleObject obj, ObjOperandId o
return true;
}
bool
SetPropIRGenerator::tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objId, HandleId id,
ValOperandId rhsId)
{
MOZ_ASSERT(IsCacheableDOMProxy(obj));
RootedValue expandoVal(cx_, GetProxyExtra(obj, GetDOMProxyExpandoSlot()));
RootedObject expandoObj(cx_);
if (expandoVal.isObject()) {
expandoObj = &expandoVal.toObject();
} else {
MOZ_ASSERT(!expandoVal.isUndefined(),
"How did a missing expando manage to shadow things?");
auto expandoAndGeneration = static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate());
MOZ_ASSERT(expandoAndGeneration);
expandoObj = &expandoAndGeneration->expando.toObject();
}
RootedShape propShape(cx_);
if (CanAttachNativeSetSlot(cx_, expandoObj, id, isTemporarilyUnoptimizable_, &propShape)) {
maybeEmitIdGuard(id);
ObjOperandId expandoObjId =
guardDOMProxyExpandoObjectAndShape(obj, objId, expandoVal, expandoObj);
NativeObject* nativeExpandoObj = &expandoObj->as<NativeObject>();
writer.guardGroup(expandoObjId, nativeExpandoObj->group());
typeCheckInfo_.set(nativeExpandoObj->group(), id);
EmitStoreSlotAndReturn(writer, expandoObjId, nativeExpandoObj, propShape, rhsId);
trackAttached("DOMProxyExpandoSlot");
return true;
}
RootedObject holder(cx_);
if (CanAttachSetter(cx_, pc_, expandoObj, id, &holder, &propShape,
isTemporarilyUnoptimizable_))
{
// Note that we don't actually use the expandoObjId here after the
// shape guard. The DOM proxy (objId) is passed to the setter as
// |this|.
maybeEmitIdGuard(id);
guardDOMProxyExpandoObjectAndShape(obj, objId, expandoVal, expandoObj);
MOZ_ASSERT(holder == expandoObj);
EmitCallSetterNoGuards(writer, expandoObj, expandoObj, propShape, objId, rhsId);
trackAttached("DOMProxyExpandoSetter");
return true;
}
return false;
}
bool
SetPropIRGenerator::tryAttachProxy(HandleObject obj, ObjOperandId objId, HandleId id,
ValOperandId rhsId)
@ -2820,6 +2891,13 @@ SetPropIRGenerator::tryAttachProxy(HandleObject obj, ObjOperandId objId, HandleI
case ProxyStubType::None:
break;
case ProxyStubType::DOMExpando:
if (tryAttachDOMProxyExpando(obj, objId, id, rhsId))
return true;
if (*isTemporarilyUnoptimizable_) {
// Scripted setter without JIT code. Just wait.
return false;
}
MOZ_FALLTHROUGH; // Fall through to the generic shadowed case.
case ProxyStubType::DOMShadowed:
return tryAttachDOMProxyShadowed(obj, objId, id, rhsId);
case ProxyStubType::DOMUnshadowed:

View File

@ -982,6 +982,9 @@ class MOZ_RAII IRGenerator
bool maybeGuardInt32Index(const Value& index, ValOperandId indexId,
uint32_t* int32Index, Int32OperandId* int32IndexId);
ObjOperandId guardDOMProxyExpandoObjectAndShape(JSObject* obj, ObjOperandId objId,
const Value& expandoVal, JSObject* expandoObj);
void emitIdGuard(ValOperandId valId, jsid id);
friend class CacheIRSpewer;
@ -1186,6 +1189,8 @@ class MOZ_RAII SetPropIRGenerator : public IRGenerator
ValOperandId rhsId);
bool tryAttachDOMProxyUnshadowed(HandleObject obj, ObjOperandId objId, HandleId id,
ValOperandId rhsId);
bool tryAttachDOMProxyExpando(HandleObject obj, ObjOperandId objId, HandleId id,
ValOperandId rhsId);
bool tryAttachProxy(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId);
bool tryAttachProxyElement(HandleObject obj, ObjOperandId objId, ValOperandId rhsId);