V8 API Reference, 7.2.502.16 (for Deno 0.2.4)
worklist.h
1 // Copyright 2017 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef V8_HEAP_WORKLIST_H_
6 #define V8_HEAP_WORKLIST_H_
7 
8 #include <cstddef>
9 #include <utility>
10 
11 #include "src/base/atomic-utils.h"
12 #include "src/base/logging.h"
13 #include "src/base/macros.h"
14 #include "src/base/platform/mutex.h"
15 #include "testing/gtest/include/gtest/gtest_prod.h" // nogncheck
16 
17 namespace v8 {
18 namespace internal {
19 
20 // A concurrent worklist based on segments. Each tasks gets private
21 // push and pop segments. Empty pop segments are swapped with their
22 // corresponding push segments. Full push segments are published to a global
23 // pool of segments and replaced with empty segments.
24 //
25 // Work stealing is best effort, i.e., there is no way to inform other tasks
26 // of the need of items.
27 template <typename EntryType, int SEGMENT_SIZE>
28 class Worklist {
29  public:
30  class View {
31  public:
32  View(Worklist<EntryType, SEGMENT_SIZE>* worklist, int task_id)
33  : worklist_(worklist), task_id_(task_id) {}
34 
35  // Pushes an entry onto the worklist.
36  bool Push(EntryType entry) { return worklist_->Push(task_id_, entry); }
37 
38  // Pops an entry from the worklist.
39  bool Pop(EntryType* entry) { return worklist_->Pop(task_id_, entry); }
40 
41  // Returns true if the local portion of the worklist is empty.
42  bool IsLocalEmpty() { return worklist_->IsLocalEmpty(task_id_); }
43 
44  // Returns true if the worklist is empty. Can only be used from the main
45  // thread without concurrent access.
46  bool IsEmpty() { return worklist_->IsEmpty(); }
47 
48  bool IsGlobalPoolEmpty() { return worklist_->IsGlobalPoolEmpty(); }
49 
50  size_t LocalPushSegmentSize() {
51  return worklist_->LocalPushSegmentSize(task_id_);
52  }
53 
54  private:
56  int task_id_;
57  };
58 
59  static const int kMaxNumTasks = 8;
60  static const size_t kSegmentCapacity = SEGMENT_SIZE;
61 
62  Worklist() : Worklist(kMaxNumTasks) {}
63 
64  explicit Worklist(int num_tasks) : num_tasks_(num_tasks) {
65  DCHECK_LE(num_tasks, kMaxNumTasks);
66  for (int i = 0; i < num_tasks_; i++) {
67  private_push_segment(i) = NewSegment();
68  private_pop_segment(i) = NewSegment();
69  }
70  }
71 
72  ~Worklist() {
73  CHECK(IsEmpty());
74  for (int i = 0; i < num_tasks_; i++) {
75  DCHECK_NOT_NULL(private_push_segment(i));
76  DCHECK_NOT_NULL(private_pop_segment(i));
77  delete private_push_segment(i);
78  delete private_pop_segment(i);
79  }
80  }
81 
82  // Swaps content with the given worklist. Local buffers need to
83  // be empty, not thread safe.
84  void Swap(Worklist<EntryType, SEGMENT_SIZE>& other) {
85  CHECK(AreLocalsEmpty());
86  CHECK(other.AreLocalsEmpty());
87 
88  global_pool_.Swap(other.global_pool_);
89  }
90 
91  bool Push(int task_id, EntryType entry) {
92  DCHECK_LT(task_id, num_tasks_);
93  DCHECK_NOT_NULL(private_push_segment(task_id));
94  if (!private_push_segment(task_id)->Push(entry)) {
95  PublishPushSegmentToGlobal(task_id);
96  bool success = private_push_segment(task_id)->Push(entry);
97  USE(success);
98  DCHECK(success);
99  }
100  return true;
101  }
102 
103  bool Pop(int task_id, EntryType* entry) {
104  DCHECK_LT(task_id, num_tasks_);
105  DCHECK_NOT_NULL(private_pop_segment(task_id));
106  if (!private_pop_segment(task_id)->Pop(entry)) {
107  if (!private_push_segment(task_id)->IsEmpty()) {
108  Segment* tmp = private_pop_segment(task_id);
109  private_pop_segment(task_id) = private_push_segment(task_id);
110  private_push_segment(task_id) = tmp;
111  } else if (!StealPopSegmentFromGlobal(task_id)) {
112  return false;
113  }
114  bool success = private_pop_segment(task_id)->Pop(entry);
115  USE(success);
116  DCHECK(success);
117  }
118  return true;
119  }
120 
121  size_t LocalPushSegmentSize(int task_id) {
122  return private_push_segment(task_id)->Size();
123  }
124 
125  bool IsLocalEmpty(int task_id) {
126  return private_pop_segment(task_id)->IsEmpty() &&
127  private_push_segment(task_id)->IsEmpty();
128  }
129 
130  bool IsGlobalPoolEmpty() { return global_pool_.IsEmpty(); }
131 
132  bool IsEmpty() {
133  if (!AreLocalsEmpty()) return false;
134  return global_pool_.IsEmpty();
135  }
136 
137  bool AreLocalsEmpty() {
138  for (int i = 0; i < num_tasks_; i++) {
139  if (!IsLocalEmpty(i)) return false;
140  }
141  return true;
142  }
143 
144  size_t LocalSize(int task_id) {
145  return private_pop_segment(task_id)->Size() +
146  private_push_segment(task_id)->Size();
147  }
148 
149  // Clears all segments. Frees the global segment pool.
150  //
151  // Assumes that no other tasks are running.
152  void Clear() {
153  for (int i = 0; i < num_tasks_; i++) {
154  private_pop_segment(i)->Clear();
155  private_push_segment(i)->Clear();
156  }
157  global_pool_.Clear();
158  }
159 
160  // Calls the specified callback on each element of the deques and replaces
161  // the element with the result of the callback.
162  // The signature of the callback is
163  // bool Callback(EntryType old, EntryType* new).
164  // If the callback returns |false| then the element is removed from the
165  // worklist. Otherwise the |new| entry is updated.
166  //
167  // Assumes that no other tasks are running.
168  template <typename Callback>
169  void Update(Callback callback) {
170  for (int i = 0; i < num_tasks_; i++) {
171  private_pop_segment(i)->Update(callback);
172  private_push_segment(i)->Update(callback);
173  }
174  global_pool_.Update(callback);
175  }
176 
177  // Calls the specified callback on each element of the deques.
178  // The signature of the callback is:
179  // void Callback(EntryType entry).
180  //
181  // Assumes that no other tasks are running.
182  template <typename Callback>
183  void Iterate(Callback callback) {
184  for (int i = 0; i < num_tasks_; i++) {
185  private_pop_segment(i)->Iterate(callback);
186  private_push_segment(i)->Iterate(callback);
187  }
188  global_pool_.Iterate(callback);
189  }
190 
191  template <typename Callback>
192  void IterateGlobalPool(Callback callback) {
193  global_pool_.Iterate(callback);
194  }
195 
196  void FlushToGlobal(int task_id) {
197  PublishPushSegmentToGlobal(task_id);
198  PublishPopSegmentToGlobal(task_id);
199  }
200 
201  void MergeGlobalPool(Worklist* other) {
202  auto pair = other->global_pool_.Extract();
203  global_pool_.MergeList(pair.first, pair.second);
204  }
205 
206  private:
207  FRIEND_TEST(WorkListTest, SegmentCreate);
208  FRIEND_TEST(WorkListTest, SegmentPush);
209  FRIEND_TEST(WorkListTest, SegmentPushPop);
210  FRIEND_TEST(WorkListTest, SegmentIsEmpty);
211  FRIEND_TEST(WorkListTest, SegmentIsFull);
212  FRIEND_TEST(WorkListTest, SegmentClear);
213  FRIEND_TEST(WorkListTest, SegmentFullPushFails);
214  FRIEND_TEST(WorkListTest, SegmentEmptyPopFails);
215  FRIEND_TEST(WorkListTest, SegmentUpdateFalse);
216  FRIEND_TEST(WorkListTest, SegmentUpdate);
217 
218  class Segment {
219  public:
220  static const size_t kCapacity = kSegmentCapacity;
221 
222  Segment() : index_(0) {}
223 
224  bool Push(EntryType entry) {
225  if (IsFull()) return false;
226  entries_[index_++] = entry;
227  return true;
228  }
229 
230  bool Pop(EntryType* entry) {
231  if (IsEmpty()) return false;
232  *entry = entries_[--index_];
233  return true;
234  }
235 
236  size_t Size() const { return index_; }
237  bool IsEmpty() const { return index_ == 0; }
238  bool IsFull() const { return index_ == kCapacity; }
239  void Clear() { index_ = 0; }
240 
241  template <typename Callback>
242  void Update(Callback callback) {
243  size_t new_index = 0;
244  for (size_t i = 0; i < index_; i++) {
245  if (callback(entries_[i], &entries_[new_index])) {
246  new_index++;
247  }
248  }
249  index_ = new_index;
250  }
251 
252  template <typename Callback>
253  void Iterate(Callback callback) const {
254  for (size_t i = 0; i < index_; i++) {
255  callback(entries_[i]);
256  }
257  }
258 
259  Segment* next() const { return next_; }
260  void set_next(Segment* segment) { next_ = segment; }
261 
262  private:
263  Segment* next_;
264  size_t index_;
265  EntryType entries_[kCapacity];
266  };
267 
268  struct PrivateSegmentHolder {
269  Segment* private_push_segment;
270  Segment* private_pop_segment;
271  char cache_line_padding[64];
272  };
273 
274  class GlobalPool {
275  public:
276  GlobalPool() : top_(nullptr) {}
277 
278  // Swaps contents, not thread safe.
279  void Swap(GlobalPool& other) {
280  Segment* temp = top_;
281  set_top(other.top_);
282  other.set_top(temp);
283  }
284 
285  V8_INLINE void Push(Segment* segment) {
286  base::MutexGuard guard(&lock_);
287  segment->set_next(top_);
288  set_top(segment);
289  }
290 
291  V8_INLINE bool Pop(Segment** segment) {
292  base::MutexGuard guard(&lock_);
293  if (top_ != nullptr) {
294  *segment = top_;
295  set_top(top_->next());
296  return true;
297  }
298  return false;
299  }
300 
301  V8_INLINE bool IsEmpty() {
302  return base::AsAtomicPointer::Relaxed_Load(&top_) == nullptr;
303  }
304 
305  void Clear() {
306  base::MutexGuard guard(&lock_);
307  Segment* current = top_;
308  while (current != nullptr) {
309  Segment* tmp = current;
310  current = current->next();
311  delete tmp;
312  }
313  set_top(nullptr);
314  }
315 
316  // See Worklist::Update.
317  template <typename Callback>
318  void Update(Callback callback) {
319  base::MutexGuard guard(&lock_);
320  Segment* prev = nullptr;
321  Segment* current = top_;
322  while (current != nullptr) {
323  current->Update(callback);
324  if (current->IsEmpty()) {
325  if (prev == nullptr) {
326  top_ = current->next();
327  } else {
328  prev->set_next(current->next());
329  }
330  Segment* tmp = current;
331  current = current->next();
332  delete tmp;
333  } else {
334  prev = current;
335  current = current->next();
336  }
337  }
338  }
339 
340  // See Worklist::Iterate.
341  template <typename Callback>
342  void Iterate(Callback callback) {
343  base::MutexGuard guard(&lock_);
344  for (Segment* current = top_; current != nullptr;
345  current = current->next()) {
346  current->Iterate(callback);
347  }
348  }
349 
350  std::pair<Segment*, Segment*> Extract() {
351  Segment* top = nullptr;
352  {
353  base::MutexGuard guard(&lock_);
354  if (top_ == nullptr) return std::make_pair(nullptr, nullptr);
355  top = top_;
356  set_top(nullptr);
357  }
358  Segment* end = top;
359  while (end->next() != nullptr) end = end->next();
360  return std::make_pair(top, end);
361  }
362 
363  void MergeList(Segment* start, Segment* end) {
364  if (start == nullptr) return;
365  {
366  base::MutexGuard guard(&lock_);
367  end->set_next(top_);
368  set_top(start);
369  }
370  }
371 
372  private:
373  void set_top(Segment* segment) {
374  base::AsAtomicPointer::Relaxed_Store(&top_, segment);
375  }
376 
377  base::Mutex lock_;
378  Segment* top_;
379  };
380 
381  V8_INLINE Segment*& private_push_segment(int task_id) {
382  return private_segments_[task_id].private_push_segment;
383  }
384 
385  V8_INLINE Segment*& private_pop_segment(int task_id) {
386  return private_segments_[task_id].private_pop_segment;
387  }
388 
389  V8_INLINE void PublishPushSegmentToGlobal(int task_id) {
390  if (!private_push_segment(task_id)->IsEmpty()) {
391  global_pool_.Push(private_push_segment(task_id));
392  private_push_segment(task_id) = NewSegment();
393  }
394  }
395 
396  V8_INLINE void PublishPopSegmentToGlobal(int task_id) {
397  if (!private_pop_segment(task_id)->IsEmpty()) {
398  global_pool_.Push(private_pop_segment(task_id));
399  private_pop_segment(task_id) = NewSegment();
400  }
401  }
402 
403  V8_INLINE bool StealPopSegmentFromGlobal(int task_id) {
404  if (global_pool_.IsEmpty()) return false;
405  Segment* new_segment = nullptr;
406  if (global_pool_.Pop(&new_segment)) {
407  delete private_pop_segment(task_id);
408  private_pop_segment(task_id) = new_segment;
409  return true;
410  }
411  return false;
412  }
413 
414  V8_INLINE Segment* NewSegment() {
415  // Bottleneck for filtering in crash dumps.
416  return new Segment();
417  }
418 
419  PrivateSegmentHolder private_segments_[kMaxNumTasks];
420  GlobalPool global_pool_;
421  int num_tasks_;
422 };
423 
424 } // namespace internal
425 } // namespace v8
426 
427 #endif // V8_HEAP_WORKLIST_H_
Definition: libplatform.h:13