Bug 929546 - Rekey initial shape table for moved proto/parent/metadata pointers r=terrence
--- 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);