Bug 1125505 - SetClassAndProto should only reshape the whole proto chain if the object is another object's proto. r=bhackett
authorJan de Mooij <jdemooij@mozilla.com>
Fri, 30 Jan 2015 12:17:10 +0100
changeset 226712 130592a094622b92a51f7c607f704d2b5b72b0b3
parent 226711 fbfefa0716cb7a4c40fbd02f683628769eb70937
child 226713 a548e7731814cf7a1692500ce02d696d2b32a897
push id54901
push userjandemooij@gmail.com
push dateFri, 30 Jan 2015 11:21:30 +0000
treeherdermozilla-inbound@130592a09462 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett
bugs1125505
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1125505 - SetClassAndProto should only reshape the whole proto chain if the object is another object's proto. r=bhackett
js/src/jit-test/tests/basic/mutable-proto-teleporting.js
js/src/jit-test/tests/basic/teleporting-mutable-proto.js
js/src/jsinfer.cpp
js/src/jsobj.cpp
js/src/jsobj.h
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/mutable-proto-teleporting.js
@@ -0,0 +1,18 @@
+// The teleporting optimization should work correctly
+// when we modify an object's proto.
+
+var A = {x: 1};
+var B = Object.create(A);
+
+var C = {};
+C.__proto__ = B;
+
+function f() {
+    for (var i=0; i<25; i++) {
+        assertEq(C.x, (i <= 20) ? 1 : 3);
+        if (i === 20) {
+            B.x = 3;
+        }
+    }
+}
+f();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/teleporting-mutable-proto.js
@@ -0,0 +1,14 @@
+var A = {x: 3};
+var B = Object.create(A);
+var C = Object.create(B);
+var D = Object.create(C);
+
+function f() {
+    for (var i=0; i<30; i++) {
+	assertEq(D.x, (i <= 20) ? 3 : 10);
+	if (i === 20) {
+	    C.__proto__ = {x: 10};
+	}
+    }
+}
+f();
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -4381,16 +4381,19 @@ JSObject::splicePrototype(JSContext *cx,
      * can be rearranged as needed without destroying type information for
      * the old or new types.
      */
     MOZ_ASSERT(self->hasSingletonType());
 
     /* Inner objects may not appear on prototype chains. */
     MOZ_ASSERT_IF(proto.isObject(), !proto.toObject()->getClass()->ext.outerObject);
 
+    if (proto.isObject() && !proto.toObject()->setDelegate(cx))
+        return false;
+
     /*
      * Force type instantiation when splicing lazy types. This may fail,
      * in which case inference will be disabled for the compartment.
      */
     Rooted<TypeObject*> type(cx, self->getType(cx));
     if (!type)
         return false;
     Rooted<TypeObject*> protoType(cx, nullptr);
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2699,47 +2699,55 @@ JSObject::fixupAfterMovingGC()
         }
     }
 }
 
 bool
 js::SetClassAndProto(JSContext *cx, HandleObject obj,
                      const Class *clasp, Handle<js::TaggedProto> proto)
 {
-    /*
-     * Regenerate shapes for all of the scopes along the old prototype chain,
-     * in case any entries were filled by looking up through obj. Stop when a
-     * non-native object is found, prototype lookups will not be cached across
-     * these.
-     *
-     * How this shape change is done is very delicate; the change can be made
-     * either by marking the object's prototype as uncacheable (such that the
-     * property cache and JIT'ed ICs cannot assume the shape determines the
-     * prototype) or by just generating a new shape for the object. Choosing
-     * the former is bad if the object is on the prototype chain of other
-     * objects, as the uncacheable prototype can inhibit iterator caches on
-     * those objects and slow down prototype accesses. Choosing the latter is
-     * bad if there are many similar objects to this one which will have their
-     * prototype mutated, as the generateOwnShape forces the object into
-     * dictionary mode and similar property lineages will be repeatedly cloned.
-     *
-     * :XXX: bug 707717 make this code less brittle.
-     */
+    // Regenerate the object's shape. If the object is a proto (isDelegate()),
+    // we also need to regenerate shapes for all of the objects along the old
+    // prototype chain, in case any entries were filled by looking up through
+    // obj. Stop when a non-native object is found, prototype lookups will not
+    // be cached across these.
+    //
+    // How this shape change is done is very delicate; the change can be made
+    // either by marking the object's prototype as uncacheable (such that the
+    // JIT'ed ICs cannot assume the shape determines the prototype) or by just
+    // generating a new shape for the object. Choosing the former is bad if the
+    // object is on the prototype chain of other objects, as the uncacheable
+    // prototype can inhibit iterator caches on those objects and slow down
+    // prototype accesses. Choosing the latter is bad if there are many similar
+    // objects to this one which will have their prototype mutated, as the
+    // generateOwnShape forces the object into dictionary mode and similar
+    // property lineages will be repeatedly cloned.
+    //
+    // :XXX: bug 707717 make this code less brittle.
     RootedObject oldproto(cx, obj);
     while (oldproto && oldproto->isNative()) {
         if (oldproto->hasSingletonType()) {
             if (!oldproto->as<NativeObject>().generateOwnShape(cx))
                 return false;
         } else {
             if (!oldproto->setUncacheableProto(cx))
                 return false;
         }
+        if (!obj->isDelegate()) {
+            // If |obj| is not a proto of another object, we don't need to
+            // reshape the whole proto chain.
+            MOZ_ASSERT(obj == oldproto);
+            break;
+        }
         oldproto = oldproto->getProto();
     }
 
+    if (proto.isObject() && !proto.toObject()->setDelegate(cx))
+        return false;
+
     if (obj->hasSingletonType()) {
         /*
          * Just splice the prototype, but mark the properties as unknown for
          * consistent behavior.
          */
         if (!obj->splicePrototype(cx, clasp, proto))
             return false;
         MarkTypeObjectUnknownProperties(cx, obj->type());
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -360,17 +360,19 @@ class JSObject : public js::gc::Cell
     }
 
     bool hasTenuredProto() const;
 
     bool uninlinedIsProxy() const;
 
     JSObject *getProto() const {
         MOZ_ASSERT(!uninlinedIsProxy());
-        return getTaggedProto().toObjectOrNull();
+        JSObject *proto = getTaggedProto().toObjectOrNull();
+        MOZ_ASSERT_IF(proto && proto->isNative(), proto->isDelegate());
+        return proto;
     }
 
     // Normal objects and a subset of proxies have uninteresting [[Prototype]].
     // For such objects the [[Prototype]] is just a value returned when needed
     // for accesses, or modified in response to requests.  These objects store
     // the [[Prototype]] directly within |obj->type_|.
     //
     // Proxies that don't have such a simple [[Prototype]] instead have a