Bug 935721, part 5 - Invert the control flow of CycleCollectNow's pre- and post-collection work, add CCResults as a field on the CC. r=smaug
authorAndrew McCreight <continuation@gmail.com>
Wed, 20 Nov 2013 14:35:16 -0800
changeset 156638 9e354b8e06ceae0b819c41e90c7a594349b46e12
parent 156637 f503f23599891bbbe812ea8bec635eb4ede59957
child 156639 eedc72464698771e60096e7b024724c4e858ae7d
push id36488
push useramccreight@mozilla.com
push dateWed, 20 Nov 2013 22:36:15 +0000
treeherdermozilla-inbound@e597cdb674ea [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs935721
milestone28.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 935721, part 5 - Invert the control flow of CycleCollectNow's pre- and post-collection work, add CCResults as a field on the CC. r=smaug nsJSEnvironment::CycleCollectNow does work before and after a CC runs. With ICC, nsJSEnv won't know where in the CC when a CC is about to begin or end, so this patch reorganizes that work into two separate callback hooks. This requires adding a new struct, CycleCollectorStats, to hold data nsJSEnv needs between the two calls. Rather than trying to pass around a pointer to a results structure, this patch just adds it to the nsCycleCollector struct, and always stores them. The results are passed back to the end CC callback.
dom/base/nsJSEnvironment.cpp
dom/base/nsJSEnvironment.h
dom/workers/RuntimeService.cpp
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/xpcprivate.h
xpcom/base/CycleCollectedJSRuntime.h
xpcom/base/nsCycleCollector.cpp
xpcom/base/nsCycleCollector.h
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -50,16 +50,17 @@
 #include "prmem.h"
 #include "WrapperFactory.h"
 #include "nsGlobalWindow.h"
 #include "nsScriptNameSpaceManager.h"
 #include "StructuredCloneTags.h"
 #include "mozilla/dom/ImageData.h"
 #include "mozilla/dom/ImageDataBinding.h"
 #include "nsAXPCNativeCallContext.h"
+#include "mozilla/CycleCollectedJSRuntime.h"
 
 #include "nsJSPrincipals.h"
 
 #ifdef XP_MACOSX
 // AssertMacros.h defines 'check' and conflicts with AccessCheck.h
 #undef check
 #endif
 #include "AccessCheck.h"
@@ -1972,82 +1973,145 @@ FireForgetSkippable(uint32_t aSuspected,
 MOZ_ALWAYS_INLINE
 static uint32_t
 TimeBetween(PRTime start, PRTime end)
 {
   MOZ_ASSERT(end >= start);
   return (uint32_t)(end - start) / PR_USEC_PER_MSEC;
 }
 
+struct CycleCollectorStats
+{
+  void Clear()
+  {
+    mBeginSliceTime = 0;
+    mBeginTime = 0;
+    mMaxGCDuration = 0;
+    mRanSyncForgetSkippable = false;
+    mSuspected = 0;
+    mMaxSkippableDuration = 0;
+    mAnyLockedOut = false;
+  }
+
+  // Time the current slice began, including any GC finishing.
+  PRTime mBeginSliceTime;
+
+  // Time the current cycle collection began.
+  PRTime mBeginTime;
+
+  // The longest GC finishing duration for any slice of the current CC.
+  uint32_t mMaxGCDuration;
+
+  // True if we ran sync forget skippable in any slice of the current CC.
+  bool mRanSyncForgetSkippable;
+
+  // Number of suspected objects at the start of the current CC.
+  uint32_t mSuspected;
+
+  // The longest duration spent on sync forget skippable in any slice of the
+  // current CC
+  uint32_t mMaxSkippableDuration;
+
+  // True if we were locked out by the GC in any slice of the current CC.
+  bool mAnyLockedOut;
+};
+
+CycleCollectorStats gCCStats;
+
 //static
 void
 nsJSContext::CycleCollectNow(nsICycleCollectorListener *aListener,
                              int32_t aExtraForgetSkippableCalls,
                              bool aManuallyTriggered)
 {
   if (!NS_IsMainThread()) {
     return;
   }
 
   PROFILER_LABEL("CC", "CycleCollectNow");
 
-  PRTime start = PR_Now();
+  gCCStats.mBeginSliceTime = PR_Now();
 
   // Before we begin the cycle collection, make sure there is no active GC.
-  bool finishedIGC = sCCLockedOut;
-  FinishAnyIncrementalGC();
-  PRTime endGCTime = PR_Now();
-  uint32_t gcDuration = TimeBetween(start, endGCTime);
-
-  KillCCTimer();
-
-  uint32_t suspected = nsCycleCollector_suspectedCount();
-  bool ranSyncForgetSkippable = false;
+  PRTime endGCTime;
+  if (sCCLockedOut) {
+    gCCStats.mAnyLockedOut = true;
+    FinishAnyIncrementalGC();
+    endGCTime = PR_Now();
+    uint32_t gcTime = TimeBetween(gCCStats.mBeginSliceTime, endGCTime);
+    gCCStats.mMaxGCDuration = std::max(gCCStats.mMaxGCDuration, gcTime);
+  } else {
+    endGCTime = gCCStats.mBeginSliceTime;
+  }
 
   // Run forgetSkippable synchronously to reduce the size of the CC graph. This
   // is particularly useful if we recently finished a GC.
-  if (sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS &&
-      aExtraForgetSkippableCalls >= 0) {
+  if (aExtraForgetSkippableCalls >= 0) {
+    bool ranSyncForgetSkippable = false;
     while (sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS) {
       FireForgetSkippable(nsCycleCollector_suspectedCount(), false);
       ranSyncForgetSkippable = true;
     }
-  }
-
-  for (int32_t i = 0; i < aExtraForgetSkippableCalls; ++i) {
-    FireForgetSkippable(nsCycleCollector_suspectedCount(), false);
-    ranSyncForgetSkippable = true;
+
+    for (int32_t i = 0; i < aExtraForgetSkippableCalls; ++i) {
+      FireForgetSkippable(nsCycleCollector_suspectedCount(), false);
+      ranSyncForgetSkippable = true;
+    }
+
+    if (ranSyncForgetSkippable) {
+      gCCStats.mMaxSkippableDuration =
+        std::max(gCCStats.mMaxSkippableDuration, TimeBetween(endGCTime, PR_Now()));
+      gCCStats.mRanSyncForgetSkippable = true;
+    }
+
   }
 
-  PRTime endSkippableTime = PR_Now();
-  uint32_t skippableDuration = TimeBetween(endGCTime, endSkippableTime);
-
-  // Prepare to actually run the CC.
-  nsCycleCollectorResults ccResults;
-  nsCycleCollector_collect(aManuallyTriggered, &ccResults, aListener);
+  nsCycleCollector_collect(aManuallyTriggered, aListener);
+
+}
+
+//static
+void
+nsJSContext::BeginCycleCollectionCallback()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  gCCStats.mBeginTime = gCCStats.mBeginSliceTime ? gCCStats.mBeginSliceTime : PR_Now();
+  gCCStats.mSuspected = nsCycleCollector_suspectedCount();
+
+  KillCCTimer();
+}
+
+//static
+void
+nsJSContext::EndCycleCollectionCallback(CycleCollectorResults &aResults)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  CycleCollectorResults &ccResults = aResults; // temporary to reduce patch size
   sCCollectedWaitingForGC += ccResults.mFreedRefCounted + ccResults.mFreedGCed;
 
   // If we collected a substantial amount of cycles, poke the GC since more objects
   // might be unreachable now.
   if (sCCollectedWaitingForGC > 250 ||
       sLikelyShortLivingObjectsNeedingGC > 2500 ||
       sNeedsGCAfterCC) {
     PokeGC(JS::gcreason::CC_WAITING);
   }
 
   PRTime endCCTime = PR_Now();
 
   // Log information about the CC via telemetry, JSON and the console.
-  uint32_t ccNowDuration = TimeBetween(start, endCCTime);
-  Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FINISH_IGC, finishedIGC);
-  Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SYNC_SKIPPABLE, ranSyncForgetSkippable);
+  uint32_t ccNowDuration = TimeBetween(gCCStats.mBeginTime, endCCTime);
+  Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FINISH_IGC, gCCStats.mAnyLockedOut);
+  Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SYNC_SKIPPABLE, gCCStats.mRanSyncForgetSkippable);
   Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FULL, ccNowDuration);
 
   if (sLastCCEndTime) {
-    uint32_t timeBetween = (uint32_t)(start - sLastCCEndTime) / PR_USEC_PER_SEC;
+    uint32_t timeBetween = TimeBetween(sLastCCEndTime, gCCStats.mBeginTime);
     Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_TIME_BETWEEN, timeBetween);
   }
   sLastCCEndTime = endCCTime;
 
   Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_MAX,
                         sMaxForgetSkippableTime / PR_USEC_PER_MSEC);
 
   PRTime delta = GetCollectionTimeDelta();
@@ -2064,44 +2128,44 @@ nsJSContext::CycleCollectNow(nsICycleCol
 
     nsCString gcMsg;
     if (ccResults.mForcedGC) {
       gcMsg.AssignLiteral(", forced a GC");
     }
 
     NS_NAMED_MULTILINE_LITERAL_STRING(kFmt,
       MOZ_UTF16("CC(T+%.1f) duration: %lums, suspected: %lu, visited: %lu RCed and %lu%s GCed, collected: %lu RCed and %lu GCed (%lu|%lu waiting for GC)%s\n")
-      MOZ_UTF16("ForgetSkippable %lu times before CC, min: %lu ms, max: %lu ms, avg: %lu ms, total: %lu ms, sync: %lu ms, removed: %lu"));
+      MOZ_UTF16("ForgetSkippable %lu times before CC, min: %lu ms, max: %lu ms, avg: %lu ms, total: %lu ms, max sync: %lu ms, removed: %lu"));
     nsString msg;
     msg.Adopt(nsTextFormatter::smprintf(kFmt.get(), double(delta) / PR_USEC_PER_SEC,
-                                        ccNowDuration, suspected,
+                                        ccNowDuration, gCCStats.mSuspected,
                                         ccResults.mVisitedRefCounted, ccResults.mVisitedGCed, mergeMsg.get(),
                                         ccResults.mFreedRefCounted, ccResults.mFreedGCed,
                                         sCCollectedWaitingForGC, sLikelyShortLivingObjectsNeedingGC,
                                         gcMsg.get(),
                                         sForgetSkippableBeforeCC,
                                         minForgetSkippableTime / PR_USEC_PER_MSEC,
                                         sMaxForgetSkippableTime / PR_USEC_PER_MSEC,
                                         (sTotalForgetSkippableTime / cleanups) /
                                           PR_USEC_PER_MSEC,
                                         sTotalForgetSkippableTime / PR_USEC_PER_MSEC,
-                                        skippableDuration, sRemovedPurples));
+                                        gCCStats.mMaxSkippableDuration, sRemovedPurples));
     nsCOMPtr<nsIConsoleService> cs =
       do_GetService(NS_CONSOLESERVICE_CONTRACTID);
     if (cs) {
       cs->LogStringMessage(msg.get());
     }
   }
 
   if (sPostGCEventsToObserver) {
     NS_NAMED_MULTILINE_LITERAL_STRING(kJSONFmt,
        MOZ_UTF16("{ \"timestamp\": %llu, ")
          MOZ_UTF16("\"duration\": %llu, ")
-         MOZ_UTF16("\"finish_gc_duration\": %llu, ")
-         MOZ_UTF16("\"sync_skippable_duration\": %llu, ")
+         MOZ_UTF16("\"max_finish_gc_duration\": %llu, ")
+         MOZ_UTF16("\"max_sync_skippable_duration\": %llu, ")
          MOZ_UTF16("\"suspected\": %lu, ")
          MOZ_UTF16("\"visited\": { ")
              MOZ_UTF16("\"RCed\": %lu, ")
              MOZ_UTF16("\"GCed\": %lu }, ")
          MOZ_UTF16("\"collected\": { ")
              MOZ_UTF16("\"RCed\": %lu, ")
              MOZ_UTF16("\"GCed\": %lu }, ")
          MOZ_UTF16("\"waiting_for_gc\": %lu, ")
@@ -2112,18 +2176,19 @@ nsJSContext::CycleCollectNow(nsICycleCol
              MOZ_UTF16("\"min\": %lu, ")
              MOZ_UTF16("\"max\": %lu, ")
              MOZ_UTF16("\"avg\": %lu, ")
              MOZ_UTF16("\"total\": %lu, ")
              MOZ_UTF16("\"removed\": %lu } ")
        MOZ_UTF16("}"));
     nsString json;
     json.Adopt(nsTextFormatter::smprintf(kJSONFmt.get(), endCCTime,
-                                         ccNowDuration, gcDuration, skippableDuration,
-                                         suspected,
+                                         ccNowDuration, gCCStats.mMaxGCDuration,
+                                         gCCStats.mMaxSkippableDuration,
+                                         gCCStats.mSuspected,
                                          ccResults.mVisitedRefCounted, ccResults.mVisitedGCed,
                                          ccResults.mFreedRefCounted, ccResults.mFreedGCed,
                                          sCCollectedWaitingForGC,
                                          sLikelyShortLivingObjectsNeedingGC,
                                          ccResults.mForcedGC,
                                          sForgetSkippableBeforeCC,
                                          minForgetSkippableTime / PR_USEC_PER_MSEC,
                                          sMaxForgetSkippableTime / PR_USEC_PER_MSEC,
@@ -2140,16 +2205,17 @@ nsJSContext::CycleCollectNow(nsICycleCol
   // Update global state to indicate we have just run a cycle collection.
   sMinForgetSkippableTime = UINT32_MAX;
   sMaxForgetSkippableTime = 0;
   sTotalForgetSkippableTime = 0;
   sRemovedPurples = 0;
   sForgetSkippableBeforeCC = 0;
   sNeedsFullCC = false;
   sNeedsGCAfterCC = false;
+  gCCStats.Clear();
 }
 
 // static
 void
 InterSliceGCTimerFired(nsITimer *aTimer, void *aClosure)
 {
   NS_RELEASE(sInterSliceGCTimer);
   nsJSContext::GarbageCollectNow(JS::gcreason::INTER_SLICE_GC,
@@ -2595,16 +2661,17 @@ mozilla::dom::StartupJSEnvironment()
   gNameSpaceManager = nullptr;
   sRuntimeService = nullptr;
   sRuntime = nullptr;
   sIsInitialized = false;
   sDidShutdown = false;
   sShuttingDown = false;
   sContextCount = 0;
   sSecurityManager = nullptr;
+  gCCStats.Clear();
 }
 
 static int
 ReportAllJSExceptionsPrefChangedCallback(const char* aPrefName, void* aClosure)
 {
   bool reportAll = Preferences::GetBool(aPrefName, false);
   nsContentUtils::XPConnect()->SetReportAllJSExceptions(reportAll);
   return 0;
--- a/dom/base/nsJSEnvironment.h
+++ b/dom/base/nsJSEnvironment.h
@@ -19,16 +19,17 @@
 class nsICycleCollectorListener;
 class nsIXPConnectJSObjectHolder;
 class nsRootedJSValueArray;
 class nsScriptNameSpaceManager;
 class nsCycleCollectionNoteRootCallback;
 
 namespace mozilla {
 template <class> class Maybe;
+struct CycleCollectorResults;
 }
 
 // The amount of time we wait between a request to GC (due to leaving
 // a page) and doing the actual GC.
 #define NS_GC_DELAY                 4000 // ms
 
 class nsJSContext : public nsIScriptContext
 {
@@ -100,16 +101,18 @@ public:
                                 IsShrinking aShrinking = NonShrinkingGC,
                                 int64_t aSliceMillis = 0);
   static void ShrinkGCBuffersNow();
   // If aExtraForgetSkippableCalls is -1, forgetSkippable won't be
   // called even if the previous collection was GC.
   static void CycleCollectNow(nsICycleCollectorListener *aListener = nullptr,
                               int32_t aExtraForgetSkippableCalls = 0,
                               bool aManuallyTriggered = true);
+  static void BeginCycleCollectionCallback();
+  static void EndCycleCollectionCallback(mozilla::CycleCollectorResults &aResults);
 
   static void PokeGC(JS::gcreason::Reason aReason, int aDelay = 0);
   static void KillGCTimer();
 
   static void PokeShrinkGCBuffers();
   static void KillShrinkGCBuffersTimer();
 
   static void MaybePokeCC();
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -875,16 +875,31 @@ public:
     // in cycles that were broken during CC shutdown.
     nsCycleCollector_shutdown();
 
     // The CC is shut down, and the superclass destructor will GC, so make sure
     // we don't try to CC again.
     mWorkerPrivate = nullptr;
   }
 
+  virtual void
+  PrepareForForgetSkippable() MOZ_OVERRIDE
+  {
+  }
+
+  virtual void
+  BeginCycleCollectionCallback() MOZ_OVERRIDE
+  {
+  }
+
+  virtual void
+  EndCycleCollectionCallback(CycleCollectorResults &aResults) MOZ_OVERRIDE
+  {
+  }
+
   void
   DispatchDeferredDeletion(bool aContinuation) MOZ_OVERRIDE
   {
     MOZ_ASSERT(!aContinuation);
 
     // Do it immediately, no need for asynchronous behavior here.
     nsCycleCollector_doDeferredDeletion();
   }
@@ -894,17 +909,17 @@ public:
     if (!mWorkerPrivate) {
       // We're shutting down, no need to do anything.
       return;
     }
 
     mWorkerPrivate->AssertIsOnWorkerThread();
 
     if (aStatus == JSGC_END) {
-      nsCycleCollector_collect(true, nullptr, nullptr);
+      nsCycleCollector_collect(true, nullptr);
     }
   }
 
 private:
   WorkerPrivate* mWorkerPrivate;
 };
 
 class WorkerThreadRunnable : public nsRunnable
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -743,25 +743,33 @@ XPCJSRuntime::PrepareForForgetSkippable(
 {
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     if (obs) {
         obs->NotifyObservers(nullptr, "cycle-collector-forget-skippable", nullptr);
     }
 }
 
 void
-XPCJSRuntime::PrepareForCollection()
+XPCJSRuntime::BeginCycleCollectionCallback()
 {
+    nsJSContext::BeginCycleCollectionCallback();
+
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     if (obs) {
         obs->NotifyObservers(nullptr, "cycle-collector-begin", nullptr);
     }
 }
 
 void
+XPCJSRuntime::EndCycleCollectionCallback(CycleCollectorResults &aResults)
+{
+    nsJSContext::EndCycleCollectionCallback(aResults);
+}
+
+void
 XPCJSRuntime::DispatchDeferredDeletion(bool aContinuation)
 {
     mAsyncSnowWhiteFreer->Dispatch(aContinuation);
 }
 
 void
 xpc_UnmarkSkippableJSHolders()
 {
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -671,17 +671,18 @@ public:
         return mStrings[index];
     }
 
     void TraceNativeBlackRoots(JSTracer* trc) MOZ_OVERRIDE;
     void TraceAdditionalNativeGrayRoots(JSTracer* aTracer) MOZ_OVERRIDE;
     void TraverseAdditionalNativeRoots(nsCycleCollectionNoteRootCallback& cb) MOZ_OVERRIDE;
     void UnmarkSkippableJSHolders();
     void PrepareForForgetSkippable() MOZ_OVERRIDE;
-    void PrepareForCollection() MOZ_OVERRIDE;
+    void BeginCycleCollectionCallback() MOZ_OVERRIDE;
+    void EndCycleCollectionCallback(mozilla::CycleCollectorResults &aResults) MOZ_OVERRIDE;
     void DispatchDeferredDeletion(bool continuation) MOZ_OVERRIDE;
 
     void CustomGCCallback(JSGCStatus status) MOZ_OVERRIDE;
     bool CustomContextCallback(JSContext *cx, unsigned operation) MOZ_OVERRIDE;
     static void GCSliceCallback(JSRuntime *rt,
                                 JS::GCProgress progress,
                                 const JS::GCDescription &desc);
     static void FinalizeCallback(JSFreeOp *fop, JSFinalizeStatus status, bool isCompartmentGC);
--- a/xpcom/base/CycleCollectedJSRuntime.h
+++ b/xpcom/base/CycleCollectedJSRuntime.h
@@ -68,16 +68,37 @@ public:
   {
   }
 
   NS_IMETHOD Traverse(void *p, nsCycleCollectionTraversalCallback &cb);
 };
 
 class IncrementalFinalizeRunnable;
 
+// Contains various stats about the cycle collection.
+struct CycleCollectorResults
+{
+  void Init()
+  {
+    mForcedGC = false;
+    mMergedZones = false;
+    mVisitedRefCounted = 0;
+    mVisitedGCed = 0;
+    mFreedRefCounted = 0;
+    mFreedGCed = 0;
+  }
+
+  bool mForcedGC;
+  bool mMergedZones;
+  uint32_t mVisitedRefCounted;
+  uint32_t mVisitedGCed;
+  uint32_t mFreedRefCounted;
+  uint32_t mFreedGCed;
+};
+
 class CycleCollectedJSRuntime
 {
   friend class JSGCThingParticipant;
   friend class JSZoneParticipant;
   friend class IncrementalFinalizeRunnable;
 protected:
   CycleCollectedJSRuntime(uint32_t aMaxbytes,
                           JSUseHelperThreads aUseHelperThreads);
@@ -178,26 +199,26 @@ public:
   nsCycleCollectionParticipant* ZoneParticipant();
 
   nsresult BeginCycleCollection(nsCycleCollectionNoteRootCallback &aCb);
   bool UsefulToMergeZones() const;
   void FixWeakMappingGrayBits() const;
   bool NeedCollect() const;
   void Collect(uint32_t reason) const;
 
-  virtual void PrepareForForgetSkippable() {}
-  virtual void PrepareForCollection() {}
-
   void DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc,
                         DeferredFinalizeFunction aFunc,
                         void* aThing);
   void DeferredFinalize(nsISupports* aSupports);
 
   void DumpJSHeap(FILE* aFile);
-  
+
+  virtual void PrepareForForgetSkippable() = 0;
+  virtual void BeginCycleCollectionCallback() = 0;
+  virtual void EndCycleCollectionCallback(CycleCollectorResults &aResults) = 0;
   virtual void DispatchDeferredDeletion(bool aContinuation) = 0;
 
   JSRuntime* Runtime() const
   {
     MOZ_ASSERT(mJSRuntime);
     return mJSRuntime;
   }
 
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -900,40 +900,34 @@ enum ccType {
 #endif
 
 ////////////////////////////////////////////////////////////////////////
 // Top level structure for the cycle collector.
 ////////////////////////////////////////////////////////////////////////
 
 class nsCycleCollector
 {
-    friend class GCGraphBuilder;
-
     bool mCollectionInProgress;
     // mScanInProgress should be false when we're collecting white objects.
     bool mScanInProgress;
-    nsCycleCollectorResults *mResults;
+    CycleCollectorResults mResults;
     TimeStamp mCollectionStart;
 
     CycleCollectedJSRuntime *mJSRuntime;
 
     GCGraph mGraph;
     nsAutoPtr<GCGraphBuilder> mBuilder;
     nsCOMPtr<nsICycleCollectorListener> mListener;
 
     nsIThread* mThread;
 
     nsCycleCollectorParams mParams;
 
     uint32_t mWhiteNodeCount;
 
-    // mVisitedRefCounted and mVisitedGCed are only used for telemetry
-    uint32_t mVisitedRefCounted;
-    uint32_t mVisitedGCed;
-
     CC_BeforeUnlinkCallback mBeforeUnlinkCB;
     CC_ForgetSkippableCallback mForgetSkippableCB;
 
     nsCOMPtr<nsIMemoryReporter> mReporter;
 
     nsPurpleBuffer mPurpleBuf;
 
     uint32_t mUnmergedNeeded;
@@ -960,32 +954,32 @@ public:
 
     void Suspect(void *n, nsCycleCollectionParticipant *cp,
                  nsCycleCollectingAutoRefCnt *aRefCnt);
     uint32_t SuspectedCount();
     void ForgetSkippable(bool aRemoveChildlessNodes, bool aAsyncSnowWhiteFreeing);
     bool FreeSnowWhite(bool aUntilNoSWInPurpleBuffer);
 
     bool Collect(ccType aCCType,
-                 nsCycleCollectorResults *aResults,
                  nsICycleCollectorListener *aManualListener);
     void Shutdown();
 
     void SizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
                              size_t *aObjectSize,
                              size_t *aGraphNodesSize,
                              size_t *aGraphEdgesSize,
                              size_t *aWeakMapsSize,
                              size_t *aPurpleBufferSize) const;
 
 private:
     void CheckThreadSafety();
     void ShutdownCollect();
 
-    void PrepareForCollection(nsCycleCollectorResults *aResults);
+    void PrepareForCollection();
+
     void FixGrayBits(bool aForceGC);
     bool ShouldMergeZones(ccType aCCType);
 
     void BeginCollection(ccType aCCType, nsICycleCollectorListener *aManualListener);
     void MarkRoots();
     void ScanRoots();
     void ScanWeakMaps();
 
@@ -1560,32 +1554,32 @@ static PLDHashTableOps PtrNodeOps = {
     PL_DHashFinalizeStub,
     nullptr
 };
 
 class GCGraphBuilder : public nsCycleCollectionTraversalCallback,
                        public nsCycleCollectionNoteRootCallback
 {
 private:
-    nsCycleCollector *mCollector;
+    CycleCollectorResults &mResults;
     NodePool::Builder mNodeBuilder;
     EdgePool::Builder mEdgeBuilder;
     nsTArray<WeakMapping> &mWeakMaps;
     PLDHashTable mPtrToNodeMap;
     PtrInfo *mCurrPi;
     nsCycleCollectionParticipant *mJSParticipant;
     nsCycleCollectionParticipant *mJSZoneParticipant;
     nsCString mNextEdgeName;
     nsICycleCollectorListener *mListener;
     bool mMergeZones;
     bool mRanOutOfMemory;
 
 public:
-    GCGraphBuilder(nsCycleCollector *aCollector,
-                   GCGraph &aGraph,
+    GCGraphBuilder(GCGraph &aGraph,
+                   CycleCollectorResults &aResults,
                    CycleCollectedJSRuntime *aJSRuntime,
                    nsICycleCollectorListener *aListener,
                    bool aMergeZones);
     virtual ~GCGraphBuilder();
 
     bool WantAllTraces() const
     {
         return nsCycleCollectionNoteRootCallback::WantAllTraces();
@@ -1657,22 +1651,22 @@ private:
         JS::Zone *zone = JS::GetGCThingZone(gcthing);
         if (js::IsSystemZone(zone)) {
             return nullptr;
         }
         return zone;
     }
 };
 
-GCGraphBuilder::GCGraphBuilder(nsCycleCollector *aCollector,
-                               GCGraph &aGraph,
+GCGraphBuilder::GCGraphBuilder(GCGraph &aGraph,
+                               CycleCollectorResults &aResults,
                                CycleCollectedJSRuntime *aJSRuntime,
                                nsICycleCollectorListener *aListener,
                                bool aMergeZones)
-    : mCollector(aCollector),
+    : mResults(aResults),
       mNodeBuilder(aGraph.mNodes),
       mEdgeBuilder(aGraph.mEdges),
       mWeakMaps(aGraph.mWeakMaps),
       mJSParticipant(nullptr),
       mJSZoneParticipant(nullptr),
       mListener(aListener),
       mMergeZones(aMergeZones),
       mRanOutOfMemory(false)
@@ -1785,32 +1779,32 @@ GCGraphBuilder::NoteNativeRoot(void *roo
 
 NS_IMETHODIMP_(void)
 GCGraphBuilder::DescribeRefCountedNode(nsrefcnt refCount, const char *objName)
 {
     if (refCount == 0)
         Fault("zero refcount", mCurrPi);
     if (refCount == UINT32_MAX)
         Fault("overflowing refcount", mCurrPi);
-    mCollector->mVisitedRefCounted++;
+    mResults.mVisitedRefCounted++;
 
     if (mListener) {
         mListener->NoteRefCountedObject((uint64_t)mCurrPi->mPointer, refCount,
                                         objName);
     }
 
     DescribeNode(refCount, objName);
 }
 
 NS_IMETHODIMP_(void)
 GCGraphBuilder::DescribeGCedNode(bool isMarked, const char *objName,
                                  uint64_t aCompartmentAddress)
 {
     uint32_t refCount = isMarked ? UINT32_MAX : 0;
-    mCollector->mVisitedGCed++;
+    mResults.mVisitedGCed++;
 
     if (mListener) {
         mListener->NoteGCedObject((uint64_t)mCurrPi->mPointer, isMarked,
                                   objName, aCompartmentAddress);
     }
 
     DescribeNode(refCount, objName);
 }
@@ -2386,20 +2380,18 @@ nsCycleCollector::CollectWhite()
                 ++numWhiteGCed;
             }
         }
     }
 
     uint32_t count = whiteNodes.Length();
     MOZ_ASSERT(numWhiteGCed <= count,
                "More freed GCed nodes than total freed nodes.");
-    if (mResults) {
-        mResults->mFreedRefCounted += count - numWhiteGCed;
-        mResults->mFreedGCed += numWhiteGCed;
-    }
+    mResults.mFreedRefCounted += count - numWhiteGCed;
+    mResults.mFreedGCed += numWhiteGCed;
 
     timeLog.Checkpoint("CollectWhite::Root");
 
     if (mBeforeUnlinkCB) {
         mBeforeUnlinkCB();
         timeLog.Checkpoint("CollectWhite::BeforeUnlinkCB");
     }
 
@@ -2496,22 +2488,19 @@ class CycleCollectorReporter MOZ_FINAL :
 
 ////////////////////////////////////////////////////////////////////////
 // Collector implementation
 ////////////////////////////////////////////////////////////////////////
 
 nsCycleCollector::nsCycleCollector() :
     mCollectionInProgress(false),
     mScanInProgress(false),
-    mResults(nullptr),
     mJSRuntime(nullptr),
     mThread(NS_GetCurrentThread()),
     mWhiteNodeCount(0),
-    mVisitedRefCounted(0),
-    mVisitedGCed(0),
     mBeforeUnlinkCB(nullptr),
     mForgetSkippableCB(nullptr),
     mReporter(nullptr),
     mUnmergedNeeded(0),
     mMergedInARow(0)
 {
 }
 
@@ -2611,42 +2600,37 @@ nsCycleCollector::FixGrayBits(bool aForc
     if (!aForceGC) {
         mJSRuntime->FixWeakMappingGrayBits();
 
         bool needGC = mJSRuntime->NeedCollect();
         // Only do a telemetry ping for non-shutdown CCs.
         CC_TELEMETRY(_NEED_GC, needGC);
         if (!needGC)
             return;
-        if (mResults)
-            mResults->mForcedGC = true;
+        mResults.mForcedGC = true;
     }
 
     TimeLog timeLog;
     mJSRuntime->Collect(aForceGC ? JS::gcreason::SHUTDOWN_CC : JS::gcreason::CC_FORCED);
     timeLog.Checkpoint("GC()");
 }
 
 void
-nsCycleCollector::PrepareForCollection(nsCycleCollectorResults *aResults)
+nsCycleCollector::PrepareForCollection()
 {
     TimeLog timeLog;
 
     mCollectionStart = TimeStamp::Now();
-    mVisitedRefCounted = 0;
-    mVisitedGCed = 0;
 
     mCollectionInProgress = true;
 
     if (mJSRuntime) {
-        mJSRuntime->PrepareForCollection();
+        mJSRuntime->BeginCycleCollectionCallback();
     }
 
-    mResults = aResults;
-
     timeLog.Checkpoint("PrepareForCollection()");
 }
 
 void
 nsCycleCollector::CleanupAfterCollection()
 {
     mGraph.Clear();
     mCollectionInProgress = false;
@@ -2656,61 +2640,54 @@ nsCycleCollector::CleanupAfterCollection
     // force the C library to give back as much memory to the system as
     // possible.
     _heapmin();
 #endif
 
     uint32_t interval = (uint32_t) ((TimeStamp::Now() - mCollectionStart).ToMilliseconds());
 #ifdef COLLECT_TIME_DEBUG
     printf("cc: total cycle collector time was %ums\n", interval);
-    if (mResults) {
-        printf("cc: visited %u ref counted and %u GCed objects, freed %d ref counted and %d GCed objects.\n",
-               mVisitedRefCounted, mVisitedGCed,
-               mResults->mFreedRefCounted, mResults->mFreedGCed);
-    } else {
-        printf("cc: visited %u ref counted and %u GCed objects, freed %d.\n",
-               mVisitedRefCounted, mVisitedGCed, mWhiteNodeCount);
-    }
+    printf("cc: visited %u ref counted and %u GCed objects, freed %d ref counted and %d GCed objects.\n",
+           mResults.mVisitedRefCounted, mResults.mVisitedGCed,
+           mResults.mFreedRefCounted, mResults.mFreedGCed);
     printf("cc: \n");
 #endif
-    if (mResults) {
-        mResults->mVisitedRefCounted = mVisitedRefCounted;
-        mResults->mVisitedGCed = mVisitedGCed;
-        mResults = nullptr;
+    CC_TELEMETRY( , interval);
+    CC_TELEMETRY(_VISITED_REF_COUNTED, mResults.mVisitedRefCounted);
+    CC_TELEMETRY(_VISITED_GCED, mResults.mVisitedGCed);
+    CC_TELEMETRY(_COLLECTED, mWhiteNodeCount);
+
+    if (mJSRuntime) {
+        mJSRuntime->EndCycleCollectionCallback(mResults);
     }
-    CC_TELEMETRY( , interval);
-    CC_TELEMETRY(_VISITED_REF_COUNTED, mVisitedRefCounted);
-    CC_TELEMETRY(_VISITED_GCED, mVisitedGCed);
-    CC_TELEMETRY(_COLLECTED, mWhiteNodeCount);
 }
 
 void
 nsCycleCollector::ShutdownCollect()
 {
     for (uint32_t i = 0; i < DEFAULT_SHUTDOWN_COLLECTIONS; ++i) {
         NS_ASSERTION(i < NORMAL_SHUTDOWN_COLLECTIONS, "Extra shutdown CC");
-        if (!Collect(ShutdownCC, nullptr, nullptr)) {
+        if (!Collect(ShutdownCC, nullptr)) {
             break;
         }
     }
 }
 
 bool
 nsCycleCollector::Collect(ccType aCCType,
-                          nsCycleCollectorResults *aResults,
                           nsICycleCollectorListener *aManualListener)
 {
     CheckThreadSafety();
 
     // This can legitimately happen in a few cases. See bug 383651.
     if (mCollectionInProgress) {
         return false;
     }
 
-    PrepareForCollection(aResults);
+    PrepareForCollection();
     BeginCollection(aCCType, aManualListener);
     MarkRoots();
     ScanRoots();
     bool collectedAny = CollectWhite();
     CleanupAfterCollection();
     return collectedAny;
 }
 
@@ -2781,23 +2758,22 @@ nsCycleCollector::BeginCollection(ccType
 
     FreeSnowWhite(true);
 
     if (mListener && NS_FAILED(mListener->Begin())) {
         mListener = nullptr;
     }
 
     // Set up the data structures for building the graph.
+    mResults.Init();
     bool mergeZones = ShouldMergeZones(aCCType);
-    if (mResults) {
-        mResults->mMergedZones = mergeZones;
-    }
+    mResults.mMergedZones = mergeZones;
 
     MOZ_ASSERT(!mBuilder, "Forgot to clear mBuilder");
-    mBuilder = new GCGraphBuilder(this, mGraph, mJSRuntime, mListener, mergeZones);
+    mBuilder = new GCGraphBuilder(mGraph, mResults, mJSRuntime, mListener, mergeZones);
 
     if (mJSRuntime) {
         mJSRuntime->BeginCycleCollection(*mBuilder);
         timeLog.Checkpoint("mJSRuntime->BeginCycleCollection()");
     }
 
     AutoRestore<bool> ar(mScanInProgress);
     MOZ_ASSERT(!mScanInProgress);
@@ -2845,17 +2821,16 @@ nsCycleCollector::SizeOfIncludingThis(Ma
     *aObjectSize = aMallocSizeOf(this);
 
     mGraph.SizeOfExcludingThis(aMallocSizeOf, aGraphNodesSize, aGraphEdgesSize,
                                aWeakMapsSize);
 
     *aPurpleBufferSize = mPurpleBuf.SizeOfExcludingThis(aMallocSizeOf);
 
     // These fields are deliberately not measured:
-    // - mResults: because it's tiny and only contains scalars.
     // - mJSRuntime: because it's non-owning and measured by JS reporters.
     // - mParams: because it only contains scalars.
 }
 
 ////////////////////////////////////////////////////////////////////////
 // Module public API (exported in nsCycleCollector.h)
 // Just functions that redirect into the singleton, once it's built.
 ////////////////////////////////////////////////////////////////////////
@@ -3153,30 +3128,28 @@ nsCycleCollector_doDeferredDeletion()
     MOZ_ASSERT(data->mCollector);
     MOZ_ASSERT(data->mRuntime);
 
     return data->mCollector->FreeSnowWhite(false);
 }
 
 void
 nsCycleCollector_collect(bool aManuallyTriggered,
-                         nsCycleCollectorResults *aResults,
                          nsICycleCollectorListener *aManualListener)
 {
     CollectorData *data = sCollectorData.get();
 
     // We should have started the cycle collector by now.
     MOZ_ASSERT(data);
     MOZ_ASSERT(data->mCollector);
 
     PROFILER_LABEL("CC", "nsCycleCollector_collect");
 
     MOZ_ASSERT_IF(aManualListener, aManuallyTriggered);
-    data->mCollector->Collect(aManuallyTriggered ? ManualCC : ScheduledCC,
-                              aResults, aManualListener);
+    data->mCollector->Collect(aManuallyTriggered ? ManualCC : ScheduledCC, aManualListener);
 }
 
 void
 nsCycleCollector_shutdown()
 {
     CollectorData *data = sCollectorData.get();
 
     if (data) {
--- a/xpcom/base/nsCycleCollector.h
+++ b/xpcom/base/nsCycleCollector.h
@@ -17,32 +17,16 @@ namespace mozilla {
 class CycleCollectedJSRuntime;
 
 // See the comments in nsContentUtils.h for explanations of these functions.
 typedef void* (*DeferredFinalizeAppendFunction)(void* pointers, void* thing);
 typedef bool (*DeferredFinalizeFunction)(uint32_t slice, void* data);
 
 }
 
-// Contains various stats about the cycle collection.
-class nsCycleCollectorResults
-{
-public:
-    nsCycleCollectorResults() :
-        mForcedGC(false), mMergedZones(false),
-        mVisitedRefCounted(0), mVisitedGCed(0),
-        mFreedRefCounted(0), mFreedGCed(0) {}
-    bool mForcedGC;
-    bool mMergedZones;
-    uint32_t mVisitedRefCounted;
-    uint32_t mVisitedGCed;
-    uint32_t mFreedRefCounted;
-    uint32_t mFreedGCed;
-};
-
 bool nsCycleCollector_init();
 
 void nsCycleCollector_startup();
 
 typedef void (*CC_BeforeUnlinkCallback)(void);
 void nsCycleCollector_setBeforeUnlinkCallback(CC_BeforeUnlinkCallback aCB);
 
 typedef void (*CC_ForgetSkippableCallback)(void);
@@ -50,17 +34,16 @@ void nsCycleCollector_setForgetSkippable
 
 void nsCycleCollector_forgetSkippable(bool aRemoveChildlessNodes = false,
                                       bool aAsyncSnowWhiteFreeing = false);
 
 void nsCycleCollector_dispatchDeferredDeletion(bool aContinuation = false);
 bool nsCycleCollector_doDeferredDeletion();
 
 void nsCycleCollector_collect(bool aManuallyTriggered,
-                              nsCycleCollectorResults *aResults,
                               nsICycleCollectorListener *aManualListener);
 uint32_t nsCycleCollector_suspectedCount();
 void nsCycleCollector_shutdown();
 
 // Helpers for interacting with JS
 void nsCycleCollector_registerJSRuntime(mozilla::CycleCollectedJSRuntime *aRt);
 void nsCycleCollector_forgetJSRuntime();