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 ¤t_->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