Loading dom/base/CCGCScheduler.cpp +3 −5 Original line number Diff line number Diff line Loading @@ -594,8 +594,7 @@ js::SliceBudget CCGCScheduler::ComputeCCSliceBudget( if (aCCBeginTime.IsNull()) { // If no CC is in progress, use the standard slice time. return js::SliceBudget(js::TimeBudget(baseBudget), kNumCCNodesBetweenTimeChecks); return js::SliceBudget(js::TimeBudget(baseBudget)); } // Only run a limited slice if we're within the max running time. Loading Loading @@ -624,9 +623,8 @@ js::SliceBudget CCGCScheduler::ComputeCCSliceBudget( // 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(js::TimeBudget(std::max( {delaySliceBudget, laterSliceBudget, baseBudget})), kNumCCNodesBetweenTimeChecks); return js::SliceBudget(js::TimeBudget( std::max({delaySliceBudget, laterSliceBudget, baseBudget}))); } TimeDuration CCGCScheduler::ComputeInterSliceGCBudget(TimeStamp aDeadline, Loading dom/base/CCGCScheduler.h +0 −3 Original line number Diff line number Diff line Loading @@ -61,9 +61,6 @@ 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; // How many cycle collected nodes to traverse between time checks. static const int64_t kNumCCNodesBetweenTimeChecks = 1000; // Actions performed by the GCRunner state machine. enum class GCRunnerAction { WaitToMajorGC, // We want to start a new major GC Loading js/public/GCAPI.h +15 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ #include "js/GCAnnotations.h" #include "js/shadow/Zone.h" #include "js/SliceBudget.h" #include "js/TypeDecls.h" #include "js/UniquePtr.h" #include "js/Utility.h" Loading Loading @@ -888,6 +889,20 @@ typedef void (*DoCycleCollectionCallback)(JSContext* cx); extern JS_PUBLIC_API DoCycleCollectionCallback SetDoCycleCollectionCallback(JSContext* cx, DoCycleCollectionCallback callback); using CreateSliceBudgetCallback = js::SliceBudget (*)(JS::GCReason reason, int64_t millis); /** * Called when generating a GC slice budget. It allows the embedding to control * the duration of slices and potentially check an interrupt flag as well. For * internally triggered GCs, the given millis parameter is the JS engine's * internal scheduling decision, which the embedding can choose to ignore. * (Otherwise, it will be the value that was passed to eg * JS::IncrementalGCSlice()). */ extern JS_PUBLIC_API void SetCreateGCSliceBudgetCallback( JSContext* cx, CreateSliceBudgetCallback cb); /** * Incremental GC defaults to enabled, but may be disabled for testing or in * embeddings that have not yet implemented barriers on their native classes. Loading js/public/SliceBudget.h +36 −10 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ #define js_SliceBudget_h #include "mozilla/Assertions.h" #include "mozilla/Atomics.h" #include "mozilla/TimeStamp.h" #include "mozilla/Variant.h" Loading Loading @@ -44,32 +45,56 @@ struct UnlimitedBudget {}; * operations. */ class JS_PUBLIC_API SliceBudget { public: using InterruptRequestFlag = mozilla::Atomic<bool>; private: static const intptr_t UnlimitedCounter = INTPTR_MAX; static const intptr_t DefaultStepsPerTimeCheck = 1000; // Most calls to isOverBudget will only check the counter value. Every N // steps, do a more "expensive" check -- look at the current time and/or // check the atomic interrupt flag. static constexpr intptr_t StepsPerExpensiveCheck = 1000; // Configuration mozilla::Variant<TimeBudget, WorkBudget, UnlimitedBudget> budget; int64_t stepsPerTimeCheck = DefaultStepsPerTimeCheck; int64_t counter; // External flag to request the current slice to be interrupted // (and return isOverBudget() early.) Applies only to time-based budgets. InterruptRequestFlag* interruptRequested = nullptr; SliceBudget() : budget(UnlimitedBudget()), counter(UnlimitedCounter) {} // How many steps to count before checking the time and possibly the interrupt // flag. int64_t counter = StepsPerExpensiveCheck; // This SliceBudget is considered interrupted from the time isOverBudget() // finds the interrupt flag set, to the next time resetOverBudget() (or // checkAndResetOverBudget()) is called. bool interrupted = false; explicit SliceBudget(InterruptRequestFlag* irqPtr) : budget(UnlimitedBudget()), interruptRequested(irqPtr), counter(irqPtr ? StepsPerExpensiveCheck : UnlimitedCounter) {} [[nodiscard]] bool isOverBudgetSlow(); public: // Use to create an unlimited budget. static SliceBudget unlimited() { return SliceBudget(); } static SliceBudget unlimited() { return SliceBudget(nullptr); } // Instantiate as SliceBudget(TimeBudget(n)). explicit SliceBudget(TimeBudget time, int64_t stepsPerTimeCheck = DefaultStepsPerTimeCheck); InterruptRequestFlag* interrupt = nullptr); explicit SliceBudget(mozilla::TimeDuration duration, InterruptRequestFlag* interrupt = nullptr) : SliceBudget(TimeBudget(duration.ToMilliseconds()), interrupt) {} // Instantiate as SliceBudget(WorkBudget(n)). explicit SliceBudget(WorkBudget work); explicit SliceBudget(mozilla::TimeDuration time) : SliceBudget(TimeBudget(time.ToMilliseconds())) {} // Register having performed the given number of steps (counted against a // work budget, or progress towards the next time or callback check). void step(uint64_t steps = 1) { Loading @@ -91,8 +116,9 @@ class JS_PUBLIC_API SliceBudget { } void resetOverBudget() { interrupted = false; if (isTimeBudget()) { counter = stepsPerTimeCheck; counter = StepsPerExpensiveCheck; } else if (isWorkBudget()) { counter = workBudget(); } Loading js/src/gc/GC.cpp +31 −5 Original line number Diff line number Diff line Loading @@ -375,6 +375,7 @@ GCRuntime::GCRuntime(JSRuntime* rt) helperThreadRatio(TuningDefaults::HelperThreadRatio), maxHelperThreads(TuningDefaults::MaxHelperThreads), helperThreadCount(1), createBudgetCallback(nullptr), rootsHash(256), nextCellUniqueId_(LargestTaggedNullCellPointer + 1), // Ensure disjoint from null tagged pointers. Loading Loading @@ -1402,16 +1403,21 @@ bool GCRuntime::isCompactingGCEnabled() const { rt->mainContextFromOwnThread()->compactingDisabledCount == 0; } SliceBudget::SliceBudget(TimeBudget time, int64_t stepsPerTimeCheckArg) JS_PUBLIC_API void JS::SetCreateGCSliceBudgetCallback( JSContext* cx, JS::CreateSliceBudgetCallback cb) { cx->runtime()->gc.createBudgetCallback = cb; } SliceBudget::SliceBudget(TimeBudget time, InterruptRequestFlag* interrupt) : budget(TimeBudget(time)), stepsPerTimeCheck(stepsPerTimeCheckArg), counter(stepsPerTimeCheckArg) { interruptRequested(interrupt), counter(StepsPerExpensiveCheck) { budget.as<TimeBudget>().deadline = ReallyNow() + TimeDuration::FromMilliseconds(timeBudget()); } SliceBudget::SliceBudget(WorkBudget work) : budget(work), counter(work.budget) {} : budget(work), interruptRequested(nullptr), counter(work.budget) {} int SliceBudget::describe(char* buffer, size_t maxlen) const { if (isUnlimited()) { Loading @@ -1419,7 +1425,8 @@ int SliceBudget::describe(char* buffer, size_t maxlen) const { } else if (isWorkBudget()) { return snprintf(buffer, maxlen, "work(%" PRId64 ")", workBudget()); } else { return snprintf(buffer, maxlen, "%" PRId64 "ms", timeBudget()); return snprintf(buffer, maxlen, "%" PRId64 "ms%s", timeBudget(), interruptRequested ? ", interruptible" : ""); } } Loading @@ -1431,6 +1438,15 @@ bool SliceBudget::isOverBudgetSlow() { return true; } if (interruptRequested && *interruptRequested) { *interruptRequested = false; interrupted = true; } if (interrupted) { return true; } if (ReallyNow() >= budget.as<TimeBudget>().deadline) { return true; } Loading Loading @@ -3847,6 +3863,9 @@ void GCRuntime::collect(bool nonincrementalByAPI, const SliceBudget& budget, } SliceBudget GCRuntime::defaultBudget(JS::GCReason reason, int64_t millis) { // millis == 0 means use internal GC scheduling logic to come up with // a duration for the slice budget. This may end up still being zero // based on preferences. if (millis == 0) { if (reason == JS::GCReason::ALLOC_TRIGGER) { millis = defaultSliceBudgetMS(); Loading @@ -3857,6 +3876,13 @@ SliceBudget GCRuntime::defaultBudget(JS::GCReason reason, int64_t millis) { } } // If the embedding has registered a callback for creating SliceBudgets, // then use it. if (createBudgetCallback) { return createBudgetCallback(reason, millis); } // Otherwise, the preference can request an unlimited duration slice. if (millis == 0) { return SliceBudget::unlimited(); } Loading Loading
dom/base/CCGCScheduler.cpp +3 −5 Original line number Diff line number Diff line Loading @@ -594,8 +594,7 @@ js::SliceBudget CCGCScheduler::ComputeCCSliceBudget( if (aCCBeginTime.IsNull()) { // If no CC is in progress, use the standard slice time. return js::SliceBudget(js::TimeBudget(baseBudget), kNumCCNodesBetweenTimeChecks); return js::SliceBudget(js::TimeBudget(baseBudget)); } // Only run a limited slice if we're within the max running time. Loading Loading @@ -624,9 +623,8 @@ js::SliceBudget CCGCScheduler::ComputeCCSliceBudget( // 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(js::TimeBudget(std::max( {delaySliceBudget, laterSliceBudget, baseBudget})), kNumCCNodesBetweenTimeChecks); return js::SliceBudget(js::TimeBudget( std::max({delaySliceBudget, laterSliceBudget, baseBudget}))); } TimeDuration CCGCScheduler::ComputeInterSliceGCBudget(TimeStamp aDeadline, Loading
dom/base/CCGCScheduler.h +0 −3 Original line number Diff line number Diff line Loading @@ -61,9 +61,6 @@ 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; // How many cycle collected nodes to traverse between time checks. static const int64_t kNumCCNodesBetweenTimeChecks = 1000; // Actions performed by the GCRunner state machine. enum class GCRunnerAction { WaitToMajorGC, // We want to start a new major GC Loading
js/public/GCAPI.h +15 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ #include "js/GCAnnotations.h" #include "js/shadow/Zone.h" #include "js/SliceBudget.h" #include "js/TypeDecls.h" #include "js/UniquePtr.h" #include "js/Utility.h" Loading Loading @@ -888,6 +889,20 @@ typedef void (*DoCycleCollectionCallback)(JSContext* cx); extern JS_PUBLIC_API DoCycleCollectionCallback SetDoCycleCollectionCallback(JSContext* cx, DoCycleCollectionCallback callback); using CreateSliceBudgetCallback = js::SliceBudget (*)(JS::GCReason reason, int64_t millis); /** * Called when generating a GC slice budget. It allows the embedding to control * the duration of slices and potentially check an interrupt flag as well. For * internally triggered GCs, the given millis parameter is the JS engine's * internal scheduling decision, which the embedding can choose to ignore. * (Otherwise, it will be the value that was passed to eg * JS::IncrementalGCSlice()). */ extern JS_PUBLIC_API void SetCreateGCSliceBudgetCallback( JSContext* cx, CreateSliceBudgetCallback cb); /** * Incremental GC defaults to enabled, but may be disabled for testing or in * embeddings that have not yet implemented barriers on their native classes. Loading
js/public/SliceBudget.h +36 −10 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ #define js_SliceBudget_h #include "mozilla/Assertions.h" #include "mozilla/Atomics.h" #include "mozilla/TimeStamp.h" #include "mozilla/Variant.h" Loading Loading @@ -44,32 +45,56 @@ struct UnlimitedBudget {}; * operations. */ class JS_PUBLIC_API SliceBudget { public: using InterruptRequestFlag = mozilla::Atomic<bool>; private: static const intptr_t UnlimitedCounter = INTPTR_MAX; static const intptr_t DefaultStepsPerTimeCheck = 1000; // Most calls to isOverBudget will only check the counter value. Every N // steps, do a more "expensive" check -- look at the current time and/or // check the atomic interrupt flag. static constexpr intptr_t StepsPerExpensiveCheck = 1000; // Configuration mozilla::Variant<TimeBudget, WorkBudget, UnlimitedBudget> budget; int64_t stepsPerTimeCheck = DefaultStepsPerTimeCheck; int64_t counter; // External flag to request the current slice to be interrupted // (and return isOverBudget() early.) Applies only to time-based budgets. InterruptRequestFlag* interruptRequested = nullptr; SliceBudget() : budget(UnlimitedBudget()), counter(UnlimitedCounter) {} // How many steps to count before checking the time and possibly the interrupt // flag. int64_t counter = StepsPerExpensiveCheck; // This SliceBudget is considered interrupted from the time isOverBudget() // finds the interrupt flag set, to the next time resetOverBudget() (or // checkAndResetOverBudget()) is called. bool interrupted = false; explicit SliceBudget(InterruptRequestFlag* irqPtr) : budget(UnlimitedBudget()), interruptRequested(irqPtr), counter(irqPtr ? StepsPerExpensiveCheck : UnlimitedCounter) {} [[nodiscard]] bool isOverBudgetSlow(); public: // Use to create an unlimited budget. static SliceBudget unlimited() { return SliceBudget(); } static SliceBudget unlimited() { return SliceBudget(nullptr); } // Instantiate as SliceBudget(TimeBudget(n)). explicit SliceBudget(TimeBudget time, int64_t stepsPerTimeCheck = DefaultStepsPerTimeCheck); InterruptRequestFlag* interrupt = nullptr); explicit SliceBudget(mozilla::TimeDuration duration, InterruptRequestFlag* interrupt = nullptr) : SliceBudget(TimeBudget(duration.ToMilliseconds()), interrupt) {} // Instantiate as SliceBudget(WorkBudget(n)). explicit SliceBudget(WorkBudget work); explicit SliceBudget(mozilla::TimeDuration time) : SliceBudget(TimeBudget(time.ToMilliseconds())) {} // Register having performed the given number of steps (counted against a // work budget, or progress towards the next time or callback check). void step(uint64_t steps = 1) { Loading @@ -91,8 +116,9 @@ class JS_PUBLIC_API SliceBudget { } void resetOverBudget() { interrupted = false; if (isTimeBudget()) { counter = stepsPerTimeCheck; counter = StepsPerExpensiveCheck; } else if (isWorkBudget()) { counter = workBudget(); } Loading
js/src/gc/GC.cpp +31 −5 Original line number Diff line number Diff line Loading @@ -375,6 +375,7 @@ GCRuntime::GCRuntime(JSRuntime* rt) helperThreadRatio(TuningDefaults::HelperThreadRatio), maxHelperThreads(TuningDefaults::MaxHelperThreads), helperThreadCount(1), createBudgetCallback(nullptr), rootsHash(256), nextCellUniqueId_(LargestTaggedNullCellPointer + 1), // Ensure disjoint from null tagged pointers. Loading Loading @@ -1402,16 +1403,21 @@ bool GCRuntime::isCompactingGCEnabled() const { rt->mainContextFromOwnThread()->compactingDisabledCount == 0; } SliceBudget::SliceBudget(TimeBudget time, int64_t stepsPerTimeCheckArg) JS_PUBLIC_API void JS::SetCreateGCSliceBudgetCallback( JSContext* cx, JS::CreateSliceBudgetCallback cb) { cx->runtime()->gc.createBudgetCallback = cb; } SliceBudget::SliceBudget(TimeBudget time, InterruptRequestFlag* interrupt) : budget(TimeBudget(time)), stepsPerTimeCheck(stepsPerTimeCheckArg), counter(stepsPerTimeCheckArg) { interruptRequested(interrupt), counter(StepsPerExpensiveCheck) { budget.as<TimeBudget>().deadline = ReallyNow() + TimeDuration::FromMilliseconds(timeBudget()); } SliceBudget::SliceBudget(WorkBudget work) : budget(work), counter(work.budget) {} : budget(work), interruptRequested(nullptr), counter(work.budget) {} int SliceBudget::describe(char* buffer, size_t maxlen) const { if (isUnlimited()) { Loading @@ -1419,7 +1425,8 @@ int SliceBudget::describe(char* buffer, size_t maxlen) const { } else if (isWorkBudget()) { return snprintf(buffer, maxlen, "work(%" PRId64 ")", workBudget()); } else { return snprintf(buffer, maxlen, "%" PRId64 "ms", timeBudget()); return snprintf(buffer, maxlen, "%" PRId64 "ms%s", timeBudget(), interruptRequested ? ", interruptible" : ""); } } Loading @@ -1431,6 +1438,15 @@ bool SliceBudget::isOverBudgetSlow() { return true; } if (interruptRequested && *interruptRequested) { *interruptRequested = false; interrupted = true; } if (interrupted) { return true; } if (ReallyNow() >= budget.as<TimeBudget>().deadline) { return true; } Loading Loading @@ -3847,6 +3863,9 @@ void GCRuntime::collect(bool nonincrementalByAPI, const SliceBudget& budget, } SliceBudget GCRuntime::defaultBudget(JS::GCReason reason, int64_t millis) { // millis == 0 means use internal GC scheduling logic to come up with // a duration for the slice budget. This may end up still being zero // based on preferences. if (millis == 0) { if (reason == JS::GCReason::ALLOC_TRIGGER) { millis = defaultSliceBudgetMS(); Loading @@ -3857,6 +3876,13 @@ SliceBudget GCRuntime::defaultBudget(JS::GCReason reason, int64_t millis) { } } // If the embedding has registered a callback for creating SliceBudgets, // then use it. if (createBudgetCallback) { return createBudgetCallback(reason, millis); } // Otherwise, the preference can request an unlimited duration slice. if (millis == 0) { return SliceBudget::unlimited(); } Loading