tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ParallelMarking.cpp (10515B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "gc/ParallelMarking.h"
      8 
      9 #include "gc/GCInternals.h"
     10 #include "gc/GCLock.h"
     11 #include "vm/GeckoProfiler.h"
     12 #include "vm/HelperThreadState.h"
     13 #include "vm/Runtime.h"
     14 
     15 using namespace js;
     16 using namespace js::gc;
     17 
     18 using mozilla::TimeDuration;
     19 using mozilla::TimeStamp;
     20 
     21 using JS::SliceBudget;
     22 
     23 class AutoAddTimeDuration {
     24  TimeStamp start_;
     25  TimeDuration& result_;
     26 
     27 public:
     28  explicit AutoAddTimeDuration(TimeDuration& result)
     29      : start_(TimeStamp::Now()), result_(result) {}
     30  TimeStamp start() const { return start_; }
     31  ~AutoAddTimeDuration() { result_ += TimeSince(start_); }
     32 };
     33 
     34 /* static */
     35 bool ParallelMarker::mark(GCRuntime* gc, const SliceBudget& sliceBudget) {
     36  if (!markOneColor(gc, MarkColor::Black, sliceBudget) ||
     37      !markOneColor(gc, MarkColor::Gray, sliceBudget)) {
     38    return false;
     39  }
     40 
     41  // Handle any delayed marking, which is not performed in parallel.
     42  if (gc->hasDelayedMarking()) {
     43    gc->markAllDelayedChildren(ReportMarkTime);
     44  }
     45 
     46  return true;
     47 }
     48 
     49 /* static */
     50 bool ParallelMarker::markOneColor(GCRuntime* gc, MarkColor color,
     51                                  const SliceBudget& sliceBudget) {
     52  ParallelMarker pm(gc, color);
     53  return pm.mark(sliceBudget);
     54 }
     55 
     56 ParallelMarker::ParallelMarker(GCRuntime* gc, MarkColor color)
     57    : gc(gc), color(color) {
     58  // There should always be enough parallel tasks to run our marking work.
     59  MOZ_ASSERT(workerCount() <= gc->getMaxParallelThreads());
     60 }
     61 
     62 size_t ParallelMarker::workerCount() const { return gc->markers.length(); }
     63 
     64 bool ParallelMarker::mark(const SliceBudget& sliceBudget) {
     65  // Run a marking slice for a single color and return whether the stack is now
     66  // empty.
     67 
     68  if (!hasWork(color)) {
     69    return true;
     70  }
     71 
     72  gcstats::AutoPhase ap(gc->stats(), gcstats::PhaseKind::PARALLEL_MARK);
     73 
     74  MOZ_ASSERT(workerCount() <= MaxParallelWorkers);
     75 
     76  for (size_t i = 0; i < workerCount(); i++) {
     77    GCMarker* marker = gc->markers[i].get();
     78    tasks[i].emplace(this, marker, color, i, sliceBudget);
     79 
     80    // Attempt to populate empty mark stacks.
     81    //
     82    // TODO: When tuning for more than two markers we may need to adopt a more
     83    // sophisticated approach.
     84    if (!marker->hasEntriesForCurrentColor() && gc->marker().canDonateWork()) {
     85      GCMarker::moveWork(marker, &gc->marker(), false);
     86    }
     87  }
     88 
     89  AutoLockHelperThreadState lock;
     90 
     91  MOZ_ASSERT(!hasActiveTasks(lock));
     92  for (size_t i = 0; i < workerCount(); i++) {
     93    ParallelMarkTask& task = *tasks[i];
     94    if (task.hasWork()) {
     95      setTaskActive(&task, lock);
     96    }
     97  }
     98 
     99  // Run the parallel tasks, using the main thread for the first one.
    100  for (size_t i = 1; i < workerCount(); i++) {
    101    gc->startTask(*tasks[i], lock);
    102  }
    103  tasks[0]->runFromMainThread(lock);
    104  tasks[0]->recordDuration();  // Record stats as if it used a helper thread.
    105  for (size_t i = 1; i < workerCount(); i++) {
    106    gc->joinTask(*tasks[i], lock);
    107  }
    108 
    109  MOZ_ASSERT(!hasWaitingTasks());
    110  MOZ_ASSERT(!hasActiveTasks(lock));
    111 
    112  return !hasWork(color);
    113 }
    114 
    115 bool ParallelMarker::hasWork(MarkColor color) const {
    116  for (const auto& marker : gc->markers) {
    117    if (marker->hasEntries(color)) {
    118      return true;
    119    }
    120  }
    121 
    122  return false;
    123 }
    124 
    125 ParallelMarkTask::ParallelMarkTask(ParallelMarker* pm, GCMarker* marker,
    126                                   MarkColor color, uint32_t id,
    127                                   const SliceBudget& budget)
    128    : GCParallelTask(pm->gc, gcstats::PhaseKind::PARALLEL_MARK, GCUse::Marking),
    129      pm(pm),
    130      marker(marker),
    131      color(*marker, color),
    132      budget(budget),
    133      id(id) {
    134  marker->enterParallelMarkingMode();
    135 }
    136 
    137 ParallelMarkTask::~ParallelMarkTask() {
    138  MOZ_ASSERT(!isWaiting.refNoCheck());
    139  marker->leaveParallelMarkingMode();
    140 }
    141 
    142 bool ParallelMarkTask::hasWork() const {
    143  return marker->hasEntriesForCurrentColor();
    144 }
    145 
    146 void ParallelMarkTask::recordDuration() {
    147  // Record times separately to avoid double counting when these are summed.
    148  gc->stats().recordParallelPhase(gcstats::PhaseKind::PARALLEL_MARK_MARK,
    149                                  markTime.ref());
    150  gc->stats().recordParallelPhase(gcstats::PhaseKind::PARALLEL_MARK_WAIT,
    151                                  waitTime.ref());
    152  TimeDuration other = duration() - markTime.ref() - waitTime.ref();
    153  if (other < TimeDuration::Zero()) {
    154    other = TimeDuration::Zero();
    155  }
    156  gc->stats().recordParallelPhase(gcstats::PhaseKind::PARALLEL_MARK_OTHER,
    157                                  other);
    158 }
    159 
    160 void ParallelMarkTask::run(AutoLockHelperThreadState& lock) {
    161  AutoUpdateMarkStackRanges updateRanges(*marker);
    162 
    163  for (;;) {
    164    if (hasWork()) {
    165      if (!tryMarking(lock)) {
    166        return;
    167      }
    168    } else {
    169      if (!requestWork(lock)) {
    170        return;
    171      }
    172    }
    173  }
    174 
    175  MOZ_ASSERT(!isWaiting);
    176 }
    177 
    178 bool ParallelMarkTask::tryMarking(AutoLockHelperThreadState& lock) {
    179  MOZ_ASSERT(hasWork());
    180  MOZ_ASSERT(marker->isParallelMarking());
    181 
    182  // Mark until budget exceeded or we run out of work.
    183  bool finished;
    184  {
    185    AutoUnlockHelperThreadState unlock(lock);
    186 
    187    AutoAddTimeDuration time(markTime.ref());
    188    finished = marker->markCurrentColorInParallel(this, budget);
    189 
    190    GeckoProfilerRuntime& profiler = gc->rt->geckoProfiler();
    191    if (profiler.enabled()) {
    192      profiler.markInterval("Parallel marking ran", time.start(), nullptr,
    193                            JS::ProfilingCategoryPair::GCCC);
    194    }
    195  }
    196 
    197  MOZ_ASSERT_IF(finished, !hasWork());
    198  pm->setTaskInactive(this, lock);
    199 
    200  return finished;
    201 }
    202 
    203 bool ParallelMarkTask::requestWork(AutoLockHelperThreadState& lock) {
    204  MOZ_ASSERT(!hasWork());
    205 
    206  if (!pm->hasActiveTasks(lock)) {
    207    return false;  // All other tasks are empty. We're finished.
    208  }
    209 
    210  budget.forceCheck();
    211  if (budget.isOverBudget()) {
    212    return false;  // Over budget or interrupted.
    213  }
    214 
    215  // Add ourselves to the waiting list and wait for another task to give us
    216  // work. The task with work calls ParallelMarker::donateWorkFrom.
    217  waitUntilResumed(lock);
    218 
    219  return true;
    220 }
    221 
    222 void ParallelMarkTask::waitUntilResumed(AutoLockHelperThreadState& lock) {
    223  AutoAddTimeDuration time(waitTime.ref());
    224 
    225  pm->addTaskToWaitingList(this, lock);
    226 
    227  // Set isWaiting flag and wait for another thread to clear it and resume us.
    228  MOZ_ASSERT(!isWaiting);
    229  isWaiting = true;
    230 
    231  do {
    232    MOZ_ASSERT(pm->hasActiveTasks(lock));
    233    resumed.wait(lock);
    234  } while (isWaiting);
    235 
    236  MOZ_ASSERT(!pm->isTaskInWaitingList(this, lock));
    237 
    238  GeckoProfilerRuntime& profiler = gc->rt->geckoProfiler();
    239  if (profiler.enabled()) {
    240    char details[32];
    241    SprintfLiteral(details, "markers=%zu", pm->workerCount());
    242    profiler.markInterval("Parallel marking wait", time.start(), details,
    243                          JS::ProfilingCategoryPair::GCCC);
    244  }
    245 }
    246 
    247 void ParallelMarkTask::resume() {
    248  {
    249    AutoLockHelperThreadState lock;
    250    MOZ_ASSERT(isWaiting);
    251 
    252    isWaiting = false;
    253 
    254    // Increment the active task count before donateWorkFrom() returns so this
    255    // can't reach zero before the waiting task runs again.
    256    if (hasWork()) {
    257      pm->setTaskActive(this, lock);
    258    }
    259  }
    260 
    261  resumed.notify_all();
    262 }
    263 
    264 void ParallelMarkTask::resumeOnFinish(const AutoLockHelperThreadState& lock) {
    265  MOZ_ASSERT(isWaiting);
    266  MOZ_ASSERT(!hasWork());
    267 
    268  isWaiting = false;
    269  resumed.notify_all();
    270 }
    271 
    272 void ParallelMarker::addTaskToWaitingList(
    273    ParallelMarkTask* task, const AutoLockHelperThreadState& lock) {
    274  MOZ_ASSERT(!task->hasWork());
    275  MOZ_ASSERT(hasActiveTasks(lock));
    276  MOZ_ASSERT(!isTaskInWaitingList(task, lock));
    277 
    278  uint32_t id = task->id;
    279  MOZ_ASSERT(id < workerCount());
    280  MOZ_ASSERT(!waitingTasks[id]);
    281  waitingTasks[id] = true;
    282 }
    283 
    284 #ifdef DEBUG
    285 bool ParallelMarker::isTaskInWaitingList(
    286    const ParallelMarkTask* task, const AutoLockHelperThreadState& lock) const {
    287  uint32_t id = task->id;
    288  MOZ_ASSERT(id < workerCount());
    289  return waitingTasks[id];
    290 }
    291 #endif
    292 
    293 ParallelMarkTask* ParallelMarker::takeWaitingTask() {
    294  MOZ_ASSERT(hasWaitingTasks());
    295  uint32_t id = waitingTasks.FindFirst();
    296  MOZ_ASSERT(id < workerCount());
    297 
    298  MOZ_ASSERT(waitingTasks[id]);
    299  waitingTasks[id] = false;
    300  return &*tasks[id];
    301 }
    302 
    303 void ParallelMarker::setTaskActive(ParallelMarkTask* task,
    304                                   const AutoLockHelperThreadState& lock) {
    305  MOZ_ASSERT(task->hasWork());
    306 
    307  uint32_t id = task->id;
    308  MOZ_ASSERT(id < workerCount());
    309  MOZ_ASSERT(!activeTasks.ref()[id]);
    310  activeTasks.ref()[id] = true;
    311 }
    312 
    313 void ParallelMarker::setTaskInactive(ParallelMarkTask* task,
    314                                     const AutoLockHelperThreadState& lock) {
    315  MOZ_ASSERT(hasActiveTasks(lock));
    316 
    317  uint32_t id = task->id;
    318  MOZ_ASSERT(id < workerCount());
    319  MOZ_ASSERT(activeTasks.ref()[id]);
    320  activeTasks.ref()[id] = false;
    321 
    322  if (!hasActiveTasks(lock)) {
    323    while (hasWaitingTasks()) {
    324      takeWaitingTask()->resumeOnFinish(lock);
    325    }
    326  }
    327 }
    328 
    329 void ParallelMarkTask::donateWork() { pm->donateWorkFrom(marker); }
    330 
    331 void ParallelMarker::donateWorkFrom(GCMarker* src) {
    332  GeckoProfilerRuntime& profiler = gc->rt->geckoProfiler();
    333 
    334  if (!gHelperThreadLock.tryLock()) {
    335    if (profiler.enabled()) {
    336      profiler.markEvent("Parallel marking donate failed", "lock already held");
    337    }
    338    return;
    339  }
    340 
    341  // Check there are tasks waiting for work while holding the lock.
    342  if (!hasWaitingTasks()) {
    343    gHelperThreadLock.unlock();
    344    if (profiler.enabled()) {
    345      profiler.markEvent("Parallel marking donate failed", "no tasks waiting");
    346    }
    347    return;
    348  }
    349 
    350  // Take a waiting task off the list.
    351  ParallelMarkTask* waitingTask = takeWaitingTask();
    352 
    353  // |task| is not running so it's safe to move work to it.
    354  MOZ_ASSERT(waitingTask->isWaiting);
    355 
    356  gHelperThreadLock.unlock();
    357 
    358  // Move some work from this thread's mark stack to the waiting task.
    359  MOZ_ASSERT(!waitingTask->hasWork());
    360  size_t wordsMoved = GCMarker::moveWork(waitingTask->marker, src, true);
    361 
    362  gc->stats().count(gcstats::COUNT_PARALLEL_MARK_INTERRUPTIONS);
    363 
    364  if (profiler.enabled()) {
    365    char details[32];
    366    SprintfLiteral(details, "words=%zu", wordsMoved);
    367    profiler.markEvent("Parallel marking donated work", details,
    368                       JS::ProfilingCategoryPair::GCCC);
    369  }
    370 
    371  // Resume waiting task.
    372  waitingTask->resume();
    373 }