Backed out 4 changesets (bug 935809) for build bustage. a=backout
authorIris Hsiao <ihsiao@mozilla.com>
Thu, 27 Apr 2017 10:13:45 +0800
changeset 403234 52be37a54d869a5a47acbf0508cf3dccf50bea89
parent 403233 d8fcfead17d1d5702b3270c3a2e7a9d35064e7cf
child 403235 b3f163644c7830ace4eddd2de38f9380c878fdd8
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbackout
bugs935809
milestone55.0a1
backs out8a48682a51504253364bf4156f88f45d35e5c09f
8260fdc2c0085ae7df18f437d131a85f00477fdb
f53c07293e1f50bccf4428f66af938b59468098a
4176ccbd49708e80e429d3dceaac0cdb77e33b47
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 4 changesets (bug 935809) for build bustage. a=backout CLOSED TREE Backed out changeset 8a48682a5150 (bug 935809) Backed out changeset 8260fdc2c008 (bug 935809) Backed out changeset f53c07293e1f (bug 935809) Backed out changeset 4176ccbd4970 (bug 935809)
js/src/jsclist.h
js/src/moz.build
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/Runtime.cpp
js/src/vm/Runtime.h
mfbt/DoublyLinkedList.h
new file mode 100644
--- /dev/null
+++ b/js/src/jsclist.h
@@ -0,0 +1,107 @@
+/* -*- 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 jsclist_h
+#define jsclist_h
+
+#include "jstypes.h"
+
+/*
+** Circular linked list
+*/
+typedef struct JSCListStr {
+    struct JSCListStr* next;
+    struct JSCListStr* prev;
+} JSCList;
+
+/*
+** Insert element "_e" into the list, before "_l".
+*/
+#define JS_INSERT_BEFORE(_e,_l)  \
+    JS_BEGIN_MACRO               \
+        (_e)->next = (_l);       \
+        (_e)->prev = (_l)->prev; \
+        (_l)->prev->next = (_e); \
+        (_l)->prev = (_e);       \
+    JS_END_MACRO
+
+/*
+** Insert element "_e" into the list, after "_l".
+*/
+#define JS_INSERT_AFTER(_e,_l)   \
+    JS_BEGIN_MACRO               \
+        (_e)->next = (_l)->next; \
+        (_e)->prev = (_l);       \
+        (_l)->next->prev = (_e); \
+        (_l)->next = (_e);       \
+    JS_END_MACRO
+
+/*
+** Return the element following element "_e"
+*/
+#define JS_NEXT_LINK(_e)         \
+        ((_e)->next)
+/*
+** Return the element preceding element "_e"
+*/
+#define JS_PREV_LINK(_e)         \
+        ((_e)->prev)
+
+/*
+** Append an element "_e" to the end of the list "_l"
+*/
+#define JS_APPEND_LINK(_e,_l) JS_INSERT_BEFORE(_e,_l)
+
+/*
+** Insert an element "_e" at the head of the list "_l"
+*/
+#define JS_INSERT_LINK(_e,_l) JS_INSERT_AFTER(_e,_l)
+
+/* Return the head/tail of the list */
+#define JS_LIST_HEAD(_l) (_l)->next
+#define JS_LIST_TAIL(_l) (_l)->prev
+
+/*
+** Remove the element "_e" from it's circular list.
+*/
+#define JS_REMOVE_LINK(_e)             \
+    JS_BEGIN_MACRO                     \
+        (_e)->prev->next = (_e)->next; \
+        (_e)->next->prev = (_e)->prev; \
+    JS_END_MACRO
+
+/*
+** Remove the element "_e" from it's circular list. Also initializes the
+** linkage.
+*/
+#define JS_REMOVE_AND_INIT_LINK(_e)    \
+    JS_BEGIN_MACRO                     \
+        (_e)->prev->next = (_e)->next; \
+        (_e)->next->prev = (_e)->prev; \
+        (_e)->next = (_e);             \
+        (_e)->prev = (_e);             \
+    JS_END_MACRO
+
+/*
+** Return non-zero if the given circular list "_l" is empty, zero if the
+** circular list is not empty
+*/
+#define JS_CLIST_IS_EMPTY(_l) \
+    bool((_l)->next == (_l))
+
+/*
+** Initialize a circular list
+*/
+#define JS_INIT_CLIST(_l)  \
+    JS_BEGIN_MACRO         \
+        (_l)->next = (_l); \
+        (_l)->prev = (_l); \
+    JS_END_MACRO
+
+#define JS_INIT_STATIC_CLIST(_l) \
+    {(_l), (_l)}
+
+#endif /* jsclist_h */
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -76,16 +76,17 @@ if CONFIG['HAVE_DTRACE']:
 # browser builds.  Don't add new files here unless you know what you're
 # doing!
 EXPORTS += [
     '!js-config.h',
     'js.msg',
     'jsalloc.h',
     'jsapi.h',
     'jsbytecode.h',
+    'jsclist.h',
     'jscpucfg.h',
     'jsfriendapi.h',
     'jsprf.h',
     'jsprototypes.h',
     'jspubtd.h',
     'jstypes.h',
     'jsversion.h',
     'jswrapper.h',
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -519,16 +519,17 @@ RequireGlobalObject(JSContext* cx, Handl
 }
 
 
 /*** Breakpoints *********************************************************************************/
 
 BreakpointSite::BreakpointSite(Type type)
   : type_(type), enabledCount(0)
 {
+    JS_INIT_CLIST(&breakpoints);
 }
 
 void
 BreakpointSite::inc(FreeOp* fop)
 {
     enabledCount++;
     if (enabledCount == 1)
         recompile(fop);
@@ -541,66 +542,79 @@ BreakpointSite::dec(FreeOp* fop)
     enabledCount--;
     if (enabledCount == 0)
         recompile(fop);
 }
 
 bool
 BreakpointSite::isEmpty() const
 {
-    return breakpoints.isEmpty();
+    return JS_CLIST_IS_EMPTY(&breakpoints);
 }
 
 Breakpoint*
 BreakpointSite::firstBreakpoint() const
 {
-    if (isEmpty())
+    if (JS_CLIST_IS_EMPTY(&breakpoints))
         return nullptr;
-    return &(*breakpoints.begin());
+    return Breakpoint::fromSiteLinks(JS_NEXT_LINK(&breakpoints));
 }
 
 bool
-BreakpointSite::hasBreakpoint(Breakpoint* toFind)
-{
-    const BreakpointList::Iterator bp(toFind);
-    for (auto p = breakpoints.begin(); p; p++)
+BreakpointSite::hasBreakpoint(Breakpoint* bp)
+{
+    for (Breakpoint* p = firstBreakpoint(); p; p = p->nextInSite())
         if (p == bp)
             return true;
     return false;
 }
 
 Breakpoint::Breakpoint(Debugger* debugger, BreakpointSite* site, JSObject* handler)
     : debugger(debugger), site(site), handler(handler)
 {
     MOZ_ASSERT(handler->compartment() == debugger->object->compartment());
-    debugger->breakpoints.pushBack(this);
-    site->breakpoints.pushBack(this);
+    JS_APPEND_LINK(&debuggerLinks, &debugger->breakpoints);
+    JS_APPEND_LINK(&siteLinks, &site->breakpoints);
+}
+
+Breakpoint*
+Breakpoint::fromDebuggerLinks(JSCList* links)
+{
+    return (Breakpoint*) ((unsigned char*) links - offsetof(Breakpoint, debuggerLinks));
+}
+
+Breakpoint*
+Breakpoint::fromSiteLinks(JSCList* links)
+{
+    return (Breakpoint*) ((unsigned char*) links - offsetof(Breakpoint, siteLinks));
 }
 
 void
 Breakpoint::destroy(FreeOp* fop)
 {
     if (debugger->enabled)
         site->dec(fop);
-    debugger->breakpoints.remove(this);
-    site->breakpoints.remove(this);
+    JS_REMOVE_LINK(&debuggerLinks);
+    JS_REMOVE_LINK(&siteLinks);
     site->destroyIfEmpty(fop);
     fop->delete_(this);
 }
 
 Breakpoint*
 Breakpoint::nextInDebugger()
 {
-    return debuggerLink.mNext;
+    JSCList* link = JS_NEXT_LINK(&debuggerLinks);
+    return (link == &debugger->breakpoints) ? nullptr : fromDebuggerLinks(link);
 }
 
 Breakpoint*
 Breakpoint::nextInSite()
 {
-    return siteLink.mNext;
+    JSCList* link = JS_NEXT_LINK(&siteLinks);
+    return (link == &site->breakpoints) ? nullptr : fromSiteLinks(link);
 }
 
 JSBreakpointSite::JSBreakpointSite(JSScript* script, jsbytecode* pc)
   : BreakpointSite(Type::JS),
     script(script),
     pc(pc)
 {
     MOZ_ASSERT(!script->hasBreakpointsAt(pc));
@@ -665,16 +679,19 @@ Debugger::Debugger(JSContext* cx, Native
     traceLoggerLastDrainedSize(0),
     traceLoggerLastDrainedIteration(0),
 #endif
     traceLoggerScriptedCallsLastDrainedSize(0),
     traceLoggerScriptedCallsLastDrainedIteration(0)
 {
     assertSameCompartment(cx, dbg);
 
+    JS_INIT_CLIST(&breakpoints);
+    JS_INIT_CLIST(&onNewGlobalObjectWatchersLink);
+
 #ifdef JS_TRACE_LOGGING
     TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
     if (logger) {
 #ifdef NIGHTLY_BUILD
         logger->getIterationAndSize(&traceLoggerLastDrainedIteration, &traceLoggerLastDrainedSize);
 #endif
         logger->getIterationAndSize(&traceLoggerScriptedCallsLastDrainedIteration,
                                     &traceLoggerScriptedCallsLastDrainedSize);
@@ -683,24 +700,25 @@ Debugger::Debugger(JSContext* cx, Native
 }
 
 Debugger::~Debugger()
 {
     MOZ_ASSERT_IF(debuggees.initialized(), debuggees.empty());
     allocationsLog.clear();
 
     /*
+     * Since the inactive state for this link is a singleton cycle, it's always
+     * safe to apply JS_REMOVE_LINK to it, regardless of whether we're in the list or not.
+     *
      * We don't have to worry about locking here since Debugger is not
      * background finalized.
      */
+    JS_REMOVE_LINK(&onNewGlobalObjectWatchersLink);
+
     JSContext* cx = TlsContext.get();
-    if (onNewGlobalObjectWatchersLink.mPrev ||
-        onNewGlobalObjectWatchersLink.mNext)
-        cx->runtime()->onNewGlobalObjectWatchers().remove(this);
-
     cx->runtime()->endSingleThreadedExecution(cx);
 }
 
 bool
 Debugger::init(JSContext* cx)
 {
     if (!debuggees.init() ||
         !debuggeeZones.init() ||
@@ -2155,29 +2173,33 @@ Debugger::fireNewGlobalObject(JSContext*
                              : handleUncaughtException(ac, vp);
     MOZ_ASSERT(!cx->isExceptionPending());
     return status;
 }
 
 void
 Debugger::slowPathOnNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global)
 {
-    MOZ_ASSERT(!cx->runtime()->onNewGlobalObjectWatchers().isEmpty());
+    MOZ_ASSERT(!JS_CLIST_IS_EMPTY(&cx->runtime()->onNewGlobalObjectWatchers()));
     if (global->compartment()->creationOptions().invisibleToDebugger())
         return;
 
     /*
      * Make a copy of the runtime's onNewGlobalObjectWatchers before running the
      * handlers. Since one Debugger's handler can disable another's, the list
      * can be mutated while we're walking it.
      */
     AutoObjectVector watchers(cx);
-    for (auto& dbg : cx->runtime()->onNewGlobalObjectWatchers()) {
-        MOZ_ASSERT(dbg.observesNewGlobalObject());
-        JSObject* obj = dbg.object;
+    for (JSCList* link = JS_LIST_HEAD(&cx->runtime()->onNewGlobalObjectWatchers());
+         link != &cx->runtime()->onNewGlobalObjectWatchers();
+         link = JS_NEXT_LINK(link))
+    {
+        Debugger* dbg = fromOnNewGlobalObjectWatchersLink(link);
+        MOZ_ASSERT(dbg->observesNewGlobalObject());
+        JSObject* obj = dbg->object;
         JS::ExposeObjectToActiveJS(obj);
         if (!watchers.append(obj)) {
             if (cx->isExceptionPending())
                 cx->clearPendingException();
             return;
         }
     }
 
@@ -3344,19 +3366,24 @@ Debugger::setEnabled(JSContext* cx, unsi
         }
 
         /*
          * Add or remove ourselves from the runtime's list of Debuggers
          * that care about new globals.
          */
         if (dbg->getHook(OnNewGlobalObject)) {
             if (!wasEnabled) {
-                cx->runtime()->onNewGlobalObjectWatchers().pushBack(dbg);
+                /* If we were not enabled, the link should be a singleton list. */
+                MOZ_ASSERT(JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink));
+                JS_APPEND_LINK(&dbg->onNewGlobalObjectWatchersLink,
+                               &cx->runtime()->onNewGlobalObjectWatchers());
             } else {
-                cx->runtime()->onNewGlobalObjectWatchers().remove(dbg);
+                /* If we were enabled, the link should be inserted in the list. */
+                MOZ_ASSERT(!JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink));
+                JS_REMOVE_AND_INIT_LINK(&dbg->onNewGlobalObjectWatchersLink);
             }
         }
 
         // Ensure the compartment is observable if we are re-enabling a
         // Debugger with hooks that observe all execution.
         if (!dbg->updateObservesAllExecutionOnDebuggees(cx, dbg->observesAllExecution()))
             return false;
 
@@ -3506,19 +3533,24 @@ Debugger::setOnNewGlobalObject(JSContext
 
     /*
      * Add or remove ourselves from the runtime's list of Debuggers that
      * care about new globals.
      */
     if (dbg->enabled) {
         JSObject* newHook = dbg->getHook(OnNewGlobalObject);
         if (!oldHook && newHook) {
-            cx->runtime()->onNewGlobalObjectWatchers().pushBack(dbg);
+            /* If we didn't have a hook, the link should be a singleton list. */
+            MOZ_ASSERT(JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink));
+            JS_APPEND_LINK(&dbg->onNewGlobalObjectWatchersLink,
+                           &cx->runtime()->onNewGlobalObjectWatchers());
         } else if (oldHook && !newHook) {
-            cx->runtime()->onNewGlobalObjectWatchers().remove(dbg);
+            /* If we did have a hook, the link should be inserted in the list. */
+            MOZ_ASSERT(!JS_CLIST_IS_EMPTY(&dbg->onNewGlobalObjectWatchersLink));
+            JS_REMOVE_AND_INIT_LINK(&dbg->onNewGlobalObjectWatchersLink);
         }
     }
 
     return true;
 }
 
 /* static */ bool
 Debugger::getUncaughtExceptionHook(JSContext* cx, unsigned argc, Value* vp)
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -2,23 +2,23 @@
  * 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 vm_Debugger_h
 #define vm_Debugger_h
 
-#include "mozilla/DoublyLinkedList.h"
 #include "mozilla/GuardObjects.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/Range.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Vector.h"
 
+#include "jsclist.h"
 #include "jscntxt.h"
 #include "jscompartment.h"
 #include "jsweakmap.h"
 #include "jswrapper.h"
 
 #include "builtin/Promise.h"
 #include "ds/TraceableFifo.h"
 #include "gc/Barrier.h"
@@ -253,17 +253,16 @@ typedef mozilla::Variant<ScriptSourceObj
 // Either a AbstractFramePtr, for ordinary JS, or a wasm::DebugFrame,
 // for synthesized frame of a wasm code.
 typedef mozilla::Variant<AbstractFramePtr, wasm::DebugFrame*> DebuggerFrameReferent;
 
 class Debugger : private mozilla::LinkedListElement<Debugger>
 {
     friend class Breakpoint;
     friend class DebuggerMemory;
-    friend class JSRuntime::GlobalObjectWatchersSiblingAccess<Debugger>;
     friend class SavedStacks;
     friend class ScriptedOnStepHandler;
     friend class ScriptedOnPopHandler;
     friend class mozilla::LinkedListElement<Debugger>;
     friend class mozilla::LinkedList<Debugger>;
     friend bool (::JS_DefineDebuggerObject)(JSContext* cx, JS::HandleObject obj);
     friend bool (::JS::dbg::IsDebugger)(JSObject&);
     friend bool (::JS::dbg::GetDebuggeeGlobals)(JSContext*, JSObject&, AutoObjectVector&);
@@ -381,37 +380,17 @@ class Debugger : private mozilla::Linked
     JS::ZoneSet debuggeeZones; /* Set of zones that we have debuggees in. */
     js::GCPtrObject uncaughtExceptionHook; /* Strong reference. */
     bool enabled;
     bool allowUnobservedAsmJS;
 
     // Whether to enable code coverage on the Debuggee.
     bool collectCoverageInfo;
 
-    template<typename T>
-    struct DebuggerSiblingAccess {
-      static T* GetNext(T* elm) {
-        return elm->debuggerLink.mNext;
-      }
-      static void SetNext(T* elm, T* next) {
-        elm->debuggerLink.mNext = next;
-      }
-      static T* GetPrev(T* elm) {
-        return elm->debuggerLink.mPrev;
-      }
-      static void SetPrev(T* elm, T* prev) {
-        elm->debuggerLink.mPrev = prev;
-      }
-    };
-
-    // List of all js::Breakpoints in this debugger.
-    using BreakpointList =
-        mozilla::DoublyLinkedList<js::Breakpoint,
-                                  DebuggerSiblingAccess<js::Breakpoint>>;
-    BreakpointList breakpoints;
+    JSCList breakpoints;                /* Circular list of all js::Breakpoints in this debugger */
 
     // The set of GC numbers for which one or more of this Debugger's observed
     // debuggees participated in.
     using GCNumberSet = HashSet<uint64_t, DefaultHasher<uint64_t>, RuntimeAllocPolicy>;
     GCNumberSet observedGCs;
 
     using AllocationsLog = js::TraceableFifo<AllocationsLogEntry>;
 
@@ -461,20 +440,21 @@ class Debugger : private mozilla::Linked
     /*
      * Add or remove allocations tracking for all debuggees.
      */
     MOZ_MUST_USE bool addAllocationsTrackingForAllDebuggees(JSContext* cx);
     void removeAllocationsTrackingForAllDebuggees();
 
     /*
      * If this Debugger is enabled, and has a onNewGlobalObject handler, then
-     * this link is inserted into the list headed by
-     * JSRuntime::onNewGlobalObjectWatchers.
+     * this link is inserted into the circular list headed by
+     * JSRuntime::onNewGlobalObjectWatchers. Otherwise, this is set to a
+     * singleton cycle.
      */
-    mozilla::DoublyLinkedListElement<Debugger> onNewGlobalObjectWatchersLink;
+    JSCList onNewGlobalObjectWatchersLink;
 
     /*
      * Map from stack frames that are currently on the stack to Debugger.Frame
      * instances.
      *
      * The keys are always live stack frames. We drop them from this map as
      * soon as they leave the stack (see slowPathOnLeaveFrame) and in
      * removeDebuggee.
@@ -804,16 +784,18 @@ class Debugger : private mozilla::Linked
                                              const FrameIter* maybeIter,
                                              MutableHandleValue vp);
     MOZ_MUST_USE bool getScriptFrameWithIter(JSContext* cx, AbstractFramePtr frame,
                                              const FrameIter* maybeIter,
                                              MutableHandleDebuggerFrame result);
 
     inline Breakpoint* firstBreakpoint() const;
 
+    static inline Debugger* fromOnNewGlobalObjectWatchersLink(JSCList* link);
+
     static MOZ_MUST_USE bool replaceFrameGuts(JSContext* cx, AbstractFramePtr from,
                                               AbstractFramePtr to,
                                               ScriptFrameIter& iter);
 
   public:
     Debugger(JSContext* cx, NativeObject* dbg);
     ~Debugger();
 
@@ -1575,37 +1557,17 @@ class BreakpointSite {
     friend class Debugger;
 
   public:
     enum class Type { JS, Wasm };
 
   private:
     Type type_;
 
-    template<typename T>
-    struct SiteSiblingAccess {
-      static T* GetNext(T* elm) {
-        return elm->siteLink.mNext;
-      }
-      static void SetNext(T* elm, T* next) {
-        elm->siteLink.mNext = next;
-      }
-      static T* GetPrev(T* elm) {
-        return elm->siteLink.mPrev;
-      }
-      static void SetPrev(T* elm, T* prev) {
-        elm->siteLink.mPrev = prev;
-      }
-    };
-
-    // List of all js::Breakpoints at this instruction.
-    using BreakpointList =
-        mozilla::DoublyLinkedList<js::Breakpoint,
-                                  SiteSiblingAccess<js::Breakpoint>>;
-    BreakpointList breakpoints;
+    JSCList breakpoints;  /* cyclic list of all js::Breakpoints at this instruction */
     size_t enabledCount;  /* number of breakpoints in the list that are enabled */
 
   protected:
     virtual void recompile(FreeOp* fop) = 0;
     bool isEmpty() const;
     inline bool isEnabled() const { return enabledCount > 0; }
 
   public:
@@ -1639,32 +1601,29 @@ class BreakpointSite {
  * Nothing else causes a breakpoint to be retained, so if its script or
  * debugger is collected, the breakpoint is destroyed during GC sweep phase,
  * even if the debugger compartment isn't being GC'd. This is implemented in
  * Zone::sweepBreakpoints.
  */
 class Breakpoint {
     friend struct ::JSCompartment;
     friend class Debugger;
-    friend class BreakpointSite;
 
   public:
     Debugger * const debugger;
     BreakpointSite * const site;
   private:
     /* |handler| is marked unconditionally during minor GC. */
     js::PreBarrieredObject handler;
-
-    /**
-     * Link elements for each list this breakpoint can be in.
-     */
-    mozilla::DoublyLinkedListElement<Breakpoint> debuggerLink;
-    mozilla::DoublyLinkedListElement<Breakpoint> siteLink;
+    JSCList debuggerLinks;
+    JSCList siteLinks;
 
   public:
+    static Breakpoint* fromDebuggerLinks(JSCList* links);
+    static Breakpoint* fromSiteLinks(JSCList* links);
     Breakpoint(Debugger* debugger, BreakpointSite* site, JSObject* handler);
     void destroy(FreeOp* fop);
     Breakpoint* nextInDebugger();
     Breakpoint* nextInSite();
     const PreBarrieredObject& getHandler() const { return handler; }
     PreBarrieredObject& getHandlerRef() { return handler; }
 
     inline WasmBreakpoint* asWasm();
@@ -1732,19 +1691,25 @@ Breakpoint::asWasm()
     MOZ_ASSERT(site && site->type() == BreakpointSite::Type::Wasm);
     return static_cast<WasmBreakpoint*>(this);
 }
 
 
 Breakpoint*
 Debugger::firstBreakpoint() const
 {
-    if (breakpoints.isEmpty())
+    if (JS_CLIST_IS_EMPTY(&breakpoints))
         return nullptr;
-    return &(*breakpoints.begin());
+    return Breakpoint::fromDebuggerLinks(JS_NEXT_LINK(&breakpoints));
+}
+
+/* static */ Debugger*
+Debugger::fromOnNewGlobalObjectWatchersLink(JSCList* link) {
+    char* p = reinterpret_cast<char*>(link);
+    return reinterpret_cast<Debugger*>(p - offsetof(Debugger, onNewGlobalObjectWatchersLink));
 }
 
 const js::GCPtrNativeObject&
 Debugger::toJSObject() const
 {
     MOZ_ASSERT(object);
     return object;
 }
@@ -1795,17 +1760,17 @@ Debugger::onNewScript(JSContext* cx, Han
 
 /* static */ void
 Debugger::onNewGlobalObject(JSContext* cx, Handle<GlobalObject*> global)
 {
     MOZ_ASSERT(!global->compartment()->firedOnNewGlobalObject);
 #ifdef DEBUG
     global->compartment()->firedOnNewGlobalObject = true;
 #endif
-    if (!cx->runtime()->onNewGlobalObjectWatchers().isEmpty())
+    if (!JS_CLIST_IS_EMPTY(&cx->runtime()->onNewGlobalObjectWatchers()))
         Debugger::slowPathOnNewGlobalObject(cx, global);
 }
 
 /* static */ bool
 Debugger::onLogAllocationSite(JSContext* cx, JSObject* obj, HandleSavedFrame frame, mozilla::TimeStamp when)
 {
     GlobalObject::DebuggerVector* dbgs = cx->global()->getDebuggers();
     if (!dbgs || dbgs->empty())
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -177,16 +177,17 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
     lastAnimationTime(0),
     performanceMonitoring_(thisFromCtor()),
     stackFormat_(parentRuntime ? js::StackFormat::Default
                                : js::StackFormat::SpiderMonkey)
 {
     liveRuntimesCount++;
 
     /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
+    JS_INIT_CLIST(&onNewGlobalObjectWatchers());
 
     PodZero(&asmJSCacheOps);
     lcovOutput().init();
 }
 
 JSRuntime::~JSRuntime()
 {
     MOZ_ASSERT(!initialized_);
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -4,27 +4,27 @@
  * 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 vm_Runtime_h
 #define vm_Runtime_h
 
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
-#include "mozilla/DoublyLinkedList.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Scoped.h"
 #include "mozilla/ThreadLocal.h"
 #include "mozilla/Vector.h"
 
 #include <setjmp.h>
 
 #include "jsatom.h"
+#include "jsclist.h"
 #include "jsscript.h"
 
 #ifdef XP_DARWIN
 # include "wasm/WasmSignalHandlers.h"
 #endif
 #include "builtin/AtomicsObject.h"
 #include "builtin/Intl.h"
 #include "builtin/Promise.h"
@@ -562,44 +562,24 @@ struct JSRuntime : public js::MallocProv
     // List of non-ephemeron weak containers to sweep during beginSweepingSweepGroup.
     js::ActiveThreadData<mozilla::LinkedList<JS::WeakCache<void*>>> weakCaches_;
   public:
     mozilla::LinkedList<JS::WeakCache<void*>>& weakCaches() { return weakCaches_.ref(); }
     void registerWeakCache(JS::WeakCache<void*>* cachep) {
         weakCaches().insertBack(cachep);
     }
 
-    template <typename T>
-    struct GlobalObjectWatchersSiblingAccess {
-      static T* GetNext(T* elm) {
-        return elm->onNewGlobalObjectWatchersLink.mNext;
-      }
-      static void SetNext(T* elm, T* next) {
-        elm->onNewGlobalObjectWatchersLink.mNext = next;
-      }
-      static T* GetPrev(T* elm) {
-        return elm->onNewGlobalObjectWatchersLink.mPrev;
-      }
-      static void SetPrev(T* elm, T* prev) {
-        elm->onNewGlobalObjectWatchersLink.mPrev = prev;
-      }
-    };
-
-    using WatchersList =
-        mozilla::DoublyLinkedList<js::Debugger,
-                                  GlobalObjectWatchersSiblingAccess<js::Debugger>>;
   private:
     /*
-     * List of all enabled Debuggers that have onNewGlobalObject handler
-     * methods established.
+     * Head of circular list of all enabled Debuggers that have
+     * onNewGlobalObject handler methods established.
      */
-    js::ActiveThreadData<WatchersList> onNewGlobalObjectWatchers_;
-
+    js::ActiveThreadData<JSCList> onNewGlobalObjectWatchers_;
   public:
-    WatchersList& onNewGlobalObjectWatchers() { return onNewGlobalObjectWatchers_.ref(); }
+    JSCList& onNewGlobalObjectWatchers() { return onNewGlobalObjectWatchers_.ref(); }
 
   private:
     /*
      * Lock taken when using per-runtime or per-zone data that could otherwise
      * be accessed simultaneously by multiple threads.
      *
      * Locking this only occurs if there is actually a thread other than the
      * active thread which could access such data.
--- a/mfbt/DoublyLinkedList.h
+++ b/mfbt/DoublyLinkedList.h
@@ -7,18 +7,16 @@
 /** A doubly-linked list with flexible next/prev naming. */
 
 #ifndef mozilla_DoublyLinkedList_h
 #define mozilla_DoublyLinkedList_h
 
 #include <algorithm>
 #include <iterator>
 
-#include "mozilla/Assertions.h"
-
 /**
  * Where mozilla::LinkedList strives for ease of use above all other
  * considerations, mozilla::DoublyLinkedList strives for flexibility. The
  * following are things that can be done with mozilla::DoublyLinkedList that
  * cannot be done with mozilla::LinkedList:
  *
  *   * Arbitrary next/prev placement and naming. With the tools provided here,
  *     the next and previous pointers can be at the end of the structure, in a