• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2016 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 #include <algorithm>
6 
7 #include "src/base/iterator.h"
8 #include "src/common/globals.h"
9 #include "src/utils/memcopy.h"
10 #include "src/zone/zone.h"
11 
12 #ifndef V8_ZONE_ZONE_CHUNK_LIST_H_
13 #define V8_ZONE_ZONE_CHUNK_LIST_H_
14 
15 namespace v8 {
16 namespace internal {
17 
18 template <typename T, bool backwards, bool modifiable>
19 class ZoneChunkListIterator;
20 
21 // A zone-backed hybrid of a vector and a linked list. Use it if you need a
22 // collection that
23 // * needs to grow indefinitely,
24 // * will mostly grow at the back, but may sometimes grow in front as well
25 // (preferably in batches),
26 // * needs to have very low overhead,
27 // * offers forward- and backwards-iteration,
28 // * offers relatively fast seeking,
29 // * offers bidirectional iterators,
30 // * can be rewound without freeing the backing store.
31 // This list will maintain a doubly-linked list of chunks. When a chunk is
32 // filled up, a new one gets appended. New chunks appended at the end will
33 // grow in size up to a certain limit to avoid over-allocation and to keep
34 // the zone clean.
35 template <typename T>
36 class ZoneChunkList : public ZoneObject {
37  public:
38   using iterator = ZoneChunkListIterator<T, false, true>;
39   using const_iterator = ZoneChunkListIterator<T, false, false>;
40   using reverse_iterator = ZoneChunkListIterator<T, true, true>;
41   using const_reverse_iterator = ZoneChunkListIterator<T, true, false>;
42 
43   enum class StartMode {
44     // The list will not allocate a starting chunk. Use if you expect your
45     // list to remain empty in many cases.
46     kEmpty = 0,
47     // The list will start with a small initial chunk. Subsequent chunks will
48     // get bigger over time.
49     kSmall = 8,
50     // The list will start with one chunk at maximum size. Use this if you
51     // expect your list to contain many items to avoid growing chunks.
52     kBig = 256
53   };
54 
55   explicit ZoneChunkList(Zone* zone, StartMode start_mode = StartMode::kEmpty)
zone_(zone)56       : zone_(zone) {
57     if (start_mode != StartMode::kEmpty) {
58       front_ = NewChunk(static_cast<uint32_t>(start_mode));
59       back_ = front_;
60     }
61   }
62 
63   ZoneChunkList(const ZoneChunkList&) = delete;
64   ZoneChunkList& operator=(const ZoneChunkList&) = delete;
65 
size()66   size_t size() const { return size_; }
is_empty()67   bool is_empty() const { return size() == 0; }
68 
69   T& front() const;
70   T& back() const;
71 
72   void push_back(const T& item);
73   void pop_back();
74 
75   // Will push a separate chunk to the front of the chunk-list.
76   // Very memory-inefficient. Do only use sparsely! If you have many items to
77   // add in front, consider using 'push_front_many'.
78   void push_front(const T& item);
79   // TODO(heimbuef): Add 'push_front_many'.
80 
81   // Cuts the last list elements so at most 'limit' many remain. Does not
82   // free the actual memory, since it is zone allocated.
83   void Rewind(const size_t limit = 0);
84 
85   // Quickly scans the list to retrieve the element at the given index. Will
86   // *not* check bounds.
87   iterator Find(const size_t index);
88   const_iterator Find(const size_t index) const;
89   // TODO(heimbuef): Add 'rFind', seeking from the end and returning a
90   // reverse iterator.
91 
92   void CopyTo(T* ptr);
93 
begin()94   iterator begin() { return iterator::Begin(this); }
end()95   iterator end() { return iterator::End(this); }
rbegin()96   reverse_iterator rbegin() { return reverse_iterator::Begin(this); }
rend()97   reverse_iterator rend() { return reverse_iterator::End(this); }
begin()98   const_iterator begin() const { return const_iterator::Begin(this); }
end()99   const_iterator end() const { return const_iterator::End(this); }
rbegin()100   const_reverse_iterator rbegin() const {
101     return const_reverse_iterator::Begin(this);
102   }
rend()103   const_reverse_iterator rend() const {
104     return const_reverse_iterator::End(this);
105   }
106 
107  private:
108   template <typename S, bool backwards, bool modifiable>
109   friend class ZoneChunkListIterator;
110 
111   static constexpr uint32_t kMaxChunkCapacity = 256u;
112 
113   STATIC_ASSERT(kMaxChunkCapacity == static_cast<uint32_t>(StartMode::kBig));
114 
115   struct Chunk {
116     uint32_t capacity_ = 0;
117     uint32_t position_ = 0;
118     Chunk* next_ = nullptr;
119     Chunk* previous_ = nullptr;
itemsChunk120     T* items() { return reinterpret_cast<T*>(this + 1); }
itemsChunk121     const T* items() const { return reinterpret_cast<const T*>(this + 1); }
122   };
123 
NewChunk(const uint32_t capacity)124   Chunk* NewChunk(const uint32_t capacity) {
125     void* memory = zone_->Allocate<Chunk>(sizeof(Chunk) + capacity * sizeof(T));
126     Chunk* chunk = new (memory) Chunk();
127     chunk->capacity_ = capacity;
128     return chunk;
129   }
130 
131   struct SeekResult {
132     Chunk* chunk_;
133     uint32_t chunk_index_;
134   };
135 
136   // Returns the chunk and relative index of the element at the given global
137   // index. Will skip entire chunks and is therefore faster than iterating.
138   SeekResult SeekIndex(size_t index) const;
139 
140   Zone* zone_;
141 
142   size_t size_ = 0;
143   Chunk* front_ = nullptr;
144   Chunk* back_ = nullptr;
145 };
146 
147 template <typename T, bool backwards, bool modifiable>
148 class ZoneChunkListIterator
149     : public base::iterator<std::bidirectional_iterator_tag, T> {
150  private:
151   template <typename S>
152   using maybe_const =
153       typename std::conditional<modifiable, S,
154                                 typename std::add_const<S>::type>::type;
155   using Chunk = maybe_const<typename ZoneChunkList<T>::Chunk>;
156   using ChunkList = maybe_const<ZoneChunkList<T>>;
157 
158  public:
159   maybe_const<T>& operator*() const { return current_->items()[position_]; }
160   maybe_const<T>* operator->() const { return &current_->items()[position_]; }
161   bool operator==(const ZoneChunkListIterator& other) const {
162     return other.current_ == current_ && other.position_ == position_;
163   }
164   bool operator!=(const ZoneChunkListIterator& other) const {
165     return !operator==(other);
166   }
167 
168   ZoneChunkListIterator& operator++() {
169     Move<backwards>();
170     return *this;
171   }
172 
173   ZoneChunkListIterator operator++(int) {
174     ZoneChunkListIterator clone(*this);
175     Move<backwards>();
176     return clone;
177   }
178 
179   ZoneChunkListIterator& operator--() {
180     Move<!backwards>();
181     return *this;
182   }
183 
184   ZoneChunkListIterator operator--(int) {
185     ZoneChunkListIterator clone(*this);
186     Move<!backwards>();
187     return clone;
188   }
189 
Advance(int amount)190   void Advance(int amount) {
191     // Move forwards.
192     DCHECK_GE(amount, 0);
193 #if DEBUG
194     ZoneChunkListIterator clone(*this);
195     for (int i = 0; i < amount; ++i) {
196       ++clone;
197     }
198 #endif
199 
200     position_ += amount;
201     while (position_ > 0 && position_ >= current_->capacity_) {
202       auto overshoot = position_ - current_->capacity_;
203       current_ = current_->next_;
204       position_ = overshoot;
205 
206       DCHECK(position_ == 0 || current_);
207     }
208 
209 #if DEBUG
210     DCHECK_EQ(clone, *this);
211 #endif
212   }
213 
214  private:
215   friend class ZoneChunkList<T>;
216 
Begin(ChunkList * list)217   static ZoneChunkListIterator Begin(ChunkList* list) {
218     // Forward iterator:
219     if (!backwards) return ZoneChunkListIterator(list->front_, 0);
220 
221     // Backward iterator:
222     if (list->back_ == nullptr) return End(list);
223     if (list->back_->position_ == 0) {
224       if (list->back_->previous_ != nullptr) {
225         return ZoneChunkListIterator(list->back_->previous_,
226                                      list->back_->previous_->capacity_ - 1);
227       } else {
228         return End(list);
229       }
230     }
231     return ZoneChunkListIterator(list->back_, list->back_->position_ - 1);
232   }
233 
End(ChunkList * list)234   static ZoneChunkListIterator End(ChunkList* list) {
235     // Backward iterator:
236     if (backwards) return ZoneChunkListIterator(nullptr, 0);
237 
238     // Forward iterator:
239     if (list->back_ == nullptr) return Begin(list);
240 
241     DCHECK_LE(list->back_->position_, list->back_->capacity_);
242     if (list->back_->position_ == list->back_->capacity_) {
243       return ZoneChunkListIterator(list->back_->next_, 0);
244     }
245 
246     return ZoneChunkListIterator(list->back_, list->back_->position_);
247   }
248 
ZoneChunkListIterator(Chunk * current,size_t position)249   ZoneChunkListIterator(Chunk* current, size_t position)
250       : current_(current), position_(position) {
251     DCHECK(current == nullptr || position < current->capacity_);
252   }
253 
254   template <bool move_backward>
Move()255   void Move() {
256     if (move_backward) {
257       // Move backwards.
258       if (position_ == 0) {
259         current_ = current_->previous_;
260         position_ = current_ ? current_->capacity_ - 1 : 0;
261       } else {
262         --position_;
263       }
264     } else {
265       // Move forwards.
266       ++position_;
267       if (position_ >= current_->capacity_) {
268         current_ = current_->next_;
269         position_ = 0;
270       }
271     }
272   }
273 
274   Chunk* current_;
275   size_t position_;
276 };
277 
278 template <typename T>
front()279 T& ZoneChunkList<T>::front() const {
280   DCHECK_LT(size_t(0), size());
281   return front_->items()[0];
282 }
283 
284 template <typename T>
back()285 T& ZoneChunkList<T>::back() const {
286   DCHECK_LT(size_t(0), size());
287 
288   if (back_->position_ == 0) {
289     return back_->previous_->items()[back_->previous_->position_ - 1];
290   } else {
291     return back_->items()[back_->position_ - 1];
292   }
293 }
294 
295 template <typename T>
push_back(const T & item)296 void ZoneChunkList<T>::push_back(const T& item) {
297   if (back_ == nullptr) {
298     front_ = NewChunk(static_cast<uint32_t>(StartMode::kSmall));
299     back_ = front_;
300   }
301 
302   DCHECK_LE(back_->position_, back_->capacity_);
303   if (back_->position_ == back_->capacity_) {
304     if (back_->next_ == nullptr) {
305       constexpr auto max_capacity = kMaxChunkCapacity;
306       Chunk* chunk = NewChunk(std::min(back_->capacity_ << 1, max_capacity));
307       back_->next_ = chunk;
308       chunk->previous_ = back_;
309     }
310     back_ = back_->next_;
311   }
312   back_->items()[back_->position_] = item;
313   ++back_->position_;
314   ++size_;
315   DCHECK_LE(back_->position_, back_->capacity_);
316 }
317 
318 template <typename T>
pop_back()319 void ZoneChunkList<T>::pop_back() {
320   DCHECK_LT(size_t(0), size());
321   if (back_->position_ == 0) {
322     back_ = back_->previous_;
323   }
324   --back_->position_;
325   --size_;
326 }
327 
328 template <typename T>
push_front(const T & item)329 void ZoneChunkList<T>::push_front(const T& item) {
330   Chunk* chunk = NewChunk(1);  // Yes, this gets really inefficient.
331   chunk->next_ = front_;
332   if (front_) {
333     front_->previous_ = chunk;
334   } else {
335     back_ = chunk;
336   }
337   front_ = chunk;
338 
339   chunk->items()[0] = item;
340   chunk->position_ = 1;
341   ++size_;
342 }
343 
344 template <typename T>
SeekIndex(size_t index)345 typename ZoneChunkList<T>::SeekResult ZoneChunkList<T>::SeekIndex(
346     size_t index) const {
347   DCHECK_LT(index, size());
348   Chunk* current = front_;
349   while (index >= current->capacity_) {
350     index -= current->capacity_;
351     current = current->next_;
352   }
353   DCHECK_LT(index, current->capacity_);
354   return {current, static_cast<uint32_t>(index)};
355 }
356 
357 template <typename T>
Rewind(const size_t limit)358 void ZoneChunkList<T>::Rewind(const size_t limit) {
359   if (limit >= size()) return;
360 
361   SeekResult seek_result = SeekIndex(limit);
362   DCHECK_NOT_NULL(seek_result.chunk_);
363 
364   // Do a partial rewind of the chunk containing the index.
365   seek_result.chunk_->position_ = seek_result.chunk_index_;
366 
367   // Set back_ so iterators will work correctly.
368   back_ = seek_result.chunk_;
369 
370   // Do full rewind of all subsequent chunks.
371   for (Chunk* current = seek_result.chunk_->next_; current != nullptr;
372        current = current->next_) {
373     current->position_ = 0;
374   }
375 
376   size_ = limit;
377 }
378 
379 template <typename T>
Find(const size_t index)380 typename ZoneChunkList<T>::iterator ZoneChunkList<T>::Find(const size_t index) {
381   SeekResult seek_result = SeekIndex(index);
382   return typename ZoneChunkList<T>::iterator(seek_result.chunk_,
383                                              seek_result.chunk_index_);
384 }
385 
386 template <typename T>
Find(const size_t index)387 typename ZoneChunkList<T>::const_iterator ZoneChunkList<T>::Find(
388     const size_t index) const {
389   SeekResult seek_result = SeekIndex(index);
390   return typename ZoneChunkList<T>::const_iterator(seek_result.chunk_,
391                                                    seek_result.chunk_index_);
392 }
393 
394 template <typename T>
CopyTo(T * ptr)395 void ZoneChunkList<T>::CopyTo(T* ptr) {
396   for (Chunk* current = front_; current != nullptr; current = current->next_) {
397     void* start = current->items();
398     void* end = current->items() + current->position_;
399     size_t bytes = static_cast<size_t>(reinterpret_cast<uintptr_t>(end) -
400                                        reinterpret_cast<uintptr_t>(start));
401 
402     MemCopy(ptr, current->items(), bytes);
403     ptr += current->position_;
404   }
405 }
406 
407 }  // namespace internal
408 }  // namespace v8
409 
410 #endif  // V8_ZONE_ZONE_CHUNK_LIST_H_
411