Bug 929546 - Rekey initial shape table for moved proto/parent/metadata pointers r=terrence
authorJon Coppeard <jcoppeard@mozilla.com>
Tue, 07 Jan 2014 10:34:25 +0000
changeset 162348 34a1652e8f528752ee7730e38421419edcf601ef
parent 162347 34b3cb13258b5a4af8e9304f9e0fb629f574dfa1
child 162349 224f3b70841f1769a4cf559bdb29386ad45f9e27
push idunknown
push userunknown
push dateunknown
reviewersterrence
bugs929546
milestone29.0a1
Bug 929546 - Rekey initial shape table for moved proto/parent/metadata pointers r=terrence
js/src/gc/Nursery.cpp
js/src/jscompartment.h
js/src/vm/Shape.cpp
js/src/vm/Shape.h
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -596,16 +596,17 @@ js::Nursery::MinorGCCallback(JSTracer *j
 
 static void
 CheckHashTablesAfterMovingGC(JSRuntime *rt)
 {
 #if defined(DEBUG)
     /* Check that internal hash tables no longer have any pointers into the nursery. */
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         c->checkNewTypeObjectTableAfterMovingGC();
+        c->checkInitialShapesTableAfterMovingGC();
         if (c->debugScopes)
             c->debugScopes->checkHashTablesAfterMovingGC(rt);
     }
 #endif
 }
 
 void
 js::Nursery::collect(JSRuntime *rt, JS::gcreason::Reason reason, TypeObjectList *pretenureTypes)
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -244,16 +244,17 @@ struct JSCompartment
     void markAllInitialShapeTableEntries(JSTracer *trc);
 
     /* Set of default 'new' or lazy types in the compartment. */
     js::types::TypeObjectWithNewScriptSet newTypeObjects;
     js::types::TypeObjectWithNewScriptSet lazyTypeObjects;
     void sweepNewTypeObjectTable(js::types::TypeObjectWithNewScriptSet &table);
 #if defined(DEBUG) && defined(JSGC_GENERATIONAL)
     void checkNewTypeObjectTableAfterMovingGC();
+    void checkInitialShapesTableAfterMovingGC();
 #endif
 
     /*
      * Hash table of all manually call site-cloned functions from within
      * self-hosted code. Cloning according to call site provides extra
      * sensitivity for type specialization and inlining.
      */
     js::CallsiteCloneTable callsiteClones;
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -1570,33 +1570,134 @@ InitialShapeEntry::getLookup() const
     return Lookup(shape->getObjectClass(), proto, shape->getObjectParent(), shape->getObjectMetadata(),
                   shape->numFixedSlots(), shape->getObjectFlags());
 }
 
 /* static */ inline HashNumber
 InitialShapeEntry::hash(const Lookup &lookup)
 {
     HashNumber hash = uintptr_t(lookup.clasp) >> 3;
-    hash = JS_ROTATE_LEFT32(hash, 4) ^ (uintptr_t(lookup.proto.toWord()) >> 3);
-    hash = JS_ROTATE_LEFT32(hash, 4) ^ (uintptr_t(lookup.parent) >> 3) ^ (uintptr_t(lookup.metadata) >> 3);
+    hash = JS_ROTATE_LEFT32(hash, 4) ^
+        (uintptr_t(lookup.hashProto.toWord()) >> 3);
+    hash = JS_ROTATE_LEFT32(hash, 4) ^
+        (uintptr_t(lookup.hashParent) >> 3) ^
+        (uintptr_t(lookup.hashMetadata) >> 3);
     return hash + lookup.nfixed;
 }
 
 /* static */ inline bool
 InitialShapeEntry::match(const InitialShapeEntry &key, const Lookup &lookup)
 {
     const Shape *shape = *key.shape.unsafeGet();
     return lookup.clasp == shape->getObjectClass()
-        && lookup.proto.toWord() == key.proto.toWord()
-        && lookup.parent == shape->getObjectParent()
-        && lookup.metadata == shape->getObjectMetadata()
+        && lookup.matchProto.toWord() == key.proto.toWord()
+        && lookup.matchParent == shape->getObjectParent()
+        && lookup.matchMetadata == shape->getObjectMetadata()
         && lookup.nfixed == shape->numFixedSlots()
         && lookup.baseFlags == shape->getObjectFlags();
 }
 
+#ifdef JSGC_GENERATIONAL
+
+/*
+ * This class is used to add a post barrier on the initialShapes set, as the key
+ * is calculated based on several objects which may be moved by generational GC.
+ */
+class InitialShapeSetRef : public BufferableRef
+{
+    InitialShapeSet *set;
+    const Class *clasp;
+    TaggedProto proto;
+    JSObject *parent;
+    JSObject *metadata;
+    size_t nfixed;
+    uint32_t objectFlags;
+
+  public:
+    InitialShapeSetRef(InitialShapeSet *set,
+                       const Class *clasp,
+                       TaggedProto proto,
+                       JSObject *parent,
+                       JSObject *metadata,
+                       size_t nfixed,
+                       uint32_t objectFlags)
+        : set(set),
+          clasp(clasp),
+          proto(proto),
+          parent(parent),
+          metadata(metadata),
+          nfixed(nfixed),
+          objectFlags(objectFlags)
+    {}
+
+    void mark(JSTracer *trc) {
+        TaggedProto priorProto = proto;
+        JSObject *priorParent = parent;
+        JSObject *priorMetadata = metadata;
+        if (proto.isObject())
+            Mark(trc, reinterpret_cast<JSObject**>(&proto), "initialShapes set proto");
+        Mark(trc, &parent, "initialShapes set parent");
+        if (metadata)
+            Mark(trc, &metadata, "initialShapes set metadata");
+        if (proto == priorProto && parent == priorParent && metadata == priorMetadata)
+            return;
+
+        /* Find the original entry, which must still be present. */
+        InitialShapeEntry::Lookup lookup(clasp, priorProto,
+                                         priorParent, parent,
+                                         priorMetadata, metadata,
+                                         nfixed, objectFlags);
+        InitialShapeSet::Ptr p = set->lookup(lookup);
+        JS_ASSERT(p);
+
+        /* Update the entry's possibly-moved proto, and ensure lookup will still match. */
+        InitialShapeEntry &entry = const_cast<InitialShapeEntry&>(*p);
+        entry.proto = proto;
+        lookup.matchProto = proto;
+
+        /* Rekey the entry. */
+        set->rekeyAs(lookup,
+                     InitialShapeEntry::Lookup(clasp, proto, parent, metadata, nfixed, objectFlags),
+                     *p);
+    }
+};
+
+#ifdef DEBUG
+void
+JSCompartment::checkInitialShapesTableAfterMovingGC()
+{
+    /*
+     * Assert that the postbarriers have worked and that nothing is left in
+     * initialShapes that points into the nursery, and that the hash table
+     * entries are discoverable.
+     */
+    JS::shadow::Runtime *rt = JS::shadow::Runtime::asShadowRuntime(runtimeFromMainThread());
+    for (InitialShapeSet::Enum e(initialShapes); !e.empty(); e.popFront()) {
+        InitialShapeEntry entry = e.front();
+        TaggedProto proto = entry.proto;
+        Shape *shape = entry.shape.get();
+
+        JS_ASSERT_IF(proto.isObject(), !IsInsideNursery(rt, proto.toObject()));
+        JS_ASSERT(!IsInsideNursery(rt, shape->getObjectParent()));
+        JS_ASSERT(!IsInsideNursery(rt, shape->getObjectMetadata()));
+
+        InitialShapeEntry::Lookup lookup(shape->getObjectClass(),
+                                         proto,
+                                         shape->getObjectParent(),
+                                         shape->getObjectMetadata(),
+                                         shape->numFixedSlots(),
+                                         shape->getObjectFlags());
+        InitialShapeSet::Ptr ptr = initialShapes.lookup(lookup);
+        JS_ASSERT(ptr.found() && &*ptr == &e.front());
+    }
+}
+#endif
+
+#endif
+
 /* static */ Shape *
 EmptyShape::getInitialShape(ExclusiveContext *cx, const Class *clasp, TaggedProto proto,
                             JSObject *parent, JSObject *metadata,
                             size_t nfixed, uint32_t objectFlags)
 {
     JS_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject()));
     JS_ASSERT_IF(parent, cx->isInsideCurrentCompartment(parent));
 
@@ -1625,16 +1726,29 @@ EmptyShape::getInitialShape(ExclusiveCon
     if (!shape)
         return nullptr;
     new (shape) EmptyShape(nbase, nfixed);
 
     Lookup lookup(clasp, protoRoot, parentRoot, metadataRoot, nfixed, objectFlags);
     if (!p.add(cx, table, lookup, InitialShapeEntry(shape, protoRoot)))
         return nullptr;
 
+#ifdef JSGC_GENERATIONAL
+    if (cx->hasNursery()) {
+        if ((protoRoot.isObject() && cx->nursery().isInside(protoRoot.toObject())) ||
+            cx->nursery().isInside(parentRoot.get()) ||
+            cx->nursery().isInside(metadataRoot.get()))
+        {
+            InitialShapeSetRef ref(
+                &table, clasp, protoRoot, parentRoot, metadataRoot, nfixed, objectFlags);
+            cx->asJSContext()->runtime()->gcStoreBuffer.putGeneric(ref);
+        }
+    }
+#endif
+
     return shape;
 }
 
 /* static */ Shape *
 EmptyShape::getInitialShape(ExclusiveContext *cx, const Class *clasp, TaggedProto proto,
                             JSObject *parent, JSObject *metadata,
                             AllocKind kind, uint32_t objectFlags)
 {
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -1443,26 +1443,50 @@ struct InitialShapeEntry
      * Matching prototype for the entry. The shape of an object determines its
      * prototype, but the prototype cannot be determined from the shape itself.
      */
     TaggedProto proto;
 
     /* State used to determine a match on an initial shape. */
     struct Lookup {
         const Class *clasp;
-        TaggedProto proto;
-        JSObject *parent;
-        JSObject *metadata;
+        TaggedProto hashProto;
+        TaggedProto matchProto;
+        JSObject *hashParent;
+        JSObject *matchParent;
+        JSObject *hashMetadata;
+        JSObject *matchMetadata;
         uint32_t nfixed;
         uint32_t baseFlags;
         Lookup(const Class *clasp, TaggedProto proto, JSObject *parent, JSObject *metadata,
                uint32_t nfixed, uint32_t baseFlags)
-          : clasp(clasp), proto(proto), parent(parent), metadata(metadata),
+          : clasp(clasp),
+            hashProto(proto), matchProto(proto),
+            hashParent(parent), matchParent(parent),
+            hashMetadata(metadata), matchMetadata(metadata),
             nfixed(nfixed), baseFlags(baseFlags)
         {}
+
+#ifdef JSGC_GENERATIONAL
+        /*
+         * For use by generational GC post barriers. Look up an entry whose
+         * parent and metadata fields may have been moved, but was hashed with
+         * the original values.
+         */
+        Lookup(const Class *clasp, TaggedProto proto,
+               JSObject *hashParent, JSObject *matchParent,
+               JSObject *hashMetadata, JSObject *matchMetadata,
+               uint32_t nfixed, uint32_t baseFlags)
+          : clasp(clasp),
+            hashProto(proto), matchProto(proto),
+            hashParent(hashParent), matchParent(matchParent),
+            hashMetadata(hashMetadata), matchMetadata(matchMetadata),
+            nfixed(nfixed), baseFlags(baseFlags)
+        {}
+#endif
     };
 
     inline InitialShapeEntry();
     inline InitialShapeEntry(const ReadBarriered<Shape> &shape, TaggedProto proto);
 
     inline Lookup getLookup() const;
 
     static inline HashNumber hash(const Lookup &lookup);