Bug 724768 - Set every blank class prototype as a delegate immediately after birth, so that we can properly and correctly cache the shape for new instances of that class that use that prototype. r=bhackett
authorJeff Walden <jwalden@mit.edu>
Thu, 05 Dec 2013 01:56:40 -0800
changeset 174980 479975fcd7368b78f47a2c8539a67702972af640
parent 174979 54eac2d5c0392d2d8138e761a394ecf8e45407b4
child 174981 3907208b88c59f14adc5bf9aa64137cff8c6c88f
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett
bugs724768
milestone28.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 724768 - Set every blank class prototype as a delegate immediately after birth, so that we can properly and correctly cache the shape for new instances of that class that use that prototype. r=bhackett
js/src/jsstr.cpp
js/src/vm/ErrorObject.cpp
js/src/vm/ErrorObject.h
js/src/vm/GlobalObject.cpp
js/src/vm/RegExpObject.cpp
js/src/vm/RegExpObject.h
js/src/vm/Shape-inl.h
js/src/vm/Shape.h
js/src/vm/StringObject-inl.h
js/src/vm/StringObject.h
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -3796,23 +3796,23 @@ static const JSFunctionSpec string_stati
     // This must be at the end because of bug 853075: functions listed after
     // self-hosted methods aren't available in self-hosted code.
 #if EXPOSE_INTL_API
     JS_SELF_HOSTED_FN("localeCompare", "String_static_localeCompare", 2,0),
 #endif
     JS_FS_END
 };
 
-Shape *
-StringObject::assignInitialShape(JSContext *cx)
+/* static */ Shape *
+StringObject::assignInitialShape(ExclusiveContext *cx, Handle<StringObject*> obj)
 {
-    JS_ASSERT(nativeEmpty());
-
-    return addDataProperty(cx, cx->names().length, LENGTH_SLOT,
-                           JSPROP_PERMANENT | JSPROP_READONLY);
+    JS_ASSERT(obj->nativeEmpty());
+
+    return obj->addDataProperty(cx, cx->names().length, LENGTH_SLOT,
+                                JSPROP_PERMANENT | JSPROP_READONLY);
 }
 
 JSObject *
 js_InitStringClass(JSContext *cx, HandleObject obj)
 {
     JS_ASSERT(obj->isNative());
 
     Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
--- a/js/src/vm/ErrorObject.cpp
+++ b/js/src/vm/ErrorObject.cpp
@@ -6,20 +6,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "vm/ErrorObject.h"
 
 #include "vm/GlobalObject.h"
 
 #include "jsobjinlines.h"
 
+#include "vm/Shape-inl.h"
+
 using namespace js;
 
 /* static */ Shape *
-js::ErrorObject::assignInitialShapeNoMessage(JSContext *cx, Handle<ErrorObject*> obj)
+js::ErrorObject::assignInitialShape(ExclusiveContext *cx, Handle<ErrorObject*> obj)
 {
     MOZ_ASSERT(obj->nativeEmpty());
 
     if (!obj->addDataProperty(cx, cx->names().fileName, FILENAME_SLOT, 0))
         return nullptr;
     if (!obj->addDataProperty(cx, cx->names().lineNumber, LINENUMBER_SLOT, 0))
         return nullptr;
     if (!obj->addDataProperty(cx, cx->names().columnNumber, COLUMNNUMBER_SLOT, 0))
@@ -31,28 +33,18 @@ js::ErrorObject::assignInitialShapeNoMes
 js::ErrorObject::init(JSContext *cx, Handle<ErrorObject*> obj, JSExnType type,
                       ScopedJSFreePtr<JSErrorReport> *errorReport, HandleString fileName,
                       HandleString stack, uint32_t lineNumber, uint32_t columnNumber,
                       HandleString message)
 {
     // Null out early in case of error, for exn_finalize's sake.
     obj->initReservedSlot(ERROR_REPORT_SLOT, PrivateValue(nullptr));
 
-    if (obj->nativeEmpty()) {
-        // Create the initial shape now.  Subsequent error objects with this
-        // object's [[Prototype]] will then start life with that shape.
-        RootedShape shape(cx, ErrorObject::assignInitialShapeNoMessage(cx, obj));
-        if (!shape)
-            return false;
-        if (!obj->isDelegate()) {
-            RootedObject proto(cx, obj->getProto());
-            EmptyShape::insertInitialShape(cx, shape, proto);
-        }
-        MOZ_ASSERT(!obj->nativeEmpty());
-    }
+    if (!EmptyShape::ensureInitialCustomShape<ErrorObject>(cx, obj))
+        return false;
 
     // The .message property isn't part of the initial shape because it's
     // present in some error objects -- |Error.prototype|, |new Error("f")|,
     // |new Error("")| -- but not in others -- |new Error(undefined)|,
     // |new Error()|.
     RootedShape messageShape(cx);
     if (message) {
         messageShape = obj->addDataProperty(cx, cx->names().message, MESSAGE_SLOT, 0);
--- a/js/src/vm/ErrorObject.h
+++ b/js/src/vm/ErrorObject.h
@@ -4,16 +4,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef vm_ErrorObject_h_
 #define vm_ErrorObject_h_
 
 #include "jsobj.h"
 
+#include "vm/Shape.h"
+
 struct JSExnPrivate;
 
 /*
  * Initialize the exception constructor/prototype hierarchy.
  */
 extern JSObject *
 js_InitExceptionClasses(JSContext *cx, JS::HandleObject obj);
 
@@ -24,18 +26,28 @@ class ErrorObject : public JSObject
     static ErrorObject *
     createProto(JSContext *cx, JS::Handle<GlobalObject*> global, JSExnType type,
                 JS::HandleObject proto);
 
     /* For access to createProto. */
     friend JSObject *
     ::js_InitExceptionClasses(JSContext *cx, JS::HandleObject global);
 
+    /* For access to assignInitialShape. */
+    friend bool
+    EmptyShape::ensureInitialCustomShape<ErrorObject>(ExclusiveContext *cx,
+                                                      Handle<ErrorObject*> obj);
+
+    /*
+     * Assign the initial error shape to the empty object.  (This shape does
+     * *not* include .message, which must be added separately if needed; see
+     * ErrorObject::init.)
+     */
     static Shape *
-    assignInitialShapeNoMessage(JSContext *cx, Handle<ErrorObject*> obj);
+    assignInitialShape(ExclusiveContext *cx, Handle<ErrorObject*> obj);
 
     static bool
     init(JSContext *cx, Handle<ErrorObject*> obj, JSExnType type,
          ScopedJSFreePtr<JSErrorReport> *errorReport, HandleString fileName, HandleString stack,
          uint32_t lineNumber, uint32_t columnNumber, HandleString message);
 
   protected:
     static const uint32_t EXNTYPE_SLOT      = 0;
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -555,17 +555,17 @@ GlobalObject::createConstructor(JSContex
 
 static JSObject *
 CreateBlankProto(JSContext *cx, const Class *clasp, JSObject &proto, GlobalObject &global)
 {
     JS_ASSERT(clasp != &JSObject::class_);
     JS_ASSERT(clasp != &JSFunction::class_);
 
     RootedObject blankProto(cx, NewObjectWithGivenProto(cx, clasp, &proto, &global, SingletonObject));
-    if (!blankProto)
+    if (!blankProto || !blankProto->setDelegate(cx))
         return nullptr;
 
     return blankProto;
 }
 
 JSObject *
 GlobalObject::createBlankPrototype(JSContext *cx, const Class *clasp)
 {
--- a/js/src/vm/RegExpObject.cpp
+++ b/js/src/vm/RegExpObject.cpp
@@ -12,16 +12,18 @@
 #include "vm/MatchPairs.h"
 #include "vm/RegExpStatics.h"
 #include "vm/StringBuffer.h"
 #include "vm/Xdr.h"
 #include "yarr/YarrSyntaxChecker.h"
 
 #include "jsobjinlines.h"
 
+#include "vm/Shape-inl.h"
+
 using namespace js;
 using js::frontend::TokenStream;
 
 JS_STATIC_ASSERT(IgnoreCaseFlag == JSREG_FOLD);
 JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB);
 JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE);
 JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY);
 
@@ -268,32 +270,29 @@ RegExpObject::createShared(ExclusiveCont
     if (!cx->compartment()->regExps.get(cx, getSource(), getFlags(), g))
         return false;
 
     self->setShared(cx, **g);
     return true;
 }
 
 Shape *
-RegExpObject::assignInitialShape(ExclusiveContext *cx)
+RegExpObject::assignInitialShape(ExclusiveContext *cx, Handle<RegExpObject*> self)
 {
-    JS_ASSERT(is<RegExpObject>());
-    JS_ASSERT(nativeEmpty());
+    JS_ASSERT(self->nativeEmpty());
 
     JS_STATIC_ASSERT(LAST_INDEX_SLOT == 0);
     JS_STATIC_ASSERT(SOURCE_SLOT == LAST_INDEX_SLOT + 1);
     JS_STATIC_ASSERT(GLOBAL_FLAG_SLOT == SOURCE_SLOT + 1);
     JS_STATIC_ASSERT(IGNORE_CASE_FLAG_SLOT == GLOBAL_FLAG_SLOT + 1);
     JS_STATIC_ASSERT(MULTILINE_FLAG_SLOT == IGNORE_CASE_FLAG_SLOT + 1);
     JS_STATIC_ASSERT(STICKY_FLAG_SLOT == MULTILINE_FLAG_SLOT + 1);
 
-    RootedObject self(cx, this);
-
     /* The lastIndex property alone is writable but non-configurable. */
-    if (!addDataProperty(cx, cx->names().lastIndex, LAST_INDEX_SLOT, JSPROP_PERMANENT))
+    if (!self->addDataProperty(cx, cx->names().lastIndex, LAST_INDEX_SLOT, JSPROP_PERMANENT))
         return nullptr;
 
     /* Remaining instance properties are non-writable and non-configurable. */
     unsigned attrs = JSPROP_PERMANENT | JSPROP_READONLY;
     if (!self->addDataProperty(cx, cx->names().source, SOURCE_SLOT, attrs))
         return nullptr;
     if (!self->addDataProperty(cx, cx->names().global, GLOBAL_FLAG_SLOT, attrs))
         return nullptr;
@@ -304,29 +303,18 @@ RegExpObject::assignInitialShape(Exclusi
     return self->addDataProperty(cx, cx->names().sticky, STICKY_FLAG_SLOT, attrs);
 }
 
 bool
 RegExpObject::init(ExclusiveContext *cx, HandleAtom source, RegExpFlag flags)
 {
     Rooted<RegExpObject *> self(cx, this);
 
-    if (nativeEmpty()) {
-        if (isDelegate()) {
-            if (!assignInitialShape(cx))
-                return false;
-        } else {
-            RootedShape shape(cx, assignInitialShape(cx));
-            if (!shape)
-                return false;
-            RootedObject proto(cx, self->getProto());
-            EmptyShape::insertInitialShape(cx, shape, proto);
-        }
-        JS_ASSERT(!self->nativeEmpty());
-    }
+    if (!EmptyShape::ensureInitialCustomShape<RegExpObject>(cx, self))
+        return false;
 
     JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().lastIndex))->slot() ==
               LAST_INDEX_SLOT);
     JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().source))->slot() ==
               SOURCE_SLOT);
     JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().global))->slot() ==
               GLOBAL_FLAG_SLOT);
     JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().ignoreCase))->slot() ==
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -10,16 +10,17 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/MemoryReporting.h"
 
 #include "jscntxt.h"
 #include "jsproxy.h"
 
 #include "gc/Marking.h"
 #include "gc/Zone.h"
+#include "vm/Shape.h"
 #if ENABLE_YARR_JIT
 #include "yarr/YarrJIT.h"
 #else
 #include "yarr/YarrInterpreter.h"
 #endif
 
 /*
  * JavaScript Regular Expressions
@@ -433,22 +434,28 @@ class RegExpObject : public JSObject
     void setShared(ExclusiveContext *cx, RegExpShared &shared) {
         shared.prepareForUse(cx);
         JSObject::setPrivate(&shared);
     }
 
   private:
     friend class RegExpObjectBuilder;
 
+    /* For access to assignInitialShape. */
+    friend bool
+    EmptyShape::ensureInitialCustomShape<RegExpObject>(ExclusiveContext *cx,
+                                                       Handle<RegExpObject*> obj);
+
     /*
      * Compute the initial shape to associate with fresh RegExp objects,
      * encoding their initial properties. Return the shape after
-     * changing this regular expression object's last property to it.
+     * changing |obj|'s last property to it.
      */
-    Shape *assignInitialShape(ExclusiveContext *cx);
+    static Shape *
+    assignInitialShape(ExclusiveContext *cx, Handle<RegExpObject*> obj);
 
     bool init(ExclusiveContext *cx, HandleAtom source, RegExpFlag flags);
 
     /*
      * Precondition: the syntax for |source| has already been validated.
      * Side effect: sets the private field.
      */
     bool createShared(ExclusiveContext *cx, RegExpGuard *g);
--- a/js/src/vm/Shape-inl.h
+++ b/js/src/vm/Shape-inl.h
@@ -4,16 +4,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef vm_Shape_inl_h
 #define vm_Shape_inl_h
 
 #include "vm/Shape.h"
 
+#include "mozilla/TypeTraits.h"
+
 #include "jsobj.h"
 
 #include "vm/Interpreter.h"
 #include "vm/ScopeObject.h"
 
 #include "jsatominlines.h"
 #include "jscntxtinlines.h"
 #include "jsgcinlines.h"
@@ -173,16 +175,49 @@ Shape::search(ExclusiveContext *cx, Shap
     for (Shape *shape = start; shape; shape = shape->parent) {
         if (shape->propidRef() == id)
             return shape;
     }
 
     return nullptr;
 }
 
+template<class ObjectSubclass>
+/* static */ inline bool
+EmptyShape::ensureInitialCustomShape(ExclusiveContext *cx, Handle<ObjectSubclass*> obj)
+{
+    static_assert(mozilla::IsBaseOf<JSObject, ObjectSubclass>::value,
+                  "ObjectSubclass must be a subclass of JSObject");
+
+    // If the provided object has a non-empty shape, it was given the cached
+    // initial shape when created: nothing to do.
+    if (!obj->nativeEmpty())
+        return true;
+
+    // If no initial shape was assigned, do so.
+    RootedShape shape(cx, ObjectSubclass::assignInitialShape(cx, obj));
+    if (!shape)
+        return false;
+    MOZ_ASSERT(!obj->nativeEmpty());
+
+    // If the object is a standard prototype -- |RegExp.prototype|,
+    // |String.prototype|, |RangeError.prototype|, &c. -- GlobalObject.cpp's
+    // |CreateBlankProto| marked it as a delegate.  These are the only objects
+    // of this class that won't use the standard prototype, and there's no
+    // reason to pollute the initial shape cache with entries for them.
+    if (obj->isDelegate())
+        return true;
+
+    // Cache the initial shape for non-prototype objects, however, so that
+    // future instances will begin life with that shape.
+    RootedObject proto(cx, obj->getProto());
+    EmptyShape::insertInitialShape(cx, shape, proto);
+    return true;
+}
+
 inline
 AutoRooterGetterSetter::Inner::Inner(ThreadSafeContext *cx, uint8_t attrs,
                                      PropertyOp *pgetter_, StrictPropertyOp *psetter_)
   : CustomAutoRooter(cx), attrs(attrs),
     pgetter(pgetter_), psetter(psetter_),
     getterRoot(cx, pgetter_), setterRoot(cx, psetter_)
 {
     JS_ASSERT_IF(attrs & JSPROP_GETTER, !IsPoisonedPtr(*pgetter));
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -1377,16 +1377,29 @@ struct EmptyShape : public js::Shape
                                   JSObject *parent, gc::AllocKind kind, uint32_t objectFlags = 0);
 
     /*
      * Reinsert an alternate initial shape, to be returned by future
      * getInitialShape calls, until the new shape becomes unreachable in a GC
      * and the table entry is purged.
      */
     static void insertInitialShape(ExclusiveContext *cx, HandleShape shape, HandleObject proto);
+
+    /*
+     * Some object subclasses are allocated with a built-in set of properties.
+     * The first time such an object is created, these built-in properties must
+     * be set manually, to compute an initial shape.  Afterward, that initial
+     * shape can be reused for newly-created objects that use the subclass's
+     * standard prototype.  This method should be used in a post-allocation
+     * init method, to ensure that objects of such subclasses compute and cache
+     * the initial shape, if it hasn't already been computed.
+     */
+    template<class ObjectSubclass>
+    static inline bool
+    ensureInitialCustomShape(ExclusiveContext *cx, Handle<ObjectSubclass*> obj);
 };
 
 /*
  * Entries for the per-compartment initialShapes set indexing initial shapes
  * for objects in the compartment and the associated types.
  */
 struct InitialShapeEntry
 {
--- a/js/src/vm/StringObject-inl.h
+++ b/js/src/vm/StringObject-inl.h
@@ -6,37 +6,29 @@
 
 #ifndef vm_StringObject_inl_h
 #define vm_StringObject_inl_h
 
 #include "vm/StringObject.h"
 
 #include "jsobjinlines.h"
 
+#include "vm/Shape-inl.h"
+
 namespace js {
 
 inline bool
 StringObject::init(JSContext *cx, HandleString str)
 {
     JS_ASSERT(numFixedSlots() == 2);
 
     Rooted<StringObject *> self(cx, this);
 
-    if (nativeEmpty()) {
-        if (isDelegate()) {
-            if (!assignInitialShape(cx))
-                return false;
-        } else {
-            RootedShape shape(cx, assignInitialShape(cx));
-            if (!shape)
-                return false;
-            RootedObject proto(cx, self->getProto());
-            EmptyShape::insertInitialShape(cx, shape, proto);
-        }
-    }
+    if (!EmptyShape::ensureInitialCustomShape<StringObject>(cx, self))
+        return false;
 
     JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().length))->slot() == LENGTH_SLOT);
 
     self->setStringThis(str);
 
     return true;
 }
 
--- a/js/src/vm/StringObject.h
+++ b/js/src/vm/StringObject.h
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef vm_StringObject_h
 #define vm_StringObject_h
 
 #include "jsobj.h"
 #include "jsstr.h"
 
+#include "vm/Shape.h"
+
 namespace js {
 
 class StringObject : public JSObject
 {
     static const unsigned PRIMITIVE_VALUE_SLOT = 0;
     static const unsigned LENGTH_SLOT = 1;
 
   public:
@@ -52,19 +54,25 @@ class StringObject : public JSObject
         setFixedSlot(PRIMITIVE_VALUE_SLOT, StringValue(str));
         setFixedSlot(LENGTH_SLOT, Int32Value(int32_t(str->length())));
     }
 
     /* For access to init, as String.prototype is special. */
     friend JSObject *
     ::js_InitStringClass(JSContext *cx, js::HandleObject global);
 
+    /* For access to assignInitialShape. */
+    friend bool
+    EmptyShape::ensureInitialCustomShape<StringObject>(ExclusiveContext *cx,
+                                                       Handle<StringObject*> obj);
+
     /*
      * Compute the initial shape to associate with fresh String objects, which
      * encodes the initial length property. Return the shape after changing
-     * this String object's last property to it.
+     * |obj|'s last property to it.
      */
-    Shape *assignInitialShape(JSContext *cx);
+    static Shape *
+    assignInitialShape(ExclusiveContext *cx, Handle<StringObject*> obj);
 };
 
 } // namespace js
 
 #endif /* vm_StringObject_h */