Bug 948755 - Log incremental cycle collector roots. r=smaug
authorAndrew McCreight <continuation@gmail.com>
Fri, 14 Mar 2014 16:07:07 -0700
changeset 190910 0a1597e03bdeb3cf65916ab77a0c9fb5aba891a4
parent 190909 6b45e3aa3fda4b435c3705cdf61d11d678feb323
child 190911 b6bf2687d93fd3322dd318b158189956fc524b9c
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs948755
milestone30.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 948755 - Log incremental cycle collector roots. r=smaug
xpcom/base/nsCycleCollector.cpp
xpcom/base/nsICycleCollectorListener.idl
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -1569,16 +1569,24 @@ public:
     {
         if (!mDisableLog) {
             fprintf(mStream, "WeakMapEntry map=%p key=%p keyDelegate=%p value=%p\n",
                     (void*)aMap, (void*)aKey, (void*)aKeyDelegate, (void*)aValue);
         }
         // We don't support after-processing for weak map entries.
         return NS_OK;
     }
+    NS_IMETHOD NoteIncrementalRoot(uint64_t aAddress)
+    {
+        if (!mDisableLog) {
+            fprintf(mStream, "IncrementalRoot %p\n", (void*)aAddress);
+        }
+        // We don't support after-processing for incremental roots.
+        return NS_OK;
+    }
     NS_IMETHOD BeginResults()
     {
         if (!mDisableLog) {
             fputs("==========\n", mStream);
         }
         return NS_OK;
     }
     NS_IMETHOD DescribeRoot(uint64_t aAddress, uint32_t aKnownEdges)
@@ -2702,18 +2710,19 @@ nsCycleCollector::ScanWeakMaps()
         CC_TELEMETRY(_OOM, true);
     }
 }
 
 // Flood black from any objects in the purple buffer that are in the CC graph.
 class PurpleScanBlackVisitor
 {
 public:
-    PurpleScanBlackVisitor(GCGraph &aGraph, uint32_t &aCount, bool &aFailed)
-        : mGraph(aGraph), mCount(aCount), mFailed(aFailed)
+    PurpleScanBlackVisitor(GCGraph &aGraph, nsICycleCollectorListener *aListener,
+                           uint32_t &aCount, bool &aFailed)
+        : mGraph(aGraph), mListener(aListener), mCount(aCount), mFailed(aFailed)
     {
     }
 
     void
     Visit(nsPurpleBuffer &aBuffer, nsPurpleBufferEntry *aEntry)
     {
         MOZ_ASSERT(aEntry->mObject, "Entries with null mObject shouldn't be in the purple buffer.");
         MOZ_ASSERT(aEntry->mRefCnt->get() != 0, "Snow-white objects shouldn't be in the purple buffer.");
@@ -2724,24 +2733,28 @@ public:
             MOZ_ASSERT(obj, "Don't add objects that don't participate in collection!");
         }
 
         PtrInfo *pi = mGraph.FindNode(obj);
         if (!pi) {
             return;
         }
         MOZ_ASSERT(pi->mParticipant, "No dead objects should be in the purple buffer.");
+        if (MOZ_UNLIKELY(mListener)) {
+            mListener->NoteIncrementalRoot((uint64_t)pi->mPointer);
+        }
         if (pi->mColor == black) {
             return;
         }
         GraphWalker<ScanBlackVisitor>(ScanBlackVisitor(mCount, mFailed)).Walk(pi);
     }
 
 private:
     GCGraph &mGraph;
+    nsICycleCollectorListener *mListener;
     uint32_t &mCount;
     bool &mFailed;
 };
 
 // Objects that have been stored somewhere since the start of incremental graph building must
 // be treated as live for this cycle collection, because we may not have accurate information
 // about who holds references to them.
 void
@@ -2754,50 +2767,69 @@ nsCycleCollector::ScanIncrementalRoots()
     // refcounted object is purple, it may have been AddRef'd during the current
     // ICC. (It may also have only been released.) If that is the case, we cannot
     // be sure that the set of things pointing to the object in the CC graph
     // is accurate. Therefore, for safety, we treat any purple objects as being
     // live during the current CC. We don't remove anything from the purple
     // buffer here, so these objects will be suspected and freed in the next CC
     // if they are garbage.
     bool failed = false;
-    PurpleScanBlackVisitor purpleScanBlackVisitor(mGraph, mWhiteNodeCount, failed);
+    PurpleScanBlackVisitor purpleScanBlackVisitor(mGraph, mListener, mWhiteNodeCount, failed);
     mPurpleBuf.VisitEntries(purpleScanBlackVisitor);
     timeLog.Checkpoint("ScanIncrementalRoots::fix purple");
 
     // Garbage collected objects:
     // If a GCed object was added to the graph with a refcount of zero, and is
     // now marked black by the GC, it was probably gray before and was exposed
     // to active JS, so it may have been stored somewhere, so it needs to be
     // treated as live.
     if (mJSRuntime) {
         nsCycleCollectionParticipant *jsParticipant = mJSRuntime->GCThingParticipant();
         nsCycleCollectionParticipant *zoneParticipant = mJSRuntime->ZoneParticipant();
         NodePool::Enumerator etor(mGraph.mNodes);
 
         while (!etor.IsDone()) {
             PtrInfo *pi = etor.GetNext();
 
-            if (pi->mRefCount != 0 || pi->mColor == black) {
+            // If the refcount is non-zero, pi can't have been a gray JS object.
+            if (pi->mRefCount != 0) {
                 continue;
             }
 
+            // As an optimization, if an object has already been determined to be live,
+            // don't consider it further.  We can't do this if there is a listener,
+            // because the listener wants to know the complete set of incremental roots.
+            if (pi->mColor == black && MOZ_LIKELY(!mListener)) {
+                continue;
+            }
+
+            // If the object is still marked gray by the GC, nothing could have gotten
+            // hold of it, so it isn't an incremental root.
             if (pi->mParticipant == jsParticipant) {
                 if (xpc_GCThingIsGrayCCThing(pi->mPointer)) {
                     continue;
                 }
             } else if (pi->mParticipant == zoneParticipant) {
                 JS::Zone *zone = static_cast<JS::Zone*>(pi->mPointer);
                 if (js::ZoneGlobalsAreAllGray(zone)) {
                     continue;
                 }
             } else {
                 MOZ_ASSERT(false, "Non-JS thing with 0 refcount? Treating as live.");
             }
 
+            // At this point, pi must be an incremental root.
+
+            // If there's a listener, tell it about this root. We don't bother with the
+            // optimization of skipping the Walk() if pi is black: it will just return
+            // without doing anything and there's no need to make this case faster.
+            if (MOZ_UNLIKELY(mListener)) {
+                mListener->NoteIncrementalRoot((uint64_t)pi->mPointer);
+            }
+
             GraphWalker<ScanBlackVisitor>(ScanBlackVisitor(mWhiteNodeCount, failed)).Walk(pi);
         }
 
         timeLog.Checkpoint("ScanIncrementalRoots::fix JS");
     }
 
     if (failed) {
         NS_ASSERTION(false, "Ran out of memory in ScanIncrementalRoots");
--- a/xpcom/base/nsICycleCollectorListener.idl
+++ b/xpcom/base/nsICycleCollectorListener.idl
@@ -21,26 +21,27 @@ interface nsICycleCollectorHandler : nsI
                       in unsigned long aKnownEdges);
     void describeGarbage(in ACString aAddress);
 };
 
 /** Interface to pass to the cycle collector to get information about
  * the CC graph while it's being built. The order of calls will be a
  * call to begin(); then for every node in the graph a call to either
  * noteRefCountedObject() or noteGCedObject(), followed by calls to
- * noteEdge() for every edge starting at that node; then a call to
- * beginResults(); then a mixture of describeRoot() for ref counted
+ * noteEdge() for every edge starting at that node. Then, there may
+ * be calls to noteIncrementalRoot(). After that, beginResults() will
+ * be called, followed by a mixture of describeRoot() for ref counted
  * nodes the CC has identified as roots and describeGarbage() for
  * nodes the CC has identified as garbage.  Ref counted nodes that are
  * not identified as either roots or garbage are neither, and have a
  * known edges count equal to their ref count.  Finally, there will be
  * a call to end().  If begin() returns an error none of the other
  * functions will be called.
  */
-[scriptable, builtinclass, uuid(786215b7-1e4b-433b-b617-96b176273601)]
+[scriptable, builtinclass, uuid(2d04dd00-abc4-11e3-a5e2-0800200c9a66)]
 interface nsICycleCollectorListener : nsISupports
 {
     nsICycleCollectorListener allTraces();
     // false if allTraces() has not been called.
     readonly attribute boolean wantAllTraces;
 
     // The default implementation of this interface will print out
     // a log to a file unless disableLog is set to true.
@@ -65,16 +66,21 @@ interface nsICycleCollectorListener : ns
 			 in string aObjectDescription,
 			 in unsigned long long aCompartmentAddress);
     void noteEdge(in unsigned long long aToAddress,
                   in string aEdgeName);
     void noteWeakMapEntry(in unsigned long long aMap,
                           in unsigned long long aKey,
                           in unsigned long long aKeyDelegate,
                           in unsigned long long aValue);
+    // An "incremental root" is an object that may have had a new
+    // reference to it created during an incremental collection,
+    // and must therefore be treated as live for safety.
+    void noteIncrementalRoot(in unsigned long long aAddress);
+
     void beginResults();
     void describeRoot(in unsigned long long aAddress,
 		      in unsigned long aKnownEdges);
     void describeGarbage(in unsigned long long aAddress);
     void end();
 
     // Returns false if there isn't anything more to process.
     boolean processNext(in nsICycleCollectorHandler aHandler);