diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/gc/ParallelMarking.cpp | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/gc/ParallelMarking.cpp')
-rw-r--r-- | js/src/gc/ParallelMarking.cpp | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/js/src/gc/ParallelMarking.cpp b/js/src/gc/ParallelMarking.cpp new file mode 100644 index 0000000000..67c29c02d7 --- /dev/null +++ b/js/src/gc/ParallelMarking.cpp @@ -0,0 +1,359 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * 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 "gc/ParallelMarking.h" + +#include "gc/GCLock.h" +#include "gc/ParallelWork.h" +#include "vm/GeckoProfiler.h" +#include "vm/HelperThreadState.h" +#include "vm/Runtime.h" + +using namespace js; +using namespace js::gc; + +using mozilla::Maybe; +using mozilla::TimeDuration; +using mozilla::TimeStamp; + +class AutoAddTimeDuration { + TimeStamp start; + TimeDuration& result; + + public: + explicit AutoAddTimeDuration(TimeDuration& result) + : start(TimeStamp::Now()), result(result) {} + ~AutoAddTimeDuration() { result += TimeSince(start); } +}; + +ParallelMarker::ParallelMarker(GCRuntime* gc) : gc(gc) {} + +size_t ParallelMarker::workerCount() const { return gc->markers.length(); } + +bool ParallelMarker::mark(SliceBudget& sliceBudget) { +#ifdef DEBUG + { + AutoLockHelperThreadState lock; + MOZ_ASSERT(workerCount() <= HelperThreadState().maxGCParallelThreads(lock)); + + // TODO: Even if the thread limits checked above are correct, there may not + // be enough threads available to start our mark tasks immediately due to + // other runtimes in the same process running GC. + } +#endif + + if (markOneColor(MarkColor::Black, sliceBudget) == NotFinished) { + return false; + } + MOZ_ASSERT(!hasWork(MarkColor::Black)); + + if (markOneColor(MarkColor::Gray, sliceBudget) == NotFinished) { + return false; + } + MOZ_ASSERT(!hasWork(MarkColor::Gray)); + + // Handle any delayed marking, which is not performed in parallel. + if (gc->hasDelayedMarking()) { + gc->markAllDelayedChildren(ReportMarkTime); + } + + return true; +} + +bool ParallelMarker::markOneColor(MarkColor color, SliceBudget& sliceBudget) { + // Run a marking slice and return whether the stack is now empty. + + if (!hasWork(color)) { + return true; + } + + gcstats::AutoPhase ap(gc->stats(), gcstats::PhaseKind::PARALLEL_MARK); + + MOZ_ASSERT(workerCount() <= MaxParallelWorkers); + mozilla::Maybe<ParallelMarkTask> tasks[MaxParallelWorkers]; + + for (size_t i = 0; i < workerCount(); i++) { + GCMarker* marker = gc->markers[i].get(); + tasks[i].emplace(this, marker, color, sliceBudget); + + // Attempt to populate empty mark stacks. + // + // TODO: When tuning for more than two markers we may need to adopt a more + // sophisticated approach. + if (!marker->hasEntriesForCurrentColor() && gc->marker().canDonateWork()) { + GCMarker::moveWork(marker, &gc->marker()); + } + } + + { + AutoLockGC lock(gc); + + activeTasks = 0; + for (size_t i = 0; i < workerCount(); i++) { + ParallelMarkTask& task = *tasks[i]; + if (task.hasWork()) { + incActiveTasks(&task, lock); + } + } + } + + { + AutoLockHelperThreadState lock; + + // There should always be enough parallel tasks to run our marking work. + MOZ_RELEASE_ASSERT(HelperThreadState().getGCParallelThreadCount(lock) >= + workerCount()); + + for (size_t i = 0; i < workerCount(); i++) { + gc->startTask(*tasks[i], lock); + } + + for (size_t i = 0; i < workerCount(); i++) { + gc->joinTask(*tasks[i], lock); + } + } + +#ifdef DEBUG + { + AutoLockGC lock(gc); + MOZ_ASSERT(waitingTasks.ref().isEmpty()); + MOZ_ASSERT(waitingTaskCount == 0); + MOZ_ASSERT(activeTasks == 0); + } +#endif + + return !hasWork(color); +} + +bool ParallelMarker::hasWork(MarkColor color) const { + for (const auto& marker : gc->markers) { + if (marker->hasEntries(color)) { + return true; + } + } + + return false; +} + +ParallelMarkTask::ParallelMarkTask(ParallelMarker* pm, GCMarker* marker, + MarkColor color, const SliceBudget& budget) + : GCParallelTask(pm->gc, gcstats::PhaseKind::PARALLEL_MARK, GCUse::Marking), + pm(pm), + marker(marker), + color(*marker, color), + budget(budget) { + marker->enterParallelMarkingMode(pm); +} + +ParallelMarkTask::~ParallelMarkTask() { + MOZ_ASSERT(!isWaiting.refNoCheck()); + marker->leaveParallelMarkingMode(); +} + +bool ParallelMarkTask::hasWork() const { + return marker->hasEntriesForCurrentColor(); +} + +void ParallelMarkTask::recordDuration() { + gc->stats().recordParallelPhase(gcstats::PhaseKind::PARALLEL_MARK, + duration()); + gc->stats().recordParallelPhase(gcstats::PhaseKind::PARALLEL_MARK_MARK, + markTime.ref()); + gc->stats().recordParallelPhase(gcstats::PhaseKind::PARALLEL_MARK_WAIT, + waitTime.ref()); +} + +void ParallelMarkTask::run(AutoLockHelperThreadState& lock) { + AutoUnlockHelperThreadState unlock(lock); + + AutoLockGC gcLock(pm->gc); + + markOrRequestWork(gcLock); + + MOZ_ASSERT(!isWaiting); +} + +void ParallelMarkTask::markOrRequestWork(AutoLockGC& lock) { + for (;;) { + if (hasWork()) { + if (!tryMarking(lock)) { + return; + } + } else { + if (!requestWork(lock)) { + return; + } + } + } +} + +bool ParallelMarkTask::tryMarking(AutoLockGC& lock) { + MOZ_ASSERT(hasWork()); + MOZ_ASSERT(marker->isParallelMarking()); + + // Mark until budget exceeded or we run out of work. + bool finished; + { + AutoUnlockGC unlock(lock); + + AutoAddTimeDuration time(markTime.ref()); + finished = marker->markCurrentColorInParallel(budget); + } + + MOZ_ASSERT_IF(finished, !hasWork()); + pm->decActiveTasks(this, lock); + + return finished; +} + +bool ParallelMarkTask::requestWork(AutoLockGC& lock) { + MOZ_ASSERT(!hasWork()); + + if (!pm->hasActiveTasks(lock)) { + return false; // All other tasks are empty. We're finished. + } + + budget.stepAndForceCheck(); + if (budget.isOverBudget()) { + return false; // Over budget or interrupted. + } + + // Add ourselves to the waiting list and wait for another task to give us + // work. The task with work calls ParallelMarker::donateWorkFrom. + waitUntilResumed(lock); + + return true; +} + +void ParallelMarkTask::waitUntilResumed(AutoLockGC& lock) { + GeckoProfilerRuntime& profiler = gc->rt->geckoProfiler(); + if (profiler.enabled()) { + profiler.markEvent("Parallel marking wait start", ""); + } + + pm->addTaskToWaitingList(this, lock); + + // Set isWaiting flag and wait for another thread to clear it and resume us. + MOZ_ASSERT(!isWaiting); + isWaiting = true; + + AutoAddTimeDuration time(waitTime.ref()); + + do { + MOZ_ASSERT(pm->hasActiveTasks(lock)); + resumed.wait(lock.guard()); + } while (isWaiting); + + MOZ_ASSERT(!pm->isTaskInWaitingList(this, lock)); + + if (profiler.enabled()) { + profiler.markEvent("Parallel marking wait end", ""); + } +} + +void ParallelMarkTask::resume() { + { + AutoLockGC lock(gc); + MOZ_ASSERT(isWaiting); + + isWaiting = false; + + // Increment the active task count before donateWorkFrom() returns so this + // can't reach zero before the waiting task runs again. + if (hasWork()) { + pm->incActiveTasks(this, lock); + } + } + + resumed.notify_all(); +} + +void ParallelMarkTask::resumeOnFinish(const AutoLockGC& lock) { + MOZ_ASSERT(isWaiting); + MOZ_ASSERT(!hasWork()); + + isWaiting = false; + resumed.notify_all(); +} + +void ParallelMarker::addTaskToWaitingList(ParallelMarkTask* task, + const AutoLockGC& lock) { + MOZ_ASSERT(!task->hasWork()); + MOZ_ASSERT(hasActiveTasks(lock)); + MOZ_ASSERT(!isTaskInWaitingList(task, lock)); + MOZ_ASSERT(waitingTaskCount < workerCount() - 1); + + waitingTasks.ref().pushBack(task); + waitingTaskCount++; +} + +#ifdef DEBUG +bool ParallelMarker::isTaskInWaitingList(const ParallelMarkTask* task, + const AutoLockGC& lock) const { + // The const cast is because ElementProbablyInList is not const. + return const_cast<ParallelMarkTaskList&>(waitingTasks.ref()) + .ElementProbablyInList(const_cast<ParallelMarkTask*>(task)); +} +#endif + +void ParallelMarker::incActiveTasks(ParallelMarkTask* task, + const AutoLockGC& lock) { + MOZ_ASSERT(task->hasWork()); + MOZ_ASSERT(activeTasks < workerCount()); + + activeTasks++; +} + +void ParallelMarker::decActiveTasks(ParallelMarkTask* task, + const AutoLockGC& lock) { + MOZ_ASSERT(activeTasks != 0); + + activeTasks--; + + if (activeTasks == 0) { + while (!waitingTasks.ref().isEmpty()) { + ParallelMarkTask* task = waitingTasks.ref().popFront(); + MOZ_ASSERT(waitingTaskCount != 0); + waitingTaskCount--; + task->resumeOnFinish(lock); + } + } +} + +void ParallelMarker::donateWorkFrom(GCMarker* src) { + if (!gc->tryLockGC()) { + return; + } + + // Check there are tasks waiting for work while holding the lock. + if (waitingTaskCount == 0) { + gc->unlockGC(); + return; + } + + // Take the first waiting task off the list. + ParallelMarkTask* waitingTask = waitingTasks.ref().popFront(); + waitingTaskCount--; + + // |task| is not running so it's safe to move work to it. + MOZ_ASSERT(waitingTask->isWaiting); + + gc->unlockGC(); + + // Move some work from this thread's mark stack to the waiting task. + MOZ_ASSERT(!waitingTask->hasWork()); + GCMarker::moveWork(waitingTask->marker, src); + + gc->stats().count(gcstats::COUNT_PARALLEL_MARK_INTERRUPTIONS); + + GeckoProfilerRuntime& profiler = gc->rt->geckoProfiler(); + if (profiler.enabled()) { + profiler.markEvent("Parallel marking donated work", ""); + } + + // Resume waiting task. + waitingTask->resume(); +} |