Bug 1173889 - Strongly type the CallbackTracer dispatch function; r=jonco, r=mccr8
authorTerrence Cole <terrence@mozilla.com>
Thu, 11 Jun 2015 10:03:33 -0700
changeset 279658 d498daf4f845bb5721bedc84c27194098be720ab
parent 279657 531cee33742c80b90db120c63a2081c4789b6f20
child 279659 bd11384b3241596c4ab1b4bb95a0fecbbaaf811d
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco, mccr8
bugs1173889
milestone41.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 1173889 - Strongly type the CallbackTracer dispatch function; r=jonco, r=mccr8
dom/bindings/BindingUtils.h
js/public/HeapAPI.h
js/public/TraceKind.h
js/public/TracingAPI.h
js/public/UbiNode.h
js/src/builtin/TestingFunctions.cpp
js/src/gc/GCInternals.h
js/src/gc/Marking.cpp
js/src/gc/RootMarking.cpp
js/src/gc/Tracer.cpp
js/src/gc/Verifier.cpp
js/src/jsapi-tests/testGCMarking.cpp
js/src/jsfriendapi.cpp
js/src/jsgc.cpp
js/src/moz.build
js/src/vm/UbiNode.cpp
xpcom/base/CycleCollectedJSRuntime.cpp
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -529,17 +529,17 @@ AllocateProtoAndIfaceCache(JSObject* obj
 struct VerifyTraceProtoAndIfaceCacheCalledTracer : public JS::CallbackTracer
 {
   bool ok;
 
   explicit VerifyTraceProtoAndIfaceCacheCalledTracer(JSRuntime *rt)
     : JS::CallbackTracer(rt), ok(false)
   {}
 
-  void trace(void** thingp, JS::TraceKind kind) override {
+  void onChild(const JS::GCCellPtr&) override {
     // We don't do anything here, we only want to verify that
     // TraceProtoAndIfaceCache was called.
   }
 
   TracerKind getTracerKind() const override { return TracerKind::VerifyTraceProtoAndIface; }
 };
 #endif
 
--- a/js/public/HeapAPI.h
+++ b/js/public/HeapAPI.h
@@ -4,17 +4,19 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef js_HeapAPI_h
 #define js_HeapAPI_h
 
 #include <limits.h>
 
-#include "js/TracingAPI.h"
+#include "jspubtd.h"
+
+#include "js/TraceKind.h"
 #include "js/Utility.h"
 
 /* These values are private to the JS engine. */
 namespace js {
 
 // Whether the current thread is permitted access to any part of the specified
 // runtime or zone.
 JS_FRIEND_API(bool)
@@ -160,16 +162,17 @@ class JS_FRIEND_API(GCCellPtr)
     // Automatically construct a null GCCellPtr from nullptr.
     MOZ_IMPLICIT GCCellPtr(decltype(nullptr)) : ptr(checkedCast(nullptr, JS::TraceKind::Null)) {}
 
     // Construction from an explicit type.
     explicit GCCellPtr(JSObject* obj) : ptr(checkedCast(obj, JS::TraceKind::Object)) { }
     explicit GCCellPtr(JSFunction* fun) : ptr(checkedCast(fun, JS::TraceKind::Object)) { }
     explicit GCCellPtr(JSString* str) : ptr(checkedCast(str, JS::TraceKind::String)) { }
     explicit GCCellPtr(JSFlatString* str) : ptr(checkedCast(str, JS::TraceKind::String)) { }
+    explicit GCCellPtr(JS::Symbol* sym) : ptr(checkedCast(sym, JS::TraceKind::Symbol)) { }
     explicit GCCellPtr(JSScript* script) : ptr(checkedCast(script, JS::TraceKind::Script)) { }
     explicit GCCellPtr(const Value& v);
 
     JS::TraceKind kind() const {
         JS::TraceKind traceKind = JS::TraceKind(ptr & OutOfLineTraceKindMask);
         if (uintptr_t(traceKind) != OutOfLineTraceKindMask)
             return traceKind;
         return outOfLineKind();
new file mode 100644
--- /dev/null
+++ b/js/public/TraceKind.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef js_TraceKind_h
+#define js_TraceKind_h
+
+namespace JS {
+
+// When tracing a thing, the GC needs to know about the layout of the object it
+// is looking at. There are a fixed number of different layouts that the GC
+// knows about. The "trace kind" is a static map which tells which layout a GC
+// thing has.
+//
+// Although this map is public, the details are completely hidden. Not all of
+// the matching C++ types are exposed, and those that are, are opaque.
+//
+// See Value::gcKind() and JSTraceCallback in Tracer.h for more details.
+enum class TraceKind
+{
+    // These trace kinds have a publicly exposed, although opaque, C++ type.
+    // Note: The order here is determined by our Value packing. Other users
+    //       should sort alphabetically, for consistency.
+    Object = 0x00,
+    String = 0x01,
+    Symbol = 0x02,
+    Script = 0x03,
+
+    // Shape details are exposed through JS_TraceShapeCycleCollectorChildren.
+    Shape = 0x04,
+
+    // ObjectGroup details are exposed through JS_TraceObjectGroupCycleCollectorChildren.
+    ObjectGroup = 0x05,
+
+    // The kind associated with a nullptr.
+    Null = 0x06,
+
+    // The following kinds do not have an exposed C++ idiom.
+    BaseShape = 0x0F,
+    JitCode = 0x1F,
+    LazyScript = 0x2F
+};
+const static uintptr_t OutOfLineTraceKindMask = 0x07;
+static_assert(uintptr_t(JS::TraceKind::BaseShape) & OutOfLineTraceKindMask, "mask bits are set");
+static_assert(uintptr_t(JS::TraceKind::JitCode) & OutOfLineTraceKindMask, "mask bits are set");
+static_assert(uintptr_t(JS::TraceKind::LazyScript) & OutOfLineTraceKindMask, "mask bits are set");
+
+} // namespace JS
+
+#endif // js_TraceKind_h
--- a/js/public/TracingAPI.h
+++ b/js/public/TracingAPI.h
@@ -3,87 +3,42 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef js_TracingAPI_h
 #define js_TracingAPI_h
 
 #include "jsalloc.h"
-#include "jspubtd.h"
 
 #include "js/HashTable.h"
+#include "js/HeapAPI.h"
+#include "js/TraceKind.h"
 
 class JS_PUBLIC_API(JSTracer);
 
 namespace JS {
 class JS_PUBLIC_API(CallbackTracer);
 template <typename T> class Heap;
 template <typename T> class TenuredHeap;
 
-// When tracing a thing, the GC needs to know about the layout of the object it
-// is looking at. There are a fixed number of different layouts that the GC
-// knows about. The "trace kind" is a static map which tells which layout a GC
-// thing has.
-//
-// Although this map is public, the details are completely hidden. Not all of
-// the matching C++ types are exposed, and those that are, are opaque.
-//
-// See Value::gcKind() and JSTraceCallback in Tracer.h for more details.
-enum class TraceKind
-{
-    // These trace kinds have a publicly exposed, although opaque, C++ type.
-    // Note: The order here is determined by our Value packing. Other users
-    //       should sort alphabetically, for consistency.
-    Object = 0x00,
-    String = 0x01,
-    Symbol = 0x02,
-    Script = 0x03,
-
-    // Shape details are exposed through JS_TraceShapeCycleCollectorChildren.
-    Shape = 0x04,
-
-    // ObjectGroup details are exposed through JS_TraceObjectGroupCycleCollectorChildren.
-    ObjectGroup = 0x05,
-
-    // The kind associated with a nullptr.
-    Null = 0x06,
-
-    // The following kinds do not have an exposed C++ idiom.
-    BaseShape = 0x0F,
-    JitCode = 0x1F,
-    LazyScript = 0x2F
-};
-const static uintptr_t OutOfLineTraceKindMask = 0x07;
-static_assert(uintptr_t(JS::TraceKind::BaseShape) & OutOfLineTraceKindMask, "mask bits are set");
-static_assert(uintptr_t(JS::TraceKind::JitCode) & OutOfLineTraceKindMask, "mask bits are set");
-static_assert(uintptr_t(JS::TraceKind::LazyScript) & OutOfLineTraceKindMask, "mask bits are set");
-
 // Returns a static string equivalent of |kind|.
 JS_FRIEND_API(const char*)
 GCTraceKindToAscii(JS::TraceKind kind);
 
 } // namespace JS
 
-// Tracer callback, called for each traceable thing directly referenced by a
-// particular object or runtime structure. It is the callback responsibility
-// to ensure the traversal of the full object graph via calling eventually
-// JS_TraceChildren on the passed thing. In this case the callback must be
-// prepared to deal with cycles in the traversal graph.
-//
-// kind argument is one of JS::TraceKind::Object, JS::TraceKind::String or a
-// tag denoting internal implementation-specific traversal kind. In the latter
-// case the only operations on thing that the callback can do is to call
-// JS_TraceChildren or JS_GetTraceThingInfo.
-//
-// If eagerlyTraceWeakMaps is true, when we trace a WeakMap visit all
-// of its mappings. This should be used in cases where the tracer
-// wants to use the existing liveness of entries.
-typedef void
-(* JSTraceCallback)(JS::CallbackTracer* trc, void** thingp, JS::TraceKind kind);
+namespace js {
+class BaseShape;
+class LazyScript;
+class ObjectGroup;
+namespace jit {
+class JitCode;
+} // namespace jit
+} // namespace js
 
 enum WeakMapTraceKind {
     DoNotTraceWeakMaps = 0,
     TraceWeakMapValues = 1,
     TraceWeakMapKeysValues = 2
 };
 
 class JS_PUBLIC_API(JSTracer)
@@ -127,18 +82,44 @@ class AutoTracingCallback;
 class JS_PUBLIC_API(CallbackTracer) : public JSTracer
 {
   public:
     CallbackTracer(JSRuntime* rt, WeakMapTraceKind weakTraceKind = TraceWeakMapValues)
       : JSTracer(rt, JSTracer::TracerKindTag::Callback, weakTraceKind),
         contextName_(nullptr), contextIndex_(InvalidIndex), contextFunctor_(nullptr)
     {}
 
-    // Override this method to receive notification when an edge is visited.
-    virtual void trace(void** thing, JS::TraceKind kind) = 0;
+    // Override these methods to receive notification when an edge is visited
+    // with the type contained in the callback. The default implementation
+    // dispatches to the fully-generic onChild implementation, so for cases that
+    // do not care about boxing overhead and do not need the actual edges,
+    // just override the generic onChild.
+    virtual void onObjectEdge(JSObject** objp) { onChild(JS::GCCellPtr(*objp)); }
+    virtual void onStringEdge(JSString** strp) { onChild(JS::GCCellPtr(*strp)); }
+    virtual void onSymbolEdge(JS::Symbol** symp) { onChild(JS::GCCellPtr(*symp)); }
+    virtual void onScriptEdge(JSScript** scriptp) { onChild(JS::GCCellPtr(*scriptp)); }
+    virtual void onShapeEdge(js::Shape** shapep) {
+        onChild(JS::GCCellPtr(*shapep, JS::TraceKind::Shape));
+    }
+    virtual void onObjectGroupEdge(js::ObjectGroup** groupp) {
+        onChild(JS::GCCellPtr(*groupp, JS::TraceKind::ObjectGroup));
+    }
+    virtual void onBaseShapeEdge(js::BaseShape** basep) {
+        onChild(JS::GCCellPtr(*basep, JS::TraceKind::BaseShape));
+    }
+    virtual void onJitCodeEdge(js::jit::JitCode** codep) {
+        onChild(JS::GCCellPtr(*codep, JS::TraceKind::JitCode));
+    }
+    virtual void onLazyScriptEdge(js::LazyScript** lazyp) {
+        onChild(JS::GCCellPtr(*lazyp, JS::TraceKind::LazyScript));
+    }
+
+    // Override this method to receive notification when a node in the GC
+    // heap graph is visited.
+    virtual void onChild(const JS::GCCellPtr& thing) = 0;
 
     // Access to the tracing context:
     // When tracing with a JS::CallbackTracer, we invoke the callback with the
     // edge location and the type of target. This is useful for operating on
     // the edge in the abstract or on the target thing, satisfying most common
     // use cases.  However, some tracers need additional detail about the
     // specific edge that is being traced in order to be useful. Unfortunately,
     // the raw pointer to the edge that we provide is not enough information to
@@ -180,16 +161,31 @@ class JS_PUBLIC_API(CallbackTracer) : pu
         virtual void operator()(CallbackTracer* trc, char* buf, size_t bufsize) = 0;
     };
 
 #ifdef DEBUG
     enum class TracerKind { DoNotCare, Moving, GrayBuffering, VerifyTraceProtoAndIface };
     virtual TracerKind getTracerKind() const { return TracerKind::DoNotCare; }
 #endif
 
+    // In C++, overriding a method hides all methods in the base class with
+    // that name, not just methods with that signature. Thus, the typed edge
+    // methods have to have distinct names to allow us to override them
+    // individually, which is freqently useful if, for example, we only want to
+    // process only one type of edge.
+    void dispatchToOnEdge(JSObject** objp) { onObjectEdge(objp); }
+    void dispatchToOnEdge(JSString** strp) { onStringEdge(strp); }
+    void dispatchToOnEdge(JS::Symbol** symp) { onSymbolEdge(symp); }
+    void dispatchToOnEdge(JSScript** scriptp) { onScriptEdge(scriptp); }
+    void dispatchToOnEdge(js::Shape** shapep) { onShapeEdge(shapep); }
+    void dispatchToOnEdge(js::ObjectGroup** groupp) { onObjectGroupEdge(groupp); }
+    void dispatchToOnEdge(js::BaseShape** basep) { onBaseShapeEdge(basep); }
+    void dispatchToOnEdge(js::jit::JitCode** codep) { onJitCodeEdge(codep); }
+    void dispatchToOnEdge(js::LazyScript** lazyp) { onLazyScriptEdge(lazyp); }
+
   private:
     friend class AutoTracingName;
     const char* contextName_;
 
     friend class AutoTracingIndex;
     size_t contextIndex_;
 
     friend class AutoTracingDetails;
--- a/js/public/UbiNode.h
+++ b/js/public/UbiNode.h
@@ -326,17 +326,17 @@ class Node {
         return *this;
     }
 
     // Constructors accepting SpiderMonkey's other generic-pointer-ish types.
     // Note that we *do* want an implicit constructor here: JS::Value and
     // JS::ubi::Node are both essentially tagged references to other sorts of
     // objects, so letting conversions happen automatically is appropriate.
     MOZ_IMPLICIT Node(JS::HandleValue value);
-    Node(JS::TraceKind kind, void* ptr);
+    explicit Node(const JS::GCCellPtr& thing);
 
     // copy construction and copy assignment just use memcpy, since we know
     // instances contain nothing but a vtable pointer and a data pointer.
     //
     // To be completely correct, concrete classes could provide a virtual
     // 'construct' member function, which we could invoke on rhs to construct an
     // instance in our storage. But this is good enough; there's no need to jump
     // through vtables for copying and assignment that are just going to move
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -803,18 +803,18 @@ NondeterministicGetWeakMapKeys(JSContext
     return true;
 }
 
 class HasChildTracer : public JS::CallbackTracer
 {
     RootedValue child_;
     bool found_;
 
-    void trace(void** thingp, JS::TraceKind kind) {
-        if (*thingp == child_.toGCThing())
+    void onChild(const JS::GCCellPtr& thing) override {
+        if (thing.asCell() == child_.toGCThing())
             found_ = true;
     }
 
   public:
     HasChildTracer(JSRuntime* rt, HandleValue child)
       : JS::CallbackTracer(rt, TraceWeakMapKeysValues), child_(rt, child), found_(false)
     {}
 
--- a/js/src/gc/GCInternals.h
+++ b/js/src/gc/GCInternals.h
@@ -133,19 +133,25 @@ struct AutoStopVerifyingBarriers
 };
 #endif /* JS_GC_ZEAL */
 
 #ifdef JSGC_HASH_TABLE_CHECKS
 void
 CheckHashTablesAfterMovingGC(JSRuntime* rt);
 #endif
 
-struct MovingTracer : JS::CallbackTracer {
+struct MovingTracer : JS::CallbackTracer
+{
     explicit MovingTracer(JSRuntime* rt) : CallbackTracer(rt, TraceWeakMapKeysValues) {}
-    void trace(void** thingp, JS::TraceKind kind) override;
+
+    void onObjectEdge(JSObject** objp) override;
+    void onChild(const JS::GCCellPtr& thing) override {
+        MOZ_ASSERT(!RelocationOverlay::isCellForwarded(thing.asCell()));
+    }
+
 #ifdef DEBUG
     TracerKind getTracerKind() const override { return TracerKind::Moving; }
 #endif
 };
 
 class AutoMaybeStartBackgroundAllocation
 {
   private:
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -2273,19 +2273,19 @@ TypeSet::MarkTypeUnbarriered(JSTracer* t
 }
 
 
 /*** Cycle Collector Barrier Implementation *******************************************************/
 
 #ifdef DEBUG
 struct AssertNonGrayTracer : public JS::CallbackTracer {
     explicit AssertNonGrayTracer(JSRuntime* rt) : JS::CallbackTracer(rt) {}
-    void trace(void** thingp, JS::TraceKind kind) override {
-        DebugOnly<Cell*> thing(static_cast<Cell*>(*thingp));
-        MOZ_ASSERT_IF(thing->isTenured(), !thing->asTenured().isMarked(js::gc::GRAY));
+    void onChild(const JS::GCCellPtr& thing) override {
+        MOZ_ASSERT_IF(thing.asCell()->isTenured(),
+                      !thing.asCell()->asTenured().isMarked(js::gc::GRAY));
     }
 };
 #endif
 
 struct UnmarkGrayTracer : public JS::CallbackTracer
 {
     /*
      * We set eagerlyTraceWeakMaps to false because the cycle collector will fix
@@ -2300,17 +2300,17 @@ struct UnmarkGrayTracer : public JS::Cal
 
     UnmarkGrayTracer(JSTracer* trc, bool tracingShape)
       : JS::CallbackTracer(trc->runtime(), DoNotTraceWeakMaps),
         tracingShape(tracingShape),
         previousShape(nullptr),
         unmarkedAny(false)
     {}
 
-    void trace(void** thingp, JS::TraceKind kind) override;
+    void onChild(const JS::GCCellPtr& thing) override;
 
     /* True iff we are tracing the immediate children of a shape. */
     bool tracingShape;
 
     /* If tracingShape, shape child or nullptr. Otherwise, nullptr. */
     Shape* previousShape;
 
     /* Whether we unmarked anything. */
@@ -2343,64 +2343,64 @@ struct UnmarkGrayTracer : public JS::Cal
  * - A special pass enumerating all of the containers that know about the
  *   implicit edges to fix any black-gray edges that have been created. This
  *   is implemented in nsXPConnect::FixWeakMappingGrayBits.
  * - To prevent any incorrectly gray objects from escaping to live JS outside
  *   of the containers, we must add unmark-graying read barriers to these
  *   containers.
  */
 void
-UnmarkGrayTracer::trace(void** thingp, JS::TraceKind kind)
+UnmarkGrayTracer::onChild(const JS::GCCellPtr& thing)
 {
     int stackDummy;
     if (!JS_CHECK_STACK_SIZE(runtime()->mainThread.nativeStackLimit[StackForSystemCode],
                              &stackDummy))
     {
         /*
          * If we run out of stack, we take a more drastic measure: require that
          * we GC again before the next CC.
          */
         runtime()->gc.setGrayBitsInvalid();
         return;
     }
 
-    Cell* cell = static_cast<Cell*>(*thingp);
+    Cell* cell = thing.asCell();
 
     // Cells in the nursery cannot be gray, and therefore must necessarily point
     // to only black edges.
     if (!cell->isTenured()) {
 #ifdef DEBUG
         AssertNonGrayTracer nongray(runtime());
-        TraceChildren(&nongray, cell, kind);
+        TraceChildren(&nongray, cell, thing.kind());
 #endif
         return;
     }
 
     TenuredCell& tenured = cell->asTenured();
     if (!tenured.isMarked(js::gc::GRAY))
         return;
     tenured.unmark(js::gc::GRAY);
 
     unmarkedAny = true;
 
     // Trace children of |tenured|. If |tenured| and its parent are both
     // shapes, |tenured| will get saved to mPreviousShape without being traced.
     // The parent will later trace |tenured|. This is done to avoid increasing
     // the stack depth during shape tracing. It is safe to do because a shape
     // can only have one child that is a shape.
-    UnmarkGrayTracer childTracer(this, kind == JS::TraceKind::Shape);
+    UnmarkGrayTracer childTracer(this, thing.kind() == JS::TraceKind::Shape);
 
-    if (kind != JS::TraceKind::Shape) {
-        TraceChildren(&childTracer, &tenured, kind);
+    if (thing.kind() != JS::TraceKind::Shape) {
+        TraceChildren(&childTracer, &tenured, thing.kind());
         MOZ_ASSERT(!childTracer.previousShape);
         unmarkedAny |= childTracer.unmarkedAny;
         return;
     }
 
-    MOZ_ASSERT(kind == JS::TraceKind::Shape);
+    MOZ_ASSERT(thing.kind() == JS::TraceKind::Shape);
     Shape* shape = static_cast<Shape*>(&tenured);
     if (tracingShape) {
         MOZ_ASSERT(!previousShape);
         previousShape = shape;
         return;
     }
 
     do {
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -547,17 +547,17 @@ js::gc::GCRuntime::markRuntime(JSTracer*
 
 // Append traced things to a buffer on the zone for use later in the GC.
 // See the comment in GCRuntime.h above grayBufferState for details.
 class BufferGrayRootsTracer : public JS::CallbackTracer
 {
     // Set to false if we OOM while buffering gray roots.
     bool bufferingGrayRootsFailed;
 
-    void trace(void** thingp, JS::TraceKind kind) override;
+    void onChild(const JS::GCCellPtr& thing) override;
 
   public:
     explicit BufferGrayRootsTracer(JSRuntime* rt)
       : JS::CallbackTracer(rt), bufferingGrayRootsFailed(false)
     {}
 
     bool failed() const { return bufferingGrayRootsFailed; }
 
@@ -600,34 +600,34 @@ js::gc::GCRuntime::bufferGrayRoots()
     }
 }
 
 struct SetMaybeAliveFunctor {
     template <typename T> void operator()(T* t) { SetMaybeAliveFlag(t); }
 };
 
 void
-BufferGrayRootsTracer::trace(void** thingp, JS::TraceKind kind)
+BufferGrayRootsTracer::onChild(const JS::GCCellPtr& thing)
 {
     MOZ_ASSERT(runtime()->isHeapBusy());
 
     if (bufferingGrayRootsFailed)
         return;
 
-    gc::TenuredCell* thing = gc::TenuredCell::fromPointer(*thingp);
+    gc::TenuredCell* tenured = gc::TenuredCell::fromPointer(thing.asCell());
 
-    Zone* zone = thing->zone();
+    Zone* zone = tenured->zone();
     if (zone->isCollecting()) {
         // See the comment on SetMaybeAliveFlag to see why we only do this for
         // objects and scripts. We rely on gray root buffering for this to work,
         // but we only need to worry about uncollected dead compartments during
         // incremental GCs (when we do gray root buffering).
-        CallTyped(SetMaybeAliveFunctor(), thing, kind);
+        CallTyped(SetMaybeAliveFunctor(), tenured, thing.kind());
 
-        if (!zone->gcGrayRoots.append(thing))
+        if (!zone->gcGrayRoots.append(tenured))
             bufferingGrayRootsFailed = true;
     }
 }
 
 void
 GCRuntime::markBufferedGrayRoots(JS::Zone* zone)
 {
     MOZ_ASSERT(grayBufferState == GrayBufferState::Okay);
--- a/js/src/gc/Tracer.cpp
+++ b/js/src/gc/Tracer.cpp
@@ -41,19 +41,18 @@ CheckTracedThing(JSTracer* trc, T thing)
 
 /*** Callback Tracer Dispatch ********************************************************************/
 
 template <typename T>
 T
 DoCallback(JS::CallbackTracer* trc, T* thingp, const char* name)
 {
     CheckTracedThing(trc, *thingp);
-    JS::TraceKind kind = MapTypeToTraceKind<typename mozilla::RemovePointer<T>::Type>::kind;
     JS::AutoTracingName ctx(trc, name);
-    trc->trace(reinterpret_cast<void**>(thingp), kind);
+    trc->dispatchToOnEdge(thingp);
     return *thingp;
 }
 #define INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS(name, type, _) \
     template type* DoCallback<type*>(JS::CallbackTracer*, type**, const char*);
 FOR_EACH_GC_LAYOUT(INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS);
 #undef INSTANTIATE_ALL_VALID_TRACE_FUNCTIONS
 
 template <typename S>
@@ -317,31 +316,29 @@ TraceObjectGroupCycleCollectorChildrenCa
 // avoid blowing the stack when running the cycle collector's callback tracer.
 struct ObjectGroupCycleCollectorTracer : public JS::CallbackTracer
 {
     explicit ObjectGroupCycleCollectorTracer(JS::CallbackTracer* innerTracer)
         : JS::CallbackTracer(innerTracer->runtime(), DoNotTraceWeakMaps),
           innerTracer(innerTracer)
     {}
 
-    void trace(void** thingp, JS::TraceKind kind) override;
+    void onChild(const JS::GCCellPtr& thing) override;
 
     JS::CallbackTracer* innerTracer;
     Vector<ObjectGroup*, 4, SystemAllocPolicy> seen, worklist;
 };
 
 void
-ObjectGroupCycleCollectorTracer::trace(void** thingp, JS::TraceKind kind)
+ObjectGroupCycleCollectorTracer::onChild(const JS::GCCellPtr& thing)
 {
-    JS::GCCellPtr thing(*thingp, kind);
-
     if (thing.isObject() || thing.isScript()) {
         // Invoke the inner cycle collector callback on this child. It will not
         // recurse back into TraceChildren.
-        innerTracer->trace(thingp, kind);
+        innerTracer->onChild(thing);
         return;
     }
 
     if (thing.isObjectGroup()) {
         // If this group is required to be in an ObjectGroup chain, trace it
         // via the provided worklist rather than continuing to recurse.
         ObjectGroup* group = static_cast<ObjectGroup*>(thing.asCell());
         if (group->maybeUnboxedLayout()) {
--- a/js/src/gc/Verifier.cpp
+++ b/js/src/gc/Verifier.cpp
@@ -76,17 +76,17 @@ typedef HashMap<void*, VerifyNode*, Defa
  *
  * The nodemap field is a hashtable that maps from the address of the GC thing
  * to the VerifyNode that represents it.
  */
 class js::VerifyPreTracer : public JS::CallbackTracer
 {
     JS::AutoDisableGenerationalGC noggc;
 
-    void trace(void** thingp, JS::TraceKind kind) override;
+    void onChild(const JS::GCCellPtr& thing) override;
 
   public:
     /* The gcNumber when the verification began. */
     uint64_t number;
 
     /* This counts up to gcZealFrequency to decide whether to verify. */
     int count;
 
@@ -107,31 +107,31 @@ class js::VerifyPreTracer : public JS::C
     }
 };
 
 /*
  * This function builds up the heap snapshot by adding edges to the current
  * node.
  */
 void
-VerifyPreTracer::trace(void** thingp, JS::TraceKind kind)
+VerifyPreTracer::onChild(const JS::GCCellPtr& thing)
 {
-    MOZ_ASSERT(!IsInsideNursery(*reinterpret_cast<Cell**>(thingp)));
+    MOZ_ASSERT(!IsInsideNursery(thing.asCell()));
 
     edgeptr += sizeof(EdgeValue);
     if (edgeptr >= term) {
         edgeptr = term;
         return;
     }
 
     VerifyNode* node = curnode;
     uint32_t i = node->count;
 
-    node->edges[i].thing = *thingp;
-    node->edges[i].kind = kind;
+    node->edges[i].thing = thing.asCell();
+    node->edges[i].kind = thing.kind();
     node->edges[i].label = contextName();
     node->count++;
 }
 
 static VerifyNode*
 MakeNode(VerifyPreTracer* trc, void* thing, JS::TraceKind kind)
 {
     NodeMap::AddPtr p = trc->nodemap.lookupForAdd(thing);
@@ -247,38 +247,38 @@ static bool
 IsMarkedOrAllocated(TenuredCell* cell)
 {
     return cell->isMarked() || cell->arenaHeader()->allocatedDuringIncremental;
 }
 
 struct CheckEdgeTracer : public JS::CallbackTracer {
     VerifyNode* node;
     explicit CheckEdgeTracer(JSRuntime* rt) : JS::CallbackTracer(rt), node(nullptr) {}
-    void trace(void** thingp, JS::TraceKind kind) override;
+    void onChild(const JS::GCCellPtr& thing) override;
 };
 
 static const uint32_t MAX_VERIFIER_EDGES = 1000;
 
 /*
  * This function is called by EndVerifyBarriers for every heap edge. If the edge
  * already existed in the original snapshot, we "cancel it out" by overwriting
  * it with nullptr. EndVerifyBarriers later asserts that the remaining
  * non-nullptr edges (i.e., the ones from the original snapshot that must have
  * been modified) must point to marked objects.
  */
 void
-CheckEdgeTracer::trace(void** thingp, JS::TraceKind kind)
+CheckEdgeTracer::onChild(const JS::GCCellPtr& thing)
 {
     /* Avoid n^2 behavior. */
     if (node->count > MAX_VERIFIER_EDGES)
         return;
 
     for (uint32_t i = 0; i < node->count; i++) {
-        if (node->edges[i].thing == *thingp) {
-            MOZ_ASSERT(node->edges[i].kind == kind);
+        if (node->edges[i].thing == thing.asCell()) {
+            MOZ_ASSERT(node->edges[i].kind == thing.kind());
             node->edges[i].thing = nullptr;
             return;
         }
     }
 }
 
 static void
 AssertMarkedOrAllocated(const EdgeValue& edge)
--- a/js/src/jsapi-tests/testGCMarking.cpp
+++ b/js/src/jsapi-tests/testGCMarking.cpp
@@ -3,26 +3,26 @@
 */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jsapi-tests/tests.h"
 
 class CCWTestTracer : public JS::CallbackTracer {
-    void trace(void** thingp, JS::TraceKind kind) {
+    void onChild(const JS::GCCellPtr& thing) override {
         numberOfThingsTraced++;
 
-        printf("*thingp         = %p\n", *thingp);
+        printf("*thingp         = %p\n", thing.asCell());
         printf("*expectedThingp = %p\n", *expectedThingp);
 
-        printf("kind         = %d\n", static_cast<int>(kind));
+        printf("kind         = %d\n", static_cast<int>(thing.kind()));
         printf("expectedKind = %d\n", static_cast<int>(expectedKind));
 
-        if (*thingp != *expectedThingp || kind != expectedKind)
+        if (thing.asCell() != *expectedThingp || thing.kind() != expectedKind)
             okay = false;
     }
 
   public:
     bool          okay;
     size_t        numberOfThingsTraced;
     void**        expectedThingp;
     JS::TraceKind expectedKind;
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -899,17 +899,17 @@ struct DumpHeapTracer : public JS::Callb
         JSObject* kdelegate = nullptr;
         if (key.isObject())
             kdelegate = js::GetWeakmapKeyDelegate(key.toObject());
 
         fprintf(output, "WeakMapEntry map=%p key=%p keyDelegate=%p value=%p\n",
                 map, key.asCell(), kdelegate, value.asCell());
     }
 
-    void trace(void** thingp, JS::TraceKind kind) override;
+    void onChild(const JS::GCCellPtr& thing) override;
 };
 
 static char
 MarkDescriptor(void* thing)
 {
     gc::TenuredCell* cell = gc::TenuredCell::fromPointer(thing);
     if (cell->isMarked(gc::BLACK))
         return cell->isMarked(gc::GRAY) ? 'G' : 'B';
@@ -953,24 +953,24 @@ DumpHeapVisitCell(JSRuntime* rt, void* d
     DumpHeapTracer* dtrc = static_cast<DumpHeapTracer*>(data);
     char cellDesc[1024 * 32];
     JS_GetTraceThingInfo(cellDesc, sizeof(cellDesc), dtrc, thing, traceKind, true);
     fprintf(dtrc->output, "%p %c %s\n", thing, MarkDescriptor(thing), cellDesc);
     JS_TraceChildren(dtrc, thing, traceKind);
 }
 
 void
-DumpHeapTracer::trace(void** thingp, JS::TraceKind kind)
+DumpHeapTracer::onChild(const JS::GCCellPtr& thing)
 {
-    if (gc::IsInsideNursery((js::gc::Cell*)*thingp))
+    if (gc::IsInsideNursery(thing.asCell()))
         return;
 
     char buffer[1024];
     getTracingEdgeName(buffer, sizeof(buffer));
-    fprintf(output, "%s%p %c %s\n", prefix, *thingp, MarkDescriptor(*thingp), buffer);
+    fprintf(output, "%s%p %c %s\n", prefix, thing.asCell(), MarkDescriptor(thing.asCell()), buffer);
 }
 
 void
 js::DumpHeap(JSRuntime* rt, FILE* fp, js::DumpHeapNurseryBehaviour nurseryBehaviour)
 {
     if (nurseryBehaviour == js::CollectNurseryBeforeDump)
         rt->gc.evictNursery(JS::gcreason::API);
 
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -2202,29 +2202,20 @@ GCRuntime::relocateArenas(Zone* zone, JS
         }
     }
 #endif
 
     return true;
 }
 
 void
-MovingTracer::trace(void** thingp, JS::TraceKind kind)
-{
-    TenuredCell* thing = TenuredCell::fromPointer(*thingp);
-
-    // Currently we only relocate objects.
-    if (kind != JS::TraceKind::Object) {
-        MOZ_ASSERT(!RelocationOverlay::isCellForwarded(thing));
-        return;
-    }
-
-    JSObject* obj = reinterpret_cast<JSObject*>(thing);
-    if (IsForwarded(obj))
-        *thingp = Forwarded(obj);
+MovingTracer::onObjectEdge(JSObject** objp)
+{
+    if (IsForwarded(*objp))
+        *objp = Forwarded(*objp);
 }
 
 void
 GCRuntime::sweepTypesAfterCompacting(Zone* zone)
 {
     FreeOp* fop = rt->defaultFreeOp();
     zone->beginSweepTypes(fop, rt->gc.releaseObservedTypes && !zone->isPreservingCode());
 
@@ -3672,17 +3663,17 @@ GCRuntime::shouldPreserveJITCode(JSCompa
         return true;
 
     return false;
 }
 
 #ifdef DEBUG
 class CompartmentCheckTracer : public JS::CallbackTracer
 {
-    void trace(void** thingp, JS::TraceKind kind) override;
+    void onChild(const JS::GCCellPtr& thing) override;
 
   public:
     explicit CompartmentCheckTracer(JSRuntime* rt)
       : JS::CallbackTracer(rt), src(nullptr), zone(nullptr), compartment(nullptr)
     {}
 
     Cell* src;
     JS::TraceKind srcKind;
@@ -3715,27 +3706,27 @@ InCrossCompartmentMap(JSObject* src, Cel
     return false;
 }
 
 struct MaybeCompartmentFunctor {
     template <typename T> JSCompartment* operator()(T* t) { return t->maybeCompartment(); }
 };
 
 void
-CompartmentCheckTracer::trace(void** thingp, JS::TraceKind kind)
-{
-    TenuredCell* thing = TenuredCell::fromPointer(*thingp);
-
-    JSCompartment* comp = CallTyped(MaybeCompartmentFunctor(), thing, kind);
+CompartmentCheckTracer::onChild(const JS::GCCellPtr& thing)
+{
+    TenuredCell* tenured = TenuredCell::fromPointer(thing.asCell());
+
+    JSCompartment* comp = CallTyped(MaybeCompartmentFunctor(), tenured, thing.kind());
     if (comp && compartment) {
         MOZ_ASSERT(comp == compartment || runtime()->isAtomsCompartment(comp) ||
                    (srcKind == JS::TraceKind::Object &&
-                    InCrossCompartmentMap(static_cast<JSObject*>(src), thing, kind)));
+                    InCrossCompartmentMap(static_cast<JSObject*>(src), tenured, thing.kind())));
     } else {
-        MOZ_ASSERT(thing->zone() == zone || thing->zone()->isAtomsZone());
+        MOZ_ASSERT(tenured->zone() == zone || tenured->zone()->isAtomsZone());
     }
 }
 
 void
 GCRuntime::checkForCompartmentMismatches()
 {
     if (disableStrictProxyCheckingCount)
         return;
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -26,17 +26,17 @@ with Files('gc/**'):
     BUG_COMPONENT = component_gc
 with Files('jit/**'):
     BUG_COMPONENT = component_jit
 
 # File-specific metadata
 for gcfile in ['jsgc*', 'devtools/rootAnalysis', 'devtools/gc-ubench', 'devtools/gctrace']:
     with Files(gcfile):
         BUG_COMPONENT = component_gc
-for header in ('GCAPI.h', 'HeapAPI.h', 'RootingAPI.h', 'SliceBudget.h', 'TracingAPI.h', 'WeakMapPtr.h'):
+for header in ('GCAPI.h', 'HeapAPI.h', 'RootingAPI.h', 'SliceBudget.h', 'TraceKind.h', 'TracingAPI.h', 'WeakMapPtr.h'):
     with Files('../public/' + header):
         BUG_COMPONENT = component_gc
 
 for stlfile in ['jsarray.*', 'jsbool*', 'jsdate.*', 'jsnum.*', 'json.*', 'jsreflect.*', 'jsstr.*']:
     with Files(stlfile):
         BUG_COMPONENT = component_stl
 
 with Files('builtin/Intl*'):
@@ -117,16 +117,17 @@ EXPORTS.js += [
     '../public/Principals.h',
     '../public/ProfilingFrameIterator.h',
     '../public/ProfilingStack.h',
     '../public/Proxy.h',
     '../public/RequiredDefines.h',
     '../public/RootingAPI.h',
     '../public/SliceBudget.h',
     '../public/StructuredClone.h',
+    '../public/TraceKind.h',
     '../public/TracingAPI.h',
     '../public/TrackedOptimizationInfo.h',
     '../public/TypeDecls.h',
     '../public/UbiNode.h',
     '../public/UbiNodeTraverse.h',
     '../public/Utility.h',
     '../public/Value.h',
     '../public/Vector.h',
--- a/js/src/vm/UbiNode.cpp
+++ b/js/src/vm/UbiNode.cpp
@@ -60,19 +60,19 @@ Concrete<void>::size(mozilla::MallocSize
 {
     MOZ_CRASH("null ubi::Node");
 }
 
 struct Node::ConstructFunctor : public js::BoolDefaultAdaptor<Value, false> {
     template <typename T> bool operator()(T* t, Node* node) { node->construct(t); return true; }
 };
 
-Node::Node(JS::TraceKind kind, void* ptr)
+Node::Node(const JS::GCCellPtr &thing)
 {
-    js::gc::CallTyped(ConstructFunctor(), ptr, kind, this);
+    js::gc::CallTyped(ConstructFunctor(), thing.asCell(), thing.kind(), this);
 }
 
 Node::Node(HandleValue value)
 {
     if (!DispatchValueTyped(ConstructFunctor(), value, this))
         construct<void>(nullptr);
 }
 
@@ -106,17 +106,17 @@ Node::exposeToJS() const
 // edge on which it is invoked.
 class SimpleEdgeVectorTracer : public JS::CallbackTracer {
     // The vector to which we add SimpleEdges.
     SimpleEdgeVector* vec;
 
     // True if we should populate the edge's names.
     bool wantNames;
 
-    void trace(void** thingp, JS::TraceKind kind) {
+    void onChild(const JS::GCCellPtr& thing) override {
         if (!okay)
             return;
 
         char16_t* name16 = nullptr;
         if (wantNames) {
             // Ask the tracer to compute an edge name for us.
             char buffer[1024];
             getTracingEdgeName(buffer, sizeof(buffer));
@@ -134,17 +134,17 @@ class SimpleEdgeVectorTracer : public JS
                 name16[i] = name[i];
             name16[i] = '\0';
         }
 
         // The simplest code is correct! The temporary SimpleEdge takes
         // ownership of name; if the append succeeds, the vector element
         // then takes ownership; if the append fails, then the temporary
         // retains it, and its destructor will free it.
-        if (!vec->append(mozilla::Move(SimpleEdge(name16, Node(kind, *thingp))))) {
+        if (!vec->append(mozilla::Move(SimpleEdge(name16, Node(thing))))) {
             okay = false;
             return;
         }
     }
 
   public:
     // True if no errors (OOM, say) have yet occurred.
     bool okay;
--- a/xpcom/base/CycleCollectedJSRuntime.cpp
+++ b/xpcom/base/CycleCollectedJSRuntime.cpp
@@ -123,42 +123,40 @@ public:
 struct NoteWeakMapChildrenTracer : public JS::CallbackTracer
 {
   NoteWeakMapChildrenTracer(JSRuntime* aRt,
                             nsCycleCollectionNoteRootCallback& aCb)
     : JS::CallbackTracer(aRt), mCb(aCb), mTracedAny(false), mMap(nullptr),
       mKey(nullptr), mKeyDelegate(nullptr)
   {
   }
-  void trace(void** aThingp, JS::TraceKind aKind) override;
+  void onChild(const JS::GCCellPtr& aThing) override;
   nsCycleCollectionNoteRootCallback& mCb;
   bool mTracedAny;
   JSObject* mMap;
   JS::GCCellPtr mKey;
   JSObject* mKeyDelegate;
 };
 
 void
-NoteWeakMapChildrenTracer::trace(void** aThingp, JS::TraceKind aKind)
+NoteWeakMapChildrenTracer::onChild(const JS::GCCellPtr& aThing)
 {
-  JS::GCCellPtr thing(*aThingp, aKind);
-
-  if (thing.isString()) {
+  if (aThing.isString()) {
     return;
   }
 
-  if (!JS::GCThingIsMarkedGray(thing) && !mCb.WantAllTraces()) {
+  if (!JS::GCThingIsMarkedGray(aThing) && !mCb.WantAllTraces()) {
     return;
   }
 
-  if (AddToCCKind(thing.kind())) {
-    mCb.NoteWeakMapping(mMap, mKey, mKeyDelegate, thing);
+  if (AddToCCKind(aThing.kind())) {
+    mCb.NoteWeakMapping(mMap, mKey, mKeyDelegate, aThing);
     mTracedAny = true;
   } else {
-    JS_TraceChildren(this, thing.asCell(), thing.kind());
+    JS_TraceChildren(this, aThing.asCell(), aThing.kind());
   }
 }
 
 struct NoteWeakMapsTracer : public js::WeakMapTracer
 {
   NoteWeakMapsTracer(JSRuntime* aRt, nsCycleCollectionNoteRootCallback& aCccb)
     : js::WeakMapTracer(aRt), mCb(aCccb), mChildTracer(aRt, aCccb)
   {
@@ -354,72 +352,65 @@ JSZoneParticipant::Traverse(void* aPtr, 
 }
 
 struct TraversalTracer : public JS::CallbackTracer
 {
   TraversalTracer(JSRuntime* aRt, nsCycleCollectionTraversalCallback& aCb)
     : JS::CallbackTracer(aRt, DoNotTraceWeakMaps), mCb(aCb)
   {
   }
-  void trace(void** aThingp, JS::TraceKind aTraceKind) override;
+  void onChild(const JS::GCCellPtr& aThing) override;
   nsCycleCollectionTraversalCallback& mCb;
 };
 
-static void
-NoteJSChild(TraversalTracer* aTrc, JS::GCCellPtr aThing)
+void
+TraversalTracer::onChild(const JS::GCCellPtr& aThing)
 {
   // Don't traverse non-gray objects, unless we want all traces.
-  if (!JS::GCThingIsMarkedGray(aThing) && !aTrc->mCb.WantAllTraces()) {
+  if (!JS::GCThingIsMarkedGray(aThing) && !mCb.WantAllTraces()) {
     return;
   }
 
   /*
    * This function needs to be careful to avoid stack overflow. Normally, when
    * AddToCCKind is true, the recursion terminates immediately as we just add
    * |thing| to the CC graph. So overflow is only possible when there are long
    * or cyclic chains of non-AddToCCKind GC things. Places where this can occur
    * use special APIs to handle such chains iteratively.
    */
   if (AddToCCKind(aThing.kind())) {
-    if (MOZ_UNLIKELY(aTrc->mCb.WantDebugInfo())) {
+    if (MOZ_UNLIKELY(mCb.WantDebugInfo())) {
       char buffer[200];
-      aTrc->getTracingEdgeName(buffer, sizeof(buffer));
-      aTrc->mCb.NoteNextEdgeName(buffer);
+      getTracingEdgeName(buffer, sizeof(buffer));
+      mCb.NoteNextEdgeName(buffer);
     }
     if (aThing.isObject()) {
-      aTrc->mCb.NoteJSObject(aThing.toObject());
+      mCb.NoteJSObject(aThing.toObject());
     } else {
-      aTrc->mCb.NoteJSScript(aThing.toScript());
+      mCb.NoteJSScript(aThing.toScript());
     }
   } else if (aThing.isShape()) {
     // The maximum depth of traversal when tracing a Shape is unbounded, due to
     // the parent pointers on the shape.
-    JS_TraceShapeCycleCollectorChildren(aTrc, aThing);
+    JS_TraceShapeCycleCollectorChildren(this, aThing);
   } else if (aThing.isObjectGroup()) {
     // The maximum depth of traversal when tracing an ObjectGroup is unbounded,
     // due to information attached to the groups which can lead other groups to
     // be traced.
-    JS_TraceObjectGroupCycleCollectorChildren(aTrc, aThing);
+    JS_TraceObjectGroupCycleCollectorChildren(this, aThing);
   } else if (!aThing.isString()) {
-    JS_TraceChildren(aTrc, aThing.asCell(), aThing.kind());
+    JS_TraceChildren(this, aThing.asCell(), aThing.kind());
   }
 }
 
-void
-TraversalTracer::trace(void** aThingp, JS::TraceKind aTraceKind)
-{
-  JS::GCCellPtr thing(*aThingp, aTraceKind);
-  NoteJSChild(this, thing);
-}
-
 static void
 NoteJSChildGrayWrapperShim(void* aData, JS::GCCellPtr aThing)
 {
   TraversalTracer* trc = static_cast<TraversalTracer*>(aData);
-  NoteJSChild(trc, aThing);
+  trc->onChild(aThing);
 }
 
 /*
  * The cycle collection participant for a Zone is intended to produce the same
  * results as if all of the gray GCthings in a zone were merged into a single node,
  * except for self-edges. This avoids the overhead of representing all of the GCthings in
  * the zone in the cycle collector graph, which should be much faster if many of
  * the GCthings in the zone are gray.