Backed out changeset d3c9b899f7d2 (bug 1143256) for frequent browser_perf-refresh.js leaks.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 20 Mar 2015 16:07:42 -0400
changeset 263659 324071d6d325ad1ecd6aa14e91e010734c1bd28a
parent 263658 c89f330bf7818b3150efc971e35b5ffe450c835f
child 263660 7ff3c70a5ad29d6aff232a7c12a60f929fa5af73
push id4718
push userraliiev@mozilla.com
push dateMon, 11 May 2015 18:39:53 +0000
treeherdermozilla-beta@c20c4ef55f08 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1143256
milestone39.0a1
backs outd3c9b899f7d205a33b53ec9c11d41955955c6089
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
Backed out changeset d3c9b899f7d2 (bug 1143256) for frequent browser_perf-refresh.js leaks.
js/public/MemoryMetrics.h
js/src/builtin/TestingFunctions.cpp
js/src/builtin/TypedObject.cpp
js/src/builtin/TypedObject.h
js/src/gc/Marking.cpp
js/src/gc/Marking.h
js/src/gc/RootMarking.cpp
js/src/jit/VMFunctions.cpp
js/src/jsarray.cpp
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsfriendapi.cpp
js/src/jsfriendapi.h
js/src/jsiter.cpp
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/jsscript.cpp
js/src/jsweakmap.cpp
js/src/vm/ArgumentsObject.cpp
js/src/vm/ArrayObject-inl.h
js/src/vm/ArrayObject.h
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/MemoryMetrics.cpp
js/src/vm/NativeObject-inl.h
js/src/vm/Runtime-inl.h
js/src/vm/SavedStacks.cpp
js/src/vm/SavedStacks.h
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
js/src/vm/Shape-inl.h
js/src/vm/Shape.cpp
js/src/vm/Shape.h
js/src/vm/TypeInference.cpp
js/src/vm/UnboxedObject.cpp
js/src/vm/WeakMapObject.h
js/xpconnect/src/XPCJSRuntime.cpp
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -594,17 +594,16 @@ struct CompartmentStats
     macro(Other,   NotLiveGCThing, typeInferenceTypeScripts) \
     macro(Other,   NotLiveGCThing, typeInferenceAllocationSiteTables) \
     macro(Other,   NotLiveGCThing, typeInferenceArrayTypeTables) \
     macro(Other,   NotLiveGCThing, typeInferenceObjectTypeTables) \
     macro(Other,   NotLiveGCThing, compartmentObject) \
     macro(Other,   NotLiveGCThing, compartmentTables) \
     macro(Other,   NotLiveGCThing, innerViewsTable) \
     macro(Other,   NotLiveGCThing, lazyArrayBuffersTable) \
-    macro(Other,   NotLiveGCThing, objectMetadataTable) \
     macro(Other,   NotLiveGCThing, crossCompartmentWrappersTable) \
     macro(Other,   NotLiveGCThing, regexpCompartment) \
     macro(Other,   NotLiveGCThing, savedStacksSet)
 
     CompartmentStats()
       : FOR_EACH_SIZE(ZERO_SIZE)
         classInfo(),
         extra(),
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1384,59 +1384,60 @@ DisplayName(JSContext *cx, unsigned argc
     }
 
     JSFunction *fun = &args[0].toObject().as<JSFunction>();
     JSString *str = fun->displayAtom();
     args.rval().setString(str ? str : cx->runtime()->emptyString);
     return true;
 }
 
-static JSObject *
-ShellObjectMetadataCallback(JSContext *cx)
+static bool
+ShellObjectMetadataCallback(JSContext *cx, JSObject **pmetadata)
 {
     RootedObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
     if (!obj)
-        CrashAtUnhandlableOOM("ShellObjectMetadataCallback");
+        return false;
 
     RootedObject stack(cx, NewDenseEmptyArray(cx));
     if (!stack)
-        CrashAtUnhandlableOOM("ShellObjectMetadataCallback");
+        return false;
 
     static int createdIndex = 0;
     createdIndex++;
 
     if (!JS_DefineProperty(cx, obj, "index", createdIndex, 0,
                            JS_STUBGETTER, JS_STUBSETTER))
     {
-        CrashAtUnhandlableOOM("ShellObjectMetadataCallback");
+        return false;
     }
 
     if (!JS_DefineProperty(cx, obj, "stack", stack, 0,
                            JS_STUBGETTER, JS_STUBSETTER))
     {
-        CrashAtUnhandlableOOM("ShellObjectMetadataCallback");
+        return false;
     }
 
     int stackIndex = 0;
     RootedId id(cx);
     RootedValue callee(cx);
     for (NonBuiltinScriptFrameIter iter(cx); !iter.done(); ++iter) {
         if (iter.isFunctionFrame() && iter.compartment() == cx->compartment()) {
             id = INT_TO_JSID(stackIndex);
             RootedObject callee(cx, iter.callee(cx));
             if (!JS_DefinePropertyById(cx, stack, id, callee, 0,
                                        JS_STUBGETTER, JS_STUBSETTER))
             {
-                CrashAtUnhandlableOOM("ShellObjectMetadataCallback");
+                return false;
             }
             stackIndex++;
         }
     }
 
-    return obj;
+    *pmetadata = obj;
+    return true;
 }
 
 static bool
 SetObjectMetadataCallback(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     bool enabled = args.length() ? ToBoolean(args[0]) : false;
@@ -1454,19 +1455,17 @@ SetObjectMetadata(JSContext *cx, unsigne
         JS_ReportError(cx, "Both arguments must be objects");
         return false;
     }
 
     args.rval().setUndefined();
 
     RootedObject obj(cx, &args[0].toObject());
     RootedObject metadata(cx, &args[1].toObject());
-    SetObjectMetadata(cx, obj, metadata);
-
-    return true;
+    return SetObjectMetadata(cx, obj, metadata);
 }
 
 static bool
 GetObjectMetadata(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     if (args.length() != 1 || !args[0].isObject()) {
         JS_ReportError(cx, "Argument must be an object");
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -14,17 +14,16 @@
 #include "jsutil.h"
 
 #include "gc/Marking.h"
 #include "js/Vector.h"
 #include "vm/GlobalObject.h"
 #include "vm/String.h"
 #include "vm/StringBuffer.h"
 #include "vm/TypedArrayObject.h"
-#include "vm/WeakMapObject.h"
 
 #include "jsatominlines.h"
 #include "jsobjinlines.h"
 
 #include "vm/NativeObject-inl.h"
 #include "vm/Shape-inl.h"
 
 using mozilla::AssertedCast;
@@ -1374,21 +1373,22 @@ TypedObject::typedMemBase() const
         return owner.as<ArrayBufferObject>().dataPointer();
     return owner.as<InlineTypedObject>().inlineTypedMem();
 }
 
 bool
 TypedObject::isAttached() const
 {
     if (is<InlineTransparentTypedObject>()) {
-        ObjectWeakMap *table = compartment()->lazyArrayBuffers;
+        LazyArrayBufferTable *table = compartment()->lazyArrayBuffers;
         if (table) {
-            JSObject *buffer = table->lookup(this);
+            ArrayBufferObject *buffer =
+                table->maybeBuffer(&const_cast<TypedObject *>(this)->as<InlineTransparentTypedObject>());
             if (buffer)
-                return !buffer->as<ArrayBufferObject>().isNeutered();
+                return !buffer->isNeutered();
         }
         return true;
     }
     if (is<InlineOpaqueTypedObject>())
         return true;
     if (!as<OutlineTypedObject>().outOfLineTypedMem())
         return false;
     JSObject &owner = as<OutlineTypedObject>().owner();
@@ -2188,70 +2188,124 @@ InlineTypedObject::objectMovedDuringMino
         trc->runtime()->gc.nursery.maybeSetForwardingPointer(trc, oldData, newData,
                                                              size_t(descr.size()) >= sizeof(uintptr_t));
     }
 }
 
 ArrayBufferObject *
 InlineTransparentTypedObject::getOrCreateBuffer(JSContext *cx)
 {
-    ObjectWeakMap *&table = cx->compartment()->lazyArrayBuffers;
+    LazyArrayBufferTable *&table = cx->compartment()->lazyArrayBuffers;
     if (!table) {
-        table = cx->new_<ObjectWeakMap>(cx);
+        table = cx->new_<LazyArrayBufferTable>(cx);
         if (!table)
             return nullptr;
     }
 
-    JSObject *obj = table->lookup(this);
-    if (obj)
-        return &obj->as<ArrayBufferObject>();
+    ArrayBufferObject *buffer = table->maybeBuffer(this);
+    if (buffer)
+        return buffer;
 
     ArrayBufferObject::BufferContents contents =
         ArrayBufferObject::BufferContents::createPlain(inlineTypedMem());
     size_t nbytes = typeDescr().size();
 
     // Prevent GC under ArrayBufferObject::create, which might move this object
     // and its contents.
     gc::AutoSuppressGC suppress(cx);
 
-    ArrayBufferObject *buffer =
-        ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::DoesntOwnData);
+    buffer = ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::DoesntOwnData);
     if (!buffer)
         return nullptr;
 
     // The owning object must always be the array buffer's first view. This
     // both prevents the memory from disappearing out from under the buffer
     // (the first view is held strongly by the buffer) and is used by the
     // buffer marking code to detect whether its data pointer needs to be
     // relocated.
     JS_ALWAYS_TRUE(buffer->addView(cx, this));
 
     buffer->setForInlineTypedObject();
     buffer->setHasTypedObjectViews();
 
-    if (!table->add(cx, this, buffer))
+    if (!table->addBuffer(cx, this, buffer))
         return nullptr;
 
-    if (IsInsideNursery(this)) {
-        // Make sure the buffer is traced by the next generational collection,
-        // so that its data pointer is updated after this typed object moves.
-        cx->runtime()->gc.storeBuffer.putWholeCellFromMainThread(buffer);
-    }
-
     return buffer;
 }
 
 ArrayBufferObject *
 OutlineTransparentTypedObject::getOrCreateBuffer(JSContext *cx)
 {
     if (owner().is<ArrayBufferObject>())
         return &owner().as<ArrayBufferObject>();
     return owner().as<InlineTransparentTypedObject>().getOrCreateBuffer(cx);
 }
 
+LazyArrayBufferTable::LazyArrayBufferTable(JSContext *cx)
+ : map(cx)
+{
+    if (!map.init())
+        CrashAtUnhandlableOOM("LazyArrayBufferTable");
+}
+
+LazyArrayBufferTable::~LazyArrayBufferTable()
+{
+    WeakMapBase::removeWeakMapFromList(&map);
+}
+
+ArrayBufferObject *
+LazyArrayBufferTable::maybeBuffer(InlineTransparentTypedObject *obj)
+{
+    if (Map::Ptr p = map.lookup(obj))
+        return &p->value()->as<ArrayBufferObject>();
+    return nullptr;
+}
+
+bool
+LazyArrayBufferTable::addBuffer(JSContext *cx, InlineTransparentTypedObject *obj, ArrayBufferObject *buffer)
+{
+    MOZ_ASSERT(!map.has(obj));
+    if (!map.put(obj, buffer)) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+
+    MOZ_ASSERT(!IsInsideNursery(buffer));
+    if (IsInsideNursery(obj)) {
+        // Strip the barriers from the type before inserting into the store
+        // buffer, as is done for DebugScopes::proxiedScopes.
+        Map::Base *baseHashMap = static_cast<Map::Base *>(&map);
+
+        typedef HashMap<JSObject *, JSObject *> UnbarrieredMap;
+        UnbarrieredMap *unbarrieredMap = reinterpret_cast<UnbarrieredMap *>(baseHashMap);
+
+        typedef gc::HashKeyRef<UnbarrieredMap, JSObject *> Ref;
+        cx->runtime()->gc.storeBuffer.putGeneric(Ref(unbarrieredMap, obj));
+
+        // Also make sure the buffer is traced, so that its data pointer is
+        // updated after the typed object moves.
+        cx->runtime()->gc.storeBuffer.putWholeCellFromMainThread(buffer);
+    }
+
+    return true;
+}
+
+void
+LazyArrayBufferTable::trace(JSTracer *trc)
+{
+    map.trace(trc);
+}
+
+size_t
+LazyArrayBufferTable::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
+{
+    return mallocSizeOf(this) + map.sizeOfExcludingThis(mallocSizeOf);
+}
+
 /******************************************************************************
  * Typed object classes
  */
 
 #define DEFINE_TYPEDOBJ_CLASS(Name, Trace)        \
     const Class Name::class_ = {                         \
         # Name,                                          \
         Class::NON_NATIVE | JSCLASS_IMPLEMENTS_BARRIERS, \
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -1016,16 +1016,40 @@ IsTypeDescrClass(const Class* clasp)
 }
 
 inline bool
 TypedObject::opaque() const
 {
     return IsOpaqueTypedObjectClass(getClass());
 }
 
+// Inline transparent typed objects do not initially have an array buffer, but
+// can have that buffer created lazily if it is accessed later. This table
+// manages references from such typed objects to their buffers.
+class LazyArrayBufferTable
+{
+  private:
+    // The map from transparent typed objects to their lazily created buffer.
+    // Keys in this map are InlineTransparentTypedObjects and values are
+    // ArrayBufferObjects, but we don't enforce this in the type system due to
+    // the extra marking code goop that requires.
+    typedef WeakMap<PreBarrieredObject, RelocatablePtrObject> Map;
+    Map map;
+
+  public:
+    explicit LazyArrayBufferTable(JSContext *cx);
+    ~LazyArrayBufferTable();
+
+    ArrayBufferObject *maybeBuffer(InlineTransparentTypedObject *obj);
+    bool addBuffer(JSContext *cx, InlineTransparentTypedObject *obj, ArrayBufferObject *buffer);
+
+    void trace(JSTracer *trc);
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
+};
+
 JSObject *
 InitTypedObjectModuleObject(JSContext *cx, JS::HandleObject obj);
 
 } // namespace js
 
 template <>
 inline bool
 JSObject::is<js::SimpleTypeDescr>() const
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1064,16 +1064,19 @@ void
 BaseShape::markChildren(JSTracer *trc)
 {
     if (isOwned())
         gc::MarkBaseShape(trc, &unowned_, "base");
 
     JSObject* global = compartment()->unsafeUnbarrieredMaybeGlobal();
     if (global)
         MarkObjectUnbarriered(trc, &global, "global");
+
+    if (metadata)
+        gc::MarkObject(trc, &metadata, "metadata");
 }
 
 static void
 PushMarkStack(GCMarker *gcmarker, BaseShape *thing)
 {
     JS_COMPARTMENT_ASSERT(gcmarker->runtime(), thing);
     MOZ_ASSERT(!IsInsideNursery(thing));
 
@@ -1110,16 +1113,19 @@ ScanBaseShape(GCMarker *gcmarker, BaseSh
 {
     base->assertConsistency();
 
     base->compartment()->mark();
 
     if (GlobalObject *global = base->compartment()->unsafeUnbarrieredMaybeGlobal())
         gcmarker->traverse(global);
 
+    if (JSObject *metadata = base->getObjectMetadata())
+        MaybePushMarkStackBetweenSlices(gcmarker, metadata);
+
     /*
      * All children of the owned base shape are consistent with its
      * unowned one, thus we do not need to trace through children of the
      * unowned base shape.
      */
     if (base->isOwned()) {
         UnownedBaseShape *unowned = base->baseUnowned();
         MOZ_ASSERT(base->compartment() == unowned->compartment());
--- a/js/src/gc/Marking.h
+++ b/js/src/gc/Marking.h
@@ -302,17 +302,17 @@ Mark(JSTracer *trc, JSObject **objp, con
 
 /* For use by Debugger::WeakMap's missingScopes HashKeyRef instantiation. */
 inline void
 Mark(JSTracer *trc, NativeObject **obj, const char *name)
 {
     MarkObjectUnbarriered(trc, obj, name);
 }
 
-/* For use by Debugger::WeakMap's liveScopes HashKeyRef instantiation. */
+/* For use by Debugger::WeakMap's proxiedScopes HashKeyRef instantiation. */
 inline void
 Mark(JSTracer *trc, ScopeObject **obj, const char *name)
 {
     MarkObjectUnbarriered(trc, obj, name);
 }
 
 inline bool
 IsMarked(BarrieredBase<Value> *v)
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -19,17 +19,16 @@
 #include "builtin/MapObject.h"
 #include "frontend/BytecodeCompiler.h"
 #include "gc/GCInternals.h"
 #include "gc/Marking.h"
 #include "jit/MacroAssembler.h"
 #include "js/HashTable.h"
 #include "vm/Debugger.h"
 #include "vm/JSONParser.h"
-#include "vm/WeakMapObject.h"
 
 #include "jsgcinlines.h"
 #include "jsobjinlines.h"
 
 using namespace js;
 using namespace js::gc;
 
 using mozilla::ArrayEnd;
@@ -512,19 +511,16 @@ js::gc::GCRuntime::markRuntime(JSTracer 
         }
 
         /* Mark debug scopes, if present */
         if (c->debugScopes)
             c->debugScopes->mark(trc);
 
         if (c->lazyArrayBuffers)
             c->lazyArrayBuffers->trace(trc);
-
-        if (c->objectMetadataTable)
-            c->objectMetadataTable->trace(trc);
     }
 
     MarkInterpreterActivations(rt, trc);
 
     jit::MarkJitActivations(rt, trc);
 
     if (!isHeapMinorCollecting()) {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_MARK_EMBEDDING);
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -85,22 +85,17 @@ InvokeFunction(JSContext *cx, HandleObje
     *rval = rv;
     return true;
 }
 
 JSObject *
 NewGCObject(JSContext *cx, gc::AllocKind allocKind, gc::InitialHeap initialHeap,
             size_t ndynamic, const js::Class *clasp)
 {
-    JSObject *obj = js::Allocate<JSObject>(cx, allocKind, ndynamic, initialHeap, clasp);
-    if (!obj)
-        return nullptr;
-
-    SetNewObjectMetadata(cx, obj);
-    return obj;
+    return js::Allocate<JSObject>(cx, allocKind, ndynamic, initialHeap, clasp);
 }
 
 bool
 CheckOverRecursed(JSContext *cx)
 {
     // We just failed the jitStackLimit check. There are two possible reasons:
     //  - jitStackLimit was the real stack limit and we're over-recursed
     //  - jitStackLimit was set to UINTPTR_MAX by JSRuntime::requestInterrupt
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -3211,18 +3211,22 @@ CreateArrayPrototype(JSContext *cx, JSPr
     if (!proto)
         return nullptr;
 
     RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &ArrayObject::class_,
                                                              TaggedProto(proto)));
     if (!group)
         return nullptr;
 
+    JSObject *metadata = nullptr;
+    if (!NewObjectMetadata(cx, &metadata))
+        return nullptr;
+
     RootedShape shape(cx, EmptyShape::getInitialShape(cx, &ArrayObject::class_, TaggedProto(proto),
-                                                      gc::AllocKind::OBJECT0));
+                                                      metadata, gc::AllocKind::OBJECT0));
     if (!shape)
         return nullptr;
 
     RootedArrayObject arrayProto(cx, ArrayObject::createArray(cx, gc::AllocKind::OBJECT4,
                                                               gc::TenuredHeap, shape, group, 0));
     if (!arrayProto ||
         !JSObject::setSingleton(cx, arrayProto) ||
         !AddLengthProperty(cx, arrayProto))
@@ -3285,17 +3289,19 @@ EnsureNewArrayElements(ExclusiveContext 
     MOZ_ASSERT_IF(cap, !obj->hasDynamicElements());
 
     return true;
 }
 
 static bool
 NewArrayIsCachable(ExclusiveContext *cxArg, NewObjectKind newKind)
 {
-    return cxArg->isJSContext() && newKind == GenericObject;
+    return cxArg->isJSContext() &&
+           newKind == GenericObject &&
+           !cxArg->asJSContext()->compartment()->hasObjectMetadataCallback();
 }
 
 template <uint32_t maxLength>
 static MOZ_ALWAYS_INLINE ArrayObject *
 NewArray(ExclusiveContext *cxArg, uint32_t length,
          HandleObject protoArg, NewObjectKind newKind = GenericObject)
 {
     gc::AllocKind allocKind = GuessArrayGCKind(length);
@@ -3330,23 +3336,27 @@ NewArray(ExclusiveContext *cxArg, uint32
     if (!proto && !GetBuiltinPrototype(cxArg, JSProto_Array, &proto))
         return nullptr;
 
     RootedObjectGroup group(cxArg, ObjectGroup::defaultNewGroup(cxArg, &ArrayObject::class_,
                                                                 TaggedProto(proto)));
     if (!group)
         return nullptr;
 
+    JSObject *metadata = nullptr;
+    if (!NewObjectMetadata(cxArg, &metadata))
+        return nullptr;
+
     /*
      * Get a shape with zero fixed slots, regardless of the size class.
      * See JSObject::createArray.
      */
     RootedShape shape(cxArg, EmptyShape::getInitialShape(cxArg, &ArrayObject::class_,
                                                          TaggedProto(proto),
-                                                         gc::AllocKind::OBJECT0));
+                                                         metadata, gc::AllocKind::OBJECT0));
     if (!shape)
         return nullptr;
 
     RootedArrayObject arr(cxArg, ArrayObject::createArray(cxArg, allocKind,
                                                           GetInitialHeap(newKind, &ArrayObject::class_),
                                                           shape, group, length));
     if (!arr)
         return nullptr;
@@ -3503,19 +3513,30 @@ js::NewDenseFullyAllocatedArrayWithTempl
     probes::CreateObject(cx, arr);
 
     return arr;
 }
 
 JSObject *
 js::NewDenseCopyOnWriteArray(JSContext *cx, HandleArrayObject templateObject, gc::InitialHeap heap)
 {
+    RootedShape shape(cx, templateObject->lastProperty());
+
     MOZ_ASSERT(!gc::IsInsideNursery(templateObject));
 
-    ArrayObject *arr = ArrayObject::createCopyOnWriteArray(cx, heap, templateObject);
+    JSObject *metadata = nullptr;
+    if (!NewObjectMetadata(cx, &metadata))
+        return nullptr;
+    if (metadata) {
+        shape = Shape::setObjectMetadata(cx, metadata, templateObject->getTaggedProto(), shape);
+        if (!shape)
+            return nullptr;
+    }
+
+    ArrayObject *arr = ArrayObject::createCopyOnWriteArray(cx, heap, shape, templateObject);
     if (!arr)
         return nullptr;
 
     probes::CreateObject(cx, arr);
     return arr;
 }
 
 #ifdef DEBUG
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -56,17 +56,16 @@ JSCompartment::JSCompartment(Zone *zone,
     data(nullptr),
     objectMetadataCallback(nullptr),
     lastAnimationTime(0),
     regExps(runtime_),
     globalWriteBarriered(false),
     neuteredTypedObjects(0),
     propertyTree(thisForCtor()),
     selfHostingScriptSource(nullptr),
-    objectMetadataTable(nullptr),
     lazyArrayBuffers(nullptr),
     gcIncomingGrayPointers(nullptr),
     gcWeakMapList(nullptr),
     gcPreserveJitCode(options.preserveJitCode()),
     debugModeBits(0),
     rngState(0),
     watchpointMap(nullptr),
     scriptCountsMap(nullptr),
@@ -87,17 +86,16 @@ JSCompartment::~JSCompartment()
 {
     reportTelemetry();
 
     js_delete(jitCompartment_);
     js_delete(watchpointMap);
     js_delete(scriptCountsMap);
     js_delete(debugScriptMap);
     js_delete(debugScopes);
-    js_delete(objectMetadataTable);
     js_delete(lazyArrayBuffers);
     js_free(enumerators);
 
     runtime_->numCompartments--;
 }
 
 bool
 JSCompartment::init(JSContext *cx)
@@ -643,16 +641,17 @@ JSCompartment::sweepCrossCompartmentWrap
         }
     }
 }
 
 void JSCompartment::fixupAfterMovingGC()
 {
     fixupGlobal();
     fixupInitialShapeTable();
+    fixupBaseShapeTable();
     objectGroups.fixupTablesAfterMovingGC();
 }
 
 void
 JSCompartment::fixupGlobal()
 {
     GlobalObject *global = *global_.unsafeGet();
     if (global)
@@ -816,32 +815,29 @@ void
 JSCompartment::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                       size_t *tiAllocationSiteTables,
                                       size_t *tiArrayTypeTables,
                                       size_t *tiObjectTypeTables,
                                       size_t *compartmentObject,
                                       size_t *compartmentTables,
                                       size_t *innerViewsArg,
                                       size_t *lazyArrayBuffersArg,
-                                      size_t *objectMetadataTablesArg,
                                       size_t *crossCompartmentWrappersArg,
                                       size_t *regexpCompartment,
                                       size_t *savedStacksSet)
 {
     *compartmentObject += mallocSizeOf(this);
     objectGroups.addSizeOfExcludingThis(mallocSizeOf, tiAllocationSiteTables,
                                         tiArrayTypeTables, tiObjectTypeTables,
                                         compartmentTables);
     *compartmentTables += baseShapes.sizeOfExcludingThis(mallocSizeOf)
                         + initialShapes.sizeOfExcludingThis(mallocSizeOf);
     *innerViewsArg += innerViews.sizeOfExcludingThis(mallocSizeOf);
     if (lazyArrayBuffers)
         *lazyArrayBuffersArg += lazyArrayBuffers->sizeOfIncludingThis(mallocSizeOf);
-    if (objectMetadataTable)
-        *objectMetadataTablesArg += objectMetadataTable->sizeOfIncludingThis(mallocSizeOf);
     *crossCompartmentWrappersArg += crossCompartmentWrappers.sizeOfExcludingThis(mallocSizeOf);
     *regexpCompartment += regExps.sizeOfExcludingThis(mallocSizeOf);
     *savedStacksSet += savedStacks_.sizeOfExcludingThis(mallocSizeOf);
 }
 
 void
 JSCompartment::reportTelemetry()
 {
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -126,17 +126,17 @@ typedef HashMap<CrossCompartmentKey, Rea
 } /* namespace js */
 
 namespace JS {
 struct TypeInferenceSizes;
 }
 
 namespace js {
 class DebugScopes;
-class ObjectWeakMap;
+class LazyArrayBufferTable;
 class WeakMapBase;
 }
 
 struct JSCompartment
 {
     JS::CompartmentOptions       options_;
 
   private:
@@ -251,17 +251,16 @@ struct JSCompartment
     void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                 size_t *tiAllocationSiteTables,
                                 size_t *tiArrayTypeTables,
                                 size_t *tiObjectTypeTables,
                                 size_t *compartmentObject,
                                 size_t *compartmentTables,
                                 size_t *innerViews,
                                 size_t *lazyArrayBuffers,
-                                size_t *objectMetadataTables,
                                 size_t *crossCompartmentWrappers,
                                 size_t *regexpCompartment,
                                 size_t *savedStacksSet);
 
     /*
      * Shared scope property tree, and arena-pool for allocating its nodes.
      */
     js::PropertyTree             propertyTree;
@@ -284,27 +283,21 @@ struct JSCompartment
 #endif
 
     /*
      * Lazily initialized script source object to use for scripts cloned
      * from the self-hosting global.
      */
     js::ReadBarrieredScriptSourceObject selfHostingScriptSource;
 
-    // Keep track of the metadata objects which can be associated with each
-    // JS object.
-    js::ObjectWeakMap *objectMetadataTable;
-
     // Map from array buffers to views sharing that storage.
     js::InnerViewTable innerViews;
 
-    // Inline transparent typed objects do not initially have an array buffer,
-    // but can have that buffer created lazily if it is accessed later. This
-    // table manages references from such typed objects to their buffers.
-    js::ObjectWeakMap *lazyArrayBuffers;
+    // Map from typed objects to array buffers lazily created for them.
+    js::LazyArrayBufferTable *lazyArrayBuffers;
 
     // All unboxed layouts in the compartment.
     mozilla::LinkedList<js::UnboxedLayout> unboxedLayouts;
 
     /* During GC, stores the index of this compartment in rt->compartments. */
     unsigned                     gcIndex;
 
     /*
@@ -393,24 +386,25 @@ struct JSCompartment
     void sweepNativeIterators();
 
     void purge();
     void clearTables();
 
     void fixupInitialShapeTable();
     void fixupAfterMovingGC();
     void fixupGlobal();
+    void fixupBaseShapeTable();
 
     bool hasObjectMetadataCallback() const { return objectMetadataCallback; }
     void setObjectMetadataCallback(js::ObjectMetadataCallback callback);
     void forgetObjectMetadataCallback() {
         objectMetadataCallback = nullptr;
     }
-    JSObject *callObjectMetadataCallback(JSContext *cx) const {
-        return objectMetadataCallback(cx);
+    bool callObjectMetadataCallback(JSContext *cx, JSObject **obj) const {
+        return objectMetadataCallback(cx, obj);
     }
     const void *addressOfMetadataCallback() const {
         return &objectMetadataCallback;
     }
 
     js::SavedStacks &savedStacks() { return savedStacks_; }
 
     void findOutgoingEdges(js::gc::ComponentFinder<JS::Zone> &finder);
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -19,17 +19,16 @@
 #include "jsweakmap.h"
 #include "jswrapper.h"
 #include "prmjtime.h"
 
 #include "builtin/TestingFunctions.h"
 #include "js/Proxy.h"
 #include "proxy/DeadObjectProxy.h"
 #include "vm/ArgumentsObject.h"
-#include "vm/WeakMapObject.h"
 #include "vm/WrapperObject.h"
 
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 #include "vm/NativeObject-inl.h"
 #include "vm/ScopeObject-inl.h"
 
@@ -1169,36 +1168,26 @@ js::AutoCTypesActivityCallback::AutoCTyp
 }
 
 JS_FRIEND_API(void)
 js::SetObjectMetadataCallback(JSContext *cx, ObjectMetadataCallback callback)
 {
     cx->compartment()->setObjectMetadataCallback(callback);
 }
 
-JS_FRIEND_API(void)
-js::SetObjectMetadata(JSContext *cx, JSObject *obj, JSObject *metadata)
+JS_FRIEND_API(bool)
+js::SetObjectMetadata(JSContext *cx, HandleObject obj, HandleObject metadata)
 {
-    ObjectWeakMap *&map = cx->compartment()->objectMetadataTable;
-    if (!map) {
-        map = cx->new_<ObjectWeakMap>(cx);
-        if (!map)
-            CrashAtUnhandlableOOM("SetObjectMetadata");
-    }
-    if (!map->add(cx, obj, metadata))
-        CrashAtUnhandlableOOM("SetObjectMetadata");
+    return JSObject::setMetadata(cx, obj, metadata);
 }
 
 JS_FRIEND_API(JSObject *)
 js::GetObjectMetadata(JSObject *obj)
 {
-    ObjectWeakMap *map = obj->compartment()->objectMetadataTable;
-    if (map)
-        return map->lookup(obj);
-    return nullptr;
+    return obj->getMetadata();
 }
 
 JS_FRIEND_API(bool)
 js::DefineOwnProperty(JSContext *cx, JSObject *objArg, jsid idArg,
                       JS::Handle<js::PropertyDescriptor> descriptor, ObjectOpResult &result)
 {
     RootedObject obj(cx, objArg);
     RootedId id(cx, idArg);
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -2578,31 +2578,32 @@ class JS_FRIEND_API(AutoCTypesActivityCa
     void DoEndCallback() {
         if (callback) {
             callback(cx, endType);
             callback = nullptr;
         }
     }
 };
 
-typedef JSObject *
-(* ObjectMetadataCallback)(JSContext *cx);
+typedef bool
+(* ObjectMetadataCallback)(JSContext *cx, JSObject **pmetadata);
 
 /*
  * Specify a callback to invoke when creating each JS object in the current
  * compartment, which may return a metadata object to associate with the
- * object.
+ * object. Objects with different metadata have different shape hierarchies,
+ * so for efficiency, objects should generally try to share metadata objects.
  */
 JS_FRIEND_API(void)
 SetObjectMetadataCallback(JSContext *cx, ObjectMetadataCallback callback);
 
 /* Manipulate the metadata associated with an object. */
 
-JS_FRIEND_API(void)
-SetObjectMetadata(JSContext *cx, JSObject *obj, JSObject *metadata);
+JS_FRIEND_API(bool)
+SetObjectMetadata(JSContext *cx, JS::HandleObject obj, JS::HandleObject metadata);
 
 JS_FRIEND_API(JSObject *)
 GetObjectMetadata(JSObject *obj);
 
 JS_FRIEND_API(bool)
 GetElementsWithAdder(JSContext *cx, JS::HandleObject obj, JS::HandleObject receiver,
                      uint32_t begin, uint32_t end, js::ElementAdder *adder);
 
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -472,18 +472,22 @@ static inline PropertyIteratorObject *
 NewPropertyIteratorObject(JSContext *cx, unsigned flags)
 {
     if (flags & JSITER_ENUMERATE) {
         RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &PropertyIteratorObject::class_,
                                                                  TaggedProto(nullptr)));
         if (!group)
             return nullptr;
 
+        JSObject *metadata = nullptr;
+        if (!NewObjectMetadata(cx, &metadata))
+            return nullptr;
+
         const Class *clasp = &PropertyIteratorObject::class_;
-        RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, TaggedProto(nullptr),
+        RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, TaggedProto(nullptr), metadata,
                                                           ITERATOR_FINALIZE_KIND));
         if (!shape)
             return nullptr;
 
         JSObject *obj = JSObject::create(cx, ITERATOR_FINALIZE_KIND,
                                          GetInitialHeap(GenericObject, clasp), shape, group);
         if (!obj)
             return nullptr;
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -944,16 +944,17 @@ js::SetIntegrityLevel(JSContext *cx, Han
 
         // Seal/freeze non-dictionary objects by constructing a new shape
         // hierarchy mirroring the original one, which can be shared if many
         // objects with the same structure are sealed/frozen. If we use the
         // generic path below then any non-empty object will be converted to
         // dictionary mode.
         RootedShape last(cx, EmptyShape::getInitialShape(cx, nobj->getClass(),
                                                          nobj->getTaggedProto(),
+                                                         nobj->getMetadata(),
                                                          nobj->numFixedSlots(),
                                                          nobj->lastProperty()->getObjectFlags()));
         if (!last)
             return false;
 
         // Get an in-order list of the shapes in this object.
         AutoShapeVector shapes(cx);
         for (Shape::Range<NoGC> r(nobj->lastProperty()); !r.empty(); r.popFront()) {
@@ -1103,24 +1104,29 @@ NewObject(ExclusiveContext *cx, HandleOb
           NewObjectKind newKind)
 {
     const Class *clasp = group->clasp();
 
     MOZ_ASSERT(clasp != &ArrayObject::class_);
     MOZ_ASSERT_IF(clasp == &JSFunction::class_,
                   kind == JSFunction::FinalizeKind || kind == JSFunction::ExtendedFinalizeKind);
 
+    JSObject *metadata = nullptr;
+    if (!NewObjectMetadata(cx, &metadata))
+        return nullptr;
+
     // For objects which can have fixed data following the object, only use
     // enough fixed slots to cover the number of reserved slots in the object,
     // regardless of the allocation kind specified.
     size_t nfixed = ClassCanHaveFixedData(clasp)
                     ? GetGCKindSlots(gc::GetGCObjectKind(clasp), clasp)
                     : GetGCKindSlots(kind, clasp);
 
-    RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, group->proto(), nfixed));
+    RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, group->proto(),
+                                                      metadata, nfixed));
     if (!shape)
         return nullptr;
 
     gc::InitialHeap heap = GetInitialHeap(newKind, clasp);
     JSObject *obj = JSObject::create(cx, kind, heap, shape, group);
     if (!obj)
         return nullptr;
 
@@ -1152,16 +1158,17 @@ NewObjectCache::fillProto(EntryIndex ent
 static bool
 NewObjectWithTaggedProtoIsCachable(ExclusiveContext *cxArg, Handle<TaggedProto> proto,
                                    NewObjectKind newKind, const Class *clasp)
 {
     return cxArg->isJSContext() &&
            proto.isObject() &&
            newKind == GenericObject &&
            clasp->isNative() &&
+           !cxArg->asJSContext()->compartment()->hasObjectMetadataCallback() &&
            !proto.toObject()->is<GlobalObject>();
 }
 
 JSObject *
 js::NewObjectWithGivenTaggedProto(ExclusiveContext *cxArg, const Class *clasp,
                                   Handle<TaggedProto> proto,
                                   gc::AllocKind allocKind, NewObjectKind newKind)
 {
@@ -1295,17 +1302,18 @@ FindProto(ExclusiveContext *cx, const js
 
 static bool
 NewObjectWithClassProtoIsCachable(ExclusiveContext *cxArg,
                                   JSProtoKey protoKey, NewObjectKind newKind, const Class *clasp)
 {
     return cxArg->isJSContext() &&
            protoKey != JSProto_Null &&
            newKind == GenericObject &&
-           clasp->isNative();
+           clasp->isNative() &&
+           !cxArg->asJSContext()->compartment()->hasObjectMetadataCallback();
 }
 
 JSObject *
 js::NewObjectWithClassProtoCommon(ExclusiveContext *cxArg, const Class *clasp,
                                   HandleObject protoArg,
                                   gc::AllocKind allocKind, NewObjectKind newKind)
 {
     if (protoArg) {
@@ -1369,16 +1377,17 @@ js::NewObjectWithClassProtoCommon(Exclus
 static bool
 NewObjectWithGroupIsCachable(ExclusiveContext *cx, HandleObjectGroup group,
                              NewObjectKind newKind)
 {
     return group->proto().isObject() &&
            newKind == GenericObject &&
            group->clasp()->isNative() &&
            (!group->newScript() || group->newScript()->analyzed()) &&
+           !cx->compartment()->hasObjectMetadataCallback() &&
            cx->isJSContext();
 }
 
 /*
  * Create a plain object with the specified group. This bypasses getNewGroup to
  * avoid losing creation site information for objects made by scripted 'new'.
  */
 JSObject *
@@ -1871,38 +1880,43 @@ js::DeepCloneObjectLiteral(JSContext *cx
 static bool
 InitializePropertiesFromCompatibleNativeObject(JSContext *cx,
                                                HandleNativeObject dst,
                                                HandleNativeObject src)
 {
     assertSameCompartment(cx, src, dst);
     MOZ_ASSERT(src->getClass() == dst->getClass());
     MOZ_ASSERT(dst->lastProperty()->getObjectFlags() == 0);
+    MOZ_ASSERT(!src->getMetadata());
     MOZ_ASSERT(!src->isSingleton());
     MOZ_ASSERT(src->numFixedSlots() == dst->numFixedSlots());
 
+    // Save the dst metadata, if any, before we start messing with its shape.
+    RootedObject dstMetadata(cx, dst->getMetadata());
+
     if (!dst->ensureElements(cx, src->getDenseInitializedLength()))
         return false;
 
     uint32_t initialized = src->getDenseInitializedLength();
     for (uint32_t i = 0; i < initialized; ++i) {
         dst->setDenseInitializedLength(i + 1);
         dst->initDenseElement(i, src->getDenseElement(i));
     }
 
     MOZ_ASSERT(!src->hasPrivate());
     RootedShape shape(cx);
     if (src->getProto() == dst->getProto()) {
         shape = src->lastProperty();
     } else {
         // We need to generate a new shape for dst that has dst's proto but all
         // the property information from src.  Note that we asserted above that
-        // dst's object flags are 0.
+        // dst's object flags are 0 and we plan to set up the metadata later, so
+        // it's OK to pass null for the metadata here.
         shape = EmptyShape::getInitialShape(cx, dst->getClass(), dst->getTaggedProto(),
-                                            dst->numFixedSlots(), 0);
+                                            nullptr, dst->numFixedSlots(), 0);
         if (!shape)
             return false;
 
         // Get an in-order list of the shapes in the src object.
         AutoShapeVector shapes(cx);
         for (Shape::Range<NoGC> r(src->lastProperty()); !r.empty(); r.popFront()) {
             if (!shapes.append(&r.front()))
                 return false;
@@ -1918,16 +1932,21 @@ InitializePropertiesFromCompatibleNative
         }
     }
     size_t span = shape->slotSpan();
     if (!dst->setLastProperty(cx, shape))
         return false;
     for (size_t i = JSCLASS_RESERVED_SLOTS(src->getClass()); i < span; i++)
         dst->setSlot(i, src->getSlot(i));
 
+    if (dstMetadata) {
+        if (!js::SetObjectMetadata(cx, dst, dstMetadata))
+            return false;
+    }
+
     return true;
 }
 
 JS_FRIEND_API(bool)
 JS_InitializePropertiesFromCompatibleNativeObject(JSContext *cx,
                                                   HandleObject dst,
                                                   HandleObject src)
 {
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -424,16 +424,20 @@ class JSObject : public js::gc::Cell
 
     /*
      * Get the enclosing scope of an object. When called on non-scope object,
      * this will just be the global (the name "enclosing scope" still applies
      * in this situation because non-scope objects can be on the scope chain).
      */
     inline JSObject *enclosingScope();
 
+    /* Access the metadata on an object. */
+    inline JSObject *getMetadata() const;
+    static bool setMetadata(JSContext *cx, js::HandleObject obj, js::HandleObject newMetadata);
+
     inline js::GlobalObject &global() const;
     inline bool isOwnGlobal() const;
 
     /*
      * ES5 meta-object properties and operations.
      */
 
   public:
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -228,35 +228,16 @@ ClassCanHaveFixedData(const Class *clasp
     // arrays we only use enough to cover the class reserved slots, so that
     // the remaining space in the object's allocation is available for the
     // buffer's data.
     return !clasp->isNative()
         || clasp == &js::ArrayBufferObject::class_
         || js::IsTypedArrayClass(clasp);
 }
 
-static MOZ_ALWAYS_INLINE void
-SetNewObjectMetadata(ExclusiveContext *cxArg, JSObject *obj)
-{
-    // The metadata callback is invoked for each object created on the main
-    // thread, except when analysis/compilation is active, to avoid recursion.
-    if (JSContext *cx = cxArg->maybeJSContext()) {
-        if (MOZ_UNLIKELY((size_t)cx->compartment()->hasObjectMetadataCallback()) &&
-            !cx->zone()->types.activeAnalysis)
-        {
-            // Use AutoEnterAnalysis to prohibit both any GC activity under the
-            // callback, and any reentering of JS via Invoke() etc.
-            AutoEnterAnalysis enter(cx);
-
-            if (JSObject *metadata = cx->compartment()->callObjectMetadataCallback(cx))
-                SetObjectMetadata(cx, obj, metadata);
-        }
-    }
-}
-
 } // namespace js
 
 /* static */ inline JSObject *
 JSObject::create(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
                  js::HandleShape shape, js::HandleObjectGroup group)
 {
     MOZ_ASSERT(shape && group);
     MOZ_ASSERT(group->clasp() == shape->getObjectClass());
@@ -297,18 +278,16 @@ JSObject::create(js::ExclusiveContext *c
 
     if (size_t span = shape->slotSpan())
         obj->as<js::NativeObject>().initializeSlotRange(0, span);
 
     // JSFunction's fixed slots expect POD-style initialization.
     if (group->clasp()->isJSFunction())
         memset(obj->as<JSFunction>().fixedSlots(), 0, sizeof(js::HeapSlot) * GetGCKindSlots(kind));
 
-    SetNewObjectMetadata(cx, obj);
-
     js::gc::TraceCreateObject(obj);
 
     return obj;
 }
 
 inline void
 JSObject::setInitialShapeMaybeNonNative(js::Shape *shape)
 {
@@ -329,16 +308,24 @@ JSObject::setInitialSlotsMaybeNonNative(
 }
 
 inline void
 JSObject::setInitialElementsMaybeNonNative(js::HeapSlot *elements)
 {
     static_cast<js::NativeObject *>(this)->elements_ = elements;
 }
 
+inline JSObject *
+JSObject::getMetadata() const
+{
+    if (js::Shape *shape = maybeShape())
+        return shape->getObjectMetadata();
+    return nullptr;
+}
+
 inline js::GlobalObject &
 JSObject::global() const
 {
     /*
      * The global is read-barriered so that it is kept live by access through
      * the JSCompartment. When accessed through a JSObject, however, the global
      * will be already be kept live by the black JSObject's parent pointer, so
      * does not need to be read-barriered.
@@ -836,16 +823,38 @@ Unbox(JSContext *cx, HandleObject obj, M
     else if (obj->is<DateObject>())
         vp.set(obj->as<DateObject>().UTCTime());
     else
         vp.setUndefined();
 
     return true;
 }
 
+static MOZ_ALWAYS_INLINE bool
+NewObjectMetadata(ExclusiveContext *cxArg, JSObject **pmetadata)
+{
+    // The metadata callback is invoked before each created object, except when
+    // analysis/compilation is active, to avoid recursion. It is also skipped
+    // when we allocate objects during a bailout, to prevent stack iterations.
+    MOZ_ASSERT(!*pmetadata);
+    if (JSContext *cx = cxArg->maybeJSContext()) {
+        if (MOZ_UNLIKELY((size_t)cx->compartment()->hasObjectMetadataCallback()) &&
+            !cx->zone()->types.activeAnalysis)
+        {
+            // Use AutoEnterAnalysis to prohibit both any GC activity under the
+            // callback, and any reentering of JS via Invoke() etc.
+            AutoEnterAnalysis enter(cx);
+
+            if (!cx->compartment()->callObjectMetadataCallback(cx, pmetadata))
+                return false;
+        }
+    }
+    return true;
+}
+
 static inline unsigned
 ApplyAttributes(unsigned attrs, bool enumerable, bool writable, bool configurable)
 {
     /*
      * Respect the fact that some callers may want to preserve existing attributes as much as
      * possible, or install defaults otherwise.
      */
     if (attrs & JSPROP_IGNORE_ENUMERATE) {
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -136,17 +136,17 @@ Bindings::initWithTemporaryStorage(Exclu
     }
     self->aliasedBodyLevelLexicalBegin_ = aliasedBodyLevelLexicalBegin;
 
     // Put as many of nslots inline into the object header as possible.
     uint32_t nfixed = gc::GetGCKindSlots(gc::GetGCObjectKind(nslots));
 
     // Start with the empty shape and then append one shape per aliased binding.
     RootedShape shape(cx,
-        EmptyShape::getInitialShape(cx, &CallObject::class_, TaggedProto(nullptr),
+        EmptyShape::getInitialShape(cx, &CallObject::class_, TaggedProto(nullptr), nullptr,
                                     nfixed, BaseShape::QUALIFIED_VAROBJ | BaseShape::DELEGATE));
     if (!shape)
         return false;
 
 #ifdef DEBUG
     HashSet<PropertyName *> added(cx);
     if (!added.init())
         return false;
@@ -159,17 +159,17 @@ Bindings::initWithTemporaryStorage(Exclu
 
 #ifdef DEBUG
         // The caller ensures no duplicate aliased names.
         MOZ_ASSERT(!added.has(bi->name()));
         if (!added.put(bi->name()))
             return false;
 #endif
 
-        StackBaseShape stackBase(cx, &CallObject::class_,
+        StackBaseShape stackBase(cx, &CallObject::class_, nullptr,
                                  BaseShape::QUALIFIED_VAROBJ | BaseShape::DELEGATE);
 
         UnownedBaseShape *base = BaseShape::getUnowned(cx, stackBase);
         if (!base)
             return false;
 
         unsigned attrs = JSPROP_PERMANENT |
                          JSPROP_ENUMERATE |
--- a/js/src/jsweakmap.cpp
+++ b/js/src/jsweakmap.cpp
@@ -668,70 +668,8 @@ js::InitWeakMapClass(JSContext *cx, Hand
 }
 
 JSObject *
 js::InitBareWeakMapCtor(JSContext *cx, HandleObject obj)
 {
     return InitWeakMapClass(cx, obj, false);
 }
 
-ObjectWeakMap::ObjectWeakMap(JSContext *cx)
-  : map(cx, nullptr)
-{
-    if (!map.init())
-        CrashAtUnhandlableOOM("ObjectWeakMap");
-}
-
-ObjectWeakMap::~ObjectWeakMap()
-{
-    WeakMapBase::removeWeakMapFromList(&map);
-}
-
-JSObject *
-ObjectWeakMap::lookup(const JSObject *obj)
-{
-    if (ObjectValueMap::Ptr p = map.lookup(const_cast<JSObject *>(obj)))
-        return &p->value().toObject();
-    return nullptr;
-}
-
-bool
-ObjectWeakMap::add(JSContext *cx, JSObject *obj, JSObject *target)
-{
-    MOZ_ASSERT(obj && target);
-
-    MOZ_ASSERT(!map.has(obj));
-    if (!map.put(obj, ObjectValue(*target))) {
-        ReportOutOfMemory(cx);
-        return false;
-    }
-
-    return true;
-}
-
-void
-ObjectWeakMap::clear()
-{
-    map.clear();
-}
-
-void
-ObjectWeakMap::trace(JSTracer *trc)
-{
-    map.trace(trc);
-}
-
-size_t
-ObjectWeakMap::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
-{
-    return map.sizeOfExcludingThis(mallocSizeOf);
-}
-
-#ifdef JSGC_HASH_TABLE_CHECKS
-void
-ObjectWeakMap::checkAfterMovingGC()
-{
-    for (ObjectValueMap::Range r = map.all(); !r.empty(); r.popFront()) {
-        CheckGCThingAfterMovingGC(r.front().key().get());
-        CheckGCThingAfterMovingGC(&r.front().value().toObject());
-    }
-}
-#endif // JSGC_HASH_TABLE_CHECKS
--- a/js/src/vm/ArgumentsObject.cpp
+++ b/js/src/vm/ArgumentsObject.cpp
@@ -164,18 +164,23 @@ ArgumentsObject::create(JSContext *cx, H
 
     bool strict = callee->strict();
     const Class *clasp = strict ? &StrictArgumentsObject::class_ : &NormalArgumentsObject::class_;
 
     RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, clasp, TaggedProto(proto.get())));
     if (!group)
         return nullptr;
 
+    JSObject *metadata = nullptr;
+    if (!NewObjectMetadata(cx, &metadata))
+        return nullptr;
+
     RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, TaggedProto(proto),
-                                                      FINALIZE_KIND, BaseShape::INDEXED));
+                                                      metadata, FINALIZE_KIND,
+                                                      BaseShape::INDEXED));
     if (!shape)
         return nullptr;
 
     unsigned numFormals = callee->nargs();
     unsigned numDeletedWords = NumWordsForBitArrayOfLength(numActuals);
     unsigned numArgs = Max(numActuals, numFormals);
     unsigned numBytes = offsetof(ArgumentsData, args) +
                         numDeletedWords * sizeof(size_t) +
--- a/js/src/vm/ArrayObject-inl.h
+++ b/js/src/vm/ArrayObject-inl.h
@@ -46,18 +46,16 @@ ArrayObject::createArrayInternal(Exclusi
     size_t nDynamicSlots = dynamicSlotsCount(0, shape->slotSpan(), group->clasp());
     JSObject *obj = Allocate<JSObject>(cx, kind, nDynamicSlots, heap, group->clasp());
     if (!obj)
         return nullptr;
 
     static_cast<ArrayObject *>(obj)->shape_.init(shape);
     static_cast<ArrayObject *>(obj)->group_.init(group);
 
-    SetNewObjectMetadata(cx, obj);
-
     return &obj->as<ArrayObject>();
 }
 
 /* static */ inline ArrayObject *
 ArrayObject::finishCreateArray(ArrayObject *obj, HandleShape shape)
 {
     size_t span = shape->slotSpan();
     if (span)
@@ -101,27 +99,27 @@ ArrayObject::createArray(ExclusiveContex
 
     obj->elements_ = elements;
 
     return finishCreateArray(obj, shape);
 }
 
 /* static */ inline ArrayObject *
 ArrayObject::createCopyOnWriteArray(ExclusiveContext *cx, gc::InitialHeap heap,
+                                    HandleShape shape,
                                     HandleArrayObject sharedElementsOwner)
 {
     MOZ_ASSERT(sharedElementsOwner->getElementsHeader()->isCopyOnWrite());
     MOZ_ASSERT(sharedElementsOwner->getElementsHeader()->ownerObject() == sharedElementsOwner);
 
     // Use the smallest allocation kind for the array, as it can't have any
     // fixed slots (see the assert in createArrayInternal) and will not be using
     // its fixed elements.
     gc::AllocKind kind = gc::AllocKind::OBJECT0_BACKGROUND;
 
-    RootedShape shape(cx, sharedElementsOwner->lastProperty());
     RootedObjectGroup group(cx, sharedElementsOwner->group());
     ArrayObject *obj = createArrayInternal(cx, kind, heap, shape, group);
     if (!obj)
         return nullptr;
 
     obj->elements_ = sharedElementsOwner->getDenseElementsAllowCopyOnWrite();
 
     return finishCreateArray(obj, shape);
--- a/js/src/vm/ArrayObject.h
+++ b/js/src/vm/ArrayObject.h
@@ -55,16 +55,17 @@ class ArrayObject : public NativeObject
                 HandleObjectGroup group,
                 HeapSlot *elements);
 
     // Make a copy-on-write array object which shares the elements of an
     // existing object.
     static inline ArrayObject *
     createCopyOnWriteArray(ExclusiveContext *cx,
                            gc::InitialHeap heap,
+                           HandleShape shape,
                            HandleArrayObject sharedElementsOwner);
 
   private:
     // Helper for the above methods.
     static inline ArrayObject *
     createArrayInternal(ExclusiveContext *cx,
                         gc::AllocKind kind,
                         gc::InitialHeap heap,
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -6742,17 +6742,17 @@ DebuggerObject_getGlobal(JSContext *cx, 
     return true;
 }
 
 static bool
 DebuggerObject_getAllocationSite(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "get allocationSite", args, obj);
 
-    RootedObject metadata(cx, GetObjectMetadata(obj));
+    RootedObject metadata(cx, obj->getMetadata());
     if (!cx->compartment()->wrap(cx, &metadata))
         return false;
     args.rval().setObjectOrNull(metadata);
     return true;
 }
 
 static bool
 DebuggerObject_getOwnPropertyDescriptor(JSContext *cx, unsigned argc, Value *vp)
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -182,17 +182,17 @@ typedef JSObject Env;
 class Debugger : private mozilla::LinkedListElement<Debugger>
 {
     friend class Breakpoint;
     friend class DebuggerMemory;
     friend class SavedStacks;
     friend class mozilla::LinkedListElement<Debugger>;
     friend bool (::JS_DefineDebuggerObject)(JSContext *cx, JS::HandleObject obj);
     friend bool (::JS::dbg::IsDebugger)(JS::Value val);
-    friend JSObject *SavedStacksMetadataCallback(JSContext *cx);
+    friend bool SavedStacksMetadataCallback(JSContext *cx, JSObject **pmetadata);
     friend void JS::dbg::onNewPromise(JSContext *cx, HandleObject promise);
     friend void JS::dbg::onPromiseSettled(JSContext *cx, HandleObject promise);
 
   public:
     enum Hook {
         OnDebuggerStatement,
         OnExceptionUnwind,
         OnNewScript,
--- a/js/src/vm/MemoryMetrics.cpp
+++ b/js/src/vm/MemoryMetrics.cpp
@@ -333,17 +333,16 @@ StatsCompartmentCallback(JSRuntime *rt, 
     compartment->addSizeOfIncludingThis(rtStats->mallocSizeOf_,
                                         &cStats.typeInferenceAllocationSiteTables,
                                         &cStats.typeInferenceArrayTypeTables,
                                         &cStats.typeInferenceObjectTypeTables,
                                         &cStats.compartmentObject,
                                         &cStats.compartmentTables,
                                         &cStats.innerViewsTable,
                                         &cStats.lazyArrayBuffersTable,
-                                        &cStats.objectMetadataTable,
                                         &cStats.crossCompartmentWrappersTable,
                                         &cStats.regexpCompartment,
                                         &cStats.savedStacksSet);
 }
 
 static void
 StatsArenaCallback(JSRuntime *rt, void *data, gc::Arena *arena,
                    JSGCTraceKind traceKind, size_t thingSize)
--- a/js/src/vm/NativeObject-inl.h
+++ b/js/src/vm/NativeObject-inl.h
@@ -49,17 +49,18 @@ NativeObject::canRemoveLastProperty()
      * Check that the information about the object stored in the last
      * property's base shape is consistent with that stored in the previous
      * shape. If not consistent, then the last property cannot be removed as it
      * will induce a change in the object itself, and the object must be
      * converted to dictionary mode instead. See BaseShape comment in jsscope.h
      */
     MOZ_ASSERT(!inDictionaryMode());
     Shape *previous = lastProperty()->previous().get();
-    return previous->getObjectFlags() == lastProperty()->getObjectFlags();
+    return previous->getObjectMetadata() == lastProperty()->getObjectMetadata()
+        && previous->getObjectFlags() == lastProperty()->getObjectFlags();
 }
 
 inline void
 NativeObject::setShouldConvertDoubleElements()
 {
     MOZ_ASSERT(is<ArrayObject>() && !hasEmptyElements());
     getElementsHeader()->setShouldConvertDoubleElements();
 }
@@ -325,18 +326,21 @@ CopyInitializerObject(JSContext *cx, Han
 
     gc::AllocKind allocKind = gc::GetGCObjectFixedSlotsKind(baseobj->numFixedSlots());
     allocKind = gc::GetBackgroundAllocKind(allocKind);
     MOZ_ASSERT_IF(baseobj->isTenured(), allocKind == baseobj->asTenured().getAllocKind());
     RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx, allocKind, newKind));
     if (!obj)
         return nullptr;
 
+    RootedObject metadata(cx, obj->getMetadata());
     if (!obj->setLastProperty(cx, baseobj->lastProperty()))
         return nullptr;
+    if (metadata && !JSObject::setMetadata(cx, obj, metadata))
+        return nullptr;
 
     return obj;
 }
 
 inline NativeObject *
 NewNativeObjectWithGivenTaggedProto(ExclusiveContext *cx, const Class *clasp,
                                     Handle<TaggedProto> proto,
                                     gc::AllocKind allocKind, NewObjectKind newKind)
--- a/js/src/vm/Runtime-inl.h
+++ b/js/src/vm/Runtime-inl.h
@@ -10,18 +10,16 @@
 #include "vm/Runtime.h"
 
 #include "jscompartment.h"
 
 #include "gc/Allocator.h"
 #include "gc/GCTrace.h"
 #include "vm/Probes.h"
 
-#include "jsobjinlines.h"
-
 namespace js {
 
 inline bool
 NewObjectCache::lookupProto(const Class *clasp, JSObject *proto, gc::AllocKind kind, EntryIndex *pentry)
 {
     MOZ_ASSERT(!proto->is<GlobalObject>());
     return lookup(clasp, proto, kind, pentry);
 }
@@ -38,16 +36,19 @@ NewObjectCache::fillGlobal(EntryIndex en
 {
     //MOZ_ASSERT(global == obj->getGlobal());
     return fill(entry, clasp, global, kind, obj);
 }
 
 inline NativeObject *
 NewObjectCache::newObjectFromHit(JSContext *cx, EntryIndex entryIndex, gc::InitialHeap heap)
 {
+    // The new object cache does not account for metadata attached via callbacks.
+    MOZ_ASSERT(!cx->compartment()->hasObjectMetadataCallback());
+
     MOZ_ASSERT(unsigned(entryIndex) < mozilla::ArrayLength(entries));
     Entry *entry = &entries[entryIndex];
 
     NativeObject *templateObj = reinterpret_cast<NativeObject *>(&entry->templateObject);
 
     // Do an end run around JSObject::group() to avoid doing AutoUnprotectCell
     // on the templateObj, which is not a GC thing and can't use runtimeFromAnyThread.
     ObjectGroup *group = templateObj->group_;
@@ -59,19 +60,16 @@ NewObjectCache::newObjectFromHit(JSConte
         return nullptr;
 
     NativeObject *obj = static_cast<NativeObject *>(Allocate<JSObject, NoGC>(cx, entry->kind, 0,
                                                                              heap, group->clasp()));
     if (!obj)
         return nullptr;
 
     copyCachedToObject(obj, templateObj, entry->kind);
-
-    SetNewObjectMetadata(cx, obj);
-
     probes::CreateObject(cx, obj);
     gc::TraceCreateObject(obj);
     return obj;
 }
 
 }  /* namespace js */
 
 #endif /* vm_Runtime_inl_h */
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -1155,28 +1155,28 @@ SavedStacks::chooseSamplingProbability(J
     }
 
     if (!allocationTrackingDbg)
         return;
 
     allocationSamplingProbability = allocationTrackingDbg->allocationSamplingProbability;
 }
 
-JSObject *
-SavedStacksMetadataCallback(JSContext *cx)
+bool
+SavedStacksMetadataCallback(JSContext *cx, JSObject **pmetadata)
 {
     SavedStacks &stacks = cx->compartment()->savedStacks();
     if (stacks.allocationSkipCount > 0) {
         stacks.allocationSkipCount--;
-        return nullptr;
+        return true;
     }
 
     stacks.chooseSamplingProbability(cx);
     if (stacks.allocationSamplingProbability == 0.0)
-        return nullptr;
+        return true;
 
     // If the sampling probability is set to 1.0, we are always taking a sample
     // and can therefore leave allocationSkipCount at 0.
     if (stacks.allocationSamplingProbability != 1.0) {
         // Rather than generating a random number on every allocation to decide
         // if we want to sample that particular allocation (which would be
         // expensive), we calculate the number of allocations to skip before
         // taking the next sample.
@@ -1195,22 +1195,20 @@ SavedStacksMetadataCallback(JSContext *c
         // greater than n is (~P)^n, as required.
         double notSamplingProb = 1.0 - stacks.allocationSamplingProbability;
         stacks.allocationSkipCount = std::floor(std::log(random_nextDouble(&stacks.rngState)) /
                                                 std::log(notSamplingProb));
     }
 
     RootedSavedFrame frame(cx);
     if (!stacks.saveCurrentStack(cx, &frame))
-        CrashAtUnhandlableOOM("SavedStacksMetadataCallback");
+        return false;
+    *pmetadata = frame;
 
-    if (!Debugger::onLogAllocationSite(cx, frame, PRMJ_Now()))
-        CrashAtUnhandlableOOM("SavedStacksMetadataCallback");
-
-    return frame;
+    return Debugger::onLogAllocationSite(cx, frame, PRMJ_Now());
 }
 
 JS_FRIEND_API(JSPrincipals *)
 GetSavedFramePrincipals(HandleObject savedFrame)
 {
     MOZ_ASSERT(savedFrame);
     MOZ_ASSERT(savedFrame->is<SavedFrame>());
     return savedFrame->as<SavedFrame>().getPrincipals();
--- a/js/src/vm/SavedStacks.h
+++ b/js/src/vm/SavedStacks.h
@@ -120,17 +120,17 @@ struct SavedFrame::HashPolicy
     static HashNumber hash(const Lookup &lookup);
     static bool       match(SavedFrame *existing, const Lookup &lookup);
 
     typedef ReadBarriered<SavedFrame*> Key;
     static void rekey(Key &key, const Key &newKey);
 };
 
 class SavedStacks {
-    friend JSObject *SavedStacksMetadataCallback(JSContext *cx);
+    friend bool SavedStacksMetadataCallback(JSContext *cx, JSObject **pmetadata);
 
   public:
     SavedStacks()
       : frames(),
         allocationSamplingProbability(1.0),
         allocationSkipCount(0),
         rngState(0)
     { }
@@ -238,13 +238,13 @@ class SavedStacks {
     typedef HashMap<PCKey, LocationValue, PCLocationHasher, SystemAllocPolicy> PCLocationMap;
 
     PCLocationMap pcLocationMap;
 
     void sweepPCLocationMap();
     bool getLocation(JSContext *cx, const FrameIter &iter, MutableHandleLocationValue locationp);
 };
 
-JSObject *SavedStacksMetadataCallback(JSContext *cx);
+bool SavedStacksMetadataCallback(JSContext *cx, JSObject **pmetadata);
 
 } /* namespace js */
 
 #endif /* vm_SavedStacks_h */
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -11,17 +11,16 @@
 
 #include "jscompartment.h"
 #include "jsiter.h"
 
 #include "vm/ArgumentsObject.h"
 #include "vm/GlobalObject.h"
 #include "vm/ProxyObject.h"
 #include "vm/Shape.h"
-#include "vm/WeakMapObject.h"
 #include "vm/Xdr.h"
 
 #include "jsatominlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 #include "vm/Stack-inl.h"
 
@@ -328,17 +327,18 @@ DeclEnvObject::createTemplateObject(JSCo
     MOZ_ASSERT(IsNurseryAllocable(FINALIZE_KIND));
 
     RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
     if (!group)
         return nullptr;
 
     RootedShape emptyDeclEnvShape(cx);
     emptyDeclEnvShape = EmptyShape::getInitialShape(cx, &class_, TaggedProto(nullptr),
-                                                    FINALIZE_KIND, BaseShape::DELEGATE);
+                                                    nullptr, FINALIZE_KIND,
+                                                    BaseShape::DELEGATE);
     if (!emptyDeclEnvShape)
         return nullptr;
 
     RootedNativeObject obj(cx, MaybeNativeObject(JSObject::create(cx, FINALIZE_KIND, heap,
                                                                   emptyDeclEnvShape, group)));
     if (!obj)
         return nullptr;
 
@@ -400,17 +400,17 @@ js::XDRStaticWithObject(XDRState<XDR_DEC
 StaticWithObject *
 StaticWithObject::create(ExclusiveContext *cx)
 {
     RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
     if (!group)
         return nullptr;
 
     RootedShape shape(cx, EmptyShape::getInitialShape(cx, &class_, TaggedProto(nullptr),
-                                                      FINALIZE_KIND));
+                                                      nullptr, FINALIZE_KIND));
     if (!shape)
         return nullptr;
 
     RootedObject obj(cx, JSObject::create(cx, FINALIZE_KIND, gc::TenuredHeap, shape, group));
     if (!obj)
         return nullptr;
 
     return &obj->as<StaticWithObject>();
@@ -434,17 +434,17 @@ DynamicWithObject::create(JSContext *cx,
 {
     MOZ_ASSERT(staticWith->is<StaticWithObject>());
     RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_,
                                                              TaggedProto(staticWith.get())));
     if (!group)
         return nullptr;
 
     RootedShape shape(cx, EmptyShape::getInitialShape(cx, &class_, TaggedProto(staticWith),
-                                                      FINALIZE_KIND));
+                                                      nullptr, FINALIZE_KIND));
     if (!shape)
         return nullptr;
 
     RootedNativeObject obj(cx, MaybeNativeObject(JSObject::create(cx, FINALIZE_KIND,
                                                                   gc::DefaultHeap, shape, group)));
     if (!obj)
         return nullptr;
 
@@ -567,17 +567,18 @@ const Class DynamicWithObject::class_ = 
 /* static */ StaticEvalObject *
 StaticEvalObject::create(JSContext *cx, HandleObject enclosing)
 {
     RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
     if (!group)
         return nullptr;
 
     RootedShape shape(cx, EmptyShape::getInitialShape(cx, &class_, TaggedProto(nullptr),
-                                                      FINALIZE_KIND, BaseShape::DELEGATE));
+                                                      nullptr, FINALIZE_KIND,
+                                                      BaseShape::DELEGATE));
     if (!shape)
         return nullptr;
 
     RootedNativeObject obj(cx, MaybeNativeObject(JSObject::create(cx, FINALIZE_KIND,
                                                                   gc::TenuredHeap, shape, group)));
     if (!obj)
         return nullptr;
 
@@ -666,17 +667,17 @@ StaticBlockObject::create(ExclusiveConte
 {
     RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &BlockObject::class_,
                                                              TaggedProto(nullptr)));
     if (!group)
         return nullptr;
 
     RootedShape emptyBlockShape(cx);
     emptyBlockShape = EmptyShape::getInitialShape(cx, &BlockObject::class_, TaggedProto(nullptr),
-                                                  FINALIZE_KIND, BaseShape::DELEGATE);
+                                                  nullptr, FINALIZE_KIND, BaseShape::DELEGATE);
     if (!emptyBlockShape)
         return nullptr;
 
     JSObject *obj = JSObject::create(cx, FINALIZE_KIND, gc::TenuredHeap, emptyBlockShape, group);
     if (!obj)
         return nullptr;
 
     return &obj->as<StaticBlockObject>();
@@ -877,17 +878,17 @@ js::CloneNestedScopeObject(JSContext *cx
 /* static */ UninitializedLexicalObject *
 UninitializedLexicalObject::create(JSContext *cx, HandleObject enclosing)
 {
     RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
     if (!group)
         return nullptr;
 
     RootedShape shape(cx, EmptyShape::getInitialShape(cx, &class_, TaggedProto(nullptr),
-                                                      FINALIZE_KIND));
+                                                      nullptr, FINALIZE_KIND));
     if (!shape)
         return nullptr;
 
     RootedObject obj(cx, JSObject::create(cx, FINALIZE_KIND, gc::DefaultHeap, shape, group));
     if (!obj)
         return nullptr;
 
     obj->as<ScopeObject>().setEnclosingScope(enclosing);
@@ -1801,16 +1802,24 @@ js::IsDebugScopeSlow(ProxyObject *proxy)
 {
     MOZ_ASSERT(proxy->hasClass(&ProxyObject::class_));
     return proxy->handler() == &DebugScopeProxy::singleton;
 }
 
 /*****************************************************************************/
 
 /* static */ MOZ_ALWAYS_INLINE void
+DebugScopes::proxiedScopesPostWriteBarrier(JSRuntime *rt, ObjectWeakMap *map,
+                                           const PreBarrieredObject &key)
+{
+    if (key && IsInsideNursery(key))
+        rt->gc.storeBuffer.putGeneric(UnbarrieredRef(map, key.get()));
+}
+
+/* static */ MOZ_ALWAYS_INLINE void
 DebugScopes::liveScopesPostWriteBarrier(JSRuntime *rt, LiveScopeMap *map, ScopeObject *key)
 {
     // As above.  Otherwise, barriers could fire during GC when moving the
     // value.
     typedef HashMap<ScopeObject *,
                     MissingScopeKey,
                     DefaultHasher<ScopeObject *>,
                     RuntimeAllocPolicy> UnbarrieredLiveScopeMap;
@@ -1823,22 +1832,29 @@ DebugScopes::DebugScopes(JSContext *cx)
  : proxiedScopes(cx),
    missingScopes(cx->runtime()),
    liveScopes(cx->runtime())
 {}
 
 DebugScopes::~DebugScopes()
 {
     MOZ_ASSERT(missingScopes.empty());
+    WeakMapBase::removeWeakMapFromList(&proxiedScopes);
 }
 
 bool
 DebugScopes::init()
 {
-    return liveScopes.init() && missingScopes.init();
+    if (!liveScopes.init() ||
+        !proxiedScopes.init() ||
+        !missingScopes.init())
+    {
+        return false;
+    }
+    return true;
 }
 
 void
 DebugScopes::mark(JSTracer *trc)
 {
     proxiedScopes.trace(trc);
 }
 
@@ -1900,17 +1916,20 @@ DebugScopes::sweep(JSRuntime *rt)
 void
 DebugScopes::checkHashTablesAfterMovingGC(JSRuntime *runtime)
 {
     /*
      * This is called at the end of StoreBuffer::mark() to check that our
      * postbarriers have worked and that no hashtable keys (or values) are left
      * pointing into the nursery.
      */
-    proxiedScopes.checkAfterMovingGC();
+    for (ObjectWeakMap::Range r = proxiedScopes.all(); !r.empty(); r.popFront()) {
+        CheckGCThingAfterMovingGC(r.front().key().get());
+        CheckGCThingAfterMovingGC(r.front().value().get());
+    }
     for (MissingScopeMap::Range r = missingScopes.all(); !r.empty(); r.popFront()) {
         CheckGCThingAfterMovingGC(r.front().key().staticScope());
         CheckGCThingAfterMovingGC(r.front().value().get());
     }
     for (LiveScopeMap::Range r = liveScopes.all(); !r.empty(); r.popFront()) {
         CheckGCThingAfterMovingGC(r.front().key());
         CheckGCThingAfterMovingGC(r.front().value().staticScope_.get());
     }
@@ -1950,19 +1969,19 @@ DebugScopes::ensureCompartmentData(JSCon
 
 DebugScopeObject *
 DebugScopes::hasDebugScope(JSContext *cx, ScopeObject &scope)
 {
     DebugScopes *scopes = scope.compartment()->debugScopes;
     if (!scopes)
         return nullptr;
 
-    if (JSObject *obj = scopes->proxiedScopes.lookup(&scope)) {
+    if (ObjectWeakMap::Ptr p = scopes->proxiedScopes.lookup(&scope)) {
         MOZ_ASSERT(CanUseDebugScopeMaps(cx));
-        return &obj->as<DebugScopeObject>();
+        return &p->value()->as<DebugScopeObject>();
     }
 
     return nullptr;
 }
 
 bool
 DebugScopes::addDebugScope(JSContext *cx, ScopeObject &scope, DebugScopeObject &debugScope)
 {
@@ -1971,17 +1990,24 @@ DebugScopes::addDebugScope(JSContext *cx
 
     if (!CanUseDebugScopeMaps(cx))
         return true;
 
     DebugScopes *scopes = ensureCompartmentData(cx);
     if (!scopes)
         return false;
 
-    return scopes->proxiedScopes.add(cx, &scope, &debugScope);
+    MOZ_ASSERT(!scopes->proxiedScopes.has(&scope));
+    if (!scopes->proxiedScopes.put(&scope, &debugScope)) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+
+    proxiedScopesPostWriteBarrier(cx->runtime(), &scopes->proxiedScopes, &scope);
+    return true;
 }
 
 DebugScopeObject *
 DebugScopes::hasDebugScope(JSContext *cx, const ScopeIter &si)
 {
     MOZ_ASSERT(!si.hasScopeObject());
 
     DebugScopes *scopes = cx->compartment()->debugScopes;
@@ -2052,18 +2078,18 @@ DebugScopes::onPopCall(AbstractFramePtr 
         if (!frame.hasCallObj())
             return;
 
         if (frame.fun()->isGenerator())
             return;
 
         CallObject &callobj = frame.scopeChain()->as<CallObject>();
         scopes->liveScopes.remove(&callobj);
-        if (JSObject *obj = scopes->proxiedScopes.lookup(&callobj))
-            debugScope = &obj->as<DebugScopeObject>();
+        if (ObjectWeakMap::Ptr p = scopes->proxiedScopes.lookup(&callobj))
+            debugScope = &p->value()->as<DebugScopeObject>();
     } else {
         ScopeIter si(cx, frame, frame.script()->main());
         if (MissingScopeMap::Ptr p = scopes->missingScopes.lookup(MissingScopeKey(si))) {
             debugScope = p->value();
             scopes->liveScopes.remove(&debugScope->scope().as<CallObject>());
             scopes->missingScopes.remove(p);
         }
     }
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -9,17 +9,16 @@
 
 #include "jscntxt.h"
 #include "jsobj.h"
 #include "jsweakmap.h"
 
 #include "gc/Barrier.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/ProxyObject.h"
-#include "vm/WeakMapObject.h"
 
 namespace js {
 
 namespace frontend { struct Definition; }
 
 class StaticWithObject;
 class StaticEvalObject;
 
@@ -909,17 +908,20 @@ class DebugScopeObject : public ProxyObj
     // (and thus does not have a synthesized ScopeObject or a snapshot)?
     bool isOptimizedOut() const;
 };
 
 /* Maintains per-compartment debug scope bookkeeping information. */
 class DebugScopes
 {
     /* The map from (non-debug) scopes to debug scopes. */
+    typedef WeakMap<PreBarrieredObject, RelocatablePtrObject> ObjectWeakMap;
     ObjectWeakMap proxiedScopes;
+    static MOZ_ALWAYS_INLINE void proxiedScopesPostWriteBarrier(JSRuntime *rt, ObjectWeakMap *map,
+                                                               const PreBarrieredObject &key);
 
     /*
      * The map from live frames which have optimized-away scopes to the
      * corresponding debug scopes.
      */
     typedef HashMap<MissingScopeKey,
                     ReadBarrieredDebugScopeObject,
                     MissingScopeKey,
--- a/js/src/vm/Shape-inl.h
+++ b/js/src/vm/Shape-inl.h
@@ -19,19 +19,21 @@
 #include "vm/TypedArrayCommon.h"
 
 #include "jsatominlines.h"
 #include "jscntxtinlines.h"
 
 namespace js {
 
 inline
-StackBaseShape::StackBaseShape(ExclusiveContext *cx, const Class *clasp, uint32_t objectFlags)
+StackBaseShape::StackBaseShape(ExclusiveContext *cx, const Class *clasp,
+                               JSObject *metadata, uint32_t objectFlags)
   : flags(objectFlags),
     clasp(clasp),
+    metadata(metadata),
     compartment(cx->compartment_)
 {}
 
 inline Shape *
 Shape::search(ExclusiveContext *cx, jsid id)
 {
     ShapeTable::Entry *_;
     return search(cx, this, id, &_);
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -318,17 +318,18 @@ ShapeTable::grow(ExclusiveContext *cx)
 Shape::replaceLastProperty(ExclusiveContext *cx, StackBaseShape &base,
                            TaggedProto proto, HandleShape shape)
 {
     MOZ_ASSERT(!shape->inDictionary());
 
     if (!shape->parent) {
         /* Treat as resetting the initial property of the shape hierarchy. */
         AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
-        return EmptyShape::getInitialShape(cx, base.clasp, proto, kind,
+        return EmptyShape::getInitialShape(cx, base.clasp, proto,
+                                           base.metadata, kind,
                                            base.flags & BaseShape::OBJECT_FLAG_MASK);
     }
 
     UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
     if (!nbase)
         return nullptr;
 
     StackShape child(shape);
@@ -618,17 +619,18 @@ js::ReshapeForAllocKind(JSContext *cx, S
             ids[nshape->slot()].set(nshape->propid());
             nshape = nshape->previous();
         }
     }
 
     // Construct the new shape, without updating type information.
     RootedId id(cx);
     RootedShape newShape(cx, EmptyShape::getInitialShape(cx, shape->getObjectClass(),
-                                                         proto, nfixed, shape->getObjectFlags()));
+                                                         proto, shape->getObjectMetadata(),
+                                                         nfixed, shape->getObjectFlags()));
     for (unsigned i = 0; i < ids.length(); i++) {
         id = ids[i];
 
         uint32_t index;
         bool indexed = IdIsIndex(id, &index);
 
         Rooted<UnownedBaseShape*> nbase(cx, newShape->base()->unowned());
         if (indexed) {
@@ -1123,16 +1125,55 @@ NativeObject::replaceWithNewEquivalentSh
 }
 
 bool
 NativeObject::shadowingShapeChange(ExclusiveContext *cx, const Shape &shape)
 {
     return generateOwnShape(cx);
 }
 
+/* static */ bool
+JSObject::setMetadata(JSContext *cx, HandleObject obj, HandleObject metadata)
+{
+    if (obj->isNative() && obj->as<NativeObject>().inDictionaryMode()) {
+        StackBaseShape base(obj->as<NativeObject>().lastProperty());
+        base.metadata = metadata;
+        UnownedBaseShape *nbase = BaseShape::getUnowned(cx, base);
+        if (!nbase)
+            return false;
+
+        obj->as<NativeObject>().lastProperty()->base()->adoptUnowned(nbase);
+        return true;
+    }
+
+    Shape *existingShape = obj->ensureShape(cx);
+    if (!existingShape)
+        return false;
+
+    Shape *newShape = Shape::setObjectMetadata(cx, metadata, obj->getTaggedProto(), existingShape);
+    if (!newShape)
+        return false;
+
+    obj->setShapeMaybeNonNative(newShape);
+    return true;
+}
+
+/* static */ Shape *
+Shape::setObjectMetadata(JSContext *cx, JSObject *metadata, TaggedProto proto, Shape *last)
+{
+    if (last->getObjectMetadata() == metadata)
+        return last;
+
+    StackBaseShape base(last);
+    base.metadata = metadata;
+
+    RootedShape lastRoot(cx, last);
+    return replaceLastProperty(cx, base, proto, lastRoot);
+}
+
 bool
 JSObject::setFlags(ExclusiveContext *cx, BaseShape::Flag flags, GenerateShape generateShape)
 {
     if (hasAllFlags(flags))
         return true;
 
     RootedObject self(cx, this);
 
@@ -1192,70 +1233,162 @@ Shape::setObjectFlags(ExclusiveContext *
     return replaceLastProperty(cx, base, proto, lastRoot);
 }
 
 /* static */ inline HashNumber
 StackBaseShape::hash(const Lookup& lookup)
 {
     HashNumber hash = lookup.flags;
     hash = RotateLeft(hash, 4) ^ (uintptr_t(lookup.clasp) >> 3);
+    hash = RotateLeft(hash, 4) ^ (uintptr_t(lookup.hashMetadata) >> 3);
     return hash;
 }
 
 /* static */ inline bool
 StackBaseShape::match(UnownedBaseShape *key, const Lookup& lookup)
 {
-    return key->flags == lookup.flags && key->clasp_ == lookup.clasp;
+    return key->flags == lookup.flags
+        && key->clasp_ == lookup.clasp
+        && key->metadata == lookup.matchMetadata;
+}
+
+void
+StackBaseShape::trace(JSTracer *trc)
+{
+    // No need to trace the global, since our compartment had better
+    // have been entered.
+    MOZ_ASSERT(compartment->hasBeenEntered());
+    if (metadata)
+        gc::MarkObjectRoot(trc, (JSObject**)&metadata, "StackBaseShape metadata");
+}
+
+/*
+ * This class is used to add a post barrier on the baseShapes set, as the key is
+ * calculated based on objects which may be moved by generational GC.
+ */
+class BaseShapeSetRef : public BufferableRef
+{
+    BaseShapeSet *set;
+    UnownedBaseShape *base;
+    JSObject *metadataPrior;
+
+  public:
+    BaseShapeSetRef(BaseShapeSet *set, UnownedBaseShape *base)
+      : set(set),
+        base(base),
+        metadataPrior(base->getObjectMetadata())
+    {
+        MOZ_ASSERT(!base->isOwned());
+    }
+
+    void mark(JSTracer *trc) {
+        JSObject *metadata = metadataPrior;
+        if (metadata)
+            Mark(trc, &metadata, "baseShapes set metadata");
+        if (metadata == metadataPrior)
+            return;
+
+        StackBaseShape::Lookup lookupPrior(base->getObjectFlags(),
+                                           base->clasp(),
+                                           metadataPrior, metadata);
+        ReadBarriered<UnownedBaseShape *> b(base);
+        MOZ_ALWAYS_TRUE(set->rekeyAs(lookupPrior, base, b));
+    }
+};
+
+static void
+BaseShapesTablePostBarrier(ExclusiveContext *cx, BaseShapeSet *table, UnownedBaseShape *base)
+{
+    if (!cx->isJSContext()) {
+        MOZ_ASSERT(!IsInsideNursery(base->getObjectMetadata()));
+        return;
+    }
+
+    if (IsInsideNursery(base->getObjectMetadata())) {
+        StoreBuffer &sb = cx->asJSContext()->runtime()->gc.storeBuffer;
+        sb.putGeneric(BaseShapeSetRef(table, base));
+    }
 }
 
 /* static */ UnownedBaseShape*
 BaseShape::getUnowned(ExclusiveContext *cx, StackBaseShape &base)
 {
     BaseShapeSet &table = cx->compartment()->baseShapes;
 
     if (!table.initialized() && !table.init())
         return nullptr;
 
     DependentAddPtr<BaseShapeSet> p(cx, table, base);
     if (p)
         return *p;
 
+    RootedGeneric<StackBaseShape*> root(cx, &base);
+
     BaseShape *nbase_ = Allocate<BaseShape>(cx);
     if (!nbase_)
         return nullptr;
 
     new (nbase_) BaseShape(base);
 
     UnownedBaseShape *nbase = static_cast<UnownedBaseShape *>(nbase_);
 
     if (!p.add(cx, table, base, nbase))
         return nullptr;
 
+    BaseShapesTablePostBarrier(cx, &table, nbase);
+
     return nbase;
 }
 
 void
 BaseShape::assertConsistency()
 {
 #ifdef DEBUG
     if (isOwned()) {
         UnownedBaseShape *unowned = baseUnowned();
+        MOZ_ASSERT(getObjectMetadata() == unowned->getObjectMetadata());
         MOZ_ASSERT(getObjectFlags() == unowned->getObjectFlags());
     }
 #endif
 }
 
+bool
+BaseShape::fixupBaseShapeTableEntry()
+{
+    if (metadata && IsForwarded(metadata.get())) {
+        metadata = Forwarded(metadata.get());
+        return true;
+    }
+    return false;
+}
+
+void
+JSCompartment::fixupBaseShapeTable()
+{
+    if (!baseShapes.initialized())
+        return;
+
+    for (BaseShapeSet::Enum e(baseShapes); !e.empty(); e.popFront()) {
+        UnownedBaseShape *base = e.front().unbarrieredGet();
+        if (base->fixupBaseShapeTableEntry()) {
+            ReadBarriered<UnownedBaseShape *> b(base);
+            e.rekeyFront(base, b);
+        }
+    }
+}
+
 void
 JSCompartment::sweepBaseShapeTable()
 {
     if (!baseShapes.initialized())
         return;
 
     for (BaseShapeSet::Enum e(baseShapes); !e.empty(); e.popFront()) {
         UnownedBaseShape *base = e.front().unbarrieredGet();
+        MOZ_ASSERT_IF(base->getObjectMetadata(), !IsForwarded(base->getObjectMetadata()));
         if (IsBaseShapeAboutToBeFinalizedFromAnyThread(&base)) {
             e.removeFront();
         } else if (base != e.front().unbarrieredGet()) {
             ReadBarriered<UnownedBaseShape *> b(base);
             e.rekeyFront(base, b);
         }
     }
 }
@@ -1266,16 +1399,18 @@ void
 JSCompartment::checkBaseShapeTableAfterMovingGC()
 {
     if (!baseShapes.initialized())
         return;
 
     for (BaseShapeSet::Enum e(baseShapes); !e.empty(); e.popFront()) {
         UnownedBaseShape *base = e.front().unbarrieredGet();
         CheckGCThingAfterMovingGC(base);
+        if (base->getObjectMetadata())
+            CheckGCThingAfterMovingGC(base->getObjectMetadata());
 
         BaseShapeSet::Ptr ptr = baseShapes.lookup(base);
         MOZ_ASSERT(ptr.found() && &*ptr == &e.front());
     }
 }
 
 #endif // JSGC_HASH_TABLE_CHECKS
 
@@ -1297,83 +1432,95 @@ inline
 InitialShapeEntry::InitialShapeEntry(const ReadBarrieredShape &shape, TaggedProto proto)
   : shape(shape), proto(proto)
 {
 }
 
 inline InitialShapeEntry::Lookup
 InitialShapeEntry::getLookup() const
 {
-    return Lookup(shape->getObjectClass(), proto, shape->numFixedSlots(), shape->getObjectFlags());
+    return Lookup(shape->getObjectClass(), proto, shape->getObjectMetadata(),
+                  shape->numFixedSlots(), shape->getObjectFlags());
 }
 
 /* static */ inline HashNumber
 InitialShapeEntry::hash(const Lookup &lookup)
 {
     HashNumber hash = uintptr_t(lookup.clasp) >> 3;
     hash = RotateLeft(hash, 4) ^
         (uintptr_t(lookup.hashProto.toWord()) >> 3);
+    hash = RotateLeft(hash, 4) ^
+        (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.matchProto.toWord() == key.proto.toWord()
+        && lookup.matchMetadata == shape->getObjectMetadata()
         && lookup.nfixed == shape->numFixedSlots()
         && lookup.baseFlags == shape->getObjectFlags();
 }
 
 /*
  * This class is used to add a post barrier on the initialShapes set, as the key
- * is calculated based on objects which may be moved by generational GC.
+ * 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 *metadata;
     size_t nfixed;
     uint32_t objectFlags;
 
   public:
     InitialShapeSetRef(InitialShapeSet *set,
                        const Class *clasp,
                        TaggedProto proto,
+                       JSObject *metadata,
                        size_t nfixed,
                        uint32_t objectFlags)
         : set(set),
           clasp(clasp),
           proto(proto),
+          metadata(metadata),
           nfixed(nfixed),
           objectFlags(objectFlags)
     {}
 
     void mark(JSTracer *trc) {
         TaggedProto priorProto = proto;
+        JSObject *priorMetadata = metadata;
         if (proto.isObject())
             Mark(trc, reinterpret_cast<JSObject**>(&proto), "initialShapes set proto");
-        if (proto == priorProto)
+        if (metadata)
+            Mark(trc, &metadata, "initialShapes set metadata");
+        if (proto == priorProto && metadata == priorMetadata)
             return;
 
         /* Find the original entry, which must still be present. */
-        InitialShapeEntry::Lookup lookup(clasp, priorProto, nfixed, objectFlags);
+        InitialShapeEntry::Lookup lookup(clasp, priorProto,
+                                         priorMetadata, metadata,
+                                         nfixed, objectFlags);
         InitialShapeSet::Ptr p = set->lookup(lookup);
         MOZ_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, nfixed, objectFlags),
+                     InitialShapeEntry::Lookup(clasp, proto, metadata, nfixed, objectFlags),
                      *p);
     }
 };
 
 #ifdef JSGC_HASH_TABLE_CHECKS
 
 void
 JSCompartment::checkInitialShapesTableAfterMovingGC()
@@ -1388,19 +1535,22 @@ JSCompartment::checkInitialShapesTableAf
      */
     for (InitialShapeSet::Enum e(initialShapes); !e.empty(); e.popFront()) {
         InitialShapeEntry entry = e.front();
         TaggedProto proto = entry.proto;
         Shape *shape = entry.shape.get();
 
         if (proto.isObject())
             CheckGCThingAfterMovingGC(proto.toObject());
+        if (shape->getObjectMetadata())
+            CheckGCThingAfterMovingGC(shape->getObjectMetadata());
 
         InitialShapeEntry::Lookup lookup(shape->getObjectClass(),
                                          proto,
+                                         shape->getObjectMetadata(),
                                          shape->numFixedSlots(),
                                          shape->getObjectFlags());
         InitialShapeSet::Ptr ptr = initialShapes.lookup(lookup);
         MOZ_ASSERT(ptr.found() && &*ptr == &e.front());
     }
 }
 
 #endif // JSGC_HASH_TABLE_CHECKS
@@ -1415,62 +1565,66 @@ EmptyShape::new_(ExclusiveContext *cx, H
     }
 
     new (shape) EmptyShape(base, nfixed);
     return shape;
 }
 
 /* static */ Shape *
 EmptyShape::getInitialShape(ExclusiveContext *cx, const Class *clasp, TaggedProto proto,
-                            size_t nfixed, uint32_t objectFlags)
+                            JSObject *metadata, size_t nfixed, uint32_t objectFlags)
 {
     MOZ_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject()));
 
     InitialShapeSet &table = cx->compartment()->initialShapes;
 
     if (!table.initialized() && !table.init())
         return nullptr;
 
     typedef InitialShapeEntry::Lookup Lookup;
     DependentAddPtr<InitialShapeSet>
-        p(cx, table, Lookup(clasp, proto, nfixed, objectFlags));
+        p(cx, table, Lookup(clasp, proto, metadata, nfixed, objectFlags));
     if (p)
         return p->shape;
 
     Rooted<TaggedProto> protoRoot(cx, proto);
+    RootedObject metadataRoot(cx, metadata);
 
-    StackBaseShape base(cx, clasp, objectFlags);
+    StackBaseShape base(cx, clasp, metadata, objectFlags);
     Rooted<UnownedBaseShape*> nbase(cx, BaseShape::getUnowned(cx, base));
     if (!nbase)
         return nullptr;
 
     Shape *shape = EmptyShape::new_(cx, nbase, nfixed);
     if (!shape)
         return nullptr;
 
-    Lookup lookup(clasp, protoRoot, nfixed, objectFlags);
+    Lookup lookup(clasp, protoRoot, metadataRoot, nfixed, objectFlags);
     if (!p.add(cx, table, lookup, InitialShapeEntry(ReadBarrieredShape(shape), protoRoot)))
         return nullptr;
 
     // Post-barrier for the initial shape table update.
     if (cx->isJSContext()) {
-        if (protoRoot.isObject() && IsInsideNursery(protoRoot.toObject())) {
-            InitialShapeSetRef ref(&table, clasp, protoRoot, nfixed, objectFlags);
+        if ((protoRoot.isObject() && IsInsideNursery(protoRoot.toObject())) ||
+            IsInsideNursery(metadataRoot.get()))
+        {
+            InitialShapeSetRef ref(
+                &table, clasp, protoRoot, metadataRoot, nfixed, objectFlags);
             cx->asJSContext()->runtime()->gc.storeBuffer.putGeneric(ref);
         }
     }
 
     return shape;
 }
 
 /* static */ Shape *
 EmptyShape::getInitialShape(ExclusiveContext *cx, const Class *clasp, TaggedProto proto,
-                            AllocKind kind, uint32_t objectFlags)
+                            JSObject *metadata, AllocKind kind, uint32_t objectFlags)
 {
-    return getInitialShape(cx, clasp, proto, GetGCKindSlots(kind, clasp), objectFlags);
+    return getInitialShape(cx, clasp, proto, metadata, GetGCKindSlots(kind, clasp), objectFlags);
 }
 
 void
 NewObjectCache::invalidateEntriesForShape(JSContext *cx, HandleShape shape, HandleObject proto)
 {
     const Class *clasp = shape->getObjectClass();
 
     gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
@@ -1488,16 +1642,17 @@ NewObjectCache::invalidateEntriesForShap
     if (lookupGroup(group, kind, &entry))
         PodZero(&entries[entry]);
 }
 
 /* static */ void
 EmptyShape::insertInitialShape(ExclusiveContext *cx, HandleShape shape, HandleObject proto)
 {
     InitialShapeEntry::Lookup lookup(shape->getObjectClass(), TaggedProto(proto),
+                                     shape->getObjectMetadata(),
                                      shape->numFixedSlots(), shape->getObjectFlags());
 
     InitialShapeSet::Ptr p = cx->compartment()->initialShapes.lookup(lookup);
     MOZ_ASSERT(p);
 
     InitialShapeEntry &entry = const_cast<InitialShapeEntry &>(*p);
 
     /* The new shape had better be rooted at the old one. */
@@ -1561,19 +1716,25 @@ JSCompartment::fixupInitialShapeTable()
         if (IsForwarded(entry.shape.get())) {
             entry.shape.set(Forwarded(entry.shape.get()));
             needRekey = true;
         }
         if (entry.proto.isObject() && IsForwarded(entry.proto.toObject())) {
             entry.proto = TaggedProto(Forwarded(entry.proto.toObject()));
             needRekey = true;
         }
+        JSObject *metadata = entry.shape->getObjectMetadata();
+        if (metadata) {
+            metadata = MaybeForwarded(metadata);
+            needRekey = true;
+        }
         if (needRekey) {
             InitialShapeEntry::Lookup relookup(entry.shape->getObjectClass(),
                                                entry.proto,
+                                               metadata,
                                                entry.shape->numFixedSlots(),
                                                entry.shape->getObjectFlags());
             e.rekeyFront(relookup, entry);
         }
     }
 }
 
 void
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -402,48 +402,73 @@ class BaseShape : public gc::TenuredCell
         // type had constructor information cleared.
         NEW_SCRIPT_CLEARED  = 0x8000,
 
         OBJECT_FLAG_MASK    = 0xfff8
     };
 
   private:
     const Class         *clasp_;        /* Class of referring object. */
+    HeapPtrObject       metadata;       /* Optional holder of metadata about
+                                         * the referring object. */
     JSCompartment       *compartment_;  /* Compartment shape belongs to. */
     uint32_t            flags;          /* Vector of above flags. */
     uint32_t            slotSpan_;      /* Object slot span for BaseShapes at
                                          * dictionary last properties. */
 
     /* For owned BaseShapes, the canonical unowned BaseShape. */
     HeapPtrUnownedBaseShape unowned_;
 
     /* For owned BaseShapes, the shape's shape table. */
     ShapeTable       *table_;
 
+    /*
+     * Our size needs to be a multiple of gc::CellSize, which happens
+     * to be 8.  On 32-bit, the above members leave us at 28 bytes, so
+     * we have to throw in a bit of padding.
+     */
+#ifndef HAVE_64BIT_BUILD
+    uint32_t unusedPadding_;
+#endif
+
     BaseShape(const BaseShape &base) = delete;
 
   public:
     void finalize(FreeOp *fop);
 
-    BaseShape(JSCompartment *comp, const Class *clasp, uint32_t objectFlags)
+    BaseShape(JSCompartment *comp, const Class *clasp, JSObject *metadata,
+              uint32_t objectFlags)
     {
         MOZ_ASSERT(!(objectFlags & ~OBJECT_FLAG_MASK));
         mozilla::PodZero(this);
         this->clasp_ = clasp;
+        this->metadata = metadata;
+        this->flags = objectFlags;
+        this->compartment_ = comp;
+    }
+
+    BaseShape(JSCompartment *comp, const Class *clasp, JSObject *metadata,
+              uint32_t objectFlags, uint8_t attrs)
+    {
+        MOZ_ASSERT(!(objectFlags & ~OBJECT_FLAG_MASK));
+        mozilla::PodZero(this);
+        this->clasp_ = clasp;
+        this->metadata = metadata;
         this->flags = objectFlags;
         this->compartment_ = comp;
     }
 
     explicit inline BaseShape(const StackBaseShape &base);
 
     /* Not defined: BaseShapes must not be stack allocated. */
     ~BaseShape();
 
     BaseShape &operator=(const BaseShape &other) {
         clasp_ = other.clasp_;
+        metadata = other.metadata;
         flags = other.flags;
         slotSpan_ = other.slotSpan_;
         compartment_ = other.compartment_;
         return *this;
     }
 
     const Class *clasp() const { return clasp_; }
 
@@ -451,16 +476,17 @@ class BaseShape : public gc::TenuredCell
 
     inline void adoptUnowned(UnownedBaseShape *other);
 
     void setOwned(UnownedBaseShape *unowned) {
         flags |= OWNED_SHAPE;
         this->unowned_ = unowned;
     }
 
+    JSObject *getObjectMetadata() const { return metadata; }
     uint32_t getObjectFlags() const { return flags & OBJECT_FLAG_MASK; }
 
     bool hasTable() const { MOZ_ASSERT_IF(table_, isOwned()); return table_ != nullptr; }
     ShapeTable &table() const { MOZ_ASSERT(table_ && isOwned()); return *table_; }
     void setTable(ShapeTable *table) { MOZ_ASSERT(isOwned()); table_ = table; }
 
     uint32_t slotSpan() const { MOZ_ASSERT(isOwned()); return slotSpan_; }
     void setSlotSpan(uint32_t slotSpan) { MOZ_ASSERT(isOwned()); slotSpan_ = slotSpan; }
@@ -488,16 +514,17 @@ class BaseShape : public gc::TenuredCell
     /* For JIT usage */
     static inline size_t offsetOfFlags() { return offsetof(BaseShape, flags); }
 
     static inline ThingRootKind rootKind() { return THING_ROOT_BASE_SHAPE; }
 
     void markChildren(JSTracer *trc);
 
     void fixupAfterMovingGC() {}
+    bool fixupBaseShapeTableEntry();
 
   private:
     static void staticAsserts() {
         JS_STATIC_ASSERT(offsetof(BaseShape, clasp_) == offsetof(js::shadow::BaseShape, clasp_));
         static_assert(sizeof(BaseShape) % gc::CellSize == 0,
                       "Things inheriting from gc::Cell must have a size that's "
                       "a multiple of gc::CellSize");
     }
@@ -541,52 +568,76 @@ BaseShape::baseUnowned()
     MOZ_ASSERT(isOwned() && unowned_); return unowned_;
 }
 
 /* Entries for the per-compartment baseShapes set of unowned base shapes. */
 struct StackBaseShape : public DefaultHasher<ReadBarrieredUnownedBaseShape>
 {
     uint32_t flags;
     const Class *clasp;
+    JSObject *metadata;
     JSCompartment *compartment;
 
     explicit StackBaseShape(BaseShape *base)
       : flags(base->flags & BaseShape::OBJECT_FLAG_MASK),
         clasp(base->clasp_),
+        metadata(base->metadata),
         compartment(base->compartment())
     {}
 
-    inline StackBaseShape(ExclusiveContext *cx, const Class *clasp, uint32_t objectFlags);
+    inline StackBaseShape(ExclusiveContext *cx, const Class *clasp,
+                          JSObject *metadata, uint32_t objectFlags);
     explicit inline StackBaseShape(Shape *shape);
 
     struct Lookup
     {
         uint32_t flags;
         const Class *clasp;
+        JSObject *hashMetadata;
+        JSObject *matchMetadata;
 
         MOZ_IMPLICIT Lookup(const StackBaseShape &base)
-          : flags(base.flags), clasp(base.clasp)
+          : flags(base.flags),
+            clasp(base.clasp),
+            hashMetadata(base.metadata),
+            matchMetadata(base.metadata)
         {}
 
         MOZ_IMPLICIT Lookup(UnownedBaseShape *base)
-          : flags(base->getObjectFlags()), clasp(base->clasp())
+          : flags(base->getObjectFlags()),
+            clasp(base->clasp()),
+            hashMetadata(base->getObjectMetadata()),
+            matchMetadata(base->getObjectMetadata())
         {
             MOZ_ASSERT(!base->isOwned());
         }
+
+        // For use by generational GC post barriers.
+        Lookup(uint32_t flags, const Class *clasp,
+               JSObject *hashMetadata, JSObject *matchMetadata)
+          : flags(flags),
+            clasp(clasp),
+            hashMetadata(hashMetadata),
+            matchMetadata(matchMetadata)
+        {}
     };
 
     static inline HashNumber hash(const Lookup& lookup);
     static inline bool match(UnownedBaseShape *key, const Lookup& lookup);
+
+    // For RootedGeneric<StackBaseShape*>
+    void trace(JSTracer *trc);
 };
 
 inline
 BaseShape::BaseShape(const StackBaseShape &base)
 {
     mozilla::PodZero(this);
     this->clasp_ = base.clasp;
+    this->metadata = base.metadata;
     this->flags = base.flags;
     this->compartment_ = base.compartment;
 }
 
 typedef HashSet<ReadBarrieredUnownedBaseShape,
                 StackBaseShape,
                 SystemAllocPolicy> BaseShapeSet;
 
@@ -751,17 +802,20 @@ class Shape : public gc::TenuredCell
             MOZ_ASSERT(!empty());
             cursor = cursor->parent;
         }
     };
 
     const Class *getObjectClass() const {
         return base()->clasp_;
     }
+    JSObject *getObjectMetadata() const { return base()->metadata; }
 
+    static Shape *setObjectMetadata(JSContext *cx,
+                                    JSObject *metadata, TaggedProto proto, Shape *last);
     static Shape *setObjectFlags(ExclusiveContext *cx,
                                  BaseShape::Flag flag, TaggedProto proto, Shape *last);
 
     uint32_t getObjectFlags() const { return base()->getObjectFlags(); }
     bool hasAllObjectFlags(BaseShape::Flag flags) const {
         MOZ_ASSERT(flags);
         MOZ_ASSERT(!(flags & ~BaseShape::OBJECT_FLAG_MASK));
         return (base()->flags & flags) == flags;
@@ -1053,16 +1107,17 @@ class AccessorShape : public Shape
     /* Get a shape identical to this one, without parent/kids information. */
     inline AccessorShape(const StackShape &other, uint32_t nfixed);
 };
 
 inline
 StackBaseShape::StackBaseShape(Shape *shape)
   : flags(shape->getObjectFlags()),
     clasp(shape->getObjectClass()),
+    metadata(shape->getObjectMetadata()),
     compartment(shape->compartment())
 {}
 
 class AutoRooterGetterSetter
 {
     class Inner : private JS::CustomAutoRooter
     {
       public:
@@ -1101,19 +1156,21 @@ struct EmptyShape : public js::Shape
 
     static Shape *new_(ExclusiveContext *cx, Handle<UnownedBaseShape *> base, uint32_t nfixed);
 
     /*
      * Lookup an initial shape matching the given parameters, creating an empty
      * shape if none was found.
      */
     static Shape *getInitialShape(ExclusiveContext *cx, const Class *clasp,
-                                  TaggedProto proto, size_t nfixed, uint32_t objectFlags = 0);
+                                  TaggedProto proto, JSObject *metadata,
+                                  size_t nfixed, uint32_t objectFlags = 0);
     static Shape *getInitialShape(ExclusiveContext *cx, const Class *clasp,
-                                  TaggedProto proto, gc::AllocKind kind, uint32_t objectFlags = 0);
+                                  TaggedProto proto, JSObject *metadata,
+                                  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);
 
@@ -1150,22 +1207,39 @@ struct InitialShapeEntry
      */
     TaggedProto proto;
 
     /* State used to determine a match on an initial shape. */
     struct Lookup {
         const Class *clasp;
         TaggedProto hashProto;
         TaggedProto matchProto;
+        JSObject *hashMetadata;
+        JSObject *matchMetadata;
         uint32_t nfixed;
         uint32_t baseFlags;
-
-        Lookup(const Class *clasp, TaggedProto proto, uint32_t nfixed, uint32_t baseFlags)
+        Lookup(const Class *clasp, TaggedProto proto, JSObject *metadata,
+               uint32_t nfixed, uint32_t baseFlags)
           : clasp(clasp),
             hashProto(proto), matchProto(proto),
+            hashMetadata(metadata), matchMetadata(metadata),
+            nfixed(nfixed), baseFlags(baseFlags)
+        {}
+
+        /*
+         * For use by generational GC post barriers. Look up an entry whose
+         * metadata field may have been moved, but was hashed with the original
+         * values.
+         */
+        Lookup(const Class *clasp, TaggedProto proto,
+               JSObject *hashMetadata, JSObject *matchMetadata,
+               uint32_t nfixed, uint32_t baseFlags)
+          : clasp(clasp),
+            hashProto(proto), matchProto(proto),
+            hashMetadata(hashMetadata), matchMetadata(matchMetadata),
             nfixed(nfixed), baseFlags(baseFlags)
         {}
     };
 
     inline InitialShapeEntry();
     inline InitialShapeEntry(const ReadBarrieredShape &shape, TaggedProto proto);
 
     inline Lookup getLookup() const;
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -3574,17 +3574,18 @@ TypeNewScript::maybeAnalyze(JSContext *c
             continue;
         PlainObject *obj = &objBase->as<PlainObject>();
 
         // For now, we require all preliminary objects to have only simple
         // lineages of plain data properties.
         Shape *shape = obj->lastProperty();
         if (shape->inDictionary() ||
             !OnlyHasDataProperties(shape) ||
-            shape->getObjectFlags() != 0)
+            shape->getObjectFlags() != 0 ||
+            shape->getObjectMetadata() != nullptr)
         {
             return true;
         }
 
         maxSlotSpan = Max<size_t>(maxSlotSpan, obj->slotSpan());
 
         if (prefixShape) {
             MOZ_ASSERT(shape->numFixedSlots() == prefixShape->numFixedSlots());
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -453,17 +453,18 @@ UnboxedLayout::makeNativeGroup(JSContext
 
         replacementNewGroup->setNewScript(replacementNewScript);
         gc::TraceTypeNewScript(replacementNewGroup);
 
         group->clearNewScript(cx, replacementNewGroup);
     }
 
     size_t nfixed = gc::GetGCKindSlots(layout.getAllocKind());
-    RootedShape shape(cx, EmptyShape::getInitialShape(cx, &PlainObject::class_, proto, nfixed, 0));
+    RootedShape shape(cx, EmptyShape::getInitialShape(cx, &PlainObject::class_, proto,
+                                                      nullptr, nfixed, 0));
     if (!shape)
         return false;
 
     for (size_t i = 0; i < layout.properties().length(); i++) {
         const UnboxedLayout::Property &property = layout.properties()[i];
 
         StackShape unrootedChild(shape->base()->unowned(), NameToId(property.name), i,
                                  JSPROP_ENUMERATE, 0);
@@ -1111,16 +1112,17 @@ js::TryConvertToUnboxedLayout(ExclusiveC
     // just constructed, so convert the existing group to be an
     // UnboxedPlainObject rather than a PlainObject, and update the preliminary
     // objects to use the new layout. Do the fallible stuff first before
     // modifying any objects.
 
     // Get an empty shape which we can use for the preliminary objects.
     Shape *newShape = EmptyShape::getInitialShape(cx, &UnboxedPlainObject::class_,
                                                   group->proto(),
+                                                  templateShape->getObjectMetadata(),
                                                   templateShape->getObjectFlags());
     if (!newShape) {
         cx->recoverFromOutOfMemory();
         return false;
     }
 
     // Accumulate a list of all the properties in each preliminary object, and
     // update their shapes.
--- a/js/src/vm/WeakMapObject.h
+++ b/js/src/vm/WeakMapObject.h
@@ -24,36 +24,11 @@ class ObjectValueMap : public WeakMap<Pr
 class WeakMapObject : public NativeObject
 {
   public:
     static const Class class_;
 
     ObjectValueMap *getMap() { return static_cast<ObjectValueMap*>(getPrivate()); }
 };
 
-// Generic weak map for mapping objects to other objects.
-class ObjectWeakMap
-{
-  private:
-    ObjectValueMap map;
-
-  public:
-    explicit ObjectWeakMap(JSContext *cx);
-    ~ObjectWeakMap();
-
-    JSObject *lookup(const JSObject *obj);
-    bool add(JSContext *cx, JSObject *obj, JSObject *target);
-    void clear();
-
-    void trace(JSTracer *trc);
-    size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
-    size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
-        return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf);
-    }
-
-#ifdef JSGC_HASH_TABLE_CHECKS
-    void checkAfterMovingGC();
-#endif
-};
-
 } // namespace js
 
 #endif /* vm_WeakMapObject_h */
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -2331,20 +2331,16 @@ ReportCompartmentStats(const JS::Compart
     ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("inner-views"),
         cStats.innerViewsTable,
         "The table for array buffer inner views.");
 
     ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("lazy-array-buffers"),
         cStats.lazyArrayBuffersTable,
         "The table for typed object lazy array buffers.");
 
-    ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("object-metadata"),
-        cStats.objectMetadataTable,
-        "The table used by debugging tools for tracking object metadata");
-
     ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("cross-compartment-wrapper-table"),
         cStats.crossCompartmentWrappersTable,
         "The cross-compartment wrapper table.");
 
     ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("regexp-compartment"),
         cStats.regexpCompartment,
         "The regexp compartment and regexp data.");