Bug 792631 - Add IC for missing properties. r=dvander
authorNicolas B. Pierron <nicolas.b.pierron@mozilla.com>
Mon, 29 Oct 2012 14:48:45 -0700
changeset 111840 4a2c17905a17d6497201498382a31979bf717ea2
parent 111839 537120d7827fc618f9e90e5622810a89f4ff85f0
child 111841 eca4483a58cfebfad17734906a19174a78ba6abb
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewersdvander
bugs792631
milestone19.0a1
Bug 792631 - Add IC for missing properties. r=dvander
js/src/ion/IonCaches.cpp
--- a/js/src/ion/IonCaches.cpp
+++ b/js/src/ion/IonCaches.cpp
@@ -153,16 +153,62 @@ IsCacheableGetPropReadSlot(JSObject *obj
 
     if (!shape->hasSlot() || !shape->hasDefaultGetter())
         return false;
 
     return true;
 }
 
 static bool
+IsCacheableNoProperty(JSObject *obj, JSObject *holder, const Shape *shape, jsbytecode *pc,
+                      const TypedOrValueRegister &output)
+{
+    if (shape)
+        return false;
+
+    JS_ASSERT(!holder);
+
+    // Just because we didn't find the property on the object doesn't mean it
+    // won't magically appear through various engine hacks:
+    if (obj->getClass()->getProperty && obj->getClass()->getProperty != JS_PropertyStub)
+        return false;
+
+    // Don't generate missing property ICs if we skipped a non-native object, as
+    // lookups may extend beyond the prototype chain (e.g.  for ListBase
+    // proxies).
+    JSObject *obj2 = obj;
+    while (obj2) {
+        if (!obj2->isNative())
+            return false;
+        obj2 = obj2->getProto();
+    }
+
+#if JS_HAS_NO_SUCH_METHOD
+    // This case cannot appear with an idempotent cache.
+    if (pc) {
+        // The __noSuchMethod__ hook may substitute in a valid method.  Since,
+        // if o.m is missing, o.m() will probably be an error, just mark all
+        // missing callprops as uncacheable.
+        if (JSOp(*pc) == JSOP_CALLPROP ||
+            JSOp(*pc) == JSOP_CALLELEM)
+        {
+            return false;
+        }
+    }
+#endif
+
+    // TI has not yet monitored an Undefined value. The fallback path will
+    // monitor and invalidate the script.
+    if (!output.hasValue())
+        return false;
+
+    return true;
+}
+
+static bool
 IsCacheableGetPropCallNative(JSObject *obj, JSObject *holder, const Shape *shape)
 {
     if (!shape || !IsCacheableProtoChain(obj, holder))
         return false;
 
     if (!shape->hasGetterValue() || !shape->getterValue().isObject())
         return false;
 
@@ -221,36 +267,65 @@ struct GetNativePropertyStub
         }
 
         // Generate prototype guards.
         Register holderReg;
         if (obj != holder) {
             // Note: this may clobber the object register if it's used as scratch.
             GeneratePrototypeGuards(cx, masm, obj, holder, object, scratchReg, &prototypeFailures);
 
-            // Guard on the holder's shape.
-            holderReg = scratchReg;
-            masm.movePtr(ImmGCPtr(holder), holderReg);
-            masm.branchPtr(Assembler::NotEqual,
-                           Address(holderReg, JSObject::offsetOfShape()),
-                           ImmGCPtr(holder->lastProperty()),
-                           &prototypeFailures);
+            if (holder) {
+                // Guard on the holder's shape.
+                holderReg = scratchReg;
+                masm.movePtr(ImmGCPtr(holder), holderReg);
+                masm.branchPtr(Assembler::NotEqual,
+                               Address(holderReg, JSObject::offsetOfShape()),
+                               ImmGCPtr(holder->lastProperty()),
+                               &prototypeFailures);
+            } else {
+                // The property does not exist. Guard on everything in the
+                // prototype chain.
+                JSObject *proto = obj->getTaggedProto().toObjectOrNull();
+                Register lastReg = object;
+                JS_ASSERT(scratchReg != object);
+                while (proto) {
+                    Address addrType(lastReg, JSObject::offsetOfType());
+                    masm.loadPtr(addrType, scratchReg);
+                    Address addrProto(scratchReg, offsetof(types::TypeObject, proto));
+                    masm.loadPtr(addrProto, scratchReg);
+                    Address addrShape(scratchReg, JSObject::offsetOfShape());
+
+                    // Guard the shape of the current prototype.
+                    masm.branchPtr(Assembler::NotEqual,
+                                   Address(scratchReg, JSObject::offsetOfShape()),
+                                   ImmGCPtr(proto->lastProperty()),
+                                   &prototypeFailures);
+
+                    proto = proto->getProto();
+                    lastReg = scratchReg;
+                }
+
+                holderReg = InvalidReg;
+            }
         } else {
             holderReg = object;
         }
 
         // Slot access.
-        if (holder->isFixedSlot(shape->slot())) {
+        if (holder && holder->isFixedSlot(shape->slot())) {
             Address addr(holderReg, JSObject::getFixedSlotOffset(shape->slot()));
             masm.loadTypedOrValue(addr, output);
-        } else {
+        } else if (holder) {
             masm.loadPtr(Address(holderReg, JSObject::offsetOfSlots()), scratchReg);
 
             Address addr(scratchReg, holder->dynamicSlotIndex(shape->slot()) * sizeof(Value));
             masm.loadTypedOrValue(addr, output);
+        } else {
+            JS_ASSERT(!holder);
+            masm.moveValue(UndefinedValue(), output.valueReg());
         }
 
         if (restoreScratch)
             masm.pop(scratchReg);
 
         RepatchLabel rejoin_;
         rejoinOffset = masm.jumpWithPatch(&rejoin_);
         masm.bind(&rejoin_);
@@ -649,17 +724,23 @@ TryAttachNativeGetPropStub(JSContext *cx
     if (!JSObject::lookupProperty(cx, checkObj, name, &holder, &shape))
         return false;
 
     // Check what kind of cache stub we can emit: either a slot read,
     // or a getter call.
     bool readSlot = false;
     bool callGetter = false;
 
-    if (IsCacheableGetPropReadSlot(checkObj, holder, shape)) {
+    RootedScript script(cx);
+    jsbytecode *pc;
+    cache.getScriptedLocation(&script, &pc);
+
+    if (IsCacheableGetPropReadSlot(checkObj, holder, shape) ||
+        IsCacheableNoProperty(checkObj, holder, shape, pc, cache.output()))
+    {
         readSlot = true;
     } else if (IsCacheableGetPropCallNative(checkObj, holder, shape) ||
                IsCacheableGetPropCallPropertyOp(checkObj, holder, shape))
     {
         // Don't enable getter call if cache is idempotent, since
         // they can be effectful.
         if (!cache.idempotent() && cache.allowGetters())
             callGetter = true;
@@ -670,16 +751,17 @@ TryAttachNativeGetPropStub(JSContext *cx
         return true;
 
     // TI infers the possible types of native object properties. There's one
     // edge case though: for singleton objects it does not add the initial
     // "undefined" type, see the propertySet comment in jsinfer.h. We can't
     // monitor the return type inside an idempotent cache though, so we don't
     // handle this case.
     if (cache.idempotent() &&
+        holder &&
         holder->hasSingletonType() &&
         holder->getSlot(shape->slot()).isUndefined())
     {
         return true;
     }
 
     *isCacheable = true;
 
@@ -1301,17 +1383,22 @@ bool
 IonCacheGetElement::attachGetProp(JSContext *cx, IonScript *ion, HandleObject obj,
                                   const Value &idval, PropertyName *name)
 {
     RootedObject holder(cx);
     RootedShape shape(cx);
     if (!JSObject::lookupProperty(cx, obj, name, &holder, &shape))
         return false;
 
-    if (!IsCacheableGetPropReadSlot(obj, holder, shape)) {
+    RootedScript script(cx);
+    jsbytecode *pc;
+    getScriptedLocation(&script, &pc);
+
+    if (!IsCacheableGetPropReadSlot(obj, holder, shape) &&
+        !IsCacheableNoProperty(obj, holder, shape, pc, output())) {
         IonSpew(IonSpew_InlineCaches, "GETELEM uncacheable property");
         return true;
     }
 
     JS_ASSERT(idval.isString());
 
     RepatchLabel failures;
     Label nonRepatchFailures;
@@ -1756,28 +1843,29 @@ IonCacheName::attach(JSContext *cx, IonS
     }
 
     IonSpew(IonSpew_InlineCaches, "Generated NAME stub at %p", code->raw());
     return true;
 }
 
 static bool
 IsCacheableName(JSContext *cx, HandleObject scopeChain, HandleObject obj, HandleObject holder,
-                HandleShape shape)
+                HandleShape shape, const TypedOrValueRegister &output)
 {
     if (!shape)
         return false;
     if (!obj->isNative())
         return false;
     if (obj != holder)
         return false;
 
     if (obj->isGlobal()) {
         // Support only simple property lookups.
-        if (!IsCacheableGetPropReadSlot(obj, holder, shape))
+        if (!IsCacheableGetPropReadSlot(obj, holder, shape) &&
+            !IsCacheableNoProperty(obj, holder, shape, NULL, output))
             return false;
     } else if (obj->isCall()) {
         if (!shape->hasDefaultGetter())
             return false;
     } else {
         // We don't yet support lookups on Block or DeclEnv objects.
         return false;
     }
@@ -1813,17 +1901,17 @@ js::ion::GetNameCache(JSContext *cx, siz
 
     RootedObject obj(cx);
     RootedObject holder(cx);
     RootedShape shape(cx);
     if (!LookupName(cx, name, scopeChain, &obj, &holder, &shape))
         return false;
 
     if (cache.stubCount() < MAX_STUBS &&
-        IsCacheableName(cx, scopeChain, obj, holder, shape))
+        IsCacheableName(cx, scopeChain, obj, holder, shape, cache.outputReg()))
     {
         if (!cache.attach(cx, ion, scopeChain, obj, shape))
             return false;
         cache.incrementStubCount();
     }
 
     if (cache.isTypeOf()) {
         if (!FetchName<true>(cx, obj, holder, name, shape, vp))