Bug 1232417 - Use a Variant to represent the CrossCompartmentWrapperMap key; r=jonco
authorTerrence Cole <terrence@mozilla.com>
Mon, 14 Dec 2015 13:28:26 -0800
changeset 298186 0fcaad4111c43ef3bbab08f66f5c3511a2af67a5
parent 298185 ca34a07172f67508bd75964c0b617e8a344d8397
child 298187 2b227a22287677ac7af098166a632e768e70d022
push id30273
push userkwierso@gmail.com
push dateFri, 20 May 2016 21:08:12 +0000
treeherdermozilla-central@c403ac05b8f4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1232417
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1232417 - Use a Variant to represent the CrossCompartmentWrapperMap key; r=jonco
js/src/gc/Tracer.cpp
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsfriendapi.cpp
js/src/jsgc.cpp
js/src/proxy/CrossCompartmentWrapper.cpp
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
--- a/js/src/gc/Tracer.cpp
+++ b/js/src/gc/Tracer.cpp
@@ -121,62 +121,48 @@ struct TraceChildrenFunctor {
 void
 js::TraceChildren(JSTracer* trc, void* thing, JS::TraceKind kind)
 {
     MOZ_ASSERT(thing);
     TraceChildrenFunctor f;
     DispatchTraceKindTyped(f, kind, trc, thing);
 }
 
+namespace {
+struct TraceIncomingFunctor {
+    JSTracer* trc_;
+    const JS::CompartmentSet& compartments_;
+    TraceIncomingFunctor(JSTracer* trc, const JS::CompartmentSet& compartments)
+      : trc_(trc), compartments_(compartments)
+    {}
+    using ReturnType = void;
+    template <typename T>
+    ReturnType operator()(T tp) {
+        if (!compartments_.has((*tp)->compartment()))
+            return;
+        TraceManuallyBarrieredEdge(trc_, tp, "cross-compartment wrapper");
+    }
+    // StringWrappers are just used to avoid copying strings
+    // across zones multiple times, and don't hold a strong
+    // reference.
+    ReturnType operator()(JSString** tp) {}
+};
+} // namespace (anonymous)
+
 JS_PUBLIC_API(void)
 JS::TraceIncomingCCWs(JSTracer* trc, const JS::CompartmentSet& compartments)
 {
     for (js::CompartmentsIter comp(trc->runtime(), SkipAtoms); !comp.done(); comp.next()) {
         if (compartments.has(comp))
             continue;
 
         for (JSCompartment::WrapperEnum e(comp); !e.empty(); e.popFront()) {
-            const CrossCompartmentKey& key = e.front().key();
-            JSObject* obj;
-            JSScript* script;
-
-            switch (key.kind) {
-              case CrossCompartmentKey::StringWrapper:
-                // StringWrappers are just used to avoid copying strings
-                // across zones multiple times, and don't hold a strong
-                // reference.
-                continue;
-
-              case CrossCompartmentKey::ObjectWrapper:
-              case CrossCompartmentKey::DebuggerObject:
-              case CrossCompartmentKey::DebuggerSource:
-              case CrossCompartmentKey::DebuggerEnvironment:
-              case CrossCompartmentKey::DebuggerWasmScript:
-              case CrossCompartmentKey::DebuggerWasmSource:
-                obj = static_cast<JSObject*>(key.wrapped);
-                // Ignore CCWs whose wrapped value doesn't live in our given
-                // set of zones.
-                if (!compartments.has(obj->compartment()))
-                    continue;
-
-                TraceManuallyBarrieredEdge(trc, &obj, "cross-compartment wrapper");
-                MOZ_ASSERT(obj == key.wrapped);
-                break;
-
-              case CrossCompartmentKey::DebuggerScript:
-                script = static_cast<JSScript*>(key.wrapped);
-                // Ignore CCWs whose wrapped value doesn't live in our given
-                // set of compartments.
-                if (!compartments.has(script->compartment()))
-                    continue;
-
-                TraceManuallyBarrieredEdge(trc, &script, "cross-compartment wrapper");
-                MOZ_ASSERT(script == key.wrapped);
-                break;
-            }
+            mozilla::DebugOnly<const CrossCompartmentKey> prior = e.front().key();
+            e.front().mutableKey().applyToWrapped(TraceIncomingFunctor(trc, compartments));
+            MOZ_ASSERT(e.front().key() == prior);
         }
     }
 }
 
 
 /*** Cycle Collector Helpers **********************************************************************/
 
 // This function is used by the Cycle Collector (CC) to trace through -- or in
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -214,83 +214,92 @@ class WrapperMapRef : public BufferableR
 {
     WrapperMap* map;
     CrossCompartmentKey key;
 
   public:
     WrapperMapRef(WrapperMap* map, const CrossCompartmentKey& key)
       : map(map), key(key) {}
 
+    struct TraceFunctor {
+        JSTracer* trc_;
+        const char* name_;
+        TraceFunctor(JSTracer *trc, const char* name) : trc_(trc), name_(name) {}
+
+        using ReturnType = void;
+        template <class T> void operator()(T* t) { TraceManuallyBarrieredEdge(trc_, t, name_); }
+    };
     void trace(JSTracer* trc) override {
         CrossCompartmentKey prior = key;
-        if (key.debugger)
-            TraceManuallyBarrieredEdge(trc, &key.debugger, "CCW debugger");
-        if (key.kind == CrossCompartmentKey::ObjectWrapper ||
-            key.kind == CrossCompartmentKey::DebuggerObject ||
-            key.kind == CrossCompartmentKey::DebuggerEnvironment ||
-            key.kind == CrossCompartmentKey::DebuggerSource ||
-            key.kind == CrossCompartmentKey::DebuggerWasmScript ||
-            key.kind == CrossCompartmentKey::DebuggerWasmSource)
-        {
-            MOZ_ASSERT(IsInsideNursery(key.wrapped) ||
-                       key.wrapped->asTenured().getTraceKind() == JS::TraceKind::Object);
-            TraceManuallyBarrieredEdge(trc, reinterpret_cast<JSObject**>(&key.wrapped),
-                                       "CCW wrapped object");
-        }
-        if (key.debugger == prior.debugger && key.wrapped == prior.wrapped)
+        key.applyToWrapped(TraceFunctor(trc, "ccw wrapped"));
+        key.applyToDebugger(TraceFunctor(trc, "ccw debugger"));
+        if (key == prior)
             return;
 
         /* Look for the original entry, which might have been removed. */
         WrapperMap::Ptr p = map->lookup(prior);
         if (!p)
             return;
 
         /* Rekey the entry. */
         map->rekeyAs(prior, key, key);
     }
 };
 
 #ifdef JSGC_HASH_TABLE_CHECKS
+namespace {
+struct CheckGCThingAfterMovingGCFunctor {
+    using ReturnType = void;
+    template <class T> void operator()(T* t) { CheckGCThingAfterMovingGC(*t); }
+};
+} // namespace (anonymous)
+
 void
 JSCompartment::checkWrapperMapAfterMovingGC()
 {
     /*
      * Assert that the postbarriers have worked and that nothing is left in
      * wrapperMap that points into the nursery, and that the hash table entries
      * are discoverable.
      */
     for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) {
-        CrossCompartmentKey key = e.front().key();
-        CheckGCThingAfterMovingGC(key.debugger);
-        CheckGCThingAfterMovingGC(key.wrapped);
-        CheckGCThingAfterMovingGC(
-                static_cast<Cell*>(e.front().value().unbarrieredGet().toGCThing()));
+        e.front().mutableKey().applyToWrapped(CheckGCThingAfterMovingGCFunctor());
+        e.front().mutableKey().applyToDebugger(CheckGCThingAfterMovingGCFunctor());
 
-        WrapperMap::Ptr ptr = crossCompartmentWrappers.lookup(key);
+        WrapperMap::Ptr ptr = crossCompartmentWrappers.lookup(e.front().key());
         MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front());
     }
 }
 #endif
 
+namespace {
+struct IsInsideNurseryFunctor {
+    using ReturnType = bool;
+    template <class T> bool operator()(T tp) { return IsInsideNursery(*tp); }
+};
+} // namespace (anonymous)
+
 bool
-JSCompartment::putWrapper(JSContext* cx, const CrossCompartmentKey& wrapped, const js::Value& wrapper)
+JSCompartment::putWrapper(JSContext* cx, const CrossCompartmentKey& wrapped,
+                          const js::Value& wrapper)
 {
-    MOZ_ASSERT(wrapped.wrapped);
-    MOZ_ASSERT_IF(wrapped.kind == CrossCompartmentKey::StringWrapper, wrapper.isString());
-    MOZ_ASSERT_IF(wrapped.kind != CrossCompartmentKey::StringWrapper, wrapper.isObject());
+    MOZ_ASSERT(wrapped.is<JSString*>() == wrapper.isString());
+    MOZ_ASSERT_IF(!wrapped.is<JSString*>(), wrapper.isObject());
 
     /* There's no point allocating wrappers in the nursery since we will tenure them anyway. */
     MOZ_ASSERT(!IsInsideNursery(static_cast<gc::Cell*>(wrapper.toGCThing())));
 
     if (!crossCompartmentWrappers.put(wrapped, ReadBarriered<Value>(wrapper))) {
         ReportOutOfMemory(cx);
         return false;
     }
 
-    if (IsInsideNursery(wrapped.wrapped) || IsInsideNursery(wrapped.debugger)) {
+    if (const_cast<CrossCompartmentKey&>(wrapped).applyToWrapped(IsInsideNurseryFunctor()) ||
+        const_cast<CrossCompartmentKey&>(wrapped).applyToDebugger(IsInsideNurseryFunctor()))
+    {
         WrapperMapRef ref(&crossCompartmentWrappers, wrapped);
         cx->runtime()->gc.storeBuffer.putGeneric(ref);
     }
 
     return true;
 }
 
 static JSString*
@@ -560,17 +569,17 @@ JSCompartment::getNonSyntacticLexicalSco
 void
 JSCompartment::traceOutgoingCrossCompartmentWrappers(JSTracer* trc)
 {
     MOZ_ASSERT(trc->runtime()->isHeapMajorCollecting());
     MOZ_ASSERT(!zone()->isCollecting() || trc->runtime()->gc.isHeapCompacting());
 
     for (WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) {
         Value v = e.front().value().unbarrieredGet();
-        if (e.front().key().kind == CrossCompartmentKey::ObjectWrapper) {
+        if (e.front().key().is<JSObject*>()) {
             ProxyObject* wrapper = &v.toObject().as<ProxyObject>();
 
             /*
              * We have a cross-compartment wrapper. Its private pointer may
              * point into the compartment being collected, so we should mark it.
              */
             TraceEdge(trc, wrapper->slotOfPrivate(), "cross-compartment wrapper");
         }
@@ -753,46 +762,42 @@ JSCompartment::sweepNativeIterators()
  * markCrossCompartmentWrappers.
  */
 void
 JSCompartment::sweepCrossCompartmentWrappers()
 {
     crossCompartmentWrappers.sweep();
 }
 
+namespace {
+struct TraceRootFunctor {
+    JSTracer* trc;
+    const char* name;
+    TraceRootFunctor(JSTracer* trc, const char* name) : trc(trc), name(name) {}
+    using ReturnType = void;
+    template <class T> ReturnType operator()(T* t) { return TraceRoot(trc, t, name); }
+};
+struct NeedsSweepUnbarrieredFunctor {
+    using ReturnType = bool;
+    template <class T> bool operator()(T* t) const { return IsAboutToBeFinalizedUnbarriered(t); }
+};
+} // namespace (anonymous)
+
+void
+CrossCompartmentKey::trace(JSTracer* trc)
+{
+    applyToWrapped(TraceRootFunctor(trc, "CrossCompartmentKey::wrapped"));
+    applyToDebugger(TraceRootFunctor(trc, "CrossCompartmentKey::debugger"));
+}
+
 bool
 CrossCompartmentKey::needsSweep()
 {
-    bool keyDying;
-    switch (kind) {
-      case CrossCompartmentKey::ObjectWrapper:
-      case CrossCompartmentKey::DebuggerObject:
-      case CrossCompartmentKey::DebuggerEnvironment:
-      case CrossCompartmentKey::DebuggerSource:
-      case CrossCompartmentKey::DebuggerWasmScript:
-      case CrossCompartmentKey::DebuggerWasmSource:
-          MOZ_ASSERT(IsInsideNursery(wrapped) ||
-                     wrapped->asTenured().getTraceKind() == JS::TraceKind::Object);
-          keyDying = IsAboutToBeFinalizedUnbarriered(reinterpret_cast<JSObject**>(&wrapped));
-          break;
-      case CrossCompartmentKey::StringWrapper:
-          MOZ_ASSERT(wrapped->asTenured().getTraceKind() == JS::TraceKind::String);
-          keyDying = IsAboutToBeFinalizedUnbarriered(reinterpret_cast<JSString**>(&wrapped));
-          break;
-      case CrossCompartmentKey::DebuggerScript:
-          MOZ_ASSERT(wrapped->asTenured().getTraceKind() == JS::TraceKind::Script);
-          keyDying = IsAboutToBeFinalizedUnbarriered(reinterpret_cast<JSScript**>(&wrapped));
-          break;
-      default:
-          MOZ_CRASH("Unknown key kind");
-    }
-
-    bool dbgDying = debugger && IsAboutToBeFinalizedUnbarriered(&debugger);
-    MOZ_ASSERT_IF(keyDying || dbgDying, kind != CrossCompartmentKey::StringWrapper);
-    return keyDying || dbgDying;
+    return applyToWrapped(NeedsSweepUnbarrieredFunctor()) ||
+           applyToDebugger(NeedsSweepUnbarrieredFunctor());
 }
 
 void
 JSCompartment::sweepTemplateObjects()
 {
     if (mappedArgumentsTemplate_ && IsAboutToBeFinalized(&mappedArgumentsTemplate_))
         mappedArgumentsTemplate_.set(nullptr);
 
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jscompartment_h
 #define jscompartment_h
 
 #include "mozilla/LinkedList.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/Tuple.h"
 #include "mozilla/Variant.h"
 #include "mozilla/XorShift128PlusRNG.h"
 
 #include "builtin/RegExp.h"
 #include "gc/Barrier.h"
 #include "gc/Zone.h"
 #include "vm/GlobalObject.h"
 #include "vm/PIC.h"
@@ -30,18 +31,20 @@ class JitCompartment;
 namespace gc {
 template<class Node> class ComponentFinder;
 } // namespace gc
 
 namespace wasm {
 class Module;
 } // namespace wasm
 
+class ClonedBlockObject;
+class ScriptSourceObject;
+class WasmModuleObject;
 struct NativeIterator;
-class ClonedBlockObject;
 
 /*
  * A single-entry cache for some base-10 double-to-string conversions. This
  * helps date-format-xparb.js.  It also avoids skewing the results for
  * v8-splay.js when measured by the SunSpider harness, where the splay tree
  * initialization (which includes many repeated double-to-string conversions)
  * is erroneously included in the measurement; see bug 562553.
  */
@@ -66,85 +69,127 @@ class DtoaCache {
 
 #ifdef JSGC_HASH_TABLE_CHECKS
     void checkCacheAfterMovingGC() { MOZ_ASSERT(!s || !IsForwarded(s)); }
 #endif
 };
 
 struct CrossCompartmentKey
 {
-    enum Kind {
-        ObjectWrapper,
-        StringWrapper,
-        DebuggerScript,
-        DebuggerSource,
-        DebuggerObject,
-        DebuggerEnvironment,
-        DebuggerWasmScript,
-        DebuggerWasmSource
-    };
+  public:
+    enum DebuggerObjectKind : uint8_t { DebuggerSource, DebuggerEnvironment, DebuggerObject,
+                                        DebuggerWasmScript, DebuggerWasmSource };
+    using DebuggerAndObject = mozilla::Tuple<NativeObject*, JSObject*, DebuggerObjectKind>;
+    using DebuggerAndScript = mozilla::Tuple<NativeObject*, JSScript*>;
+    using WrappedType = mozilla::Variant<
+        JSObject*,
+        JSString*,
+        DebuggerAndScript,
+        DebuggerAndObject>;
 
-    Kind kind;
-    JSObject* debugger;
-    js::gc::Cell* wrapped;
-
-    explicit CrossCompartmentKey(JSObject* wrapped)
-      : kind(ObjectWrapper), debugger(nullptr), wrapped(wrapped)
-    {
-        MOZ_RELEASE_ASSERT(wrapped);
-    }
-    explicit CrossCompartmentKey(JSString* wrapped)
-      : kind(StringWrapper), debugger(nullptr), wrapped(wrapped)
+    explicit CrossCompartmentKey(JSObject* obj) : wrapped(obj) { MOZ_RELEASE_ASSERT(obj); }
+    explicit CrossCompartmentKey(JSString* str) : wrapped(str) { MOZ_RELEASE_ASSERT(str); }
+    explicit CrossCompartmentKey(JS::Value v)
+      : wrapped(v.isString() ? WrappedType(v.toString()) : WrappedType(&v.toObject()))
+    {}
+    explicit CrossCompartmentKey(NativeObject* debugger, JSObject* obj, DebuggerObjectKind kind)
+      : wrapped(DebuggerAndObject(debugger, obj, kind))
     {
-        MOZ_RELEASE_ASSERT(wrapped);
+        MOZ_RELEASE_ASSERT(debugger);
+        MOZ_RELEASE_ASSERT(obj);
     }
-    explicit CrossCompartmentKey(Value wrappedArg)
-      : kind(wrappedArg.isString() ? StringWrapper : ObjectWrapper),
-        debugger(nullptr),
-        wrapped((js::gc::Cell*)wrappedArg.toGCThing())
+    explicit CrossCompartmentKey(NativeObject* debugger, JSScript* script)
+      : wrapped(DebuggerAndScript(debugger, script))
     {
-        MOZ_RELEASE_ASSERT(wrappedArg.isString() || wrappedArg.isObject());
-        MOZ_RELEASE_ASSERT(wrapped);
+        MOZ_RELEASE_ASSERT(debugger);
+        MOZ_RELEASE_ASSERT(script);
     }
-    explicit CrossCompartmentKey(const RootedValue& wrappedArg)
-      : kind(wrappedArg.get().isString() ? StringWrapper : ObjectWrapper),
-        debugger(nullptr),
-        wrapped((js::gc::Cell*)wrappedArg.get().toGCThing())
-    {
-        MOZ_RELEASE_ASSERT(wrappedArg.isString() || wrappedArg.isObject());
-        MOZ_RELEASE_ASSERT(wrapped);
-    }
-    CrossCompartmentKey(Kind kind, JSObject* dbg, js::gc::Cell* wrapped)
-      : kind(kind), debugger(dbg), wrapped(wrapped)
-    {
-        MOZ_RELEASE_ASSERT(dbg);
-        MOZ_RELEASE_ASSERT(wrapped);
+
+    bool operator==(const CrossCompartmentKey& other) const { return wrapped == other.wrapped; }
+    bool operator!=(const CrossCompartmentKey& other) const { return wrapped != other.wrapped; }
+
+    template <typename T> bool is() const { return wrapped.is<T>(); }
+    template <typename T> const T& as() const { return wrapped.as<T>(); }
+
+    template <typename F>
+    auto applyToWrapped(F f) -> typename F::ReturnType {
+        struct WrappedMatcher {
+            using ReturnType = typename F::ReturnType;
+            F f_;
+            explicit WrappedMatcher(F f) : f_(f) {}
+            ReturnType match(JSObject*& obj) { return f_(&obj); }
+            ReturnType match(JSString*& str) { return f_(&str); }
+            ReturnType match(DebuggerAndScript& tpl) { return f_(&mozilla::Get<1>(tpl)); }
+            ReturnType match(DebuggerAndObject& tpl) { return f_(&mozilla::Get<1>(tpl)); }
+        } matcher(f);
+        return wrapped.match(matcher);
     }
 
+    template <typename F>
+    auto applyToDebugger(F f) -> typename F::ReturnType {
+        struct DebuggerMatcher {
+            using ReturnType = typename F::ReturnType;
+            F f_;
+            explicit DebuggerMatcher(F f) : f_(f) {}
+            ReturnType match(JSObject*& obj) { return ReturnType(); }
+            ReturnType match(JSString*& str) { return ReturnType(); }
+            ReturnType match(DebuggerAndScript& tpl) { return f_(&mozilla::Get<0>(tpl)); }
+            ReturnType match(DebuggerAndObject& tpl) { return f_(&mozilla::Get<0>(tpl)); }
+        } matcher(f);
+        return wrapped.match(matcher);
+    }
+
+    // Valid for JSObject* and Debugger keys. Crashes immediately if used on a
+    // JSString* key.
+    JSCompartment* compartment() {
+        struct GetCompartmentFunctor {
+            using ReturnType = JSCompartment*;
+            ReturnType operator()(JSObject** tp) const { return (*tp)->compartment(); }
+            ReturnType operator()(JSScript** tp) const { return (*tp)->compartment(); }
+            ReturnType operator()(JSString** tp) const {
+                MOZ_CRASH("invalid ccw key"); return nullptr;
+            }
+        };
+        return applyToWrapped(GetCompartmentFunctor());
+    }
+
+    struct Hasher : public DefaultHasher<CrossCompartmentKey>
+    {
+        struct HashFunctor {
+            using ReturnType = HashNumber;
+            ReturnType match(JSObject* obj) { return DefaultHasher<JSObject*>::hash(obj); }
+            ReturnType match(JSString* str) { return DefaultHasher<JSString*>::hash(str); }
+            ReturnType match(const DebuggerAndScript& tpl) {
+                return DefaultHasher<NativeObject*>::hash(mozilla::Get<0>(tpl)) ^
+                       DefaultHasher<JSScript*>::hash(mozilla::Get<1>(tpl));
+            }
+            ReturnType match(const DebuggerAndObject& tpl) {
+                return DefaultHasher<NativeObject*>::hash(mozilla::Get<0>(tpl)) ^
+                       DefaultHasher<JSObject*>::hash(mozilla::Get<1>(tpl)) ^
+                       (mozilla::Get<2>(tpl) << 5);
+            }
+        };
+        static HashNumber hash(const CrossCompartmentKey& key) {
+            return key.wrapped.match(HashFunctor());
+        }
+
+        static bool match(const CrossCompartmentKey& l, const CrossCompartmentKey& k) {
+            return l.wrapped == k.wrapped;
+        }
+    };
+    void trace(JSTracer* trc);
     bool needsSweep();
 
   private:
     CrossCompartmentKey() = delete;
-};
-
-struct WrapperHasher : public DefaultHasher<CrossCompartmentKey>
-{
-    static HashNumber hash(const CrossCompartmentKey& key) {
-        static_assert(sizeof(HashNumber) == sizeof(uint32_t),
-                      "subsequent code assumes a four-byte hash");
-        return uint32_t(uintptr_t(key.wrapped)) | uint32_t(key.kind);
-    }
-
-    static bool match(const CrossCompartmentKey& l, const CrossCompartmentKey& k) {
-        return l.kind == k.kind && l.debugger == k.debugger && l.wrapped == k.wrapped;
-    }
+    WrappedType wrapped;
 };
 
 using WrapperMap = GCRekeyableHashMap<CrossCompartmentKey, ReadBarrieredValue,
-                                      WrapperHasher, SystemAllocPolicy>;
+                                      CrossCompartmentKey::Hasher, SystemAllocPolicy>;
 
 // We must ensure that all newly allocated JSObjects get their metadata
 // set. However, metadata builders may require the new object be in a sane
 // state (eg, have its reserved slots initialized so they can get the
 // sizeOfExcludingThis of the object). Therefore, for objects of certain
 // JSClasses (those marked with JSCLASS_DELAY_METADATA_BUILDER), it is not safe
 // for the allocation paths to call the object metadata builder
 // immediately. Instead, the JSClass-specific "constructor" C++ function up the
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -592,25 +592,39 @@ js::ZoneGlobalsAreAllGray(JS::Zone* zone
     for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
         JSObject* obj = comp->maybeGlobal();
         if (!obj || !JS::ObjectIsMarkedGray(obj))
             return false;
     }
     return true;
 }
 
+namespace {
+struct VisitGrayCallbackFunctor {
+    GCThingCallback callback_;
+    void* closure_;
+    VisitGrayCallbackFunctor(GCThingCallback callback, void* closure)
+      : callback_(callback), closure_(closure)
+    {}
+
+    using ReturnType = void;
+    template <class T>
+    ReturnType operator()(T tp) const {
+        if ((*tp)->isTenured() && (*tp)->asTenured().isMarked(gc::GRAY))
+            callback_(closure_, JS::GCCellPtr(*tp));
+    }
+};
+} // namespace (anonymous)
+
 JS_FRIEND_API(void)
 js::VisitGrayWrapperTargets(Zone* zone, GCThingCallback callback, void* closure)
 {
     for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next()) {
-        for (JSCompartment::WrapperEnum e(comp); !e.empty(); e.popFront()) {
-            gc::Cell* thing = e.front().key().wrapped;
-            if (thing->isTenured() && thing->asTenured().isMarked(gc::GRAY))
-                callback(closure, JS::GCCellPtr(thing, thing->asTenured().getTraceKind()));
-        }
+        for (JSCompartment::WrapperEnum e(comp); !e.empty(); e.popFront())
+            e.front().mutableKey().applyToWrapped(VisitGrayCallbackFunctor(callback, closure));
     }
 }
 
 JS_FRIEND_API(JSObject*)
 js::GetWeakmapKeyDelegate(JSObject* key)
 {
     if (JSWeakmapKeyDelegateOp op = key->getClass()->extWeakmapKeyDelegateOp())
         return op(key);
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -234,16 +234,17 @@
 
 #include "vm/Stack-inl.h"
 #include "vm/String-inl.h"
 
 using namespace js;
 using namespace js::gc;
 
 using mozilla::ArrayLength;
+using mozilla::Get;
 using mozilla::Maybe;
 using mozilla::Swap;
 
 using JS::AutoGCRooter;
 
 /* Perform a Full GC every 20 seconds if MaybeGC is called */
 static const uint64_t GC_IDLE_FULL_SPAN = 20 * 1000 * 1000;
 
@@ -3902,56 +3903,68 @@ class CompartmentCheckTracer : public JS
     {}
 
     Cell* src;
     JS::TraceKind srcKind;
     Zone* zone;
     JSCompartment* compartment;
 };
 
+namespace {
+struct IsDestComparatorFunctor {
+    JS::GCCellPtr dst_;
+    IsDestComparatorFunctor(JS::GCCellPtr dst) : dst_(dst) {}
+
+    using ReturnType = bool;
+    template <typename T> bool operator()(T* t) { return (*t) == dst_.asCell(); }
+};
+} // namespace (anonymous)
+
 static bool
-InCrossCompartmentMap(JSObject* src, Cell* dst, JS::TraceKind dstKind)
+InCrossCompartmentMap(JSObject* src, JS::GCCellPtr dst)
 {
     JSCompartment* srccomp = src->compartment();
 
-    if (dstKind == JS::TraceKind::Object) {
-        Value key = ObjectValue(*static_cast<JSObject*>(dst));
+    if (dst.is<JSObject>()) {
+        Value key = ObjectValue(dst.as<JSObject>());
         if (WrapperMap::Ptr p = srccomp->lookupWrapper(key)) {
             if (*p->value().unsafeGet() == ObjectValue(*src))
                 return true;
         }
     }
 
     /*
      * If the cross-compartment edge is caused by the debugger, then we don't
      * know the right hashtable key, so we have to iterate.
      */
     for (JSCompartment::WrapperEnum e(srccomp); !e.empty(); e.popFront()) {
-        if (e.front().key().wrapped == dst && ToMarkable(e.front().value()) == src)
+        if (e.front().mutableKey().applyToWrapped(IsDestComparatorFunctor(dst)) &&
+            ToMarkable(e.front().value()) == src)
+        {
             return true;
+        }
     }
 
     return false;
 }
 
 struct MaybeCompartmentFunctor {
     template <typename T> JSCompartment* operator()(T* t) { return t->maybeCompartment(); }
 };
 
 void
 CompartmentCheckTracer::onChild(const JS::GCCellPtr& thing)
 {
-    TenuredCell* tenured = TenuredCell::fromPointer(thing.asCell());
-
     JSCompartment* comp = DispatchTyped(MaybeCompartmentFunctor(), thing);
     if (comp && compartment) {
         MOZ_ASSERT(comp == compartment || runtime()->isAtomsCompartment(comp) ||
                    (srcKind == JS::TraceKind::Object &&
-                    InCrossCompartmentMap(static_cast<JSObject*>(src), tenured, thing.kind())));
+                    InCrossCompartmentMap(static_cast<JSObject*>(src), thing)));
     } else {
+        TenuredCell* tenured = TenuredCell::fromPointer(thing.asCell());
         MOZ_ASSERT(tenured->zone() == zone || tenured->zone()->isAtomsZone());
     }
 }
 
 void
 GCRuntime::checkForCompartmentMismatches()
 {
     if (disableStrictProxyCheckingCount)
@@ -4176,44 +4189,30 @@ GCRuntime::markCompartments()
      * compartments, live or dead, and operates on their objects. See bug 803376
      * for details on this problem. To avoid the problem, we try to avoid
      * allocation and read barriers during JS_TransplantObject and the like.
      */
 
     /* Set the maybeAlive flag based on cross-compartment edges. */
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) {
-            const CrossCompartmentKey& key = e.front().key();
-            JSCompartment* dest;
-            switch (key.kind) {
-              case CrossCompartmentKey::ObjectWrapper:
-              case CrossCompartmentKey::DebuggerObject:
-              case CrossCompartmentKey::DebuggerSource:
-              case CrossCompartmentKey::DebuggerEnvironment:
-              case CrossCompartmentKey::DebuggerWasmScript:
-              case CrossCompartmentKey::DebuggerWasmSource:
-                dest = static_cast<JSObject*>(key.wrapped)->compartment();
-                break;
-              case CrossCompartmentKey::DebuggerScript:
-                dest = static_cast<JSScript*>(key.wrapped)->compartment();
-                break;
-              default:
-                dest = nullptr;
-                break;
-            }
+            if (e.front().key().is<JSString*>())
+                continue;
+            JSCompartment* dest = e.front().mutableKey().compartment();
             if (dest)
                 dest->maybeAlive = true;
         }
     }
 
     /*
      * For black roots, code in gc/Marking.cpp will already have set maybeAlive
      * during MarkRuntime.
      */
 
+    /* Propogate maybeAlive to scheduleForDestruction. */
     for (GCCompartmentsIter c(rt); !c.done(); c.next()) {
         if (!c->maybeAlive && !rt->isAtomsCompartment(c))
             c->scheduledForDestruction = true;
     }
 }
 
 template <class ZoneIterT>
 void
@@ -4585,17 +4584,17 @@ DropStringWrappers(JSRuntime* rt)
 {
     /*
      * String "wrappers" are dropped on GC because their presence would require
      * us to sweep the wrappers in all compartments every time we sweep a
      * compartment group.
      */
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) {
-            if (e.front().key().kind == CrossCompartmentKey::StringWrapper)
+            if (e.front().key().is<JSString*>())
                 e.removeFront();
         }
     }
 }
 
 /*
  * Group zones that must be swept at the same time.
  *
@@ -4605,51 +4604,56 @@ DropStringWrappers(JSRuntime* rt)
  * marked. However, if we had already swept that object, we would be in trouble.
  *
  * If we consider these dependencies as a graph, then all the compartments in
  * any strongly-connected component of this graph must be swept in the same
  * slice.
  *
  * Tarjan's algorithm is used to calculate the components.
  */
+namespace {
+struct AddOutgoingEdgeFunctor {
+    bool needsEdge_;
+    ComponentFinder<JS::Zone>& finder_;
+
+    AddOutgoingEdgeFunctor(bool needsEdge, ComponentFinder<JS::Zone>& finder)
+      : needsEdge_(needsEdge), finder_(finder)
+    {}
+
+    using ReturnType = void;
+    template <typename T>
+    ReturnType operator()(T tp) {
+        TenuredCell& other = (*tp)->asTenured();
+
+        /*
+         * Add edge to wrapped object compartment if wrapped object is not
+         * marked black to indicate that wrapper compartment not be swept
+         * after wrapped compartment.
+         */
+        if (needsEdge_) {
+            JS::Zone* zone = other.zone();
+            if (zone->isGCMarking())
+                finder_.addEdgeTo(zone);
+        }
+    }
+};
+} // namespace (anonymous)
 
 void
 JSCompartment::findOutgoingEdges(ComponentFinder<JS::Zone>& finder)
 {
     for (js::WrapperMap::Enum e(crossCompartmentWrappers); !e.empty(); e.popFront()) {
-        CrossCompartmentKey::Kind kind = e.front().key().kind;
-        MOZ_ASSERT(kind != CrossCompartmentKey::StringWrapper);
-        TenuredCell& other = e.front().key().wrapped->asTenured();
-        if (kind == CrossCompartmentKey::ObjectWrapper) {
-            /*
-             * Add edge to wrapped object compartment if wrapped object is not
-             * marked black to indicate that wrapper compartment not be swept
-             * after wrapped compartment.
-             */
-            if (!other.isMarked(BLACK) || other.isMarked(GRAY)) {
-                JS::Zone* w = other.zone();
-                if (w->isGCMarking())
-                    finder.addEdgeTo(w);
-            }
-        } else {
-            MOZ_ASSERT(kind == CrossCompartmentKey::DebuggerScript ||
-                       kind == CrossCompartmentKey::DebuggerSource ||
-                       kind == CrossCompartmentKey::DebuggerObject ||
-                       kind == CrossCompartmentKey::DebuggerEnvironment ||
-                       kind == CrossCompartmentKey::DebuggerWasmScript ||
-                       kind == CrossCompartmentKey::DebuggerWasmSource);
-            /*
-             * Add edge for debugger object wrappers, to ensure (in conjuction
-             * with call to Debugger::findCompartmentEdges below) that debugger
-             * and debuggee objects are always swept in the same group.
-             */
-            JS::Zone* w = other.zone();
-            if (w->isGCMarking())
-                finder.addEdgeTo(w);
+        CrossCompartmentKey& key = e.front().mutableKey();
+        MOZ_ASSERT(!key.is<JSString*>());
+        bool needsEdge = true;
+        if (key.is<JSObject*>()) {
+            TenuredCell& other = key.as<JSObject*>()->asTenured();
+            needsEdge = !other.isMarked(BLACK) || other.isMarked(GRAY);
         }
+        key.applyToWrapped(AddOutgoingEdgeFunctor(needsEdge, finder));
     }
 }
 
 void
 Zone::findOutgoingEdges(ComponentFinder<JS::Zone>& finder)
 {
     /*
      * Any compartment may have a pointer to an atom in the atoms
@@ -4798,16 +4802,30 @@ ProxyObject::grayLinkExtraSlot(JSObject*
 static void
 AssertNotOnGrayList(JSObject* obj)
 {
     MOZ_ASSERT_IF(IsGrayListObject(obj),
                   GetProxyExtra(obj, ProxyObject::grayLinkExtraSlot(obj)).isUndefined());
 }
 #endif
 
+static void
+AssertNoWrappersInGrayList(JSRuntime* rt)
+{
+#ifdef DEBUG
+    for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
+        MOZ_ASSERT(!c->gcIncomingGrayPointers);
+        for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) {
+            if (!e.front().key().is<JSString*>())
+                AssertNotOnGrayList(&e.front().value().unbarrieredGet().toObject());
+        }
+    }
+#endif
+}
+
 static JSObject*
 CrossCompartmentPointerReferent(JSObject* obj)
 {
     MOZ_ASSERT(IsGrayListObject(obj));
     return &obj->as<ProxyObject>().private_().toObject();
 }
 
 static JSObject*
@@ -5402,26 +5420,17 @@ GCRuntime::beginSweepPhase(bool destroyi
 
     gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP);
 
     sweepOnBackgroundThread =
         !destroyingRuntime && !TraceEnabled() && CanUseExtraThreads();
 
     releaseObservedTypes = shouldReleaseObservedTypes();
 
-#ifdef DEBUG
-    for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
-        MOZ_ASSERT(!c->gcIncomingGrayPointers);
-        for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) {
-            if (e.front().key().kind != CrossCompartmentKey::StringWrapper)
-                AssertNotOnGrayList(&e.front().value().unbarrieredGet().toObject());
-        }
-    }
-#endif
-
+    AssertNoWrappersInGrayList(rt);
     DropStringWrappers(rt);
 
     findZoneGroups();
     endMarkingZoneGroup();
     beginSweepingZoneGroup();
 }
 
 bool
@@ -5691,26 +5700,19 @@ GCRuntime::endSweepPhase(bool destroying
 #ifdef DEBUG
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         for (auto i : AllAllocKinds()) {
             MOZ_ASSERT_IF(!IsBackgroundFinalized(i) ||
                           !sweepOnBackgroundThread,
                           !zone->arenas.arenaListsToSweep[i]);
         }
     }
-
-    for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
-        MOZ_ASSERT(!c->gcIncomingGrayPointers);
-
-        for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) {
-            if (e.front().key().kind != CrossCompartmentKey::StringWrapper)
-                AssertNotOnGrayList(&e.front().value().unbarrieredGet().toObject());
-        }
-    }
 #endif
+
+    AssertNoWrappersInGrayList(rt);
 }
 
 void
 GCRuntime::beginCompactPhase()
 {
     MOZ_ASSERT(!isBackgroundSweeping());
 
     gcstats::AutoPhase ap(stats, gcstats::PHASE_COMPACT);
--- a/js/src/proxy/CrossCompartmentWrapper.cpp
+++ b/js/src/proxy/CrossCompartmentWrapper.cpp
@@ -482,17 +482,17 @@ js::NukeCrossCompartmentWrappers(JSConte
         if (!sourceFilter.match(c))
             continue;
 
         // Iterate the wrappers looking for anything interesting.
         for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) {
             // Some cross-compartment wrappers are for strings.  We're not
             // interested in those.
             const CrossCompartmentKey& k = e.front().key();
-            if (k.kind != CrossCompartmentKey::ObjectWrapper)
+            if (!k.is<JSObject*>())
                 continue;
 
             AutoWrapperRooter wobj(cx, WrapperValue(e));
             JSObject* wrapped = UncheckedUnwrap(wobj);
 
             if (nukeReferencesToWindow == DontNukeWindowReferences &&
                 IsWindowProxy(wrapped))
             {
@@ -611,22 +611,22 @@ js::RecomputeWrappers(JSContext* cx, con
     for (CompartmentsIter c(cx->runtime(), SkipAtoms); !c.done(); c.next()) {
         // Filter by source compartment.
         if (!sourceFilter.match(c))
             continue;
 
         // Iterate over the wrappers, filtering appropriately.
         for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) {
             // Filter out non-objects.
-            const CrossCompartmentKey& k = e.front().key();
-            if (k.kind != CrossCompartmentKey::ObjectWrapper)
+            CrossCompartmentKey& k = e.front().mutableKey();
+            if (!k.is<JSObject*>())
                 continue;
 
             // Filter by target compartment.
-            if (!targetFilter.match(static_cast<JSObject*>(k.wrapped)->compartment()))
+            if (!targetFilter.match(k.compartment()))
                 continue;
 
             // Add it to the list.
             if (!toRecompute.append(WrapperValue(e)))
                 return false;
         }
     }
 
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -943,17 +943,17 @@ Debugger::wrapEnvironment(JSContext* cx,
         envobj->setPrivateGCThing(env);
         envobj->setReservedSlot(JSSLOT_DEBUGENV_OWNER, ObjectValue(*object));
 
         if (!p.add(cx, environments, env, envobj)) {
             NukeDebuggerWrapper(envobj);
             return false;
         }
 
-        CrossCompartmentKey key(CrossCompartmentKey::DebuggerEnvironment, object, env);
+        CrossCompartmentKey key(object, env, CrossCompartmentKey::DebuggerEnvironment);
         if (!object->compartment()->putWrapper(cx, key, ObjectValue(*envobj))) {
             NukeDebuggerWrapper(envobj);
             environments.remove(env);
             return false;
         }
 
     }
     rval.setObject(*envobj);
@@ -987,17 +987,17 @@ Debugger::wrapDebuggeeValue(JSContext* c
                 return false;
 
             if (!p.add(cx, objects, obj, dobj)) {
                 NukeDebuggerWrapper(dobj);
                 return false;
             }
 
             if (obj->compartment() != object->compartment()) {
-                CrossCompartmentKey key(CrossCompartmentKey::DebuggerObject, object, obj);
+                CrossCompartmentKey key(object, obj, CrossCompartmentKey::DebuggerObject);
                 if (!object->compartment()->putWrapper(cx, key, ObjectValue(*dobj))) {
                     NukeDebuggerWrapper(dobj);
                     objects.remove(obj);
                     ReportOutOfMemory(cx);
                     return false;
                 }
             }
 
@@ -5058,17 +5058,17 @@ Debugger::newDebuggerScript(JSContext* c
     DebuggerScriptSetPrivateMatcher matcher(scriptobj);
     referent.match(matcher);
 
     return scriptobj;
 }
 
 template <typename ReferentVariant, typename Referent, typename Map>
 JSObject*
-Debugger::wrapVariantReferent(JSContext* cx, Map& map, CrossCompartmentKey::Kind keyKind,
+Debugger::wrapVariantReferent(JSContext* cx, Map& map, Handle<CrossCompartmentKey> key,
                               Handle<ReferentVariant> referent)
 {
     assertSameCompartment(cx, object);
 
     Handle<Referent> untaggedReferent = referent.template as<Referent>();
     MOZ_ASSERT(cx->compartment() != untaggedReferent->compartment());
 
     DependentAddPtr<Map> p(cx, map, untaggedReferent);
@@ -5077,17 +5077,16 @@ Debugger::wrapVariantReferent(JSContext*
         if (!wrapper)
             return nullptr;
 
         if (!p.add(cx, map, untaggedReferent, wrapper)) {
             NukeDebuggerWrapper(wrapper);
             return nullptr;
         }
 
-        CrossCompartmentKey key(keyKind, object, untaggedReferent);
         if (!object->compartment()->putWrapper(cx, key, ObjectValue(*wrapper))) {
             NukeDebuggerWrapper(wrapper);
             map.remove(untaggedReferent);
             ReportOutOfMemory(cx);
             return nullptr;
         }
 
     }
@@ -5095,21 +5094,26 @@ Debugger::wrapVariantReferent(JSContext*
     return p->value();
 }
 
 JSObject*
 Debugger::wrapVariantReferent(JSContext* cx, Handle<DebuggerScriptReferent> referent)
 {
     JSObject* obj;
     if (referent.is<JSScript*>()) {
+        Handle<JSScript*> untaggedReferent = referent.template as<JSScript*>();
+        Rooted<CrossCompartmentKey> key(cx, CrossCompartmentKey(object, untaggedReferent));
         obj = wrapVariantReferent<DebuggerScriptReferent, JSScript*, ScriptWeakMap>(
-            cx, scripts, CrossCompartmentKey::DebuggerScript, referent);
+            cx, scripts, key, referent);
     } else {
+        Handle<WasmModuleObject*> untaggedReferent = referent.template as<WasmModuleObject*>();
+        Rooted<CrossCompartmentKey> key(cx, CrossCompartmentKey(object, untaggedReferent,
+                                        CrossCompartmentKey::DebuggerObjectKind::DebuggerWasmScript));
         obj = wrapVariantReferent<DebuggerScriptReferent, WasmModuleObject*, WasmModuleWeakMap>(
-            cx, wasmModuleScripts, CrossCompartmentKey::DebuggerWasmScript, referent);
+            cx, wasmModuleScripts, key, referent);
     }
     MOZ_ASSERT_IF(obj, GetScriptReferent(obj) == referent);
     return obj;
 }
 
 JSObject*
 Debugger::wrapScript(JSContext* cx, HandleScript script)
 {
@@ -6405,21 +6409,27 @@ Debugger::newDebuggerSource(JSContext* c
     return sourceobj;
 }
 
 JSObject*
 Debugger::wrapVariantReferent(JSContext* cx, Handle<DebuggerSourceReferent> referent)
 {
     JSObject* obj;
     if (referent.is<ScriptSourceObject*>()) {
+        Handle<ScriptSourceObject*> untaggedReferent = referent.template as<ScriptSourceObject*>();
+        Rooted<CrossCompartmentKey> key(cx, CrossCompartmentKey(object, untaggedReferent,
+                                    CrossCompartmentKey::DebuggerObjectKind::DebuggerSource));
         obj = wrapVariantReferent<DebuggerSourceReferent, ScriptSourceObject*, SourceWeakMap>(
-            cx, sources, CrossCompartmentKey::DebuggerSource, referent);
+            cx, sources, key, referent);
     } else {
+        Handle<WasmModuleObject*> untaggedReferent = referent.template as<WasmModuleObject*>();
+        Rooted<CrossCompartmentKey> key(cx, CrossCompartmentKey(object, untaggedReferent,
+                                    CrossCompartmentKey::DebuggerObjectKind::DebuggerWasmSource));
         obj = wrapVariantReferent<DebuggerSourceReferent, WasmModuleObject*, WasmModuleWeakMap>(
-            cx, wasmModuleSources, CrossCompartmentKey::DebuggerWasmSource, referent);
+            cx, wasmModuleSources, key, referent);
     }
     MOZ_ASSERT_IF(obj, GetSourceReferent(obj) == referent);
     return obj;
 }
 
 JSObject*
 Debugger::wrapSource(JSContext* cx, HandleScriptSource source)
 {
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -701,17 +701,17 @@ class Debugger : private mozilla::Linked
      * Helper function to help wrap Debugger objects whose referents may be
      * variants. Currently Debugger.Script and Debugger.Source referents may
      * be variants.
      *
      * Prefer using wrapScript, wrapWasmScript, wrapSource, and wrapWasmSource
      * whenever possible.
      */
     template <typename ReferentVariant, typename Referent, typename Map>
-    JSObject* wrapVariantReferent(JSContext* cx, Map& map, CrossCompartmentKey::Kind keyKind,
+    JSObject* wrapVariantReferent(JSContext* cx, Map& map, Handle<CrossCompartmentKey> key,
                                   Handle<ReferentVariant> referent);
     JSObject* wrapVariantReferent(JSContext* cx, Handle<DebuggerScriptReferent> referent);
     JSObject* wrapVariantReferent(JSContext* cx, Handle<DebuggerSourceReferent> referent);
 
     /*
      * Allocate and initialize a Debugger.Script instance whose referent is
      * |referent|.
      */