v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
zone-chunk-list.h
Go to the documentation of this file.
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#ifndef V8_ZONE_ZONE_CHUNK_LIST_H_
6#define V8_ZONE_ZONE_CHUNK_LIST_H_
7
8#include <algorithm>
9
10#include "src/base/iterator.h"
11#include "src/common/globals.h"
12#include "src/utils/memcopy.h"
13#include "src/zone/zone.h"
14
15namespace v8 {
16namespace internal {
17
18template <typename T, bool backwards, bool modifiable>
19class 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// * can be split and joined again efficiently.
32// This list will maintain a doubly-linked list of chunks. When a chunk is
33// filled up, a new one gets appended. New chunks appended at the end will
34// grow in size up to a certain limit to avoid over-allocation and to keep
35// the zone clean. Chunks may be partially filled. In particular, chunks may
36// be empty after rewinding, such that they can be reused when inserting
37// again at a later point in time.
38template <typename T>
39class ZoneChunkList : public ZoneObject {
40 public:
45
46 static constexpr uint32_t kInitialChunkCapacity = 8;
47 static constexpr uint32_t kMaxChunkCapacity = 256;
48
49 explicit ZoneChunkList(Zone* zone) : zone_(zone) {}
50
52
53 size_t size() const { return size_; }
54 bool empty() const { return size() == 0; }
55
56 T& front();
57 const T& front() const;
58 T& back();
59 const T& back() const;
60
61 void push_back(const T& item);
62
63 // If the first chunk has space, inserts into it at the front. Otherwise
64 // allocate a new chunk with the same growth strategy as `push_back`.
65 // This limits the amount of copying to O(`kMaxChunkCapacity`).
66 void push_front(const T& item);
67
68 // Cuts the last list elements so at most 'limit' many remain. Does not
69 // free the actual memory, since it is zone allocated.
70 void Rewind(const size_t limit = 0);
71
72 // Quickly scans the list to retrieve the element at the given index. Will
73 // *not* check bounds.
74 iterator Find(const size_t index);
75 const_iterator Find(const size_t index) const;
76 // TODO(heimbuef): Add 'rFind', seeking from the end and returning a
77 // reverse iterator.
78
79 // Splits off a new list that contains the elements from `split_begin` to
80 // `end()`. The current list is truncated to end just before `split_begin`.
81 // This naturally invalidates all iterators, including `split_begin`.
84
85 void CopyTo(T* ptr);
86
87 iterator begin() { return iterator::Begin(this); }
88 iterator end() { return iterator::End(this); }
91 const_iterator begin() const { return const_iterator::Begin(this); }
92 const_iterator end() const { return const_iterator::End(this); }
99
100 void swap(ZoneChunkList<T>& other) {
101 DCHECK_EQ(zone_, other.zone_);
102 std::swap(size_, other.size_);
103 std::swap(front_, other.front_);
104 std::swap(last_nonempty_, other.last_nonempty_);
105 }
106
107 private:
108 template <typename S, bool backwards, bool modifiable>
110
111 struct Chunk {
112 uint32_t capacity_ = 0;
113 uint32_t position_ = 0;
114 Chunk* next_ = nullptr;
115 Chunk* previous_ = nullptr;
116 T* items() { return reinterpret_cast<T*>(this + 1); }
117 const T* items() const { return reinterpret_cast<const T*>(this + 1); }
118 uint32_t size() const {
120 return position_;
121 }
122 bool empty() const { return size() == 0; }
123 bool full() const { return size() == capacity_; }
124 };
125
126 Chunk* NewChunk(const uint32_t capacity) {
127 void* memory = zone_->Allocate<Chunk>(sizeof(Chunk) + capacity * sizeof(T));
128 Chunk* chunk = new (memory) Chunk();
129 chunk->capacity_ = capacity;
130 return chunk;
131 }
132
133 static uint32_t NextChunkCapacity(uint32_t previous_capacity) {
134 return std::min(previous_capacity * 2, kMaxChunkCapacity);
135 }
136
137 struct SeekResult {
139 uint32_t chunk_index_;
140 };
141
142 // Returns the chunk and relative index of the element at the given global
143 // index. Will skip entire chunks and is therefore faster than iterating.
144 SeekResult SeekIndex(size_t index) const;
145
146#ifdef DEBUG
147 // Check the invariants.
148 void Verify() const {
149 if (front_ == nullptr) {
150 // Initial empty state.
152 DCHECK_EQ(0, size());
153 } else if (empty()) {
154 // Special case: Fully rewound list, with only empty chunks.
156 DCHECK_EQ(0, size());
157 for (Chunk* chunk = front_; chunk != nullptr; chunk = chunk->next_) {
158 DCHECK(chunk->empty());
159 }
160 } else {
161 // Normal state: Somewhat filled and (partially) rewound.
163
164 size_t size_check = 0;
165 bool in_empty_tail = false;
166 for (Chunk* chunk = front_; chunk != nullptr; chunk = chunk->next_) {
167 // Chunks from `front_` to `last_nonempty_` (inclusive) are non-empty.
168 DCHECK_EQ(in_empty_tail, chunk->empty());
169 size_check += chunk->size();
170
171 if (chunk == last_nonempty_) {
172 in_empty_tail = true;
173 }
174 }
175 DCHECK_EQ(size_check, size());
176 }
177 }
178#endif
179
181
182 size_t size_ = 0;
183 Chunk* front_ = nullptr;
185};
186
187template <typename T, bool backwards, bool modifiable>
189 : public base::iterator<std::bidirectional_iterator_tag, T> {
190 private:
191 template <typename S>
193 typename std::conditional<modifiable, S,
194 typename std::add_const<S>::type>::type;
197
198 public:
199 maybe_const<T>& operator*() const { return current_->items()[position_]; }
200 maybe_const<T>* operator->() const { return &current_->items()[position_]; }
201 bool operator==(const ZoneChunkListIterator& other) const {
202 return other.current_ == current_ && other.position_ == position_;
203 }
204 bool operator!=(const ZoneChunkListIterator& other) const {
205 return !operator==(other);
206 }
207
210 return *this;
211 }
212
214 ZoneChunkListIterator clone(*this);
216 return clone;
217 }
218
221 return *this;
222 }
223
225 ZoneChunkListIterator clone(*this);
227 return clone;
228 }
229
230 void Advance(uint32_t amount) {
231 static_assert(!backwards, "Advance only works on forward iterators");
232
233#ifdef DEBUG
234 ZoneChunkListIterator clone(*this);
235 for (uint32_t i = 0; i < amount; ++i) {
236 ++clone;
237 }
238#endif
239
241 while (position_ > 0 && position_ >= current_->position_) {
242 auto overshoot = position_ - current_->position_;
243 current_ = current_->next_;
244 position_ = overshoot;
245
246 DCHECK(position_ == 0 || current_);
247 }
248
249#ifdef DEBUG
250 DCHECK_EQ(clone, *this);
251#endif
252 }
253
254 private:
255 friend class ZoneChunkList<T>;
256
258 // Forward iterator:
259 if (!backwards) return ZoneChunkListIterator(list->front_, 0);
260
261 // Backward iterator:
262 if (list->empty()) return End(list);
263
264 DCHECK(!list->last_nonempty_->empty());
265 return ZoneChunkListIterator(list->last_nonempty_,
266 list->last_nonempty_->position_ - 1);
267 }
268
270 // Backward iterator:
271 if (backwards) return ZoneChunkListIterator(nullptr, 0);
272
273 // Forward iterator:
274 if (list->empty()) return Begin(list);
275
276 // NOTE: Decrementing `end()` is not supported if `last_nonempty_->next_`
277 // is nullptr (in that case `Move` will crash on dereference).
278 return ZoneChunkListIterator(list->last_nonempty_->next_, 0);
279 }
280
282 : current_(current), position_(position) {
283 DCHECK(current == nullptr || position < current->capacity_);
284 }
285
286 template <bool move_backward>
287 void Move() {
288 if (move_backward) {
289 // Move backwards.
290 if (position_ == 0) {
291 current_ = current_->previous_;
292 position_ = current_ ? current_->position_ - 1 : 0;
293 } else {
294 --position_;
295 }
296 } else {
297 // Move forwards.
298 ++position_;
299 if (position_ >= current_->position_) {
300 current_ = current_->next_;
301 position_ = 0;
302 }
303 }
304 }
305
307 uint32_t position_;
308};
309
310template <typename T>
312 DCHECK(!empty());
313 return *begin();
314}
315
316template <typename T>
317const T& ZoneChunkList<T>::front() const {
318 DCHECK(!empty());
319 return *begin();
320}
321
322template <typename T>
324 DCHECK(!empty());
325 // Avoid the branch in `ZoneChunkListIterator::Begin()`.
326 V8_ASSUME(size_ != 0);
327 return *rbegin();
328}
329
330template <typename T>
331const T& ZoneChunkList<T>::back() const {
332 DCHECK(!empty());
333 // Avoid the branch in `ZoneChunkListIterator::Begin()`.
334 V8_ASSUME(size_ != 0);
335 return *rbegin();
336}
337
338template <typename T>
339void ZoneChunkList<T>::push_back(const T& item) {
340 if (last_nonempty_ == nullptr) {
341 // Initially empty chunk list.
342 front_ = NewChunk(kInitialChunkCapacity);
343 last_nonempty_ = front_;
344 } else if (last_nonempty_->full()) {
345 // If there is an empty chunk following, reuse that, otherwise allocate.
346 if (last_nonempty_->next_ == nullptr) {
347 Chunk* chunk = NewChunk(NextChunkCapacity(last_nonempty_->capacity_));
348 last_nonempty_->next_ = chunk;
349 chunk->previous_ = last_nonempty_;
350 }
351 last_nonempty_ = last_nonempty_->next_;
352 DCHECK(!last_nonempty_->full());
353 }
354
355 last_nonempty_->items()[last_nonempty_->position_] = item;
356 ++last_nonempty_->position_;
357 ++size_;
358 DCHECK_LE(last_nonempty_->position_, last_nonempty_->capacity_);
359}
360
361template <typename T>
362void ZoneChunkList<T>::push_front(const T& item) {
363 if (front_ == nullptr) {
364 // Initially empty chunk list.
365 front_ = NewChunk(kInitialChunkCapacity);
366 last_nonempty_ = front_;
367 } else if (front_->full()) {
368 // First chunk at capacity, so prepend a new chunk.
369 DCHECK_NULL(front_->previous_);
370 Chunk* chunk = NewChunk(NextChunkCapacity(front_->capacity_));
371 front_->previous_ = chunk;
372 chunk->next_ = front_;
373 front_ = chunk;
374 }
375 DCHECK(!front_->full());
376
377 T* end = front_->items() + front_->position_;
378 std::move_backward(front_->items(), end, end + 1);
379 front_->items()[0] = item;
380 ++front_->position_;
381 ++size_;
382 DCHECK_LE(front_->position_, front_->capacity_);
383}
384
385template <typename T>
387 size_t index) const {
388 DCHECK_LT(index, size());
389 Chunk* current = front_;
390 while (index >= current->capacity_) {
391 index -= current->capacity_;
392 current = current->next_;
393 }
394 DCHECK_LT(index, current->capacity_);
395 return {current, static_cast<uint32_t>(index)};
396}
397
398template <typename T>
399void ZoneChunkList<T>::Rewind(const size_t limit) {
400 if (limit >= size()) return;
401
402 SeekResult seek_result = SeekIndex(limit);
403 DCHECK_NOT_NULL(seek_result.chunk_);
404
405 // Do a partial rewind of the chunk containing the index.
406 seek_result.chunk_->position_ = seek_result.chunk_index_;
407
408 // Set last_nonempty_ so iterators will work correctly.
409 last_nonempty_ = seek_result.chunk_;
410
411 // Do full rewind of all subsequent chunks.
412 for (Chunk* current = seek_result.chunk_->next_; current != nullptr;
413 current = current->next_) {
414 current->position_ = 0;
415 }
416
417 size_ = limit;
418
419#ifdef DEBUG
420 Verify();
421#endif
422}
423
424template <typename T>
426 SeekResult seek_result = SeekIndex(index);
427 return typename ZoneChunkList<T>::iterator(seek_result.chunk_,
428 seek_result.chunk_index_);
429}
430
431template <typename T>
433 const size_t index) const {
434 SeekResult seek_result = SeekIndex(index);
435 return typename ZoneChunkList<T>::const_iterator(seek_result.chunk_,
436 seek_result.chunk_index_);
437}
438
439template <typename T>
442
443 // `result` is an empty freshly-constructed list.
444 if (split_begin == end()) return result;
445
446 // `this` is empty after the split and `result` contains everything.
447 if (split_begin == begin()) {
448 this->swap(result);
449 return result;
450 }
451
452 // There is at least one element in both `this` and `result`.
453
454 // Split the chunk.
455 Chunk* split_chunk = split_begin.current_;
456 DCHECK_LE(split_begin.position_, split_chunk->position_);
457 T* chunk_split_begin = split_chunk->items() + split_begin.position_;
458 T* chunk_split_end = split_chunk->items() + split_chunk->position_;
459 uint32_t new_chunk_size =
460 static_cast<uint32_t>(chunk_split_end - chunk_split_begin);
461 uint32_t new_chunk_capacity = std::max(
462 kInitialChunkCapacity, base::bits::RoundUpToPowerOfTwo32(new_chunk_size));
463 CHECK_LE(new_chunk_size, new_chunk_capacity);
464 Chunk* new_chunk = NewChunk(new_chunk_capacity);
465 std::copy(chunk_split_begin, chunk_split_end, new_chunk->items());
466 new_chunk->position_ = new_chunk_size;
467 split_chunk->position_ = split_begin.position_;
468
469 // Split the linked list.
470 result.front_ = new_chunk;
471 result.last_nonempty_ =
472 (last_nonempty_ == split_chunk) ? new_chunk : last_nonempty_;
473 new_chunk->next_ = split_chunk->next_;
474 if (new_chunk->next_) {
475 new_chunk->next_->previous_ = new_chunk;
476 }
477
478 last_nonempty_ = split_chunk;
479 split_chunk->next_ = nullptr;
480
481 // Compute the new size.
482 size_t new_size = 0;
483 for (Chunk* chunk = front_; chunk != split_chunk; chunk = chunk->next_) {
484 DCHECK(!chunk->empty());
485 new_size += chunk->size();
486 }
487 new_size += split_chunk->size();
488 DCHECK_LT(new_size, size());
489 result.size_ = size() - new_size;
490 size_ = new_size;
491
492#ifdef DEBUG
493 Verify();
494 result.Verify();
495#endif
496
497 return result;
498}
499
500template <typename T>
502 DCHECK_EQ(zone_, other.zone_);
503
504 if (other.front_ == nullptr) return;
505
506 last_nonempty_->next_ = other.front_;
507 other.front_->previous_ = last_nonempty_;
508
509 last_nonempty_ = other.last_nonempty_;
510
511 size_ += other.size_;
512#ifdef DEBUG
513 Verify();
514#endif
515
516 // Leave `other` in empty, but valid state.
517 other.front_ = nullptr;
518 other.last_nonempty_ = nullptr;
519 other.size_ = 0;
520}
521
522template <typename T>
524 for (Chunk* current = front_; current != nullptr; current = current->next_) {
525 void* start = current->items();
526 void* end = current->items() + current->position_;
527 size_t bytes = static_cast<size_t>(reinterpret_cast<uintptr_t>(end) -
528 reinterpret_cast<uintptr_t>(start));
529
530 MemCopy(ptr, current->items(), bytes);
531 ptr += current->position_;
532 }
533}
534
535} // namespace internal
536} // namespace v8
537
538#endif // V8_ZONE_ZONE_CHUNK_LIST_H_
#define T
bool operator==(const ZoneChunkListIterator &other) const
static ZoneChunkListIterator End(ChunkList *list)
maybe_const< ZoneChunkList< T > > ChunkList
ZoneChunkListIterator operator++(int)
ZoneChunkListIterator & operator++()
ZoneChunkListIterator(Chunk *current, uint32_t position)
ZoneChunkListIterator operator--(int)
static ZoneChunkListIterator Begin(ChunkList *list)
maybe_const< T > & operator*() const
ZoneChunkListIterator & operator--()
bool operator!=(const ZoneChunkListIterator &other) const
maybe_const< T > * operator->() const
maybe_const< typename ZoneChunkList< T >::Chunk > Chunk
typename std::conditional< modifiable, S, typename std::add_const< S >::type >::type maybe_const
iterator Find(const size_t index)
void push_back(const T &item)
const_iterator end() const
void Append(ZoneChunkList< T > &other)
static constexpr uint32_t kInitialChunkCapacity
const_iterator Find(const size_t index) const
SeekResult SeekIndex(size_t index) const
MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(ZoneChunkList)
Chunk * NewChunk(const uint32_t capacity)
void push_front(const T &item)
ZoneChunkListIterator< T, false, false > const_iterator
void swap(ZoneChunkList< T > &other)
static constexpr uint32_t kMaxChunkCapacity
ZoneChunkList< T > SplitAt(iterator split_begin)
static uint32_t NextChunkCapacity(uint32_t previous_capacity)
const_iterator begin() const
ZoneChunkListIterator< T, false, true > iterator
const_reverse_iterator rbegin() const
const_reverse_iterator rend() const
void Rewind(const size_t limit=0)
void * Allocate(size_t size)
Definition zone.h:61
Zone * zone_
const int size_
Definition assembler.cc:132
int start
int end
LineAndColumn current
ZoneVector< RpoNumber > & result
int position
Definition liveedit.cc:290
bool UnsignedAddOverflow32(uint32_t lhs, uint32_t rhs, uint32_t *val)
Definition bits.h:432
V8_BASE_EXPORT constexpr uint32_t RoundUpToPowerOfTwo32(uint32_t value)
Definition bits.h:219
constexpr int S
void MemCopy(void *dest, const void *src, size_t size)
Definition memcopy.h:124
#define DCHECK_LE(v1, v2)
Definition logging.h:490
#define DCHECK_NULL(val)
Definition logging.h:491
#define CHECK(condition)
Definition logging.h:124
#define CHECK_LE(lhs, rhs)
#define DCHECK_NOT_NULL(val)
Definition logging.h:492
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_LT(v1, v2)
Definition logging.h:489
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
#define V8_ASSUME
Definition v8config.h:533
wasm::ValueType type