summaryrefslogtreecommitdiffstats
path: root/js/src/gc/ParallelWork.h
blob: db97c750094ea60ec4a20847bf016eb7f2cb6bcd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
/* -*- 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/. */

#ifndef gc_ParallelWork_h
#define gc_ParallelWork_h

#include "mozilla/Maybe.h"

#include <algorithm>

#include "gc/GCParallelTask.h"
#include "gc/GCRuntime.h"
#include "js/SliceBudget.h"
#include "vm/HelperThreads.h"

namespace js {

namespace gcstats {
enum class PhaseKind : uint8_t;
}

namespace gc {

template <typename WorkItem>
using ParallelWorkFunc = size_t (*)(GCRuntime*, const WorkItem&);

// A GCParallelTask task that executes WorkItems from a WorkItemIterator.
//
// The WorkItemIterator class must supply done(), next() and get() methods. The
// get() method must return WorkItems objects.
template <typename WorkItem, typename WorkItemIterator>
class ParallelWorker : public GCParallelTask {
 public:
  using WorkFunc = ParallelWorkFunc<WorkItem>;

  ParallelWorker(GCRuntime* gc, gcstats::PhaseKind phaseKind, GCUse use,
                 WorkFunc func, WorkItemIterator& work,
                 const SliceBudget& budget, AutoLockHelperThreadState& lock)
      : GCParallelTask(gc, phaseKind, use),
        func_(func),
        work_(work),
        budget_(budget),
        item_(work.get()) {
    // Consume a work item on creation so that we can stop creating workers if
    // the number of workers exceeds the number of work items.
    work.next();
  }

  void run(AutoLockHelperThreadState& lock) {
    AutoUnlockHelperThreadState unlock(lock);

    for (;;) {
      size_t steps = func_(gc, item_);
      budget_.step(std::max(steps, size_t(1)));
      if (budget_.isOverBudget()) {
        break;
      }

      AutoLockHelperThreadState lock;
      if (work().done()) {
        break;
      }

      item_ = work().get();
      work().next();
    }
  }

 private:
  WorkItemIterator& work() { return work_.ref(); }

  // A function to execute work items on the helper thread.
  WorkFunc func_;

  // An iterator which produces work items to execute.
  HelperThreadLockData<WorkItemIterator&> work_;

  // The budget that determines how long to run for.
  SliceBudget budget_;

  // The next work item to process.
  WorkItem item_;
};

static constexpr size_t MaxParallelWorkers = 8;

// An RAII class that starts a number of ParallelWorkers and waits for them to
// finish.
template <typename WorkItem, typename WorkItemIterator>
class MOZ_RAII AutoRunParallelWork {
 public:
  using Worker = ParallelWorker<WorkItem, WorkItemIterator>;
  using WorkFunc = ParallelWorkFunc<WorkItem>;

  AutoRunParallelWork(GCRuntime* gc, WorkFunc func,
                      gcstats::PhaseKind phaseKind, GCUse use,
                      WorkItemIterator& work, const SliceBudget& budget,
                      AutoLockHelperThreadState& lock)
      : gc(gc), phaseKind(phaseKind), lock(lock), tasksStarted(0) {
    size_t workerCount = gc->parallelWorkerCount();
    MOZ_ASSERT(workerCount <= MaxParallelWorkers);
    MOZ_ASSERT_IF(workerCount == 0, work.done());

    for (size_t i = 0; i < workerCount && !work.done(); i++) {
      tasks[i].emplace(gc, phaseKind, use, func, work, budget, lock);
      gc->startTask(*tasks[i], lock);
      tasksStarted++;
    }
  }

  ~AutoRunParallelWork() {
    gHelperThreadLock.assertOwnedByCurrentThread();

    for (size_t i = 0; i < tasksStarted; i++) {
      gc->joinTask(*tasks[i], lock);
    }
    for (size_t i = tasksStarted; i < MaxParallelWorkers; i++) {
      MOZ_ASSERT(tasks[i].isNothing());
    }
  }

 private:
  GCRuntime* gc;
  gcstats::PhaseKind phaseKind;
  AutoLockHelperThreadState& lock;
  size_t tasksStarted;
  mozilla::Maybe<Worker> tasks[MaxParallelWorkers];
};

} /* namespace gc */
} /* namespace js */

#endif /* gc_ParallelWork_h */