• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
View(Worklist<EntryType,SEGMENT_SIZE> * worklist,int task_id)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.
Push(EntryType entry)36     bool Push(EntryType entry) { return worklist_->Push(task_id_, entry); }
37 
38     // Pops an entry from the worklist.
Pop(EntryType * entry)39     bool Pop(EntryType* entry) { return worklist_->Pop(task_id_, entry); }
40 
41     // Returns true if the local portion of the worklist is empty.
IsLocalEmpty()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.
IsEmpty()46     bool IsEmpty() { return worklist_->IsEmpty(); }
47 
IsGlobalPoolEmpty()48     bool IsGlobalPoolEmpty() { return worklist_->IsGlobalPoolEmpty(); }
49 
LocalPushSegmentSize()50     size_t LocalPushSegmentSize() {
51       return worklist_->LocalPushSegmentSize(task_id_);
52     }
53 
FlushToGlobal()54     void FlushToGlobal() { worklist_->FlushToGlobal(task_id_); }
55 
56    private:
57     Worklist<EntryType, SEGMENT_SIZE>* worklist_;
58     int task_id_;
59   };
60 
61   static const int kMaxNumTasks = 8;
62   static const size_t kSegmentCapacity = SEGMENT_SIZE;
63 
Worklist()64   Worklist() : Worklist(kMaxNumTasks) {}
65 
Worklist(int num_tasks)66   explicit Worklist(int num_tasks) : num_tasks_(num_tasks) {
67     DCHECK_LE(num_tasks, kMaxNumTasks);
68     for (int i = 0; i < num_tasks_; i++) {
69       private_push_segment(i) = NewSegment();
70       private_pop_segment(i) = NewSegment();
71     }
72   }
73 
~Worklist()74   ~Worklist() {
75     CHECK(IsEmpty());
76     for (int i = 0; i < num_tasks_; i++) {
77       DCHECK_NOT_NULL(private_push_segment(i));
78       DCHECK_NOT_NULL(private_pop_segment(i));
79       delete private_push_segment(i);
80       delete private_pop_segment(i);
81     }
82   }
83 
84   // Swaps content with the given worklist. Local buffers need to
85   // be empty, not thread safe.
Swap(Worklist<EntryType,SEGMENT_SIZE> & other)86   void Swap(Worklist<EntryType, SEGMENT_SIZE>& other) {
87     CHECK(AreLocalsEmpty());
88     CHECK(other.AreLocalsEmpty());
89 
90     global_pool_.Swap(other.global_pool_);
91   }
92 
Push(int task_id,EntryType entry)93   bool Push(int task_id, EntryType entry) {
94     DCHECK_LT(task_id, num_tasks_);
95     DCHECK_NOT_NULL(private_push_segment(task_id));
96     if (!private_push_segment(task_id)->Push(entry)) {
97       PublishPushSegmentToGlobal(task_id);
98       bool success = private_push_segment(task_id)->Push(entry);
99       USE(success);
100       DCHECK(success);
101     }
102     return true;
103   }
104 
Pop(int task_id,EntryType * entry)105   bool Pop(int task_id, EntryType* entry) {
106     DCHECK_LT(task_id, num_tasks_);
107     DCHECK_NOT_NULL(private_pop_segment(task_id));
108     if (!private_pop_segment(task_id)->Pop(entry)) {
109       if (!private_push_segment(task_id)->IsEmpty()) {
110         Segment* tmp = private_pop_segment(task_id);
111         private_pop_segment(task_id) = private_push_segment(task_id);
112         private_push_segment(task_id) = tmp;
113       } else if (!StealPopSegmentFromGlobal(task_id)) {
114         return false;
115       }
116       bool success = private_pop_segment(task_id)->Pop(entry);
117       USE(success);
118       DCHECK(success);
119     }
120     return true;
121   }
122 
LocalPushSegmentSize(int task_id)123   size_t LocalPushSegmentSize(int task_id) {
124     return private_push_segment(task_id)->Size();
125   }
126 
IsLocalEmpty(int task_id)127   bool IsLocalEmpty(int task_id) {
128     return private_pop_segment(task_id)->IsEmpty() &&
129            private_push_segment(task_id)->IsEmpty();
130   }
131 
IsGlobalPoolEmpty()132   bool IsGlobalPoolEmpty() { return global_pool_.IsEmpty(); }
133 
IsEmpty()134   bool IsEmpty() {
135     if (!AreLocalsEmpty()) return false;
136     return global_pool_.IsEmpty();
137   }
138 
AreLocalsEmpty()139   bool AreLocalsEmpty() {
140     for (int i = 0; i < num_tasks_; i++) {
141       if (!IsLocalEmpty(i)) return false;
142     }
143     return true;
144   }
145 
LocalSize(int task_id)146   size_t LocalSize(int task_id) {
147     return private_pop_segment(task_id)->Size() +
148            private_push_segment(task_id)->Size();
149   }
150 
151   // Thread-safe but may return an outdated result.
GlobalPoolSize()152   size_t GlobalPoolSize() const { return global_pool_.Size(); }
153 
154   // Clears all segments. Frees the global segment pool.
155   //
156   // Assumes that no other tasks are running.
Clear()157   void Clear() {
158     for (int i = 0; i < num_tasks_; i++) {
159       private_pop_segment(i)->Clear();
160       private_push_segment(i)->Clear();
161     }
162     global_pool_.Clear();
163   }
164 
165   // Calls the specified callback on each element of the deques and replaces
166   // the element with the result of the callback.
167   // The signature of the callback is
168   //   bool Callback(EntryType old, EntryType* new).
169   // If the callback returns |false| then the element is removed from the
170   // worklist. Otherwise the |new| entry is updated.
171   //
172   // Assumes that no other tasks are running.
173   template <typename Callback>
Update(Callback callback)174   void Update(Callback callback) {
175     for (int i = 0; i < num_tasks_; i++) {
176       private_pop_segment(i)->Update(callback);
177       private_push_segment(i)->Update(callback);
178     }
179     global_pool_.Update(callback);
180   }
181 
182   // Calls the specified callback on each element of the deques.
183   // The signature of the callback is:
184   //   void Callback(EntryType entry).
185   //
186   // Assumes that no other tasks are running.
187   template <typename Callback>
Iterate(Callback callback)188   void Iterate(Callback callback) {
189     for (int i = 0; i < num_tasks_; i++) {
190       private_pop_segment(i)->Iterate(callback);
191       private_push_segment(i)->Iterate(callback);
192     }
193     global_pool_.Iterate(callback);
194   }
195 
196   template <typename Callback>
IterateGlobalPool(Callback callback)197   void IterateGlobalPool(Callback callback) {
198     global_pool_.Iterate(callback);
199   }
200 
FlushToGlobal(int task_id)201   void FlushToGlobal(int task_id) {
202     PublishPushSegmentToGlobal(task_id);
203     PublishPopSegmentToGlobal(task_id);
204   }
205 
MergeGlobalPool(Worklist * other)206   void MergeGlobalPool(Worklist* other) {
207     global_pool_.Merge(&other->global_pool_);
208   }
209 
210  private:
211   FRIEND_TEST(WorkListTest, SegmentCreate);
212   FRIEND_TEST(WorkListTest, SegmentPush);
213   FRIEND_TEST(WorkListTest, SegmentPushPop);
214   FRIEND_TEST(WorkListTest, SegmentIsEmpty);
215   FRIEND_TEST(WorkListTest, SegmentIsFull);
216   FRIEND_TEST(WorkListTest, SegmentClear);
217   FRIEND_TEST(WorkListTest, SegmentFullPushFails);
218   FRIEND_TEST(WorkListTest, SegmentEmptyPopFails);
219   FRIEND_TEST(WorkListTest, SegmentUpdateFalse);
220   FRIEND_TEST(WorkListTest, SegmentUpdate);
221 
222   class Segment {
223    public:
224     static const size_t kCapacity = kSegmentCapacity;
225 
Segment()226     Segment() : index_(0) {}
227 
Push(EntryType entry)228     bool Push(EntryType entry) {
229       if (IsFull()) return false;
230       entries_[index_++] = entry;
231       return true;
232     }
233 
Pop(EntryType * entry)234     bool Pop(EntryType* entry) {
235       if (IsEmpty()) return false;
236       *entry = entries_[--index_];
237       return true;
238     }
239 
Size()240     size_t Size() const { return index_; }
IsEmpty()241     bool IsEmpty() const { return index_ == 0; }
IsFull()242     bool IsFull() const { return index_ == kCapacity; }
Clear()243     void Clear() { index_ = 0; }
244 
245     template <typename Callback>
Update(Callback callback)246     void Update(Callback callback) {
247       size_t new_index = 0;
248       for (size_t i = 0; i < index_; i++) {
249         if (callback(entries_[i], &entries_[new_index])) {
250           new_index++;
251         }
252       }
253       index_ = new_index;
254     }
255 
256     template <typename Callback>
Iterate(Callback callback)257     void Iterate(Callback callback) const {
258       for (size_t i = 0; i < index_; i++) {
259         callback(entries_[i]);
260       }
261     }
262 
next()263     Segment* next() const { return next_; }
set_next(Segment * segment)264     void set_next(Segment* segment) { next_ = segment; }
265 
266    private:
267     Segment* next_;
268     size_t index_;
269     EntryType entries_[kCapacity];
270   };
271 
272   struct PrivateSegmentHolder {
273     Segment* private_push_segment;
274     Segment* private_pop_segment;
275     char cache_line_padding[64];
276   };
277 
278   class GlobalPool {
279    public:
GlobalPool()280     GlobalPool() : top_(nullptr) {}
281 
282     // Swaps contents, not thread safe.
Swap(GlobalPool & other)283     void Swap(GlobalPool& other) {
284       Segment* temp = top_;
285       set_top(other.top_);
286       other.set_top(temp);
287       size_t other_size = other.size_.exchange(
288           size_.load(std::memory_order_relaxed), std::memory_order_relaxed);
289       size_.store(other_size, std::memory_order_relaxed);
290     }
291 
Push(Segment * segment)292     V8_INLINE void Push(Segment* segment) {
293       base::MutexGuard guard(&lock_);
294       segment->set_next(top_);
295       set_top(segment);
296       size_.fetch_add(1, std::memory_order_relaxed);
297     }
298 
Pop(Segment ** segment)299     V8_INLINE bool Pop(Segment** segment) {
300       base::MutexGuard guard(&lock_);
301       if (top_ != nullptr) {
302         DCHECK_LT(0U, size_);
303         size_.fetch_sub(1, std::memory_order_relaxed);
304         *segment = top_;
305         set_top(top_->next());
306         return true;
307       }
308       return false;
309     }
310 
IsEmpty()311     V8_INLINE bool IsEmpty() {
312       return base::AsAtomicPointer::Relaxed_Load(&top_) == nullptr;
313     }
314 
Size()315     V8_INLINE size_t Size() const {
316       // It is safe to read |size_| without a lock since this variable is
317       // atomic, keeping in mind that threads may not immediately see the new
318       // value when it is updated.
319       return size_.load(std::memory_order_relaxed);
320     }
321 
Clear()322     void Clear() {
323       base::MutexGuard guard(&lock_);
324       size_.store(0, std::memory_order_relaxed);
325       Segment* current = top_;
326       while (current != nullptr) {
327         Segment* tmp = current;
328         current = current->next();
329         delete tmp;
330       }
331       set_top(nullptr);
332     }
333 
334     // See Worklist::Update.
335     template <typename Callback>
Update(Callback callback)336     void Update(Callback callback) {
337       base::MutexGuard guard(&lock_);
338       Segment* prev = nullptr;
339       Segment* current = top_;
340       size_t num_deleted = 0;
341       while (current != nullptr) {
342         current->Update(callback);
343         if (current->IsEmpty()) {
344           DCHECK_LT(0U, size_);
345           ++num_deleted;
346           if (prev == nullptr) {
347             top_ = current->next();
348           } else {
349             prev->set_next(current->next());
350           }
351           Segment* tmp = current;
352           current = current->next();
353           delete tmp;
354         } else {
355           prev = current;
356           current = current->next();
357         }
358       }
359       size_.fetch_sub(num_deleted, std::memory_order_relaxed);
360     }
361 
362     // See Worklist::Iterate.
363     template <typename Callback>
Iterate(Callback callback)364     void Iterate(Callback callback) {
365       base::MutexGuard guard(&lock_);
366       for (Segment* current = top_; current != nullptr;
367            current = current->next()) {
368         current->Iterate(callback);
369       }
370     }
371 
Merge(GlobalPool * other)372     void Merge(GlobalPool* other) {
373       Segment* top = nullptr;
374       size_t other_size = 0;
375       {
376         base::MutexGuard guard(&other->lock_);
377         if (!other->top_) return;
378         top = other->top_;
379         other_size = other->size_.load(std::memory_order_relaxed);
380         other->size_.store(0, std::memory_order_relaxed);
381         other->set_top(nullptr);
382       }
383 
384       // It's safe to iterate through these segments because the top was
385       // extracted from |other|.
386       Segment* end = top;
387       while (end->next()) end = end->next();
388 
389       {
390         base::MutexGuard guard(&lock_);
391         size_.fetch_add(other_size, std::memory_order_relaxed);
392         end->set_next(top_);
393         set_top(top);
394       }
395     }
396 
397    private:
set_top(Segment * segment)398     void set_top(Segment* segment) {
399       base::AsAtomicPointer::Relaxed_Store(&top_, segment);
400     }
401 
402     base::Mutex lock_;
403     Segment* top_;
404     std::atomic<size_t> size_{0};
405   };
406 
private_push_segment(int task_id)407   V8_INLINE Segment*& private_push_segment(int task_id) {
408     return private_segments_[task_id].private_push_segment;
409   }
410 
private_pop_segment(int task_id)411   V8_INLINE Segment*& private_pop_segment(int task_id) {
412     return private_segments_[task_id].private_pop_segment;
413   }
414 
PublishPushSegmentToGlobal(int task_id)415   V8_INLINE void PublishPushSegmentToGlobal(int task_id) {
416     if (!private_push_segment(task_id)->IsEmpty()) {
417       global_pool_.Push(private_push_segment(task_id));
418       private_push_segment(task_id) = NewSegment();
419     }
420   }
421 
PublishPopSegmentToGlobal(int task_id)422   V8_INLINE void PublishPopSegmentToGlobal(int task_id) {
423     if (!private_pop_segment(task_id)->IsEmpty()) {
424       global_pool_.Push(private_pop_segment(task_id));
425       private_pop_segment(task_id) = NewSegment();
426     }
427   }
428 
StealPopSegmentFromGlobal(int task_id)429   V8_INLINE bool StealPopSegmentFromGlobal(int task_id) {
430     if (global_pool_.IsEmpty()) return false;
431     Segment* new_segment = nullptr;
432     if (global_pool_.Pop(&new_segment)) {
433       delete private_pop_segment(task_id);
434       private_pop_segment(task_id) = new_segment;
435       return true;
436     }
437     return false;
438   }
439 
NewSegment()440   V8_INLINE Segment* NewSegment() {
441     // Bottleneck for filtering in crash dumps.
442     return new Segment();
443   }
444 
445   PrivateSegmentHolder private_segments_[kMaxNumTasks];
446   GlobalPool global_pool_;
447   int num_tasks_;
448 };
449 
450 }  // namespace internal
451 }  // namespace v8
452 
453 #endif  // V8_HEAP_WORKLIST_H_
454