forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			690 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			690 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "js/SliceBudget.h"
 | |
| #include "mozilla/ArrayUtils.h"
 | |
| #include "mozilla/CycleCollectedJSContext.h"
 | |
| #include "mozilla/MainThreadIdlePeriod.h"
 | |
| #include "mozilla/Telemetry.h"
 | |
| #include "mozilla/TimeStamp.h"
 | |
| #include "nsCycleCollector.h"
 | |
| #include "nsJSEnvironment.h"
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| static const TimeDuration kOneMinute = TimeDuration::FromSeconds(60.0f);
 | |
| 
 | |
| // The amount of time we wait between a request to CC (after GC ran)
 | |
| // and doing the actual CC.
 | |
| static const TimeDuration kCCDelay = TimeDuration::FromSeconds(6);
 | |
| 
 | |
| static const TimeDuration kCCSkippableDelay =
 | |
|     TimeDuration::FromMilliseconds(250);
 | |
| 
 | |
| // In case the cycle collector isn't run at all, we don't want forget skippables
 | |
| // to run too often. So limit the forget skippable cycle to start at earliest 2
 | |
| // seconds after the end of the previous cycle.
 | |
| static const TimeDuration kTimeBetweenForgetSkippableCycles =
 | |
|     TimeDuration::FromSeconds(2);
 | |
| 
 | |
| // ForgetSkippable is usually fast, so we can use small budgets.
 | |
| // This isn't a real budget but a hint to IdleTaskRunner whether there
 | |
| // is enough time to call ForgetSkippable.
 | |
| static const TimeDuration kForgetSkippableSliceDuration =
 | |
|     TimeDuration::FromMilliseconds(2);
 | |
| 
 | |
| // Maximum amount of time that should elapse between incremental CC slices
 | |
| static const TimeDuration kICCIntersliceDelay =
 | |
|     TimeDuration::FromMilliseconds(64);
 | |
| 
 | |
| // Time budget for an incremental CC slice when using timer to run it.
 | |
| static const TimeDuration kICCSliceBudget = TimeDuration::FromMilliseconds(3);
 | |
| // Minimum budget for an incremental CC slice when using idle time to run it.
 | |
| static const TimeDuration kIdleICCSliceBudget =
 | |
|     TimeDuration::FromMilliseconds(2);
 | |
| 
 | |
| // Maximum total duration for an ICC
 | |
| static const TimeDuration kMaxICCDuration = TimeDuration::FromSeconds(2);
 | |
| 
 | |
| // Force a CC after this long if there's more than NS_CC_FORCED_PURPLE_LIMIT
 | |
| // objects in the purple buffer.
 | |
| static const TimeDuration kCCForced = kOneMinute * 2;
 | |
| static const uint32_t kCCForcedPurpleLimit = 10;
 | |
| 
 | |
| // Don't allow an incremental GC to lock out the CC for too long.
 | |
| static const TimeDuration kMaxCCLockedoutTime = TimeDuration::FromSeconds(30);
 | |
| 
 | |
| // Trigger a CC if the purple buffer exceeds this size when we check it.
 | |
| static const uint32_t kCCPurpleLimit = 200;
 | |
| 
 | |
| // Actions performed by the GCRunner state machine.
 | |
| enum class GCRunnerAction {
 | |
|   MajorGC,  // Start a new major GC
 | |
|   GCSlice,  // Run a single slice of a major GC
 | |
|   None
 | |
| };
 | |
| 
 | |
| struct GCRunnerStep {
 | |
|   GCRunnerAction mAction;
 | |
|   JS::GCReason mReason;
 | |
| };
 | |
| 
 | |
| enum class CCRunnerAction {
 | |
|   None,
 | |
|   ForgetSkippable,
 | |
|   CleanupContentUnbinder,
 | |
|   CleanupDeferred,
 | |
|   CycleCollect,
 | |
|   StopRunning
 | |
| };
 | |
| 
 | |
| enum CCRunnerYield { Continue, Yield };
 | |
| 
 | |
| enum CCRunnerForgetSkippableRemoveChildless {
 | |
|   KeepChildless = false,
 | |
|   RemoveChildless = true
 | |
| };
 | |
| 
 | |
| struct CCRunnerStep {
 | |
|   // The action to scheduler is instructing the caller to perform.
 | |
|   CCRunnerAction mAction;
 | |
| 
 | |
|   // Whether to stop processing actions for this invocation of the timer
 | |
|   // callback.
 | |
|   CCRunnerYield mYield;
 | |
| 
 | |
|   // If the action is ForgetSkippable, then whether to remove childless nodes
 | |
|   // or not. (ForgetSkippable is the only action requiring a parameter; if
 | |
|   // that changes, this will become a union.)
 | |
|   CCRunnerForgetSkippableRemoveChildless mRemoveChildless;
 | |
| };
 | |
| 
 | |
| class CCGCScheduler {
 | |
|  public:
 | |
|   // Mockable functions to interface with the code being scheduled.
 | |
| 
 | |
|   // Current time. In real usage, this will just return TimeStamp::Now(), but
 | |
|   // tests can reimplement it to return a value controlled by the test.
 | |
|   static inline TimeStamp Now();
 | |
| 
 | |
|   // Number of entries in the purple buffer (those objects whose ref counts
 | |
|   // have been decremented since the previous CC, roughly), and are therefore
 | |
|   // "suspected" of being members of cyclic garbage.
 | |
|   static inline uint32_t SuspectedCCObjects();
 | |
| 
 | |
|   // Parameter setting
 | |
| 
 | |
|   void SetActiveIntersliceGCBudget(TimeDuration aDuration) {
 | |
|     mActiveIntersliceGCBudget = aDuration;
 | |
|   }
 | |
| 
 | |
|   // State retrieval
 | |
| 
 | |
|   TimeDuration GetCCBlockedTime(TimeStamp aNow) const {
 | |
|     MOZ_ASSERT(mInIncrementalGC);
 | |
|     MOZ_ASSERT(!mCCBlockStart.IsNull());
 | |
|     return aNow - mCCBlockStart;
 | |
|   }
 | |
| 
 | |
|   bool InIncrementalGC() const { return mInIncrementalGC; }
 | |
| 
 | |
|   TimeStamp GetLastCCEndTime() const { return mLastCCEndTime; }
 | |
| 
 | |
|   bool IsEarlyForgetSkippable(uint32_t aN = kMajorForgetSkippableCalls) const {
 | |
|     return mCleanupsSinceLastGC < aN;
 | |
|   }
 | |
| 
 | |
|   bool NeedsFullGC() const { return mNeedsFullGC; }
 | |
| 
 | |
|   // State modification
 | |
| 
 | |
|   void SetNeedsFullGC(bool aNeedGC = true) { mNeedsFullGC = aNeedGC; }
 | |
| 
 | |
|   void SetWantMajorGC(JS::GCReason aReason) {
 | |
|     if (mMajorGCReason == JS::GCReason::NO_REASON) {
 | |
|       // If there was already a reason for doing a major GC, do not override it
 | |
|       // with a newer reason. When looking at the profiler, the initial reason
 | |
|       // is probably more useful to know than any followup reasons while things
 | |
|       // are getting worse.
 | |
|       mMajorGCReason = aReason;
 | |
|     }
 | |
| 
 | |
|     // Force full GCs when called from reftests so that we collect dead zones
 | |
|     // that have not been scheduled for collection.
 | |
|     if (aReason == JS::GCReason::DOM_WINDOW_UTILS) {
 | |
|       SetNeedsFullGC();
 | |
|     }
 | |
|   }
 | |
|   // Ensure that the current runner does a cycle collection, and trigger a GC
 | |
|   // after it finishes.
 | |
|   void EnsureCCThenGC() {
 | |
|     MOZ_ASSERT(mCCRunnerState != CCRunnerState::Inactive);
 | |
|     mNeedsFullCC = true;
 | |
|     mNeedsGCAfterCC = true;
 | |
|   }
 | |
| 
 | |
|   void NoteGCBegin() {
 | |
|     // Treat all GC as incremental here; non-incremental GC will just appear to
 | |
|     // be one slice.
 | |
|     mInIncrementalGC = true;
 | |
|   }
 | |
| 
 | |
|   void NoteGCEnd() {
 | |
|     mInIncrementalGC = false;
 | |
|     mCCBlockStart = TimeStamp();
 | |
|     mInIncrementalGC = false;
 | |
|     mNeedsFullCC = true;
 | |
|     mHasRunGC = true;
 | |
| 
 | |
|     mCleanupsSinceLastGC = 0;
 | |
|     mCCollectedWaitingForGC = 0;
 | |
|     mCCollectedZonesWaitingForGC = 0;
 | |
|     mLikelyShortLivingObjectsNeedingGC = 0;
 | |
|   }
 | |
| 
 | |
|   // When we decide to do a cycle collection but we're in the middle of an
 | |
|   // incremental GC, the CC is "locked out" until the GC completes -- unless
 | |
|   // the wait is too long, and we decide to finish the incremental GC early.
 | |
|   void BlockCC(TimeStamp aNow) {
 | |
|     MOZ_ASSERT(mInIncrementalGC);
 | |
|     MOZ_ASSERT(mCCBlockStart.IsNull());
 | |
|     mCCBlockStart = aNow;
 | |
|   }
 | |
| 
 | |
|   void UnblockCC() { mCCBlockStart = TimeStamp(); }
 | |
| 
 | |
|   // Returns the number of purple buffer items that were processed and removed.
 | |
|   uint32_t NoteForgetSkippableComplete(
 | |
|       TimeStamp aNow, uint32_t aSuspectedBeforeForgetSkippable) {
 | |
|     mLastForgetSkippableEndTime = aNow;
 | |
|     uint32_t suspected = SuspectedCCObjects();
 | |
|     mPreviousSuspectedCount = suspected;
 | |
|     mCleanupsSinceLastGC++;
 | |
|     return aSuspectedBeforeForgetSkippable - suspected;
 | |
|   }
 | |
| 
 | |
|   // After collecting cycles, record the results that are used in scheduling
 | |
|   // decisions.
 | |
|   void NoteCycleCollected(const CycleCollectorResults& aResults) {
 | |
|     mCCollectedWaitingForGC += aResults.mFreedGCed;
 | |
|     mCCollectedZonesWaitingForGC += aResults.mFreedJSZones;
 | |
|   }
 | |
| 
 | |
|   // This is invoked when the whole process of collection is done -- i.e., CC
 | |
|   // preparation (eg ForgetSkippables), the CC itself, and the optional
 | |
|   // followup GC. There really ought to be a separate name for the overall CC
 | |
|   // as opposed to the actual cycle collection portion.
 | |
|   void NoteCCEnd(TimeStamp aWhen) {
 | |
|     mLastCCEndTime = aWhen;
 | |
|     mNeedsFullCC = false;
 | |
| 
 | |
|     // The GC for this CC has already been requested.
 | |
|     mNeedsGCAfterCC = false;
 | |
|   }
 | |
| 
 | |
|   // The CC was abandoned without running a slice, so we only did forget
 | |
|   // skippables. Prevent running another cycle soon.
 | |
|   void NoteForgetSkippableOnlyCycle() {
 | |
|     mLastForgetSkippableCycleEndTime = Now();
 | |
|   }
 | |
| 
 | |
|   void Shutdown() { mDidShutdown = true; }
 | |
| 
 | |
|   // Scheduling
 | |
| 
 | |
|   // Return a budget along with a boolean saying whether to prefer to run short
 | |
|   // slices and stop rather than continuing to the next phase of cycle
 | |
|   // collection.
 | |
|   inline js::SliceBudget ComputeCCSliceBudget(TimeStamp aDeadline,
 | |
|                                               TimeStamp aCCBeginTime,
 | |
|                                               TimeStamp aPrevSliceEndTime,
 | |
|                                               bool* aPreferShorterSlices) const;
 | |
| 
 | |
|   inline TimeDuration ComputeInterSliceGCBudget(TimeStamp aDeadline,
 | |
|                                                 TimeStamp aNow) const;
 | |
| 
 | |
|   bool ShouldForgetSkippable() const {
 | |
|     // Only do a forget skippable if there are more than a few new objects
 | |
|     // or we're doing the initial forget skippables.
 | |
|     return ((mPreviousSuspectedCount + 100) <= SuspectedCCObjects()) ||
 | |
|            mCleanupsSinceLastGC < kMajorForgetSkippableCalls;
 | |
|   }
 | |
| 
 | |
|   // There is reason to suspect that there may be a significant amount of
 | |
|   // garbage to cycle collect: either we just finished a GC, or the purple
 | |
|   // buffer is getting really big, or it's getting somewhat big and it has been
 | |
|   // too long since the last CC.
 | |
|   bool IsCCNeeded(TimeStamp aNow = Now()) const {
 | |
|     if (mNeedsFullCC) {
 | |
|       return true;
 | |
|     }
 | |
|     uint32_t suspected = SuspectedCCObjects();
 | |
|     return suspected > kCCPurpleLimit ||
 | |
|            (suspected > kCCForcedPurpleLimit && mLastCCEndTime &&
 | |
|             aNow - mLastCCEndTime > kCCForced);
 | |
|   }
 | |
| 
 | |
|   inline bool ShouldScheduleCC() const;
 | |
| 
 | |
|   // If we collected a substantial amount of cycles, poke the GC since more
 | |
|   // objects might be unreachable now.
 | |
|   bool NeedsGCAfterCC() const {
 | |
|     return mCCollectedWaitingForGC > 250 || mCCollectedZonesWaitingForGC > 0 ||
 | |
|            mLikelyShortLivingObjectsNeedingGC > 2500 || mNeedsGCAfterCC;
 | |
|   }
 | |
| 
 | |
|   bool IsLastEarlyCCTimer(int32_t aCurrentFireCount) const {
 | |
|     int32_t numEarlyTimerFires =
 | |
|         std::max(int32_t(mCCDelay / kCCSkippableDelay) - 2, 1);
 | |
| 
 | |
|     return aCurrentFireCount >= numEarlyTimerFires;
 | |
|   }
 | |
| 
 | |
|   enum class CCRunnerState {
 | |
|     Inactive,
 | |
|     ReducePurple,
 | |
|     CleanupChildless,
 | |
|     CleanupContentUnbinder,
 | |
|     CleanupDeferred,
 | |
|     StartCycleCollection,
 | |
|     CycleCollecting,
 | |
|     Canceled,
 | |
|     NumStates
 | |
|   };
 | |
| 
 | |
|   void InitCCRunnerStateMachine(CCRunnerState initialState) {
 | |
|     // The state machine should always have been deactivated after the previous
 | |
|     // collection, however far that collection may have gone.
 | |
|     MOZ_ASSERT(mCCRunnerState == CCRunnerState::Inactive,
 | |
|                "DeactivateCCRunner should have been called");
 | |
|     mCCRunnerState = initialState;
 | |
| 
 | |
|     // Currently, there are only two entry points to the non-Inactive part of
 | |
|     // the state machine.
 | |
|     if (initialState == CCRunnerState::ReducePurple) {
 | |
|       mCCDelay = kCCDelay;
 | |
|       mCCRunnerEarlyFireCount = 0;
 | |
|     } else if (initialState == CCRunnerState::CycleCollecting) {
 | |
|       // Nothing needed.
 | |
|     } else {
 | |
|       MOZ_CRASH("Invalid initial state");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   void DeactivateCCRunner() { mCCRunnerState = CCRunnerState::Inactive; }
 | |
| 
 | |
|   inline GCRunnerStep GetNextGCRunnerAction(TimeStamp aDeadline);
 | |
| 
 | |
|   inline CCRunnerStep GetNextCCRunnerAction(TimeStamp aDeadline);
 | |
| 
 | |
|   // aStartTimeStamp : when the ForgetSkippable timer fired. This may be some
 | |
|   // time ago, if an incremental GC needed to be finished.
 | |
|   js::SliceBudget ComputeForgetSkippableBudget(TimeStamp aStartTimeStamp,
 | |
|                                                TimeStamp aDeadline);
 | |
| 
 | |
|  private:
 | |
|   // State
 | |
| 
 | |
|   // An incremental GC is in progress, which blocks the CC from running for its
 | |
|   // duration (or until it goes too long and is finished synchronously.)
 | |
|   bool mInIncrementalGC = false;
 | |
| 
 | |
|   // When the CC started actually waiting for the GC to finish. This will be
 | |
|   // set to non-null at a later time than mCCLockedOut.
 | |
|   TimeStamp mCCBlockStart;
 | |
| 
 | |
|   bool mDidShutdown = false;
 | |
| 
 | |
|   TimeStamp mLastForgetSkippableEndTime;
 | |
|   uint32_t mForgetSkippableCounter = 0;
 | |
|   TimeStamp mForgetSkippableFrequencyStartTime;
 | |
|   TimeStamp mLastCCEndTime;
 | |
|   TimeStamp mLastForgetSkippableCycleEndTime;
 | |
| 
 | |
|   CCRunnerState mCCRunnerState = CCRunnerState::Inactive;
 | |
|   int32_t mCCRunnerEarlyFireCount = 0;
 | |
|   TimeDuration mCCDelay = kCCDelay;
 | |
| 
 | |
|   // Prevent the very first CC from running before we have GC'd and set the
 | |
|   // gray bits.
 | |
|   bool mHasRunGC = false;
 | |
| 
 | |
|   bool mNeedsFullCC = false;
 | |
|   bool mNeedsFullGC = true;
 | |
|   bool mNeedsGCAfterCC = false;
 | |
|   uint32_t mPreviousSuspectedCount = 0;
 | |
| 
 | |
|   JS::GCReason mMajorGCReason = JS::GCReason::NO_REASON;
 | |
| 
 | |
|   uint32_t mCleanupsSinceLastGC = UINT32_MAX;
 | |
| 
 | |
|  public:
 | |
|   uint32_t mCCollectedWaitingForGC = 0;
 | |
|   uint32_t mCCollectedZonesWaitingForGC = 0;
 | |
|   uint32_t mLikelyShortLivingObjectsNeedingGC = 0;
 | |
| 
 | |
|   // Configuration parameters
 | |
| 
 | |
|   TimeDuration mActiveIntersliceGCBudget = TimeDuration::FromMilliseconds(5);
 | |
| };
 | |
| 
 | |
| js::SliceBudget CCGCScheduler::ComputeCCSliceBudget(
 | |
|     TimeStamp aDeadline, TimeStamp aCCBeginTime, TimeStamp aPrevSliceEndTime,
 | |
|     bool* aPreferShorterSlices) const {
 | |
|   TimeStamp now = Now();
 | |
| 
 | |
|   *aPreferShorterSlices =
 | |
|       aDeadline.IsNull() || (aDeadline - now) < kICCSliceBudget;
 | |
| 
 | |
|   TimeDuration baseBudget =
 | |
|       aDeadline.IsNull() ? kICCSliceBudget : aDeadline - now;
 | |
| 
 | |
|   if (aCCBeginTime.IsNull()) {
 | |
|     // If no CC is in progress, use the standard slice time.
 | |
|     return js::SliceBudget(baseBudget);
 | |
|   }
 | |
| 
 | |
|   // Only run a limited slice if we're within the max running time.
 | |
|   MOZ_ASSERT(now >= aCCBeginTime);
 | |
|   TimeDuration runningTime = now - aCCBeginTime;
 | |
|   if (runningTime >= kMaxICCDuration) {
 | |
|     return js::SliceBudget::unlimited();
 | |
|   }
 | |
| 
 | |
|   const TimeDuration maxSlice =
 | |
|       TimeDuration::FromMilliseconds(MainThreadIdlePeriod::GetLongIdlePeriod());
 | |
| 
 | |
|   // Try to make up for a delay in running this slice.
 | |
|   MOZ_ASSERT(now >= aPrevSliceEndTime);
 | |
|   double sliceDelayMultiplier = (now - aPrevSliceEndTime) / kICCIntersliceDelay;
 | |
|   TimeDuration delaySliceBudget =
 | |
|       std::min(baseBudget.MultDouble(sliceDelayMultiplier), maxSlice);
 | |
| 
 | |
|   // Increase slice budgets up to |maxSlice| as we approach
 | |
|   // half way through the ICC, to avoid large sync CCs.
 | |
|   double percentToHalfDone =
 | |
|       std::min(2.0 * (runningTime / kMaxICCDuration), 1.0);
 | |
|   TimeDuration laterSliceBudget = maxSlice.MultDouble(percentToHalfDone);
 | |
| 
 | |
|   // Note: We may have already overshot the deadline, in which case
 | |
|   // baseBudget will be negative and we will end up returning
 | |
|   // laterSliceBudget.
 | |
|   return js::SliceBudget(
 | |
|       std::max({delaySliceBudget, laterSliceBudget, baseBudget}));
 | |
| }
 | |
| 
 | |
| inline TimeDuration CCGCScheduler::ComputeInterSliceGCBudget(
 | |
|     TimeStamp aDeadline, TimeStamp aNow) const {
 | |
|   // We use longer budgets when the CC has been locked out but the CC has
 | |
|   // tried to run since that means we may have a significant amount of
 | |
|   // garbage to collect and it's better to GC in several longer slices than
 | |
|   // in a very long one.
 | |
|   TimeDuration budget =
 | |
|       aDeadline.IsNull() ? mActiveIntersliceGCBudget * 2 : aDeadline - aNow;
 | |
|   if (!mCCBlockStart) {
 | |
|     return budget;
 | |
|   }
 | |
| 
 | |
|   TimeDuration blockedTime = aNow - mCCBlockStart;
 | |
|   TimeDuration maxSliceGCBudget = mActiveIntersliceGCBudget * 10;
 | |
|   double percentOfBlockedTime =
 | |
|       std::min(blockedTime / kMaxCCLockedoutTime, 1.0);
 | |
|   return std::max(budget, maxSliceGCBudget.MultDouble(percentOfBlockedTime));
 | |
| }
 | |
| 
 | |
| bool CCGCScheduler::ShouldScheduleCC() const {
 | |
|   if (!mHasRunGC) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   TimeStamp now = Now();
 | |
| 
 | |
|   // Don't run consecutive CCs too often.
 | |
|   if (mCleanupsSinceLastGC && !mLastCCEndTime.IsNull()) {
 | |
|     if (now - mLastCCEndTime < kCCDelay) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If GC hasn't run recently and forget skippable only cycle was run,
 | |
|   // don't start a new cycle too soon.
 | |
|   if ((mCleanupsSinceLastGC > kMajorForgetSkippableCalls) &&
 | |
|       !mLastForgetSkippableCycleEndTime.IsNull()) {
 | |
|     if (now - mLastForgetSkippableCycleEndTime <
 | |
|         kTimeBetweenForgetSkippableCycles) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return IsCCNeeded(now);
 | |
| }
 | |
| 
 | |
| CCRunnerStep CCGCScheduler::GetNextCCRunnerAction(TimeStamp aDeadline) {
 | |
|   struct StateDescriptor {
 | |
|     // When in this state, should we first check to see if we still have
 | |
|     // enough reason to CC?
 | |
|     bool mCanAbortCC;
 | |
| 
 | |
|     // If we do decide to abort the CC, should we still try to forget
 | |
|     // skippables one more time?
 | |
|     bool mTryFinalForgetSkippable;
 | |
|   };
 | |
| 
 | |
|   // The state descriptors for Inactive and Canceled will never actually be
 | |
|   // used. We will never call this function while Inactive, and Canceled is
 | |
|   // handled specially at the beginning.
 | |
|   constexpr StateDescriptor stateDescriptors[] = {
 | |
|       {false, false},  /* CCRunnerState::Inactive */
 | |
|       {false, false},  /* CCRunnerState::ReducePurple */
 | |
|       {true, true},    /* CCRunnerState::CleanupChildless */
 | |
|       {true, false},   /* CCRunnerState::CleanupContentUnbinder */
 | |
|       {false, false},  /* CCRunnerState::CleanupDeferred */
 | |
|       {false, false},  /* CCRunnerState::StartCycleCollection */
 | |
|       {false, false},  /* CCRunnerState::CycleCollecting */
 | |
|       {false, false}}; /* CCRunnerState::Canceled */
 | |
|   static_assert(
 | |
|       ArrayLength(stateDescriptors) == size_t(CCRunnerState::NumStates),
 | |
|       "need one state descriptor per state");
 | |
|   const StateDescriptor& desc = stateDescriptors[int(mCCRunnerState)];
 | |
| 
 | |
|   // Make sure we initialized the state machine.
 | |
|   MOZ_ASSERT(mCCRunnerState != CCRunnerState::Inactive);
 | |
| 
 | |
|   if (mDidShutdown) {
 | |
|     return {CCRunnerAction::StopRunning, Yield};
 | |
|   }
 | |
| 
 | |
|   if (mCCRunnerState == CCRunnerState::Canceled) {
 | |
|     // When we cancel a cycle, there may have been a final ForgetSkippable.
 | |
|     return {CCRunnerAction::StopRunning, Yield};
 | |
|   }
 | |
| 
 | |
|   TimeStamp now = Now();
 | |
| 
 | |
|   if (InIncrementalGC()) {
 | |
|     if (mCCBlockStart.IsNull()) {
 | |
|       BlockCC(now);
 | |
| 
 | |
|       // If we have reached the CycleCollecting state, then ignore CC timer
 | |
|       // fires while incremental GC is running. (Running ICC during an IGC
 | |
|       // would cause us to synchronously finish the GC, which is bad.)
 | |
|       //
 | |
|       // If we have not yet started cycle collecting, then reset our state so
 | |
|       // that we run forgetSkippable often enough before CC. Because of reduced
 | |
|       // mCCDelay, forgetSkippable will be called just a few times.
 | |
|       //
 | |
|       // The kMaxCCLockedoutTime limit guarantees that we end up calling
 | |
|       // forgetSkippable and CycleCollectNow eventually.
 | |
| 
 | |
|       if (mCCRunnerState != CCRunnerState::CycleCollecting) {
 | |
|         mCCRunnerState = CCRunnerState::ReducePurple;
 | |
|         mCCRunnerEarlyFireCount = 0;
 | |
|         mCCDelay = kCCDelay / int64_t(3);
 | |
|       }
 | |
|       return {CCRunnerAction::None, Yield};
 | |
|     }
 | |
| 
 | |
|     if (GetCCBlockedTime(now) < kMaxCCLockedoutTime) {
 | |
|       return {CCRunnerAction::None, Yield};
 | |
|     }
 | |
| 
 | |
|     // Locked out for too long, so proceed and finish the incremental GC
 | |
|     // synchronously.
 | |
|   }
 | |
| 
 | |
|   // For states that aren't just continuations of previous states, check
 | |
|   // whether a CC is still needed (after doing various things to reduce the
 | |
|   // purple buffer).
 | |
|   if (desc.mCanAbortCC && !IsCCNeeded(now)) {
 | |
|     // If we don't pass the threshold for wanting to cycle collect, stop now
 | |
|     // (after possibly doing a final ForgetSkippable).
 | |
|     mCCRunnerState = CCRunnerState::Canceled;
 | |
|     NoteForgetSkippableOnlyCycle();
 | |
| 
 | |
|     // Preserve the previous code's idea of when to check whether a
 | |
|     // ForgetSkippable should be fired.
 | |
|     if (desc.mTryFinalForgetSkippable && ShouldForgetSkippable()) {
 | |
|       // The Canceled state will make us StopRunning after this action is
 | |
|       // performed (see conditional at top of function).
 | |
|       return {CCRunnerAction::ForgetSkippable, Yield, KeepChildless};
 | |
|     }
 | |
| 
 | |
|     return {CCRunnerAction::StopRunning, Yield};
 | |
|   }
 | |
| 
 | |
|   switch (mCCRunnerState) {
 | |
|       // ReducePurple: a GC ran (or we otherwise decided to try CC'ing). Wait
 | |
|       // for some amount of time (kCCDelay, or less if incremental GC blocked
 | |
|       // this CC) while firing regular ForgetSkippable actions before continuing
 | |
|       // on.
 | |
|     case CCRunnerState::ReducePurple:
 | |
|       ++mCCRunnerEarlyFireCount;
 | |
|       if (IsLastEarlyCCTimer(mCCRunnerEarlyFireCount)) {
 | |
|         mCCRunnerState = CCRunnerState::CleanupChildless;
 | |
|       }
 | |
| 
 | |
|       if (ShouldForgetSkippable()) {
 | |
|         return {CCRunnerAction::ForgetSkippable, Yield, KeepChildless};
 | |
|       }
 | |
| 
 | |
|       if (aDeadline.IsNull()) {
 | |
|         return {CCRunnerAction::None, Yield};
 | |
|       }
 | |
| 
 | |
|       // If we're called during idle time, try to find some work to do by
 | |
|       // advancing to the next state, effectively bypassing some possible forget
 | |
|       // skippable calls.
 | |
|       mCCRunnerState = CCRunnerState::CleanupChildless;
 | |
| 
 | |
|       // Continue on to CleanupChildless, but only after checking IsCCNeeded
 | |
|       // again.
 | |
|       return {CCRunnerAction::None, Continue};
 | |
| 
 | |
|       // CleanupChildless: do a stronger ForgetSkippable that removes nodes with
 | |
|       // no children in the cycle collector graph. This state is split into 3
 | |
|       // parts; the other Cleanup* actions will happen within the same callback
 | |
|       // (unless the ForgetSkippable shrinks the purple buffer enough for the CC
 | |
|       // to be skipped entirely.)
 | |
|     case CCRunnerState::CleanupChildless:
 | |
|       mCCRunnerState = CCRunnerState::CleanupContentUnbinder;
 | |
|       return {CCRunnerAction::ForgetSkippable, Yield, RemoveChildless};
 | |
| 
 | |
|       // CleanupContentUnbinder: continuing cleanup, clear out the content
 | |
|       // unbinder.
 | |
|     case CCRunnerState::CleanupContentUnbinder:
 | |
|       if (aDeadline.IsNull()) {
 | |
|         // Non-idle (waiting) callbacks skip the rest of the cleanup, but still
 | |
|         // wait for another fire before the actual CC.
 | |
|         mCCRunnerState = CCRunnerState::StartCycleCollection;
 | |
|         return {CCRunnerAction::None, Yield};
 | |
|       }
 | |
| 
 | |
|       // Running in an idle callback.
 | |
| 
 | |
|       // The deadline passed, so go straight to CC in the next slice.
 | |
|       if (now >= aDeadline) {
 | |
|         mCCRunnerState = CCRunnerState::StartCycleCollection;
 | |
|         return {CCRunnerAction::None, Yield};
 | |
|       }
 | |
| 
 | |
|       mCCRunnerState = CCRunnerState::CleanupDeferred;
 | |
|       return {CCRunnerAction::CleanupContentUnbinder, Continue};
 | |
| 
 | |
|       // CleanupDeferred: continuing cleanup, do deferred deletion.
 | |
|     case CCRunnerState::CleanupDeferred:
 | |
|       MOZ_ASSERT(!aDeadline.IsNull(),
 | |
|                  "Should only be in CleanupDeferred state when idle");
 | |
| 
 | |
|       // Our efforts to avoid a CC have failed. Let the timer fire once more
 | |
|       // to trigger a CC.
 | |
|       mCCRunnerState = CCRunnerState::StartCycleCollection;
 | |
|       if (now >= aDeadline) {
 | |
|         // The deadline passed, go straight to CC in the next slice.
 | |
|         return {CCRunnerAction::None, Yield};
 | |
|       }
 | |
| 
 | |
|       return {CCRunnerAction::CleanupDeferred, Yield};
 | |
| 
 | |
|       // StartCycleCollection: start actually doing cycle collection slices.
 | |
|     case CCRunnerState::StartCycleCollection:
 | |
|       // We are in the final timer fire and still meet the conditions for
 | |
|       // triggering a CC. Let RunCycleCollectorSlice finish the current IGC if
 | |
|       // any, because that will allow us to include the GC time in the CC pause.
 | |
|       mCCRunnerState = CCRunnerState::CycleCollecting;
 | |
|       [[fallthrough]];
 | |
| 
 | |
|       // CycleCollecting: continue running slices until done.
 | |
|     case CCRunnerState::CycleCollecting:
 | |
|       return {CCRunnerAction::CycleCollect, Yield};
 | |
| 
 | |
|     default:
 | |
|       MOZ_CRASH("Unexpected CCRunner state");
 | |
|   };
 | |
| }
 | |
| 
 | |
| GCRunnerStep CCGCScheduler::GetNextGCRunnerAction(TimeStamp aDeadline) {
 | |
|   if (InIncrementalGC()) {
 | |
|     return {GCRunnerAction::GCSlice, JS::GCReason::INTER_SLICE_GC};
 | |
|   }
 | |
| 
 | |
|   if (mMajorGCReason != JS::GCReason::NO_REASON) {
 | |
|     GCRunnerStep step{GCRunnerAction::MajorGC, mMajorGCReason};
 | |
|     mMajorGCReason = JS::GCReason::NO_REASON;
 | |
|     return step;
 | |
|   }
 | |
| 
 | |
|   return {GCRunnerAction::None, JS::GCReason::NO_REASON};
 | |
| }
 | |
| 
 | |
| inline js::SliceBudget CCGCScheduler::ComputeForgetSkippableBudget(
 | |
|     TimeStamp aStartTimeStamp, TimeStamp aDeadline) {
 | |
|   if (mForgetSkippableFrequencyStartTime.IsNull()) {
 | |
|     mForgetSkippableFrequencyStartTime = aStartTimeStamp;
 | |
|   } else if (aStartTimeStamp - mForgetSkippableFrequencyStartTime >
 | |
|              kOneMinute) {
 | |
|     TimeStamp startPlusMinute = mForgetSkippableFrequencyStartTime + kOneMinute;
 | |
| 
 | |
|     // If we had forget skippables only at the beginning of the interval, we
 | |
|     // still want to use the whole time, minute or more, for frequency
 | |
|     // calculation. mLastForgetSkippableEndTime is needed if forget skippable
 | |
|     // takes enough time to push the interval to be over a minute.
 | |
|     TimeStamp endPoint = std::max(startPlusMinute, mLastForgetSkippableEndTime);
 | |
| 
 | |
|     // Duration in minutes.
 | |
|     double duration =
 | |
|         (endPoint - mForgetSkippableFrequencyStartTime).ToSeconds() / 60;
 | |
|     uint32_t frequencyPerMinute = uint32_t(mForgetSkippableCounter / duration);
 | |
|     Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_FREQUENCY,
 | |
|                           frequencyPerMinute);
 | |
|     mForgetSkippableCounter = 0;
 | |
|     mForgetSkippableFrequencyStartTime = aStartTimeStamp;
 | |
|   }
 | |
|   ++mForgetSkippableCounter;
 | |
| 
 | |
|   TimeDuration budgetTime =
 | |
|       aDeadline ? (aDeadline - aStartTimeStamp) : kForgetSkippableSliceDuration;
 | |
|   return js::SliceBudget(budgetTime);
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla
 | 
