Avoid needless prototype-shape purges (454035, r=igor).
authorBrendan Eich <brendan@mozilla.org>
Tue, 09 Sep 2008 09:57:10 -0700
changeset 19087 1e8705a505019c0049a2b8cc72e687b29d452ed0
parent 19086 dba82b744d758163ea346691beb7c909436f030b
child 19088 b225e0d94dec57136b666dac297ce7b6421d8ff4
push id1930
push usermrbkap@mozilla.com
push dateWed, 10 Sep 2008 06:40:47 +0000
treeherderautoland@ee61af1469cd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersigor
bugs454035
milestone1.9.1b1pre
Avoid needless prototype-shape purges (454035, r=igor).
js/src/jsarray.cpp
js/src/jsbuiltins.cpp
js/src/jsinterp.cpp
js/src/jsinterp.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsscope.h
js/src/jstracer.cpp
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -1165,19 +1165,19 @@ js_MakeArraySlow(JSContext *cx, JSObject
      * we can tell when only named properties have been added to a dense array
      * to make it slow-but-not-sparse.
      */
     length = obj->fslots[JSSLOT_ARRAY_LENGTH];
     obj->fslots[JSSLOT_ARRAY_COUNT] = INT_FITS_IN_JSVAL(length)
                                       ? INT_TO_JSVAL(length)
                                       : JSVAL_VOID;
 
-    /* Make sure we preserve any flags borrowing bits in JSSLOT_CLASS. */
-    obj->fslots[JSSLOT_CLASS] ^= (jsval) &js_ArrayClass;
-    obj->fslots[JSSLOT_CLASS] |= (jsval) &js_SlowArrayClass;
+    /* Make sure we preserve any flags borrowing bits in classword. */
+    obj->classword ^= (jsuword) &js_ArrayClass;
+    obj->classword |= (jsuword) &js_SlowArrayClass;
 
     /* Swap in our new map. */
     oldmap = obj->map;
     obj->map = map;
     array_destroyObjectMap(cx, oldmap);
 
     return JS_TRUE;
 
--- a/js/src/jsbuiltins.cpp
+++ b/js/src/jsbuiltins.cpp
@@ -493,22 +493,22 @@ js_FastNewArray(JSContext* cx, JSObject*
 {
     JS_ASSERT(OBJ_IS_ARRAY(cx, proto));
 
     JS_ASSERT(JS_ON_TRACE(cx));
     JSObject* obj = (JSObject*) js_NewGCThing(cx, GCX_OBJECT, sizeof(JSObject));
     if (!obj)
         return NULL;
 
+    JSClass* clasp = &js_ArrayClass;
+    obj->classword = jsuword(clasp);
+
     obj->fslots[JSSLOT_PROTO] = OBJECT_TO_JSVAL(proto);
     obj->fslots[JSSLOT_PARENT] = proto->fslots[JSSLOT_PARENT];
 
-    JSClass* clasp = &js_ArrayClass;
-    obj->fslots[JSSLOT_CLASS] = PRIVATE_TO_JSVAL(clasp);
-
     obj->fslots[JSSLOT_ARRAY_LENGTH] = 0;
     obj->fslots[JSSLOT_ARRAY_COUNT] = 0;
     for (unsigned i = JSSLOT_ARRAY_COUNT + 1; i != JS_INITIAL_NSLOTS; ++i)
         obj->fslots[i] = JSVAL_VOID;
 
     JSObjectOps* ops = clasp->getObjectOps(cx, clasp);
     obj->map = ops->newObjectMap(cx, 1, ops, clasp, obj);
     if (!obj->map)
@@ -538,19 +538,19 @@ js_FastNewObject(JSContext* cx, JSObject
     JSScopeProperty *sprop = SCOPE_GET_PROPERTY(scope, ATOM_TO_JSID(atom));
     JS_ASSERT(SPROP_HAS_VALID_SLOT(sprop, scope));
     jsval v = LOCKED_OBJ_GET_SLOT(ctor, sprop->slot);
     JS_UNLOCK_SCOPE(cx, scope);
 
     JS_ASSERT(!JSVAL_IS_PRIMITIVE(v));
     JSObject* proto = JSVAL_TO_OBJECT(v);
 
+    obj->classword = jsuword(clasp);
     obj->fslots[JSSLOT_PROTO] = OBJECT_TO_JSVAL(proto);
     obj->fslots[JSSLOT_PARENT] = ctor->fslots[JSSLOT_PARENT];
-    obj->fslots[JSSLOT_CLASS] = PRIVATE_TO_JSVAL(clasp);
     for (unsigned i = JSSLOT_PRIVATE; i != JS_INITIAL_NSLOTS; ++i)
         obj->fslots[i] = JSVAL_VOID;
 
     obj->map = js_HoldObjectMap(cx, proto->map);
     obj->dslots = NULL;
     return obj;
 }
 
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -326,31 +326,30 @@ js_FullTestPropertyCache(JSContext *cx, 
     entry = &JS_PROPERTY_CACHE(cx).table[PROPERTY_CACHE_HASH_ATOM(atom, obj, NULL)];
     *entryp = entry;
     vcap = entry->vcap;
 
     if (entry->kpc != (jsbytecode *) atom) {
         PCMETER(JS_PROPERTY_CACHE(cx).idmisses++);
 
 #ifdef DEBUG_notme
-        entry = &JS_PROPERTY_CACHE(cx)
-                 .table[PROPERTY_CACHE_HASH_PC(pc, OBJ_SCOPE(obj)->shape)];
+        entry = &JS_PROPERTY_CACHE(cx).table[PROPERTY_CACHE_HASH_PC(pc, OBJ_SHAPE(obj))];
         fprintf(stderr,
                 "id miss for %s from %s:%u"
                 " (pc %u, kpc %u, kshape %u, shape %u)\n",
                 js_AtomToPrintableString(cx, atom),
                 cx->fp->script->filename,
                 js_PCToLineNumber(cx, cx->fp->script, pc),
                 pc - cx->fp->script->code,
                 entry->kpc - cx->fp->script->code,
                 entry->kshape,
-                OBJ_SCOPE(obj)->shape);
+                OBJ_SHAPE(obj));
                 js_Disassemble1(cx, cx->fp->script, pc,
-                        PTRDIFF(pc, cx->fp->script->code, jsbytecode),
-                        JS_FALSE, stderr);
+                                PTRDIFF(pc, cx->fp->script->code, jsbytecode),
+                                JS_FALSE, stderr);
 #endif
 
         return atom;
     }
 
     if (entry->kshape != (jsuword) obj) {
         PCMETER(JS_PROPERTY_CACHE(cx).komisses++);
         return atom;
@@ -378,17 +377,17 @@ js_FullTestPropertyCache(JSContext *cx, 
         if (!tmp || !OBJ_IS_NATIVE(tmp))
             break;
         JS_UNLOCK_OBJ(cx, pobj);
         pobj = tmp;
         JS_LOCK_OBJ(cx, pobj);
         --vcap;
     }
 
-    if (PCVCAP_SHAPE(vcap) == OBJ_SCOPE(pobj)->shape) {
+    if (PCVCAP_SHAPE(vcap) == OBJ_SHAPE(pobj)) {
 #ifdef DEBUG
         jsid id = ATOM_TO_JSID(atom);
 
         CHECK_FOR_STRING_INDEX(id);
         JS_ASSERT(SCOPE_GET_PROPERTY(OBJ_SCOPE(pobj), id));
         JS_ASSERT(OBJ_SCOPE(pobj)->object == pobj);
 #endif
         *pobjp = pobj;
@@ -4414,17 +4413,17 @@ js_Interpret(JSContext *cx)
 
             do {
                 JSPropCacheEntry *entry;
 
                 entry = NULL;
                 atom = NULL;
                 if (JS_LIKELY(obj->map->ops->setProperty == js_SetProperty)) {
                     JSPropertyCache *cache = &JS_PROPERTY_CACHE(cx);
-                    uint32 kshape = OBJ_SCOPE(obj)->shape;
+                    uint32 kshape = OBJ_SHAPE(obj);
 
                     /*
                      * Open-code JS_PROPERTY_CACHE_TEST, specializing for two
                      * important set-property cases. First:
                      *
                      *   function f(a, b, c) {
                      *     var o = {p:a, q:b, r:c};
                      *     return o;
--- a/js/src/jsinterp.h
+++ b/js/src/jsinterp.h
@@ -158,17 +158,17 @@ typedef struct JSInlineFrame {
 #define PROPERTY_CACHE_HASH(pc,kshape)                                        \
     (((((jsuword)(pc) >> PROPERTY_CACHE_LOG2) ^ (jsuword)(pc)) + (kshape)) &  \
      PROPERTY_CACHE_MASK)
 
 #define PROPERTY_CACHE_HASH_PC(pc,kshape)                                     \
     PROPERTY_CACHE_HASH(pc, kshape)
 
 #define PROPERTY_CACHE_HASH_ATOM(atom,obj,pobj)                               \
-    PROPERTY_CACHE_HASH((jsuword)(atom) >> 2, OBJ_SCOPE(obj)->shape)
+    PROPERTY_CACHE_HASH((jsuword)(atom) >> 2, OBJ_SHAPE(obj))
 
 /*
  * Property cache value capability macros.
  */
 #define PCVCAP_PROTOBITS        4
 #define PCVCAP_PROTOSIZE        JS_BIT(PCVCAP_PROTOBITS)
 #define PCVCAP_PROTOMASK        JS_BITMASK(PCVCAP_PROTOBITS)
 
@@ -272,18 +272,18 @@ typedef struct JSPropertyCache {
 #define SLOT_TO_PCVAL(i)        (((jsuword)(i) << 1) | PCVAL_SLOT)
 
 #define PCVAL_IS_SPROP(v)       (PCVAL_TAG(v) == PCVAL_SPROP)
 #define PCVAL_TO_SPROP(v)       ((JSScopeProperty *) PCVAL_CLRTAG(v))
 #define SPROP_TO_PCVAL(sprop)   PCVAL_SETTAG(sprop, PCVAL_SPROP)
 
 /*
  * Fill property cache entry for key cx->fp->pc, optimized value word computed
- * from obj and sprop, and entry capability forged from OBJ_SCOPE(obj)->shape,
- * scopeIndex, and protoIndex.
+ * from obj and sprop, and entry capability forged from 24-bit OBJ_SHAPE(obj),
+ * 4-bit scopeIndex, and 4-bit protoIndex.
  */
 extern void
 js_FillPropertyCache(JSContext *cx, JSObject *obj, jsuword kshape,
                      uintN scopeIndex, uintN protoIndex,
                      JSObject *pobj, JSScopeProperty *sprop,
                      JSPropCacheEntry **entryp);
 
 /*
@@ -300,34 +300,33 @@ js_FillPropertyCache(JSContext *cx, JSOb
  *
  * We must lock pobj on a hit in order to close races with threads that might
  * be deleting a property from its scope, or otherwise invalidating property
  * caches (on all threads) by re-generating scope->shape.
  */
 #define PROPERTY_CACHE_TEST(cx, pc, obj, pobj, entry, atom)                   \
     do {                                                                      \
         JSPropertyCache *cache_ = &JS_PROPERTY_CACHE(cx);                     \
-        uint32 kshape_ = (JS_ASSERT(OBJ_IS_NATIVE(obj)),                      \
-                          OBJ_SCOPE(obj)->shape);                             \
+        uint32 kshape_ = (JS_ASSERT(OBJ_IS_NATIVE(obj)), OBJ_SHAPE(obj));     \
         entry = &cache_->table[PROPERTY_CACHE_HASH_PC(pc, kshape_)];          \
         PCMETER(cache_->tests++);                                             \
         JS_ASSERT(&obj != &pobj);                                             \
         if (entry->kpc == pc && entry->kshape == kshape_) {                   \
             JSObject *tmp_;                                                   \
             pobj = obj;                                                       \
             JS_LOCK_OBJ(cx, pobj);                                            \
             JS_ASSERT(PCVCAP_TAG(entry->vcap) <= 1);                          \
             if (PCVCAP_TAG(entry->vcap) == 1 &&                               \
                 (tmp_ = LOCKED_OBJ_GET_PROTO(pobj)) != NULL &&                \
                 OBJ_IS_NATIVE(tmp_)) {                                        \
                 JS_UNLOCK_OBJ(cx, pobj);                                      \
                 pobj = tmp_;                                                  \
                 JS_LOCK_OBJ(cx, pobj);                                        \
             }                                                                 \
-            if (PCVCAP_SHAPE(entry->vcap) == OBJ_SCOPE(pobj)->shape) {        \
+            if (PCVCAP_SHAPE(entry->vcap) == OBJ_SHAPE(pobj)) {               \
                 PCMETER(cache_->pchits++);                                    \
                 PCMETER(!PCVCAP_TAG(entry->vcap) || cache_->protopchits++);   \
                 pobj = OBJ_SCOPE(pobj)->object;                               \
                 atom = NULL;                                                  \
                 break;                                                        \
             }                                                                 \
             JS_UNLOCK_OBJ(cx, pobj);                                          \
         }                                                                     \
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2560,28 +2560,29 @@ js_NewObjectWithGivenProto(JSContext *cx
      */
     obj = (JSObject *) js_NewGCThing(cx, GCX_OBJECT, objectSize);
     if (!obj)
         goto earlybad;
 
     obj->map = NULL;
     obj->dslots = NULL;
 
+    /*
+     * Set the class slot with the initial value of the system and delegate
+     * flags set to false.
+     */
+    JS_ASSERT(((jsuword) clasp & 3) == 0);
+    obj->classword = jsuword(clasp);
+    JS_ASSERT(!STOBJ_IS_DELEGATE(obj));
+    JS_ASSERT(!STOBJ_IS_SYSTEM(obj));
+
     /* Set the proto and parent properties. */
     STOBJ_SET_PROTO(obj, proto);
     STOBJ_SET_PARENT(obj, parent);
 
-    /*
-     * Set the class slot with the initial value of the system flag set to
-     * false.
-     */
-    JS_ASSERT(((jsuword) clasp & 3) == 0);
-    STOBJ_SET_SLOT(obj, JSSLOT_CLASS, PRIVATE_TO_JSVAL(clasp));
-    JS_ASSERT(!STOBJ_IS_SYSTEM(obj));
-
     /* Initialize the remaining fixed slots. */
     for (i = JSSLOT_PRIVATE; i != JS_INITIAL_NSLOTS; ++i)
         obj->fslots[i] = JSVAL_VOID;
 
 #ifdef DEBUG
     memset((uint8 *) obj + sizeof(JSObject), JS_FREE_PATTERN,
            objectSize - sizeof(JSObject));
 #endif
@@ -3026,16 +3027,19 @@ PurgeProtoChain(JSContext *cx, JSObject 
         JS_UNLOCK_SCOPE(cx, scope);
     }
     return JS_FALSE;
 }
 
 static void
 PurgeScopeChain(JSContext *cx, JSObject *obj, jsid id)
 {
+    if (!OBJ_IS_DELEGATE(cx, obj))
+        return;
+
     PurgeProtoChain(cx, OBJ_GET_PROTO(cx, obj), id);
     while ((obj = OBJ_GET_PARENT(cx, obj)) != NULL) {
         if (PurgeProtoChain(cx, obj, id))
             return;
     }
 }
 
 JSScopeProperty *
@@ -3491,38 +3495,38 @@ out:
 }
 
 int
 js_FindPropertyHelper(JSContext *cx, jsid id, JSObject **objp,
                       JSObject **pobjp, JSProperty **propp,
                       JSPropCacheEntry **entryp)
 {
     JSObject *obj, *pobj, *lastobj;
-    uint32 type;
+    uint32 shape;
     int scopeIndex, protoIndex;
     JSProperty *prop;
     JSScopeProperty *sprop;
 
     obj = cx->fp->scopeChain;
-    type = OBJ_SCOPE(obj)->shape;
+    shape = OBJ_SHAPE(obj);
     for (scopeIndex = 0; ; scopeIndex++) {
         if (obj->map->ops->lookupProperty == js_LookupProperty) {
             protoIndex =
                 js_LookupPropertyWithFlags(cx, obj, id, 0, &pobj, &prop);
         } else {
             if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &pobj, &prop))
                 return -1;
             protoIndex = -1;
         }
 
         if (prop) {
             if (entryp) {
                 if (protoIndex >= 0 && OBJ_IS_NATIVE(pobj)) {
                     sprop = (JSScopeProperty *) prop;
-                    js_FillPropertyCache(cx, cx->fp->scopeChain, type,
+                    js_FillPropertyCache(cx, cx->fp->scopeChain, shape,
                                          scopeIndex, protoIndex, pobj, sprop,
                                          entryp);
                 } else {
                     PCMETER(JS_PROPERTY_CACHE(cx).nofills++);
                     *entryp = NULL;
                 }
             }
             SCOPE_DEPTH_ACCUM(&rt->scopeSearchDepthStats, scopeIndex);
@@ -3687,27 +3691,27 @@ js_NativeSet(JSContext *cx, JSObject *ob
 
     return JS_TRUE;
 }
 
 JSBool
 js_GetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, jsval *vp,
                      JSPropCacheEntry **entryp)
 {
-    uint32 type;
+    uint32 shape;
     int protoIndex;
     JSObject *obj2;
     JSProperty *prop;
     JSScopeProperty *sprop;
 
     /* Convert string indices to integers if appropriate. */
     CHECK_FOR_STRING_INDEX(id);
     JS_COUNT_OPERATION(cx, JSOW_GET_PROPERTY);
 
-    type = OBJ_SCOPE(obj)->shape;
+    shape = OBJ_SHAPE(obj);
     protoIndex = js_LookupPropertyWithFlags(cx, obj, id, 0, &obj2, &prop);
     if (protoIndex < 0)
         return JS_FALSE;
     if (!prop) {
         jsbytecode *pc;
 
         *vp = JSVAL_VOID;
 
@@ -3767,50 +3771,48 @@ js_GetPropertyHelper(JSContext *cx, JSOb
         OBJ_DROP_PROPERTY(cx, obj2, prop);
         return OBJ_GET_PROPERTY(cx, obj2, id, vp);
     }
 
     sprop = (JSScopeProperty *) prop;
     if (!js_NativeGet(cx, obj, obj2, sprop, vp))
         return JS_FALSE;
 
-    if (entryp) {
-        js_FillPropertyCache(cx, obj, type, 0, protoIndex, obj2, sprop,
-                             entryp);
-    }
+    if (entryp)
+        js_FillPropertyCache(cx, obj, shape, 0, protoIndex, obj2, sprop, entryp);
     JS_UNLOCK_OBJ(cx, obj2);
     return JS_TRUE;
 }
 
 JSBool
 js_GetProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
 {
     return js_GetPropertyHelper(cx, obj, id, vp, NULL);
 }
 
 JSBool
 js_SetPropertyHelper(JSContext *cx, JSObject *obj, jsid id, jsval *vp,
                      JSPropCacheEntry **entryp)
 {
-    uint32 type;
+    uint32 shape;
     int protoIndex;
     JSObject *pobj;
     JSProperty *prop;
     JSScopeProperty *sprop;
     JSScope *scope;
     uintN attrs, flags;
     intN shortid;
     JSClass *clasp;
     JSPropertyOp getter, setter;
 
     /* Convert string indices to integers if appropriate. */
     CHECK_FOR_STRING_INDEX(id);
     JS_COUNT_OPERATION(cx, JSOW_SET_PROPERTY);
 
-    type = OBJ_SCOPE(obj)->shape;
+    shape = OBJ_SHAPE(obj);
     protoIndex = js_LookupPropertyWithFlags(cx, obj, id, 0, &pobj, &prop);
     if (protoIndex < 0)
         return JS_FALSE;
 
     if (prop && !OBJ_IS_NATIVE(pobj)) {
         OBJ_DROP_PROPERTY(cx, pobj, prop);
         prop = NULL;
     }
@@ -3868,26 +3870,17 @@ js_SetPropertyHelper(JSContext *cx, JSOb
 
         if (pobj != obj) {
             /*
              * We found id in a prototype object: prepare to share or shadow.
              *
              * NB: Thanks to the immutable, garbage-collected property tree
              * maintained by jsscope.c in cx->runtime, we needn't worry about
              * sprop going away behind our back after we've unlocked scope.
-             *
-             * But if we are shadowing (not sharing) the proto-property, then
-             * we need to regenerate the property cache shape id for scope, in
-             * case the cache contains the old type in an entry value that was
-             * filled by a get on obj that delegated up the prototype chain to
-             * pobj. Once we've shadowed the proto-property, that cache entry
-             * must not be hit.
              */
-            if (!(attrs & JSPROP_SHARED))
-                SCOPE_MAKE_UNIQUE_SHAPE(cx, scope);
             JS_UNLOCK_SCOPE(cx, scope);
 
             /*
              * Don't clone a shared prototype property. Don't fill it in the
              * property cache either, since the JSOP_SETPROP/JSOP_SETNAME code
              * in js_Interpret does not handle shared or prototype properties.
              * Shared prototype properties require more hit qualification than
              * the fast-path code for those ops, which is targeted on direct,
@@ -3979,17 +3972,17 @@ js_SetPropertyHelper(JSContext *cx, JSOb
                             return JS_FALSE);
     }
 
     if (!js_NativeSet(cx, obj, sprop, vp))
         return JS_FALSE;
 
     if (entryp) {
         if (!(attrs & JSPROP_SHARED))
-            js_FillPropertyCache(cx, obj, type, 0, 0, obj, sprop, entryp);
+            js_FillPropertyCache(cx, obj, shape, 0, 0, obj, sprop, entryp);
         else
             PCMETER(JS_PROPERTY_CACHE(cx).nofills++);
     }
     JS_UNLOCK_SCOPE(cx, scope);
     return JS_TRUE;
 
   read_only_error:
     return js_ReportValueErrorFlags(cx, flags, JSMSG_READ_ONLY,
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -115,35 +115,35 @@ struct JSObjectMap {
         JSClass *clasp_ = OBJ_GET_CLASS(cx, obj);                             \
         if (clasp_->flags & JSCLASS_IS_EXTENDED) {                            \
             JSExtendedClass *xclasp_ = (JSExtendedClass*)clasp_;              \
             if (xclasp_->outerObject)                                         \
                 obj = xclasp_->outerObject(cx, obj);                          \
         }                                                                     \
     JS_END_MACRO
 
-#define JS_INITIAL_NSLOTS   6
+#define JS_INITIAL_NSLOTS   5
 
 /*
  * When JSObject.dslots is not null, JSObject.dslots[-1] records the number of
  * available slots.
  */
 struct JSObject {
     JSObjectMap *map;
+    jsuword     classword;
     jsval       fslots[JS_INITIAL_NSLOTS];
     jsval       *dslots;        /* dynamically allocated slots */
 };
 
 #define JSSLOT_PROTO        0
 #define JSSLOT_PARENT       1
-#define JSSLOT_CLASS        2
-#define JSSLOT_PRIVATE      3
+#define JSSLOT_PRIVATE      2
 #define JSSLOT_START(clasp) (((clasp)->flags & JSCLASS_HAS_PRIVATE)           \
                              ? JSSLOT_PRIVATE + 1                             \
-                             : JSSLOT_CLASS + 1)
+                             : JSSLOT_PARENT + 1)
 
 #define JSSLOT_FREE(clasp)  (JSSLOT_START(clasp)                              \
                              + JSCLASS_RESERVED_SLOTS(clasp))
 
 /*
  * STOBJ prefix means Single Threaded Object. Use the following fast macros to
  * directly manipulate slots in obj when only one thread can access obj and
  * when obj->map->freeslot can be inconsistent with slots.
@@ -162,36 +162,39 @@ struct JSObject {
     ((slot) < JS_INITIAL_NSLOTS                                               \
      ? (obj)->fslots[(slot)] = (value)                                        \
      : (JS_ASSERT((slot) < (uint32)(obj)->dslots[-1]),                        \
         (obj)->dslots[(slot) - JS_INITIAL_NSLOTS] = (value)))
 
 #define STOBJ_GET_PROTO(obj)                                                  \
     JSVAL_TO_OBJECT((obj)->fslots[JSSLOT_PROTO])
 #define STOBJ_SET_PROTO(obj,proto)                                            \
-    ((obj)->fslots[JSSLOT_PROTO] = OBJECT_TO_JSVAL(proto))
+    (void)((!(proto) || STOBJ_SET_DELEGATE(proto)),                           \
+           (obj)->fslots[JSSLOT_PROTO] = OBJECT_TO_JSVAL(proto))
 #define STOBJ_CLEAR_PROTO(obj)                                                \
     ((obj)->fslots[JSSLOT_PROTO] = JSVAL_NULL)
 
 #define STOBJ_GET_PARENT(obj)                                                 \
     JSVAL_TO_OBJECT((obj)->fslots[JSSLOT_PARENT])
 #define STOBJ_SET_PARENT(obj,parent)                                          \
-    ((obj)->fslots[JSSLOT_PARENT] = OBJECT_TO_JSVAL(parent))
+    (void)((!(parent) || STOBJ_SET_DELEGATE(parent)),                         \
+           (obj)->fslots[JSSLOT_PARENT] = OBJECT_TO_JSVAL(parent))
 #define STOBJ_CLEAR_PARENT(obj)                                               \
     ((obj)->fslots[JSSLOT_PARENT] = JSVAL_NULL)
 
 /*
- * We use JSSLOT_CLASS to store both JSClass* and the system flag as an int-
- * tagged value (see jsapi.h for details) with the system flag stored in the
- * second lowest bit.
+ * We use JSObject.classword to store both JSClass* and the delegate and system
+ * flags in the two least significant bits. We do *not* synchronize updates of
+ * obj->classword -- API clients must take care.
  */
-#define STOBJ_GET_CLASS(obj)    ((JSClass *)((obj)->fslots[JSSLOT_CLASS] & ~3))
-#define STOBJ_IS_SYSTEM(obj)    (((obj)->fslots[JSSLOT_CLASS] & 2) != 0)
-
-#define STOBJ_SET_SYSTEM(obj)   ((void)((obj)->fslots[JSSLOT_CLASS] |= 2))
+#define STOBJ_GET_CLASS(obj)    ((JSClass *)((obj)->classword & ~3))
+#define STOBJ_IS_DELEGATE(obj)  (((obj)->classword & 1) != 0)
+#define STOBJ_SET_DELEGATE(obj) ((obj)->classword |= 1)
+#define STOBJ_IS_SYSTEM(obj)    (((obj)->classword & 2) != 0)
+#define STOBJ_SET_SYSTEM(obj)   ((obj)->classword |= 2)
 
 #define STOBJ_GET_PRIVATE(obj)                                                \
     (JS_ASSERT(JSVAL_IS_INT(STOBJ_GET_SLOT(obj, JSSLOT_PRIVATE))),            \
      JSVAL_TO_PRIVATE(STOBJ_GET_SLOT(obj, JSSLOT_PRIVATE)))
 
 #define OBJ_CHECK_SLOT(obj,slot)                                              \
     JS_ASSERT(slot < (obj)->map->freeslot)
 
@@ -222,17 +225,17 @@ struct JSObject {
     (OBJ_CHECK_SLOT(obj, JSSLOT_PROTO), STOBJ_SET_PROTO(obj, proto))
 
 #define LOCKED_OBJ_GET_PARENT(obj) \
     (OBJ_CHECK_SLOT(obj, JSSLOT_PARENT), STOBJ_GET_PARENT(obj))
 #define LOCKED_OBJ_SET_PARENT(obj,parent) \
     (OBJ_CHECK_SLOT(obj, JSSLOT_PARENT), STOBJ_SET_PARENT(obj, parent))
 
 #define LOCKED_OBJ_GET_CLASS(obj) \
-    (OBJ_CHECK_SLOT(obj, JSSLOT_CLASS), STOBJ_GET_CLASS(obj))
+    STOBJ_GET_CLASS(obj)
 
 #define LOCKED_OBJ_GET_PRIVATE(obj) \
     (OBJ_CHECK_SLOT(obj, JSSLOT_PRIVATE), STOBJ_GET_PRIVATE(obj))
 
 #ifdef JS_THREADSAFE
 
 /* Thread-safe functions and wrapper macros for accessing slots in obj. */
 #define OBJ_GET_SLOT(cx,obj,slot)                                             \
@@ -271,27 +274,30 @@ struct JSObject {
 #else   /* !JS_THREADSAFE */
 
 #define OBJ_GET_SLOT(cx,obj,slot)       LOCKED_OBJ_GET_SLOT(obj,slot)
 #define OBJ_SET_SLOT(cx,obj,slot,value) LOCKED_OBJ_WRITE_BARRIER(cx,obj,slot, \
                                                                  value)
 
 #endif /* !JS_THREADSAFE */
 
-/* Thread-safe proto, parent, and class access macros. */
+/* Thread-safe delegate, proto, parent, and class access macros. */
+#define OBJ_IS_DELEGATE(cx,obj)         STOBJ_IS_DELEGATE(obj)
+#define OBJ_SET_DELEGATE(cx,obj)        STOBJ_SET_DELEGATE(obj)
+
 #define OBJ_GET_PROTO(cx,obj)           STOBJ_GET_PROTO(obj)
 #define OBJ_SET_PROTO(cx,obj,proto)     STOBJ_SET_PROTO(obj, proto)
 #define OBJ_CLEAR_PROTO(cx,obj)         STOBJ_CLEAR_PROTO(obj)
 
 #define OBJ_GET_PARENT(cx,obj)          STOBJ_GET_PARENT(obj)
 #define OBJ_SET_PARENT(cx,obj,parent)   STOBJ_SET_PARENT(obj, parent)
 #define OBJ_CLEAR_PARENT(cx,obj)        STOBJ_CLEAR_PARENT(obj)
 
 /*
- * Class is invariant and comes from the fixed JSSLOT_CLASS. Thus no locking
+ * Class is invariant and comes from the fixed clasp member. Thus no locking
  * is necessary to read it. Same for the private slot.
  */
 #define OBJ_GET_CLASS(cx,obj)           STOBJ_GET_CLASS(obj)
 #define OBJ_GET_PRIVATE(cx,obj)         STOBJ_GET_PRIVATE(obj)
 
 /* Test whether a map or object is native. */
 #define MAP_IS_NATIVE(map)                                                    \
     JS_LIKELY((map)->ops == &js_ObjectOps ||                                  \
--- a/js/src/jsscope.h
+++ b/js/src/jsscope.h
@@ -213,16 +213,17 @@ struct JSScope {
 
 #ifdef JS_THREADSAFE
 JS_STATIC_ASSERT(offsetof(JSScope, title) == sizeof(JSObjectMap));
 #endif
 
 #define JS_IS_SCOPE_LOCKED(cx, scope)   JS_IS_TITLE_LOCKED(cx, &(scope)->title)
 
 #define OBJ_SCOPE(obj)                  ((JSScope *)(obj)->map)
+#define OBJ_SHAPE(obj)                  (OBJ_SCOPE(obj)->shape)
 
 #define SCOPE_MAKE_UNIQUE_SHAPE(cx,scope)                                     \
     ((scope)->shape = js_GenerateShape((cx), JS_FALSE))
 
 #define SCOPE_EXTEND_SHAPE(cx,scope,sprop)                                    \
     JS_BEGIN_MACRO                                                            \
         if (!(scope)->lastProp ||                                             \
             (scope)->shape == (scope)->lastProp->shape) {                     \
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -1355,17 +1355,17 @@ TraceRecorder::lazilyImportGlobalSlot(un
     if (slot != uint16(slot)) /* we use a table of 16-bit ints, bail out if that's not enough */
         return false;
     jsval* vp = &STOBJ_GET_SLOT(globalObj, slot);
     if (tracker.has(vp))
         return true; /* we already have it */
     unsigned index = traceMonitor->globalSlots->length();
     /* If this the first global we are adding, remember the shape of the global object. */
     if (index == 0)
-        traceMonitor->globalShape = OBJ_SCOPE(JS_GetGlobalForObject(cx, cx->fp->scopeChain))->shape;
+        traceMonitor->globalShape = OBJ_SHAPE(JS_GetGlobalForObject(cx, cx->fp->scopeChain));
     /* Add the slot to the list of interned global slots. */
     traceMonitor->globalSlots->add(slot);
     uint8 type = getCoercedType(*vp);
     if ((type == JSVAL_INT) && oracle->isGlobalSlotUndemotable(cx->fp->script, slot))
         type = JSVAL_DOUBLE;
     traceMonitor->globalTypeMap->add(type);
     import(gp_ins, slot*sizeof(double), vp, type, "global", index, NULL);
     return true;
@@ -2057,17 +2057,17 @@ js_SynthesizeFrame(JSContext* cx, const 
            ((fun->nargs > cx->fp->argc) ? fun->nargs - cx->fp->argc : 0) +
            script->nfixed;
 }
 
 bool
 js_RecordTree(JSContext* cx, JSTraceMonitor* tm, Fragment* f)
 {
     /* Make sure the global type map didn't change on us. */
-    uint32 globalShape = OBJ_SCOPE(JS_GetGlobalForObject(cx, cx->fp->scopeChain))->shape;
+    uint32 globalShape = OBJ_SHAPE(JS_GetGlobalForObject(cx, cx->fp->scopeChain));
     if (tm->globalShape != globalShape) {
         debug_only_v(printf("Global shape mismatch (%u vs. %u) in RecordTree, flushing cache.\n",
                           globalShape, tm->globalShape);)
         js_FlushJITCache(cx);
         return false;
     }
     TypeMap current;
     current.captureGlobalTypes(cx, *tm->globalSlots);
@@ -2282,21 +2282,21 @@ js_ExecuteTree(JSContext* cx, Fragment**
     double* global = (double*)alloca((globalFrameSize+1) * sizeof(double));
     debug_only(*(uint64*)&global[globalFrameSize] = 0xdeadbeefdeadbeefLL;)
     double* stack = (double*)alloca(MAX_NATIVE_STACK_SLOTS * sizeof(double));
 
     /* If any of our trees uses globals, the shape of the global object must not change and
        the global type map must remain applicable at all times (we expect absolute type 
        stability for globals). */
     if (ngslots &&
-        (OBJ_SCOPE(globalObj)->shape != tm->globalShape || 
+        (OBJ_SHAPE(globalObj) != tm->globalShape || 
          !BuildNativeGlobalFrame(cx, ngslots, gslots, tm->globalTypeMap->data(), global))) {
         AUDIT(globalShapeMismatchAtEntry);
         debug_only_v(printf("Global shape mismatch (%u vs. %u), flushing cache.\n",
-                            OBJ_SCOPE(globalObj)->shape, tm->globalShape);)
+                            OBJ_SHAPE(globalObj), tm->globalShape);)
         const void* ip = f->ip;
         js_FlushJITCache(cx);
         *treep = tm->fragmento->newLoop(ip);
         return NULL;
     }
 
     if (!BuildNativeStackFrame(cx, 0/*callDepth*/, ti->stackTypeMap.data(), stack)) {
         AUDIT(typeMapMismatchAtEntry);
@@ -2699,17 +2699,17 @@ js_FlushJITCache(JSContext* cx)
 #ifdef DEBUG
         JS_ASSERT(fragmento->labels);
         delete fragmento->labels;
         fragmento->labels = new (&gc) LabelMap(core, NULL);
 #endif
     }
     memset(&tm->fcache, 0, sizeof(tm->fcache));
     if (cx->fp) {
-        tm->globalShape = OBJ_SCOPE(JS_GetGlobalForObject(cx, cx->fp->scopeChain))->shape;
+        tm->globalShape = OBJ_SHAPE(JS_GetGlobalForObject(cx, cx->fp->scopeChain));
         tm->globalSlots->clear();
         tm->globalTypeMap->clear();
     }
 }
 
 void
 js_ShutDownJIT()
 {
@@ -3354,17 +3354,17 @@ TraceRecorder::test_property_cache(JSObj
             if (js_FindPropertyHelper(cx, id, &obj, &obj2, &prop, &entry) < 0)
                 ABORT_TRACE("failed to find name");
         } else {
             int protoIndex = js_LookupPropertyWithFlags(cx, aobj, id, 0, &obj2, &prop);
             if (protoIndex < 0)
                 ABORT_TRACE("failed to lookup property");
 
             if (prop) {
-                js_FillPropertyCache(cx, aobj, OBJ_SCOPE(aobj)->shape, 0, protoIndex, obj2,
+                js_FillPropertyCache(cx, aobj, OBJ_SHAPE(aobj), 0, protoIndex, obj2,
                                      (JSScopeProperty*) prop, &entry);
             }
         }
 
         if (!prop) {
             // Propagate obj from js_FindPropertyHelper to record_JSOP_BINDNAME
             // via our obj2 out-parameter. If we are recording JSOP_SETNAME and
             // the global it's assigning does not yet exist, create it.
@@ -3424,17 +3424,17 @@ TraceRecorder::test_property_cache(JSObj
         }
     }
 
     // For any hit that goes up the scope and or proto chains, we will need to
     // guard on the shape of the object containing the property.
     if (PCVCAP_TAG(entry->vcap) >= 1) {
         jsuword vcap = entry->vcap;
         uint32 vshape = PCVCAP_SHAPE(vcap);
-        JS_ASSERT(OBJ_SCOPE(obj2)->shape == vshape);
+        JS_ASSERT(OBJ_SHAPE(obj2) == vshape);
 
         LIns* obj2_ins = INS_CONSTPTR(obj2);
         map_ins = lir->insLoad(LIR_ldp, obj2_ins, (int)offsetof(JSObject, map));
         if (!map_is_native(obj2->map, map_ins, ops_ins))
             return false;
 
         LIns* shape_ins = addName(lir->insLoad(LIR_ld, map_ins, offsetof(JSScope, shape)),
                                   "shape");
@@ -3635,17 +3635,17 @@ TraceRecorder::getThis(LIns*& this_ins)
 }
 
 bool
 TraceRecorder::guardClass(JSObject* obj, LIns* obj_ins, JSClass* clasp)
 {
     if (STOBJ_GET_CLASS(obj) != clasp)
         return false;
 
-    LIns* class_ins = stobj_get_fslot(obj_ins, JSSLOT_CLASS);
+    LIns* class_ins = lir->insLoad(LIR_ldp, obj_ins, offsetof(JSObject, classword));
     class_ins = lir->ins2(LIR_piand, class_ins, lir->insImm(~3));
 
     char namebuf[32];
     JS_snprintf(namebuf, sizeof namebuf, "guard(class is %s)", clasp->name);
     guard(true, addName(lir->ins2(LIR_eq, class_ins, INS_CONSTPTR(clasp)), namebuf),
           MISMATCH_EXIT);
     return true;
 }
@@ -4393,17 +4393,17 @@ TraceRecorder::record_JSOP_SETPROP()
     JSObject* obj = JSVAL_TO_OBJECT(l);
 
     if (obj->map->ops->setProperty != js_SetProperty)
         ABORT_TRACE("non-native JSObjectOps::setProperty");
 
     LIns* obj_ins = get(&l);
 
     JSPropertyCache* cache = &JS_PROPERTY_CACHE(cx);
-    uint32 kshape = OBJ_SCOPE(obj)->shape;
+    uint32 kshape = OBJ_SHAPE(obj);
     jsbytecode* pc = cx->fp->regs->pc;
 
     JSPropCacheEntry* entry = &cache->table[PROPERTY_CACHE_HASH_PC(pc, kshape)];
     if (entry->kpc != pc || entry->kshape != kshape)
         ABORT_TRACE("cache miss");
     if (!PCVAL_IS_SPROP(entry->vword))
         ABORT_TRACE("hit non-sprop cache value");