summaryrefslogtreecommitdiffstats
path: root/js/src/gc/ParallelMarking.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/gc/ParallelMarking.cpp')
-rw-r--r--js/src/gc/ParallelMarking.cpp286
1 files changed, 286 insertions, 0 deletions
diff --git a/js/src/gc/ParallelMarking.cpp b/js/src/gc/ParallelMarking.cpp
new file mode 100644
index 0000000000..e10c98cc74
--- /dev/null
+++ b/js/src/gc/ParallelMarking.cpp
@@ -0,0 +1,286 @@
+/* -*- 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"
+
+using namespace js;
+using namespace js::gc;
+
+ParallelMarker::ParallelMarker(GCRuntime* gc) : gc(gc) {}
+
+size_t ParallelMarker::workerCount() const { return gc->markers.length(); }
+
+bool ParallelMarker::mark(SliceBudget& sliceBudget) {
+ 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;
+ }
+
+ 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 supporting more than two markers we will need a more
+ // sophisticated approach.
+ if (!marker->hasEntries(color) && gc->marker().hasEntries(color)) {
+ marker->stealWorkFrom(&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;
+
+ 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::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->hasEntries(marker->markColor());
+}
+
+void ParallelMarkTask::run(AutoLockHelperThreadState& lock) {
+ AutoUnlockHelperThreadState unlock(lock);
+
+ {
+ AutoLockGC gcLock(pm->gc);
+
+ markOrSteal(gcLock);
+
+ MOZ_ASSERT(!isWaiting);
+ if (hasWork()) {
+ pm->decActiveTasks(this, gcLock);
+ }
+ }
+}
+
+void ParallelMarkTask::markOrSteal(AutoLockGC& lock) {
+ for (;;) {
+ if (hasWork() && !tryMarking(lock)) {
+ return;
+ }
+
+ while (!hasWork()) {
+ if (!tryStealing(lock)) {
+ return;
+ }
+ }
+ }
+}
+
+bool ParallelMarkTask::tryMarking(AutoLockGC& lock) {
+ MOZ_ASSERT(hasWork());
+ MOZ_ASSERT(marker->isParallelMarking());
+
+ // Mark until budget exceeded or we run out of work.
+ {
+ AutoUnlockGC unlock(lock);
+ marker->markCurrentColorInParallel(budget);
+ }
+
+ if (!hasWork()) {
+ pm->decActiveTasks(this, lock);
+ }
+
+ return !budget.isOverBudget();
+}
+
+bool ParallelMarkTask::tryStealing(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::stealWorkFrom.
+ waitUntilResumed(lock);
+
+ if (hasWork()) {
+ pm->incActiveTasks(this, lock);
+ }
+
+ return true;
+}
+
+void ParallelMarkTask::waitUntilResumed(AutoLockGC& lock) {
+ pm->addTaskToWaitingList(this, lock);
+
+ // Set isWaiting flag and wait for another thread to clear it and resume us.
+ MOZ_ASSERT(!isWaiting);
+ isWaiting = true;
+ do {
+ MOZ_ASSERT(pm->hasActiveTasks(lock));
+ resumed.wait(lock.guard());
+ } while (isWaiting);
+
+ MOZ_ASSERT(!pm->isTaskInWaitingList(this, lock));
+}
+
+void ParallelMarkTask::resume(const AutoLockGC& lock) {
+ MOZ_ASSERT(isWaiting);
+
+ 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) {
+ // We're finished. Wake up any tasks waiting for work.
+ activeTasksAvailable.ref().notify_all();
+
+ while (!waitingTasks.ref().isEmpty()) {
+ ParallelMarkTask* task = waitingTasks.ref().popFront();
+ MOZ_ASSERT(waitingTaskCount != 0);
+ waitingTaskCount--;
+ task->resume(lock);
+ }
+ }
+}
+
+void ParallelMarker::stealWorkFrom(GCMarker* victim) {
+ AutoLockGC lock(gc);
+
+ // Check there are tasks waiting for work while holding the lock.
+ if (waitingTaskCount == 0) {
+ return;
+ }
+
+ // Take the first waiting task off the list.
+ ParallelMarkTask* task = waitingTasks.ref().popFront();
+ waitingTaskCount--;
+
+ // |task| is not running so it's safe to move work to it.
+ MOZ_ASSERT(task->isWaiting);
+
+ // TODO: When using more than two marking threads it may be better to
+ // release the lock while we steal.
+
+ // Move some work from this thread's mark stack to the waiting task.
+ task->marker->stealWorkFrom(victim);
+
+ // Resume waiting task.
+ task->resume(lock);
+}