Bug 1376614 - Pass budget to forgetSkippable and return early when budget has been used, r=mccr8
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Fri, 30 Jun 2017 13:44:59 +0300
changeset 366792 6487315302782c3058bf522c14a6fe8e2fe9d05d
parent 366791 cd4549c335752aca80ac293015d0b85ada94c332
child 366938 d865e836f74aa1d4acf349c0a013895201ccada6
push id92070
push useropettay@mozilla.com
push dateFri, 30 Jun 2017 10:46:05 +0000
treeherdermozilla-inbound@648731530278 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmccr8
bugs1376614
milestone56.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 1376614 - Pass budget to forgetSkippable and return early when budget has been used, r=mccr8
dom/base/nsJSEnvironment.cpp
xpcom/base/nsCycleCollector.cpp
xpcom/base/nsCycleCollector.h
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -1436,17 +1436,23 @@ FireForgetSkippable(uint32_t aSuspected,
 {
   AutoProfilerTracing
     tracing("CC", aDeadline.IsNull() ? "ForgetSkippable" : "IdleForgetSkippable");
   PRTime startTime = PR_Now();
   TimeStamp startTimeStamp = TimeStamp::Now();
   FinishAnyIncrementalGC();
   bool earlyForgetSkippable =
     sCleanupsSinceLastGC < NS_MAJOR_FORGET_SKIPPABLE_CALLS;
-  nsCycleCollector_forgetSkippable(aRemoveChildless, earlyForgetSkippable);
+
+  int64_t budgetMs = aDeadline.IsNull() ?
+    kForgetSkippableSliceDuration :
+    int64_t((aDeadline - TimeStamp::Now()).ToMilliseconds());
+  js::SliceBudget budget = js::SliceBudget(js::TimeBudget(budgetMs));
+  nsCycleCollector_forgetSkippable(budget, aRemoveChildless, earlyForgetSkippable);
+
   sPreviousSuspectedCount = nsCycleCollector_suspectedCount();
   ++sCleanupsSinceLastGC;
   PRTime delta = PR_Now() - startTime;
   if (sMinForgetSkippableTime > delta) {
     sMinForgetSkippableTime = delta;
   }
   if (sMaxForgetSkippableTime < delta) {
     sMaxForgetSkippableTime = delta;
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -1069,30 +1069,34 @@ public:
     auto revIter = mEntries.IterFromLast();
     auto iter = mEntries.Iter();
      // After iteration this points to the first empty entry.
     auto firstEmptyIter = mEntries.Iter();
     auto iterFromLastEntry = mEntries.IterFromLast();
     for (; !iter.Done(); iter.Next()) {
       nsPurpleBufferEntry& e = iter.Get();
       if (e.mObject) {
-        aVisitor.Visit(*this, &e);
+        if (!aVisitor.Visit(*this, &e)) {
+          return;
+        }
       }
 
       // Visit call above may have cleared the entry, or the entry was empty
       // already.
       if (!e.mObject) {
         // Try to find a non-empty entry from the end of the vector.
         for (; !revIter.Done(); revIter.Prev()) {
           nsPurpleBufferEntry& otherEntry = revIter.Get();
           if (&e == &otherEntry) {
             break;
           }
           if (otherEntry.mObject) {
-            aVisitor.Visit(*this, &otherEntry);
+            if (!aVisitor.Visit(*this, &otherEntry)) {
+              return;
+            }
             // Visit may have cleared otherEntry.
             if (otherEntry.mObject) {
               e.Swap(otherEntry);
               revIter.Prev(); // We've swapped this now empty entry.
               break;
             }
           }
         }
@@ -1144,16 +1148,17 @@ public:
   // RemoveSkippable removes entries from the purple buffer synchronously
   // (1) if aAsyncSnowWhiteFreeing is false and nsPurpleBufferEntry::mRefCnt is 0 or
   // (2) if the object's nsXPCOMCycleCollectionParticipant::CanSkip() returns true or
   // (3) if nsPurpleBufferEntry::mRefCnt->IsPurple() is false.
   // (4) If removeChildlessNodes is true, then any nodes in the purple buffer
   //     that will have no children in the cycle collector graph will also be
   //     removed. CanSkip() may be run on these children.
   void RemoveSkippable(nsCycleCollector* aCollector,
+                       js::SliceBudget& aBudget,
                        bool aRemoveChildlessNodes,
                        bool aAsyncSnowWhiteFreeing,
                        CC_ForgetSkippableCallback aCb);
 
   MOZ_ALWAYS_INLINE void Put(void* aObject, nsCycleCollectionParticipant* aCp,
                              nsCycleCollectingAutoRefCnt* aRefCnt)
   {
     nsPurpleBufferEntry entry(aObject, aRefCnt, aCp);
@@ -1186,26 +1191,27 @@ AddPurpleRoot(CCGraphBuilder& aBuilder, 
 
 struct SelectPointersVisitor
 {
   explicit SelectPointersVisitor(CCGraphBuilder& aBuilder)
     : mBuilder(aBuilder)
   {
   }
 
-  void
+  bool
   Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry)
   {
     MOZ_ASSERT(aEntry->mObject, "Null object in purple buffer");
     MOZ_ASSERT(aEntry->mRefCnt->get() != 0,
                "SelectPointersVisitor: snow-white object in the purple buffer");
     if (!aEntry->mRefCnt->IsPurple() ||
         AddPurpleRoot(mBuilder, aEntry->mObject, aEntry->mParticipant)) {
       aBuffer.Remove(aEntry);
     }
+    return true;
   }
 
 private:
   CCGraphBuilder& mBuilder;
 };
 
 void
 nsPurpleBuffer::SelectPointers(CCGraphBuilder& aBuilder)
@@ -1300,17 +1306,18 @@ public:
   {
     CheckThreadSafety();
     mForgetSkippableCB = aForgetSkippableCB;
   }
 
   void Suspect(void* aPtr, nsCycleCollectionParticipant* aCp,
                nsCycleCollectingAutoRefCnt* aRefCnt);
   uint32_t SuspectedCount();
-  void ForgetSkippable(bool aRemoveChildlessNodes, bool aAsyncSnowWhiteFreeing);
+  void ForgetSkippable(js::SliceBudget& aBudget, bool aRemoveChildlessNodes,
+                       bool aAsyncSnowWhiteFreeing);
   bool FreeSnowWhite(bool aUntilNoSWInPurpleBuffer);
 
   // This method assumes its argument is already canonicalized.
   void RemoveObjectFromGraph(void* aPtr);
 
   void PrepareForGarbageCollection();
   void FinishAnyCurrentCollection();
 
@@ -2651,28 +2658,29 @@ public:
           JS::AutoEnterCycleCollection autocc(mCollector->Runtime()->Runtime());
           o.mParticipant->Trace(o.mPointer, *this, nullptr);
         }
         o.mParticipant->DeleteCycleCollectable(o.mPointer);
       }
     }
   }
 
-  void
+  bool
   Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry)
   {
     MOZ_ASSERT(aEntry->mObject, "Null object in purple buffer");
     if (!aEntry->mRefCnt->get()) {
       void* o = aEntry->mObject;
       nsCycleCollectionParticipant* cp = aEntry->mParticipant;
       CanonicalizeParticipant(&o, &cp);
       SnowWhiteObject swo = { o, cp, aEntry->mRefCnt };
       mObjects.InfallibleAppend(swo);
       aBuffer.Remove(aEntry);
     }
+    return true;
   }
 
   bool HasSnowWhiteObjects() const
   {
     return !mObjects.IsEmpty();
   }
 
   virtual void Trace(JS::Heap<JS::Value>* aValue, const char* aName,
@@ -2735,20 +2743,22 @@ private:
   RefPtr<nsCycleCollector> mCollector;
   ObjectsVector mObjects;
 };
 
 class RemoveSkippableVisitor : public SnowWhiteKiller
 {
 public:
   RemoveSkippableVisitor(nsCycleCollector* aCollector,
+                         js::SliceBudget& aBudget,
                          bool aRemoveChildlessNodes,
                          bool aAsyncSnowWhiteFreeing,
                          CC_ForgetSkippableCallback aCb)
     : SnowWhiteKiller(aCollector)
+    , mBudget(aBudget)
     , mRemoveChildlessNodes(aRemoveChildlessNodes)
     , mAsyncSnowWhiteFreeing(aAsyncSnowWhiteFreeing)
     , mDispatchedDeferredDeletion(false)
     , mCallback(aCb)
   {
   }
 
   ~RemoveSkippableVisitor()
@@ -2759,53 +2769,63 @@ public:
       mCallback();
     }
     if (HasSnowWhiteObjects()) {
       // Effectively a continuation.
       nsCycleCollector_dispatchDeferredDeletion(true);
     }
   }
 
-  void
+  bool
   Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry)
   {
+    if (mBudget.isOverBudget()) {
+      return false;
+    }
+
+    // CanSkip calls can be a bit slow, so increase the likelihood that
+    // isOverBudget actually checks whether we're over the time budget.
+    mBudget.step(5);
     MOZ_ASSERT(aEntry->mObject, "null mObject in purple buffer");
     if (!aEntry->mRefCnt->get()) {
       if (!mAsyncSnowWhiteFreeing) {
         SnowWhiteKiller::Visit(aBuffer, aEntry);
       } else if (!mDispatchedDeferredDeletion) {
         mDispatchedDeferredDeletion = true;
         nsCycleCollector_dispatchDeferredDeletion(false);
       }
-      return;
+      return true;
     }
     void* o = aEntry->mObject;
     nsCycleCollectionParticipant* cp = aEntry->mParticipant;
     CanonicalizeParticipant(&o, &cp);
     if (aEntry->mRefCnt->IsPurple() && !cp->CanSkip(o, false) &&
         (!mRemoveChildlessNodes || MayHaveChild(o, cp))) {
-      return;
+      return true;
     }
     aBuffer.Remove(aEntry);
+    return true;
   }
 
 private:
+  js::SliceBudget& mBudget;
   bool mRemoveChildlessNodes;
   bool mAsyncSnowWhiteFreeing;
   bool mDispatchedDeferredDeletion;
   CC_ForgetSkippableCallback mCallback;
 };
 
 void
 nsPurpleBuffer::RemoveSkippable(nsCycleCollector* aCollector,
+                                js::SliceBudget& aBudget,
                                 bool aRemoveChildlessNodes,
                                 bool aAsyncSnowWhiteFreeing,
                                 CC_ForgetSkippableCallback aCb)
 {
-  RemoveSkippableVisitor visitor(aCollector, aRemoveChildlessNodes,
+  RemoveSkippableVisitor visitor(aCollector, aBudget, aRemoveChildlessNodes,
                                  aAsyncSnowWhiteFreeing, aCb);
   VisitEntries(visitor);
 }
 
 bool
 nsCycleCollector::FreeSnowWhite(bool aUntilNoSWInPurpleBuffer)
 {
   CheckThreadSafety();
@@ -2826,17 +2846,18 @@ nsCycleCollector::FreeSnowWhite(bool aUn
     if (!visitor.HasSnowWhiteObjects()) {
       break;
     }
   } while (aUntilNoSWInPurpleBuffer);
   return hadSnowWhiteObjects;
 }
 
 void
-nsCycleCollector::ForgetSkippable(bool aRemoveChildlessNodes,
+nsCycleCollector::ForgetSkippable(js::SliceBudget& aBudget,
+                                  bool aRemoveChildlessNodes,
                                   bool aAsyncSnowWhiteFreeing)
 {
   CheckThreadSafety();
 
   mozilla::Maybe<mozilla::AutoGlobalTimelineMarker> marker;
   if (NS_IsMainThread()) {
     marker.emplace("nsCycleCollector::ForgetSkippable", MarkerStackRequest::NO_STACK);
   }
@@ -2845,17 +2866,17 @@ nsCycleCollector::ForgetSkippable(bool a
   // lose track of an object that was mutated during graph building.
   MOZ_ASSERT(IsIdle());
 
   if (mCCJSRuntime) {
     mCCJSRuntime->PrepareForForgetSkippable();
   }
   MOZ_ASSERT(!mScanInProgress,
              "Don't forget skippable or free snow-white while scan is in progress.");
-  mPurpleBuf.RemoveSkippable(this, aRemoveChildlessNodes,
+  mPurpleBuf.RemoveSkippable(this, aBudget, aRemoveChildlessNodes,
                              aAsyncSnowWhiteFreeing, mForgetSkippableCB);
 }
 
 MOZ_NEVER_INLINE void
 nsCycleCollector::MarkRoots(SliceBudget& aBudget)
 {
   JS::AutoAssertNoGC nogc;
   TimeLog timeLog;
@@ -2968,42 +2989,43 @@ class PurpleScanBlackVisitor
 {
 public:
   PurpleScanBlackVisitor(CCGraph& aGraph, nsCycleCollectorLogger* aLogger,
                          uint32_t& aCount, bool& aFailed)
     : mGraph(aGraph), mLogger(aLogger), mCount(aCount), mFailed(aFailed)
   {
   }
 
-  void
+  bool
   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.");
 
     void* obj = aEntry->mObject;
     if (!aEntry->mParticipant) {
       obj = CanonicalizeXPCOMParticipant(static_cast<nsISupports*>(obj));
       MOZ_ASSERT(obj, "Don't add objects that don't participate in collection!");
     }
 
     PtrInfo* pi = mGraph.FindNode(obj);
     if (!pi) {
-      return;
+      return true;
     }
     MOZ_ASSERT(pi->mParticipant, "No dead objects should be in the purple buffer.");
     if (MOZ_UNLIKELY(mLogger)) {
       mLogger->NoteIncrementalRoot((uint64_t)pi->mPointer);
     }
     if (pi->mColor == black) {
-      return;
+      return true;
     }
     FloodBlackNode(mCount, mFailed, pi);
+    return true;
   }
 
 private:
   CCGraph& mGraph;
   RefPtr<nsCycleCollectorLogger> mLogger;
   uint32_t& mCount;
   bool& mFailed;
 };
@@ -4119,29 +4141,31 @@ nsCycleCollector_setForgetSkippableCallb
   // We should have started the cycle collector by now.
   MOZ_ASSERT(data);
   MOZ_ASSERT(data->mCollector);
 
   data->mCollector->SetForgetSkippableCallback(aCB);
 }
 
 void
-nsCycleCollector_forgetSkippable(bool aRemoveChildlessNodes,
+nsCycleCollector_forgetSkippable(js::SliceBudget& aBudget,
+                                 bool aRemoveChildlessNodes,
                                  bool aAsyncSnowWhiteFreeing)
 {
   CollectorData* data = sCollectorData.get();
 
   // We should have started the cycle collector by now.
   MOZ_ASSERT(data);
   MOZ_ASSERT(data->mCollector);
 
   AUTO_PROFILER_LABEL("nsCycleCollector_forgetSkippable", CC);
 
   TimeLog timeLog;
-  data->mCollector->ForgetSkippable(aRemoveChildlessNodes,
+  data->mCollector->ForgetSkippable(aBudget,
+                                    aRemoveChildlessNodes,
                                     aAsyncSnowWhiteFreeing);
   timeLog.Checkpoint("ForgetSkippable()");
 }
 
 void
 nsCycleCollector_dispatchDeferredDeletion(bool aContinuation, bool aPurge)
 {
   CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get();
--- a/xpcom/base/nsCycleCollector.h
+++ b/xpcom/base/nsCycleCollector.h
@@ -26,17 +26,18 @@ bool nsCycleCollector_init();
 void nsCycleCollector_startup();
 
 typedef void (*CC_BeforeUnlinkCallback)(void);
 void nsCycleCollector_setBeforeUnlinkCallback(CC_BeforeUnlinkCallback aCB);
 
 typedef void (*CC_ForgetSkippableCallback)(void);
 void nsCycleCollector_setForgetSkippableCallback(CC_ForgetSkippableCallback aCB);
 
-void nsCycleCollector_forgetSkippable(bool aRemoveChildlessNodes = false,
+void nsCycleCollector_forgetSkippable(js::SliceBudget& aBudget,
+                                      bool aRemoveChildlessNodes = false,
                                       bool aAsyncSnowWhiteFreeing = false);
 
 void nsCycleCollector_prepareForGarbageCollection();
 
 // If an incremental cycle collection is in progress, finish it.
 void nsCycleCollector_finishAnyCurrentCollection();
 
 void nsCycleCollector_dispatchDeferredDeletion(bool aContinuation = false,