Bug 1189490 - Part 2: Stop using mozilla::LinkedList for the allocations and tenure promotions logs and use js::TraceableFifo instead; r=terrence
☠☠ backed out by af32792fe260 ☠ ☠
authorNick Fitzgerald <fitzgen@gmail.com>
Sat, 08 Aug 2015 15:19:52 -0700
changeset 256979 8bf626d3a647f94a8512c831f7f634e8c98acee1
parent 256978 1a1ad27321de3383031cc3fdf38d042ada6c34ee
child 256980 1ec4867e4e8c0c5acb59a8d8d9b1f85b1b6def90
push id29197
push userphilringnalda@gmail.com
push dateSun, 09 Aug 2015 20:35:19 +0000
treeherdermozilla-central@fd69d51a4068 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersterrence
bugs1189490
milestone42.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 1189490 - Part 2: Stop using mozilla::LinkedList for the allocations and tenure promotions logs and use js::TraceableFifo instead; r=terrence
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/DebuggerMemory.cpp
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -352,25 +352,25 @@ Breakpoint::nextInSite()
 
 
 /*** Debugger hook dispatch **********************************************************************/
 
 Debugger::Debugger(JSContext* cx, NativeObject* dbg)
   : object(dbg),
     uncaughtExceptionHook(nullptr),
     enabled(true),
+    allowUnobservedAsmJS(false),
     observedGCs(cx),
+    tenurePromotionsLog(cx),
     trackingTenurePromotions(false),
-    tenurePromotionsLogLength(0),
     maxTenurePromotionsLogLength(DEFAULT_MAX_LOG_LENGTH),
     tenurePromotionsLogOverflowed(false),
-    allowUnobservedAsmJS(false),
+    allocationsLog(cx),
     trackingAllocationSites(false),
     allocationSamplingProbability(1.0),
-    allocationsLogLength(0),
     maxAllocationsLogLength(DEFAULT_MAX_LOG_LENGTH),
     allocationsLogOverflowed(false),
     frames(cx->runtime()),
     scripts(cx),
     sources(cx),
     objects(cx),
     environments(cx),
 #ifdef NIGHTLY_BUILD
@@ -385,18 +385,18 @@ Debugger::Debugger(JSContext* cx, Native
     cx->runtime()->debuggerList.insertBack(this);
     JS_INIT_CLIST(&breakpoints);
     JS_INIT_CLIST(&onNewGlobalObjectWatchersLink);
 }
 
 Debugger::~Debugger()
 {
     MOZ_ASSERT_IF(debuggees.initialized(), debuggees.empty());
-    emptyAllocationsLog();
-    emptyTenurePromotionsLog();
+    allocationsLog.clear();
+    tenurePromotionsLog.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.
      */
@@ -1696,105 +1696,75 @@ Debugger::slowPathOnIonCompilation(JSCon
 
 bool
 Debugger::isDebuggee(const JSCompartment* compartment) const
 {
     MOZ_ASSERT(compartment);
     return compartment->isDebuggee() && debuggees.has(compartment->maybeGlobal());
 }
 
-Debugger::TenurePromotionsEntry::TenurePromotionsEntry(JSRuntime* rt, JSObject& obj, double when)
+Debugger::TenurePromotionsLogEntry::TenurePromotionsLogEntry(JSRuntime* rt, JSObject& obj, double when)
   : className(obj.getClass()->name),
     when(when),
     frame(getObjectAllocationSite(obj)),
     size(JS::ubi::Node(&obj).size(rt->debuggerMallocSizeOf))
 { }
 
 
 void
 Debugger::logTenurePromotion(JSRuntime* rt, JSObject& obj, double when)
 {
-    auto* entry = js_new<TenurePromotionsEntry>(rt, obj, when);
-    if (!entry)
+    if (!tenurePromotionsLog.emplaceBack(rt, obj, when))
         CrashAtUnhandlableOOM("Debugger::logTenurePromotion");
 
-    tenurePromotionsLog.insertBack(entry);
-    if (tenurePromotionsLogLength >= maxTenurePromotionsLogLength) {
-        js_delete(tenurePromotionsLog.popFirst());
+    if (tenurePromotionsLog.length() > maxTenurePromotionsLogLength) {
+        if (!tenurePromotionsLog.popFront())
+            CrashAtUnhandlableOOM("Debugger::logTenurePromotion");
+        MOZ_ASSERT(tenurePromotionsLog.length() == maxTenurePromotionsLogLength);
         tenurePromotionsLogOverflowed = true;
-    } else {
-        tenurePromotionsLogLength++;
-    }
-}
-
-/* static */ Debugger::AllocationSite*
-Debugger::AllocationSite::create(JSContext* cx, HandleObject frame, double when, HandleObject obj)
-{
-    assertSameCompartment(cx, frame);
-
-    RootedAtom ctorName(cx);
-    {
-        AutoCompartment ac(cx, obj);
-        if (!obj->constructorDisplayAtom(cx, &ctorName))
-            return nullptr;
-    }
-
-    AllocationSite* allocSite = cx->new_<AllocationSite>(frame, when);
-    if (!allocSite)
-        return nullptr;
-
-    allocSite->className = obj->getClass()->name;
-    allocSite->ctorName = ctorName.get();
-    allocSite->size = JS::ubi::Node(obj.get()).size(cx->runtime()->debuggerMallocSizeOf);
-
-    return allocSite;
-}
-
+    }
+}
 
 bool
 Debugger::appendAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame,
                                double when)
 {
     MOZ_ASSERT(trackingAllocationSites);
 
     AutoCompartment ac(cx, object);
     RootedObject wrappedFrame(cx, frame);
     if (!cx->compartment()->wrap(cx, &wrappedFrame))
         return false;
 
-    AllocationSite* allocSite = AllocationSite::create(cx, wrappedFrame, when, obj);
-    if (!allocSite)
-        return false;
-
-    allocationsLog.insertBack(allocSite);
-
-    if (allocationsLogLength >= maxAllocationsLogLength) {
-        js_delete(allocationsLog.popFirst());
+    RootedAtom ctorName(cx);
+    {
+        AutoCompartment ac(cx, obj);
+        if (!obj->constructorDisplayAtom(cx, &ctorName))
+            return false;
+    }
+
+    auto className = obj->getClass()->name;
+    auto size = JS::ubi::Node(obj.get()).size(cx->runtime()->debuggerMallocSizeOf);
+
+    if (!allocationsLog.emplaceBack(wrappedFrame, when, className, ctorName, size))
+    {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+
+    if (allocationsLog.length() > maxAllocationsLogLength) {
+        if (!allocationsLog.popFront()) {
+            ReportOutOfMemory(cx);
+            return false;
+        }
+        MOZ_ASSERT(allocationsLog.length() == maxAllocationsLogLength);
         allocationsLogOverflowed = true;
-    } else {
-        allocationsLogLength++;
-    }
-
-    return true;
-}
-
-void
-Debugger::emptyAllocationsLog()
-{
-    while (!allocationsLog.isEmpty())
-        js_delete(allocationsLog.popFirst());
-    allocationsLogLength = 0;
-}
-
-void
-Debugger::emptyTenurePromotionsLog()
-{
-    while (!tenurePromotionsLog.isEmpty())
-        js_delete(tenurePromotionsLog.popFirst());
-    tenurePromotionsLogLength = 0;
+    }
+
+    return true;
 }
 
 JSTrapStatus
 Debugger::firePromiseHook(JSContext* cx, Hook hook, HandleObject promise, MutableHandleValue vp)
 {
     MOZ_ASSERT(hook == OnNewPromise || hook == OnPromiseSettled);
 
     RootedObject hookObj(cx, getHook(hook));
@@ -2327,17 +2297,17 @@ Debugger::addAllocationsTrackingForAllDe
 }
 
 void
 Debugger::removeAllocationsTrackingForAllDebuggees()
 {
     for (WeakGlobalObjectSet::Range r = debuggees.all(); !r.empty(); r.popFront()) {
         Debugger::removeAllocationsTracking(*r.front().get());
     }
-    emptyAllocationsLog();
+    allocationsLog.clear();
 }
 
 
 
 /*** Debugger JSObjects **************************************************************************/
 
 void
 Debugger::markCrossCompartmentEdges(JSTracer* trc)
@@ -2346,29 +2316,17 @@ Debugger::markCrossCompartmentEdges(JSTr
     environments.markCrossCompartmentEdges<DebuggerEnv_trace>(trc);
     scripts.markCrossCompartmentEdges<DebuggerScript_trace>(trc);
     sources.markCrossCompartmentEdges<DebuggerSource_trace>(trc);
 
     // Because we don't have access to a `cx` inside
     // `Debugger::logTenurePromotion`, we can't hold onto CCWs inside the log,
     // and instead have unwrapped cross-compartment edges. We need to be sure to
     // mark those here.
-    traceTenurePromotionsLog(trc);
-}
-
-/*
- * Trace every entry in the promoted to tenured heap log.
- */
-void
-Debugger::traceTenurePromotionsLog(JSTracer* trc)
-{
-    for (TenurePromotionsEntry* e = tenurePromotionsLog.getFirst(); e; e = e->getNext()) {
-        if (e->frame)
-            TraceEdge(trc, &e->frame, "Debugger::tenurePromotionsLog SavedFrame");
-    }
+    TenurePromotionsLog::trace(&tenurePromotionsLog, trc);
 }
 
 /*
  * Ordinarily, WeakMap keys and values are marked because at some point it was
  * discovered that the WeakMap was live; that is, some object containing the
  * WeakMap was marked during mark phase.
  *
  * However, during zone GC, we have to do something about cross-compartment
@@ -2539,27 +2497,18 @@ Debugger::trace(JSTracer* trc)
      * frames.)
      */
     for (FrameMap::Range r = frames.all(); !r.empty(); r.popFront()) {
         RelocatablePtrNativeObject& frameobj = r.front().value();
         MOZ_ASSERT(MaybeForwarded(frameobj.get())->getPrivate());
         TraceEdge(trc, &frameobj, "live Debugger.Frame");
     }
 
-    /*
-     * Mark every allocation site in our allocation log.
-     */
-    for (AllocationSite* s = allocationsLog.getFirst(); s; s = s->getNext()) {
-        if (s->frame)
-            TraceEdge(trc, &s->frame, "allocation log SavedFrame");
-        if (s->ctorName)
-            TraceEdge(trc, &s->ctorName, "allocation log constructor name");
-    }
-
-    traceTenurePromotionsLog(trc);
+    AllocationsLog::trace(&allocationsLog, trc);
+    TenurePromotionsLog::trace(&tenurePromotionsLog, trc);
 
     /* Trace the weak map from JSScript instances to Debugger.Script objects. */
     scripts.trace(trc);
 
     /* Trace the referent ->Debugger.Source weak map */
     sources.trace(trc);
 
     /* Trace the referent -> Debugger.Object weak map. */
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -19,16 +19,17 @@
 #include "jsweakmap.h"
 #include "jswrapper.h"
 
 #include "gc/Barrier.h"
 #include "js/Debug.h"
 #include "js/HashTable.h"
 #include "vm/GlobalObject.h"
 #include "vm/SavedStacks.h"
+#include "js/TraceableFifo.h"
 
 enum JSTrapStatus {
     JSTRAP_ERROR,
     JSTRAP_CONTINUE,
     JSTRAP_RETURN,
     JSTRAP_THROW,
     JSTRAP_LIMIT
 };
@@ -278,82 +279,89 @@ class Debugger : private mozilla::Linked
 
     bool isEnabled() const {
         return enabled;
     }
 
     void logTenurePromotion(JSRuntime* rt, JSObject& obj, double when);
     static JSObject* getObjectAllocationSite(JSObject& obj);
 
+    struct TenurePromotionsLogEntry : public JS::Traceable
+    {
+        TenurePromotionsLogEntry(JSRuntime* rt, JSObject& obj, double when);
+
+        const char* className;
+        double when;
+        RelocatablePtrObject frame;
+        size_t size;
+
+        static void trace(TenurePromotionsLogEntry* e, JSTracer* trc) {
+            if (e->frame)
+                TraceEdge(trc, &e->frame, "Debugger::TenurePromotionsLogEntry::frame");
+        }
+    };
+
+    struct AllocationsLogEntry : public JS::Traceable
+    {
+        AllocationsLogEntry(HandleObject frame, double when, const char* className,
+                            HandleAtom ctorName, size_t size)
+            : frame(frame),
+              when(when),
+              className(className),
+              ctorName(ctorName),
+              size(size)
+        {
+            MOZ_ASSERT_IF(frame, UncheckedUnwrap(frame)->is<SavedFrame>());
+        };
+
+        RelocatablePtrObject frame;
+        double when;
+        const char* className;
+        RelocatablePtrAtom ctorName;
+        size_t size;
+
+        static void trace(AllocationsLogEntry* e, JSTracer* trc) {
+            if (e->frame)
+                TraceEdge(trc, &e->frame, "Debugger::AllocationsLogEntry::frame");
+            if (e->ctorName)
+                TraceEdge(trc, &e->ctorName, "Debugger::AllocationsLogEntry::ctorName");
+        }
+    };
+
   private:
     HeapPtrNativeObject object;         /* The Debugger object. Strong reference. */
     WeakGlobalObjectSet debuggees;      /* Debuggee globals. Cross-compartment weak references. */
     JS::ZoneSet debuggeeZones; /* Set of zones that we have debuggees in. */
     js::HeapPtrObject uncaughtExceptionHook; /* Strong reference. */
     bool enabled;
+    bool allowUnobservedAsmJS;
     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.
     js::HashSet<uint64_t> observedGCs;
 
-    struct TenurePromotionsEntry : public mozilla::LinkedListElement<TenurePromotionsEntry>
-    {
-        TenurePromotionsEntry(JSRuntime* rt, JSObject& obj, double when);
-
-        const char* className;
-        double when;
-        RelocatablePtrObject frame;
-        size_t size;
-    };
-
-    using TenurePromotionsLog = mozilla::LinkedList<TenurePromotionsEntry>;
+    using TenurePromotionsLog = js::TraceableFifo<TenurePromotionsLogEntry>;
     TenurePromotionsLog tenurePromotionsLog;
     bool trackingTenurePromotions;
-    size_t tenurePromotionsLogLength;
     size_t maxTenurePromotionsLogLength;
     bool tenurePromotionsLogOverflowed;
 
-    struct AllocationSite : public mozilla::LinkedListElement<AllocationSite>
-    {
-        AllocationSite(HandleObject frame, double when)
-            : frame(frame),
-              when(when),
-              className(nullptr),
-              ctorName(nullptr),
-              size(0)
-        {
-            MOZ_ASSERT_IF(frame, UncheckedUnwrap(frame)->is<SavedFrame>());
-        };
+    using AllocationsLog = js::TraceableFifo<AllocationsLogEntry>;
 
-        static AllocationSite* create(JSContext* cx, HandleObject frame, double when,
-                                      HandleObject obj);
-
-        RelocatablePtrObject frame;
-        double when;
-        const char* className;
-        RelocatablePtrAtom ctorName;
-        size_t size;
-    };
-    typedef mozilla::LinkedList<AllocationSite> AllocationSiteList;
-
-    bool allowUnobservedAsmJS;
+    AllocationsLog allocationsLog;
     bool trackingAllocationSites;
     double allocationSamplingProbability;
-    AllocationSiteList allocationsLog;
-    size_t allocationsLogLength;
     size_t maxAllocationsLogLength;
     bool allocationsLogOverflowed;
 
     static const size_t DEFAULT_MAX_LOG_LENGTH = 5000;
 
     bool appendAllocationSite(JSContext* cx, HandleObject obj, HandleSavedFrame frame,
                               double when);
-    void emptyAllocationsLog();
-    void emptyTenurePromotionsLog();
 
     /*
      * Recompute the set of debuggee zones based on the set of debuggee globals.
      */
     bool recomputeDebuggeeZoneSet();
 
     /*
      * Return true if there is an existing object metadata callback for the
@@ -498,17 +506,16 @@ class Debugger : private mozilla::Linked
                                       MutableHandleValue vp, bool callHook = true);
 
     GlobalObject* unwrapDebuggeeArgument(JSContext* cx, const Value& v);
 
     static void traceObject(JSTracer* trc, JSObject* obj);
     void trace(JSTracer* trc);
     static void finalize(FreeOp* fop, JSObject* obj);
     void markCrossCompartmentEdges(JSTracer* tracer);
-    void traceTenurePromotionsLog(JSTracer* trc);
 
     static const Class jsclass;
 
     static bool getHookImpl(JSContext* cx, CallArgs& args, Debugger& dbg, Hook which);
     static bool setHookImpl(JSContext* cx, CallArgs& args, Debugger& dbg, Hook which);
 
     static Debugger* fromThisValue(JSContext* cx, const CallArgs& ca, const char* fnname);
     static bool getEnabled(JSContext* cx, unsigned argc, Value* vp);
@@ -915,16 +922,30 @@ class Debugger : private mozilla::Linked
      */
     JSObject* wrapSource(JSContext* cx, js::HandleScriptSource source);
 
   private:
     Debugger(const Debugger&) = delete;
     Debugger & operator=(const Debugger&) = delete;
 };
 
+template<>
+struct DefaultTracer<Debugger::TenurePromotionsLogEntry> {
+    static void trace(JSTracer* trc, Debugger::TenurePromotionsLogEntry* e, const char*) {
+        Debugger::TenurePromotionsLogEntry::trace(e, trc);
+    }
+};
+
+template<>
+struct DefaultTracer<Debugger::AllocationsLogEntry> {
+    static void trace(JSTracer* trc, Debugger::AllocationsLogEntry* e, const char*) {
+        Debugger::AllocationsLogEntry::trace(e, trc);
+    }
+};
+
 class BreakpointSite {
     friend class Breakpoint;
     friend struct ::JSCompartment;
     friend class ::JSScript;
     friend class Debugger;
 
   public:
     JSScript* script;
--- a/js/src/vm/DebuggerMemory.cpp
+++ b/js/src/vm/DebuggerMemory.cpp
@@ -180,70 +180,71 @@ DebuggerMemory::drainAllocationsLog(JSCo
     Debugger* dbg = memory->getDebugger();
 
     if (!dbg->trackingAllocationSites) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_TRACKING_ALLOCATIONS,
                              "drainAllocationsLog");
         return false;
     }
 
-    size_t length = dbg->allocationsLogLength;
+    size_t length = dbg->allocationsLog.length();
 
     RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, length));
     if (!result)
         return false;
     result->ensureDenseInitializedLength(cx, 0, length);
 
     for (size_t i = 0; i < length; i++) {
         RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
         if (!obj)
             return false;
 
-        // Don't pop the AllocationSite yet. The queue's links are followed by
-        // the GC to find the AllocationSite, but are not barriered, so we must
-        // edit them with great care. Use the queue entry in place, and then
-        // pop and delete together.
-        Debugger::AllocationSite* allocSite = dbg->allocationsLog.getFirst();
+        // Don't pop the AllocationsLogEntry yet. The queue's links are followed
+        // by the GC to find the AllocationsLogEntry, but are not barriered, so
+        // we must edit them with great care. Use the queue entry in place, and
+        // then pop and delete together.
+        Debugger::AllocationsLogEntry& entry = dbg->allocationsLog.front();
 
-        RootedValue frame(cx, ObjectOrNullValue(allocSite->frame));
+        RootedValue frame(cx, ObjectOrNullValue(entry.frame));
         if (!DefineProperty(cx, obj, cx->names().frame, frame))
             return false;
 
-        RootedValue timestampValue(cx, NumberValue(allocSite->when));
+        RootedValue timestampValue(cx, NumberValue(entry.when));
         if (!DefineProperty(cx, obj, cx->names().timestamp, timestampValue))
             return false;
 
-        RootedString className(cx, Atomize(cx, allocSite->className, strlen(allocSite->className)));
+        RootedString className(cx, Atomize(cx, entry.className, strlen(entry.className)));
         if (!className)
             return false;
         RootedValue classNameValue(cx, StringValue(className));
         if (!DefineProperty(cx, obj, cx->names().class_, classNameValue))
             return false;
 
         RootedValue ctorName(cx, NullValue());
-        if (allocSite->ctorName)
-            ctorName.setString(allocSite->ctorName);
+        if (entry.ctorName)
+            ctorName.setString(entry.ctorName);
         if (!DefineProperty(cx, obj, cx->names().constructor, ctorName))
             return false;
 
-        RootedValue size(cx, NumberValue(allocSite->size));
+        RootedValue size(cx, NumberValue(entry.size));
         if (!DefineProperty(cx, obj, cx->names().size, size))
             return false;
 
         result->setDenseElement(i, ObjectValue(*obj));
 
-        // Pop the front queue entry, and delete it immediately, so that
-        // the GC sees the AllocationSite's RelocatablePtr barriers run
-        // atomically with the change to the graph (the queue link).
-        MOZ_ALWAYS_TRUE(dbg->allocationsLog.popFirst() == allocSite);
-        js_delete(allocSite);
+        // Pop the front queue entry, and delete it immediately, so that the GC
+        // sees the AllocationsLogEntry's RelocatablePtr barriers run atomically
+        // with the change to the graph (the queeue link).
+        if (!dbg->allocationsLog.popFront()) {
+            ReportOutOfMemory(cx);
+            return false;
+        }
     }
 
     dbg->allocationsLogOverflowed = false;
-    dbg->allocationsLogLength = 0;
     args.rval().setObject(*result);
     return true;
 }
 
 /* static */ bool
 DebuggerMemory::getMaxAllocationsLogLength(JSContext* cx, unsigned argc, Value* vp)
 {
     THIS_DEBUGGER_MEMORY(cx, argc, vp, "(get maxAllocationsLogLength)", args, memory);
@@ -267,19 +268,21 @@ DebuggerMemory::setMaxAllocationsLogLeng
                              "(set maxAllocationsLogLength)'s parameter",
                              "not a positive integer");
         return false;
     }
 
     Debugger* dbg = memory->getDebugger();
     dbg->maxAllocationsLogLength = max;
 
-    while (dbg->allocationsLogLength > dbg->maxAllocationsLogLength) {
-        js_delete(dbg->allocationsLog.getFirst());
-        dbg->allocationsLogLength--;
+    while (dbg->allocationsLog.length() > dbg->maxAllocationsLogLength) {
+        if (!dbg->allocationsLog.popFront()) {
+            ReportOutOfMemory(cx);
+            return false;
+        }
     }
 
     args.rval().setUndefined();
     return true;
 }
 
 /* static */ bool
 DebuggerMemory::getAllocationSamplingProbability(JSContext* cx, unsigned argc, Value* vp)
@@ -347,67 +350,68 @@ DebuggerMemory::drainTenurePromotionsLog
     Debugger* dbg = memory->getDebugger();
 
     if (!dbg->trackingTenurePromotions) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_NOT_TRACKING_TENURINGS,
                              "drainTenurePromotionsLog");
         return false;
     }
 
-    size_t length = dbg->tenurePromotionsLogLength;
+    size_t length = dbg->tenurePromotionsLog.length();
 
     RootedArrayObject result(cx, NewDenseFullyAllocatedArray(cx, length));
     if (!result)
         return false;
     result->ensureDenseInitializedLength(cx, 0, length);
 
     for (size_t i = 0; i < length; i++) {
         RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx));
         if (!obj)
             return false;
 
         // Don't pop the TenurePromotionsEntry yet. The queue's links are
         // followed by the GC to find the TenurePromotionsEntry, but are not
         // barriered, so we must edit them with great care. Use the queue entry
         // in place, and then pop and delete together.
-        auto* entry = dbg->tenurePromotionsLog.getFirst();
+        auto& entry = dbg->tenurePromotionsLog.front();
 
-        RootedValue frame(cx, ObjectOrNullValue(entry->frame));
+        RootedValue frame(cx, ObjectOrNullValue(entry.frame));
         if (!cx->compartment()->wrap(cx, &frame) ||
             !DefineProperty(cx, obj, cx->names().frame, frame))
         {
             return false;
         }
 
-        RootedValue timestampValue(cx, NumberValue(entry->when));
+        RootedValue timestampValue(cx, NumberValue(entry.when));
         if (!DefineProperty(cx, obj, cx->names().timestamp, timestampValue))
             return false;
 
-        RootedString className(cx, Atomize(cx, entry->className, strlen(entry->className)));
+        RootedString className(cx, Atomize(cx, entry.className, strlen(entry.className)));
         if (!className)
             return false;
         RootedValue classNameValue(cx, StringValue(className));
         if (!DefineProperty(cx, obj, cx->names().class_, classNameValue))
             return false;
 
-        RootedValue sizeValue(cx, NumberValue(entry->size));
+        RootedValue sizeValue(cx, NumberValue(entry.size));
         if (!DefineProperty(cx, obj, cx->names().size, sizeValue))
             return false;
 
         result->setDenseElement(i, ObjectValue(*obj));
 
-        // Pop the front queue entry, and delete it immediately, so that
-        // the GC sees the TenurePromotionsEntry's RelocatablePtr barriers run
+        // Pop the front queue entry, and delete it immediately, so that the GC
+        // sees the TenurePromotionsEntry's RelocatablePtr barriers run
         // atomically with the change to the graph (the queue link).
-        MOZ_ALWAYS_TRUE(dbg->tenurePromotionsLog.popFirst() == entry);
-        js_delete(entry);
+        if (!dbg->tenurePromotionsLog.popFront()) {
+            ReportOutOfMemory(cx);
+            return false;
+        }
     }
 
     dbg->tenurePromotionsLogOverflowed = false;
-    dbg->tenurePromotionsLogLength = 0;
     args.rval().setObject(*result);
     return true;
 }
 
 /* static */ bool
 DebuggerMemory::getMaxTenurePromotionsLogLength(JSContext* cx, unsigned argc, Value* vp)
 {
     THIS_DEBUGGER_MEMORY(cx, argc, vp, "(get maxTenurePromotionsLogLength)", args, memory);
@@ -431,19 +435,21 @@ DebuggerMemory::setMaxTenurePromotionsLo
                              "(set maxTenurePromotionsLogLength)'s parameter",
                              "not a positive integer");
         return false;
     }
 
     Debugger* dbg = memory->getDebugger();
     dbg->maxTenurePromotionsLogLength = max;
 
-    while (dbg->tenurePromotionsLogLength > dbg->maxAllocationsLogLength) {
-        js_delete(dbg->tenurePromotionsLog.getFirst());
-        dbg->tenurePromotionsLogLength--;
+    while (dbg->tenurePromotionsLog.length() > dbg->maxAllocationsLogLength) {
+        if (!dbg->tenurePromotionsLog.popFront()) {
+            ReportOutOfMemory(cx);
+            return false;
+        }
     }
 
     args.rval().setUndefined();
     return true;
 }
 
 /* static */ bool
 DebuggerMemory::getTenurePromotionsLogOverflowed(JSContext* cx, unsigned argc, Value* vp)