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 256998 8bf626d3a647f94a8512c831f7f634e8c98acee1
parent 256997 1a1ad27321de3383031cc3fdf38d042ada6c34ee
child 256999 1ec4867e4e8c0c5acb59a8d8d9b1f85b1b6def90
push id14559
push userphilringnalda@gmail.com
push dateSun, 09 Aug 2015 23:41:14 +0000
treeherderfx-team@0e269a1f1beb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersterrence
bugs1189490
milestone42.0a1
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)