diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index 91cbbcba05ea..ff931e8e7f92 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -106,7 +106,8 @@ static bool sIncrementalCC = false; static bool sIsInitialized; static bool sShuttingDown; -static CCGCScheduler sScheduler; +static CCGCScheduler* sScheduler = nullptr; +static std::aligned_storage_t sSchedulerStorage; struct CycleCollectorStats { constexpr CycleCollectorStats() = default; @@ -310,14 +311,14 @@ nsJSEnvironmentObserver::Observe(nsISupports* aSubject, const char* aTopic, } else if (!nsCRT::strcmp(aTopic, "memory-pressure-stop")) { nsJSContext::SetLowMemoryState(false); } else if (!nsCRT::strcmp(aTopic, "user-interaction-inactive")) { - sScheduler.UserIsInactive(); + sScheduler->UserIsInactive(); } else if (!nsCRT::strcmp(aTopic, "user-interaction-active")) { - sScheduler.UserIsActive(); + sScheduler->UserIsActive(); } else if (!nsCRT::strcmp(aTopic, "quit-application") || !nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) || !nsCRT::strcmp(aTopic, "content-child-will-shutdown")) { sShuttingDown = true; - sScheduler.Shutdown(); + sScheduler->Shutdown(); } return NS_OK; @@ -551,7 +552,7 @@ nsJSContext::~nsJSContext() { void nsJSContext::Destroy() { if (mGCOnDestruction) { - sScheduler.PokeGC(JS::GCReason::NSJSCONTEXT_DESTROY, mWindowProxy); + sScheduler->PokeGC(JS::GCReason::NSJSCONTEXT_DESTROY, mWindowProxy); } DropJSObjects(this); @@ -1052,7 +1053,7 @@ static void GarbageCollectImpl(JS::GCReason aReason, return; } - if (sScheduler.InIncrementalGC() && wantIncremental) { + if (sScheduler->InIncrementalGC() && wantIncremental) { // We're in the middle of incremental GC. Do another slice. JS::PrepareForIncrementalGC(cx); JS::IncrementalGCSlice(cx, aReason, aBudget); @@ -1064,10 +1065,10 @@ static void GarbageCollectImpl(JS::GCReason aReason, : JS::GCOptions::Normal; if (!wantIncremental || aReason == JS::GCReason::FULL_GC_TIMER) { - sScheduler.SetNeedsFullGC(); + sScheduler->SetNeedsFullGC(); } - if (sScheduler.NeedsFullGC()) { + if (sScheduler->NeedsFullGC()) { JS::PrepareForFullGC(cx); } @@ -1098,7 +1099,7 @@ void nsJSContext::RunIncrementalGCSlice(JS::GCReason aReason, static void FinishAnyIncrementalGC() { AUTO_PROFILER_LABEL("FinishAnyIncrementalGC", GCCC); - if (sScheduler.InIncrementalGC()) { + if (sScheduler->InIncrementalGC()) { AutoJSAPI jsapi; jsapi.Init(); @@ -1136,12 +1137,12 @@ static void FireForgetSkippable(bool aRemoveChildless, TimeStamp aDeadline) { uint32_t suspectedBefore = nsCycleCollector_suspectedCount(); js::SliceBudget budget = - sScheduler.ComputeForgetSkippableBudget(startTimeStamp, aDeadline); - bool earlyForgetSkippable = sScheduler.IsEarlyForgetSkippable(); + sScheduler->ComputeForgetSkippableBudget(startTimeStamp, aDeadline); + bool earlyForgetSkippable = sScheduler->IsEarlyForgetSkippable(); nsCycleCollector_forgetSkippable(budget, aRemoveChildless, earlyForgetSkippable); TimeStamp now = TimeStamp::Now(); - uint32_t removedPurples = sScheduler.NoteForgetSkippableComplete( + uint32_t removedPurples = sScheduler->NoteForgetSkippableComplete( now, suspectedBefore, nsCycleCollector_suspectedCount()); TimeDuration duration = now - startTimeStamp; @@ -1339,9 +1340,9 @@ void CycleCollectorStats::MaybeLogStats(const CycleCollectorResults& aResults, mTotalSliceTime.ToMilliseconds(), aResults.mNumSlices, mSuspected, aResults.mVisitedRefCounted, aResults.mVisitedGCed, mergeMsg.get(), aResults.mFreedRefCounted, aResults.mFreedGCed, - sScheduler.mCCollectedWaitingForGC, - sScheduler.mCCollectedZonesWaitingForGC, - sScheduler.mLikelyShortLivingObjectsNeedingGC, gcMsg.get(), + sScheduler->mCCollectedWaitingForGC, + sScheduler->mCCollectedZonesWaitingForGC, + sScheduler->mLikelyShortLivingObjectsNeedingGC, gcMsg.get(), mForgetSkippableBeforeCC, mMinForgetSkippableTime.ToMilliseconds(), mMaxForgetSkippableTime.ToMilliseconds(), mTotalForgetSkippableTime.ToMilliseconds() / aCleanups, @@ -1400,9 +1401,9 @@ void CycleCollectorStats::MaybeNotifyStats( mMaxGCDuration.ToMilliseconds(), mMaxSkippableDuration.ToMilliseconds(), mSuspected, aResults.mVisitedRefCounted, aResults.mVisitedGCed, aResults.mFreedRefCounted, aResults.mFreedGCed, - sScheduler.mCCollectedWaitingForGC, - sScheduler.mCCollectedZonesWaitingForGC, - sScheduler.mLikelyShortLivingObjectsNeedingGC, aResults.mForcedGC, + sScheduler->mCCollectedWaitingForGC, + sScheduler->mCCollectedZonesWaitingForGC, + sScheduler->mLikelyShortLivingObjectsNeedingGC, aResults.mForcedGC, mForgetSkippableBeforeCC, mMinForgetSkippableTime.ToMilliseconds(), mMaxForgetSkippableTime.ToMilliseconds(), mTotalForgetSkippableTime.ToMilliseconds() / aCleanups, @@ -1436,16 +1437,16 @@ void nsJSContext::PrepareForCycleCollectionSlice(CCReason aReason, // Before we begin the cycle collection, make sure there is no active GC. TimeStamp afterGCTime; - if (sScheduler.InIncrementalGC()) { + if (sScheduler->InIncrementalGC()) { FinishAnyIncrementalGC(); afterGCTime = TimeStamp::Now(); } - if (!sScheduler.IsCollectingCycles()) { + if (!sScheduler->IsCollectingCycles()) { sCCStats.PrepareForCycleCollection(beginTime); - sScheduler.NoteCCBegin(aReason, beginTime, - sCCStats.mForgetSkippableBeforeCC, - sCCStats.mSuspected, sCCStats.mRemovedPurples); + sScheduler->NoteCCBegin(aReason, beginTime, + sCCStats.mForgetSkippableBeforeCC, + sCCStats.mSuspected, sCCStats.mRemovedPurples); } sCCStats.AfterPrepareForCycleCollectionSlice(aDeadline, beginTime, @@ -1464,7 +1465,7 @@ void nsJSContext::RunCycleCollectorSlice(CCReason aReason, // Decide how long we want to budget for this slice. if (sIncrementalCC) { bool preferShorterSlices; - js::SliceBudget budget = sScheduler.ComputeCCSliceBudget( + js::SliceBudget budget = sScheduler->ComputeCCSliceBudget( aDeadline, sCCStats.mBeginTime, sCCStats.mEndSliceTime, TimeStamp::Now(), &preferShorterSlices); nsCycleCollector_collectSlice(budget, aReason, preferShorterSlices); @@ -1509,8 +1510,8 @@ void nsJSContext::BeginCycleCollectionCallback(CCReason aReason) { // Run forgetSkippable synchronously to reduce the size of the CC graph. This // is particularly useful if we recently finished a GC. - if (sScheduler.IsEarlyForgetSkippable()) { - while (sScheduler.IsEarlyForgetSkippable()) { + if (sScheduler->IsEarlyForgetSkippable()) { + while (sScheduler->IsEarlyForgetSkippable()) { FireForgetSkippable(false, TimeStamp()); } sCCStats.AfterSyncForgetSkippable(startTime); @@ -1520,9 +1521,9 @@ void nsJSContext::BeginCycleCollectionCallback(CCReason aReason) { return; } - sScheduler.InitCCRunnerStateMachine( + sScheduler->InitCCRunnerStateMachine( mozilla::CCGCScheduler::CCRunnerState::CycleCollecting, aReason); - sScheduler.EnsureCCRunner(kICCIntersliceDelay, kIdleICCSliceBudget); + sScheduler->EnsureCCRunner(kICCIntersliceDelay, kIdleICCSliceBudget); } // static @@ -1530,7 +1531,7 @@ void nsJSContext::EndCycleCollectionCallback( const CycleCollectorResults& aResults) { MOZ_ASSERT(NS_IsMainThread()); - sScheduler.KillCCRunner(); + sScheduler->KillCCRunner(); // Update timing information for the current slice before we log it, if // we previously called PrepareForCycleCollectionSlice(). During shutdown @@ -1539,9 +1540,9 @@ void nsJSContext::EndCycleCollectionCallback( TimeStamp endCCTimeStamp = TimeStamp::Now(); TimeDuration ccNowDuration = TimeBetween(sCCStats.mBeginTime, endCCTimeStamp); - TimeStamp prevCCEnd = sScheduler.GetLastCCEndTime(); + TimeStamp prevCCEnd = sScheduler->GetLastCCEndTime(); - sScheduler.NoteCCEnd(aResults, endCCTimeStamp, sCCStats.mMaxSliceTime); + sScheduler->NoteCCEnd(aResults, endCCTimeStamp, sCCStats.mMaxSliceTime); // Log information about the CC via telemetry, JSON and the console. @@ -1559,16 +1560,16 @@ void nsJSContext::EndCycleCollectionCallback( // If we need a GC after this CC (typically because lots of GCed objects or // zones have been collected in the CC), schedule it. - if (sScheduler.NeedsGCAfterCC()) { + if (sScheduler->NeedsGCAfterCC()) { MOZ_ASSERT( TimeDuration::FromMilliseconds( StaticPrefs::javascript_options_gc_delay()) > kMaxICCDuration, "A max duration ICC shouldn't reduce GC delay to 0"); - sScheduler.PokeGC(JS::GCReason::CC_FINISHED, nullptr, - TimeDuration::FromMilliseconds( - StaticPrefs::javascript_options_gc_delay()) - - std::min(ccNowDuration, kMaxICCDuration)); + sScheduler->PokeGC(JS::GCReason::CC_FINISHED, nullptr, + TimeDuration::FromMilliseconds( + StaticPrefs::javascript_options_gc_delay()) - + std::min(ccNowDuration, kMaxICCDuration)); } #if defined(MOZ_MEMORY) else if ( @@ -1594,8 +1595,8 @@ bool CCGCScheduler::CCRunnerFired(TimeStamp aDeadline) { // `Yield` in step.mYield. CCRunnerStep step; do { - step = sScheduler.AdvanceCCRunner(aDeadline, TimeStamp::Now(), - nsCycleCollector_suspectedCount()); + step = sScheduler->AdvanceCCRunner(aDeadline, TimeStamp::Now(), + nsCycleCollector_suspectedCount()); switch (step.mAction) { case CCRunnerAction::None: break; @@ -1603,7 +1604,7 @@ bool CCGCScheduler::CCRunnerFired(TimeStamp aDeadline) { case CCRunnerAction::MinorGC: JS::MaybeRunNurseryCollection(CycleCollectedJSRuntime::Get()->Runtime(), step.mParam.mReason); - sScheduler.NoteMinorGCEnd(); + sScheduler->NoteMinorGCEnd(); break; case CCRunnerAction::ForgetSkippable: @@ -1629,7 +1630,7 @@ bool CCGCScheduler::CCRunnerFired(TimeStamp aDeadline) { case CCRunnerAction::StopRunning: // End this CC, either because we have run a cycle collection slice, or // because a CC is no longer needed. - sScheduler.KillCCRunner(); + sScheduler->KillCCRunner(); break; } @@ -1643,13 +1644,13 @@ bool CCGCScheduler::CCRunnerFired(TimeStamp aDeadline) { // static bool nsJSContext::HasHadCleanupSinceLastGC() { - return sScheduler.IsEarlyForgetSkippable(1); + return sScheduler->IsEarlyForgetSkippable(1); } // static void nsJSContext::RunNextCollectorTimer(JS::GCReason aReason, mozilla::TimeStamp aDeadline) { - sScheduler.RunNextCollectorTimer(aReason, aDeadline); + sScheduler->RunNextCollectorTimer(aReason, aDeadline); } // static @@ -1692,13 +1693,13 @@ void nsJSContext::MaybeRunNextCollectorSlice(nsIDocShell* aDocShell, return; } - if (!sScheduler.IsUserActive()) { - if (sScheduler.InIncrementalGC() || sScheduler.IsCollectingCycles()) { + if (!sScheduler->IsUserActive()) { + if (sScheduler->InIncrementalGC() || sScheduler->IsCollectingCycles()) { Maybe next = nsRefreshDriver::GetNextTickHint(); if (next.isSome()) { // Try to not delay the next RefreshDriver tick, so give a reasonable // deadline for collectors. - sScheduler.RunNextCollectorTimer(aReason, next.value()); + sScheduler->RunNextCollectorTimer(aReason, next.value()); } } else { nsCOMPtr shell = aDocShell; @@ -1729,7 +1730,7 @@ void nsJSContext::MaybeRunNextCollectorSlice(nsIDocShell* aDocShell, // static void nsJSContext::PokeGC(JS::GCReason aReason, JSObject* aObj, TimeDuration aDelay) { - sScheduler.PokeGC(aReason, aObj, aDelay); + sScheduler->PokeGC(aReason, aObj, aDelay); } // static @@ -1742,7 +1743,7 @@ void nsJSContext::MaybePokeGC() { JS::GCReason reason = JS::WantEagerMinorGC(rt); if (reason != JS::GCReason::NO_REASON) { MOZ_ASSERT(reason == JS::GCReason::EAGER_NURSERY_COLLECTION); - sScheduler.PokeMinorGC(reason); + sScheduler->PokeMinorGC(reason); } // Bug 1772638: For now, only do eager minor GCs. Eager major GCs regress some @@ -1757,7 +1758,7 @@ void nsJSContext::DoLowMemoryGC() { nsJSContext::GarbageCollectNow(JS::GCReason::MEM_PRESSURE, nsJSContext::ShrinkingGC); nsJSContext::CycleCollectNow(CCReason::MEM_PRESSURE); - if (sScheduler.NeedsGCAfterCC()) { + if (sScheduler->NeedsGCAfterCC()) { nsJSContext::GarbageCollectNow(JS::GCReason::MEM_PRESSURE, nsJSContext::ShrinkingGC); } @@ -1780,8 +1781,8 @@ void nsJSContext::LowMemoryGC() { // static void nsJSContext::MaybePokeCC() { - sScheduler.MaybePokeCC(TimeStamp::NowLoRes(), - nsCycleCollector_suspectedCount()); + sScheduler->MaybePokeCC(TimeStamp::NowLoRes(), + nsCycleCollector_suspectedCount()); } static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress, @@ -1793,7 +1794,7 @@ static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress, switch (aProgress) { case JS::GC_CYCLE_BEGIN: { // Prevent cycle collections and shrinking during incremental GC. - sScheduler.NoteGCBegin(aDesc.reason_); + sScheduler->NoteGCBegin(aDesc.reason_); sCurrentGCStartTime = TimeStamp::Now(); break; } @@ -1816,10 +1817,10 @@ static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress, } } - sScheduler.NoteGCEnd(); + sScheduler->NoteGCEnd(); // May need to kill the GC runner - sScheduler.KillGCRunner(); + sScheduler->KillGCRunner(); nsJSContext::MaybePokeCC(); @@ -1827,17 +1828,17 @@ static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress, bool freeDirty = false; #endif if (aDesc.isZone_) { - sScheduler.PokeFullGC(); + sScheduler->PokeFullGC(); } else { #if defined(MOZ_MEMORY) freeDirty = true; #endif - sScheduler.SetNeedsFullGC(false); - sScheduler.KillFullGCTimer(); + sScheduler->SetNeedsFullGC(false); + sScheduler->KillFullGCTimer(); } - if (sScheduler.IsCCNeeded(TimeStamp::Now(), - nsCycleCollector_suspectedCount()) != + if (sScheduler->IsCCNeeded(TimeStamp::Now(), + nsCycleCollector_suspectedCount()) != CCReason::NO_REASON) { #if defined(MOZ_MEMORY) // We're likely to free the dirty pages after CC. @@ -1863,20 +1864,20 @@ static void DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress, break; case JS::GC_SLICE_END: - sScheduler.NoteGCSliceEnd(aDesc.lastSliceStart(aCx), - aDesc.lastSliceEnd(aCx)); + sScheduler->NoteGCSliceEnd(aDesc.lastSliceStart(aCx), + aDesc.lastSliceEnd(aCx)); if (sShuttingDown) { - sScheduler.KillGCRunner(); + sScheduler->KillGCRunner(); } else { // If incremental GC wasn't triggered by GCTimerFired, we may not have a // runner to ensure all the slices are handled. So, create the runner // here. - sScheduler.EnsureGCRunner(0); + sScheduler->EnsureGCRunner(0); } - if (sScheduler.IsCCNeeded(TimeStamp::Now(), - nsCycleCollector_suspectedCount()) != + if (sScheduler->IsCCNeeded(TimeStamp::Now(), + nsCycleCollector_suspectedCount()) != CCReason::NO_REASON) { nsCycleCollector_dispatchDeferredDeletion(); } @@ -1913,14 +1914,13 @@ void nsJSContext::SetWindowProxy(JS::Handle aWindowProxy) { JSObject* nsJSContext::GetWindowProxy() { return mWindowProxy; } void nsJSContext::LikelyShortLivingObjectCreated() { - ++sScheduler.mLikelyShortLivingObjectsNeedingGC; + ++sScheduler->mLikelyShortLivingObjectsNeedingGC; } void mozilla::dom::StartupJSEnvironment() { // initialize all our statics, so that we can restart XPCOM sIsInitialized = false; sShuttingDown = false; - new (&sScheduler) CCGCScheduler(); // Reset the scheduler state. sCCStats.Init(); } @@ -1982,7 +1982,7 @@ static void SetMemoryGCSliceTimePrefChangedCallback(const char* aPrefName, int32_t pref = Preferences::GetInt(aPrefName, -1); // handle overflow and negative pref values if (pref > 0 && pref < 100000) { - sScheduler.SetActiveIntersliceGCBudget( + sScheduler->SetActiveIntersliceGCBudget( TimeDuration::FromMilliseconds(pref)); SetGCParameter(JSGC_SLICE_TIME_BUDGET_MS, pref); } else { @@ -2055,7 +2055,7 @@ static bool ConsumeStream(JSContext* aCx, JS::Handle aObj, static js::SliceBudget CreateGCSliceBudget(JS::GCReason aReason, int64_t aMillis) { - return sScheduler.CreateGCSliceBudget( + return sScheduler->CreateGCSliceBudget( mozilla::TimeDuration::FromMilliseconds(aMillis), false, false); } @@ -2070,6 +2070,9 @@ void nsJSContext::EnsureStatics() { // Let's make sure that our main thread is the same as the xpcom main thread. MOZ_ASSERT(NS_IsMainThread()); + sScheduler = + new (&sSchedulerStorage) CCGCScheduler(); // Reset the scheduler state. + AutoJSAPI jsapi; jsapi.Init(); @@ -2228,7 +2231,7 @@ void nsJSContext::EnsureStatics() { void mozilla::dom::ShutdownJSEnvironment() { sShuttingDown = true; - sScheduler.Shutdown(); + sScheduler->Shutdown(); } AsyncErrorReporter::AsyncErrorReporter(xpc::ErrorReport* aReport)