1 /*
2 * Copyright 2010 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #ifndef SkTBlockList_DEFINED
9 #define SkTBlockList_DEFINED
10
11 #include "include/private/base/SkAssert.h"
12 #include "include/private/base/SkDebug.h"
13 #include "include/private/base/SkTo.h"
14 #include "src/base/SkBlockAllocator.h"
15
16 #include <algorithm>
17 #include <cstring>
18 #include <type_traits>
19 #include <utility>
20
21 // Forward declarations for the iterators used by SkTBlockList
22 using IndexFn = int (*)(const SkBlockAllocator::Block*);
23 using NextFn = int (*)(const SkBlockAllocator::Block*, int);
24 template<typename T, typename B> using ItemFn = T (*)(B*, int);
25 template <typename T, bool Forward, bool Const, IndexFn Start, IndexFn End, NextFn Next,
26 ItemFn<T, typename std::conditional<Const, const SkBlockAllocator::Block,
27 SkBlockAllocator::Block>::type> Resolve>
28 class BlockIndexIterator;
29
30 /**
31 * SkTBlockList manages dynamic storage for instances of T, reserving fixed blocks such that
32 * allocation is amortized across every N instances. In this way it is a hybrid of an array-based
33 * vector and a linked-list. T can be any type and non-trivial destructors are automatically
34 * invoked when the SkTBlockList is destructed. The addresses of instances are guaranteed
35 * not to move except when a list is concatenated to another.
36 *
37 * The collection supports storing a templated number of elements inline before heap-allocated
38 * blocks are made to hold additional instances. By default, the heap blocks are sized to hold the
39 * same number of items as the inline block. A common pattern is to have the inline size hold only
40 * a small number of items for the common case and then allocate larger blocks when needed.
41 *
42 * If the size of a collection is N, and its block size is B, the complexity of the common
43 * operations are:
44 * - push_back()/emplace_back(): O(1), with malloc O(B)
45 * - pop_back(): O(1), with free O(B)
46 * - front()/back(): O(1)
47 * - reset(): O(N) for non-trivial types, O(N/B) for trivial types
48 * - concat(): O(B)
49 * - random access: O(N/B)
50 * - iteration: O(1) at each step
51 *
52 * These characteristics make it well suited for allocating items in a LIFO ordering, or otherwise
53 * acting as a stack, or simply using it as a typed allocator.
54 */
55 template <typename T, int StartingItems = 1>
56 class SkTBlockList {
57 public:
58 /**
59 * Create an allocator that defaults to using StartingItems as heap increment.
60 */
SkTBlockList()61 SkTBlockList() : SkTBlockList(StartingItems) {}
62
63 /**
64 * Create an allocator
65 *
66 * @param itemsPerBlock the number of items to allocate at once
67 */
68 explicit SkTBlockList(int itemsPerBlock,
69 SkBlockAllocator::GrowthPolicy policy =
70 SkBlockAllocator::GrowthPolicy::kFixed)
71 : fAllocator(policy,
72 SkBlockAllocator::BlockOverhead<alignof(T)>() + sizeof(T)*itemsPerBlock) {}
73
~SkTBlockList()74 ~SkTBlockList() { this->reset(); }
75
76 /**
77 * Adds an item and returns it.
78 *
79 * @return the added item.
80 */
push_back()81 T& push_back() {
82 return *new (this->pushItem()) T;
83 }
push_back(const T & t)84 T& push_back(const T& t) {
85 return *new (this->pushItem()) T(t);
86 }
push_back(T && t)87 T& push_back(T&& t) {
88 return *new (this->pushItem()) T(std::move(t));
89 }
90
91 template <typename... Args>
emplace_back(Args &&...args)92 T& emplace_back(Args&&... args) {
93 return *new (this->pushItem()) T(std::forward<Args>(args)...);
94 }
95
96 /**
97 * Move all items from 'other' to the end of this collection. When this returns, 'other' will
98 * be empty. Items in 'other' may be moved as part of compacting the pre-allocated start of
99 * 'other' into this list (using T's move constructor or memcpy if T is trivially copyable), but
100 * this is O(StartingItems) and not O(N). All other items are concatenated in O(1).
101 */
102 template <int SI>
103 void concat(SkTBlockList<T, SI>&& other);
104
105 /**
106 * Allocate, if needed, space to hold N more Ts before another malloc will occur.
107 */
reserve(int n)108 void reserve(int n) {
109 int avail = fAllocator->currentBlock()->template avail<alignof(T)>() / sizeof(T);
110 if (n > avail) {
111 int reserved = n - avail;
112 // Don't consider existing bytes since we've already determined how to split the N items
113 fAllocator->template reserve<alignof(T)>(
114 reserved * sizeof(T), SkBlockAllocator::kIgnoreExistingBytes_Flag);
115 }
116 }
117
118 /**
119 * Remove the last item, only call if count() != 0
120 */
pop_back()121 void pop_back() {
122 SkASSERT(this->count() > 0);
123
124 SkBlockAllocator::Block* block = fAllocator->currentBlock();
125
126 // Run dtor for the popped item
127 int releaseIndex = Last(block);
128 GetItem(block, releaseIndex).~T();
129
130 if (releaseIndex == First(block)) {
131 fAllocator->releaseBlock(block);
132 } else {
133 // Since this always follows LIFO, the block should always be able to release the memory
134 SkAssertResult(block->release(releaseIndex, releaseIndex + sizeof(T)));
135 block->setMetadata(Decrement(block, releaseIndex));
136 }
137
138 fAllocator->setMetadata(fAllocator->metadata() - 1);
139 }
140
141 /**
142 * Removes all added items.
143 */
reset()144 void reset() {
145 // Invoke destructors in reverse order if not trivially destructible
146 if constexpr (!std::is_trivially_destructible<T>::value) {
147 for (T& t : this->ritems()) {
148 t.~T();
149 }
150 }
151
152 fAllocator->reset();
153 }
154
155 /**
156 * Returns the item count.
157 */
count()158 int count() const {
159 #ifdef SK_DEBUG
160 // Confirm total count matches sum of block counts
161 int count = 0;
162 for (const auto* b :fAllocator->blocks()) {
163 if (b->metadata() == 0) {
164 continue; // skip empty
165 }
166 count += (sizeof(T) + Last(b) - First(b)) / sizeof(T);
167 }
168 SkASSERT(count == fAllocator->metadata());
169 #endif
170 return fAllocator->metadata();
171 }
172
173 /**
174 * Is the count 0?
175 */
empty()176 bool empty() const { return this->count() == 0; }
177
178 /**
179 * Access first item, only call if count() != 0
180 */
front()181 T& front() {
182 // This assumes that the head block actually have room to store the first item.
183 static_assert(StartingItems >= 1);
184 SkASSERT(this->count() > 0 && fAllocator->headBlock()->metadata() > 0);
185 return GetItem(fAllocator->headBlock(), First(fAllocator->headBlock()));
186 }
front()187 const T& front() const {
188 SkASSERT(this->count() > 0 && fAllocator->headBlock()->metadata() > 0);
189 return GetItem(fAllocator->headBlock(), First(fAllocator->headBlock()));
190 }
191
192 /**
193 * Access last item, only call if count() != 0
194 */
back()195 T& back() {
196 SkASSERT(this->count() > 0 && fAllocator->currentBlock()->metadata() > 0);
197 return GetItem(fAllocator->currentBlock(), Last(fAllocator->currentBlock()));
198 }
back()199 const T& back() const {
200 SkASSERT(this->count() > 0 && fAllocator->currentBlock()->metadata() > 0);
201 return GetItem(fAllocator->currentBlock(), Last(fAllocator->currentBlock()));
202 }
203
204 /**
205 * Access item by index. Not an operator[] since it should not be considered constant time.
206 * Use for-range loops by calling items() or ritems() instead to access all added items in order
207 */
item(int i)208 T& item(int i) {
209 SkASSERT(i >= 0 && i < this->count());
210
211 // Iterate over blocks until we find the one that contains i.
212 for (auto* b : fAllocator->blocks()) {
213 if (b->metadata() == 0) {
214 continue; // skip empty
215 }
216
217 int start = First(b);
218 int end = Last(b) + sizeof(T); // exclusive
219 int index = start + i * sizeof(T);
220 if (index < end) {
221 return GetItem(b, index);
222 } else {
223 i -= (end - start) / sizeof(T);
224 }
225 }
226 SkUNREACHABLE;
227 }
item(int i)228 const T& item(int i) const {
229 return const_cast<SkTBlockList*>(this)->item(i);
230 }
231
232 private:
233 // Let other SkTBlockLists have access (only ever used when T and S are the same but you
234 // cannot have partial specializations declared as a friend...)
235 template<typename S, int N> friend class SkTBlockList;
236 friend class TBlockListTestAccess; // for fAllocator
237
238 inline static constexpr size_t StartingSize =
239 SkBlockAllocator::Overhead<alignof(T)>() + StartingItems * sizeof(T);
240
GetItem(SkBlockAllocator::Block * block,int index)241 static T& GetItem(SkBlockAllocator::Block* block, int index) {
242 return *static_cast<T*>(block->ptr(index));
243 }
GetItem(const SkBlockAllocator::Block * block,int index)244 static const T& GetItem(const SkBlockAllocator::Block* block, int index) {
245 return *static_cast<const T*>(block->ptr(index));
246 }
First(const SkBlockAllocator::Block * b)247 static int First(const SkBlockAllocator::Block* b) {
248 return b->firstAlignedOffset<alignof(T)>();
249 }
Last(const SkBlockAllocator::Block * b)250 static int Last(const SkBlockAllocator::Block* b) {
251 return b->metadata();
252 }
Increment(const SkBlockAllocator::Block * b,int index)253 static int Increment(const SkBlockAllocator::Block* b, int index) {
254 return index + sizeof(T);
255 }
Decrement(const SkBlockAllocator::Block * b,int index)256 static int Decrement(const SkBlockAllocator::Block* b, int index) {
257 return index - sizeof(T);
258 }
259
pushItem()260 void* pushItem() {
261 // 'template' required because fAllocator is a template, calling a template member
262 auto br = fAllocator->template allocate<alignof(T)>(sizeof(T));
263 SkASSERT(br.fStart == br.fAlignedOffset ||
264 br.fAlignedOffset == First(fAllocator->currentBlock()));
265 br.fBlock->setMetadata(br.fAlignedOffset);
266 fAllocator->setMetadata(fAllocator->metadata() + 1);
267 return br.fBlock->ptr(br.fAlignedOffset);
268 }
269
270 // N represents the number of items, whereas SkSBlockAllocator takes total bytes, so must
271 // account for the block allocator's size too.
272 //
273 // This class uses the SkBlockAllocator's metadata to track total count of items, and per-block
274 // metadata to track the index of the last allocated item within each block.
275 SkSBlockAllocator<StartingSize> fAllocator;
276
277 public:
278 using Iter = BlockIndexIterator<T&, true, false, &First, &Last, &Increment, &GetItem>;
279 using CIter = BlockIndexIterator<const T&, true, true, &First, &Last, &Increment, &GetItem>;
280 using RIter = BlockIndexIterator<T&, false, false, &Last, &First, &Decrement, &GetItem>;
281 using CRIter = BlockIndexIterator<const T&, false, true, &Last, &First, &Decrement, &GetItem>;
282
283 /**
284 * Iterate over all items in allocation order (oldest to newest) using a for-range loop:
285 *
286 * for (auto&& T : this->items()) {}
287 */
items()288 Iter items() { return Iter(fAllocator.allocator()); }
items()289 CIter items() const { return CIter(fAllocator.allocator()); }
290
291 // Iterate from newest to oldest using a for-range loop.
ritems()292 RIter ritems() { return RIter(fAllocator.allocator()); }
ritems()293 CRIter ritems() const { return CRIter(fAllocator.allocator()); }
294 };
295
296 template <typename T, int SI1>
297 template <int SI2>
concat(SkTBlockList<T,SI2> && other)298 void SkTBlockList<T, SI1>::concat(SkTBlockList<T, SI2>&& other) {
299 // Optimize the common case where the list to append only has a single item
300 if (other.empty()) {
301 return;
302 } else if (other.count() == 1) {
303 this->push_back(other.back());
304 other.pop_back();
305 return;
306 }
307
308 // Manually move all items in other's head block into this list; all heap blocks from 'other'
309 // will be appended to the block linked list (no per-item moves needed then).
310 int headItemCount = 0;
311 SkBlockAllocator::Block* headBlock = other.fAllocator->headBlock();
312 SkDEBUGCODE(int oldCount = this->count();)
313 if (headBlock->metadata() > 0) {
314 int headStart = First(headBlock);
315 int headEnd = Last(headBlock) + sizeof(T); // exclusive
316 headItemCount = (headEnd - headStart) / sizeof(T);
317 int avail = fAllocator->currentBlock()->template avail<alignof(T)>() / sizeof(T);
318 if (headItemCount > avail) {
319 // Make sure there is extra room for the items beyond what's already avail. Use the
320 // kIgnoreGrowthPolicy_Flag to make this reservation as tight as possible since
321 // 'other's heap blocks will be appended after it and any extra space is wasted.
322 fAllocator->template reserve<alignof(T)>((headItemCount - avail) * sizeof(T),
323 SkBlockAllocator::kIgnoreExistingBytes_Flag |
324 SkBlockAllocator::kIgnoreGrowthPolicy_Flag);
325 }
326
327 if constexpr (std::is_trivially_copy_constructible<T>::value) {
328 // memcpy all items at once (or twice between current and reserved space).
329 SkASSERT(std::is_trivially_destructible<T>::value);
330 auto copy = [](SkBlockAllocator::Block* src, int start, SkBlockAllocator* dst, int n) {
331 auto target = dst->template allocate<alignof(T)>(n * sizeof(T));
332 memcpy(target.fBlock->ptr(target.fAlignedOffset), src->ptr(start), n * sizeof(T));
333 target.fBlock->setMetadata(target.fAlignedOffset + (n - 1) * sizeof(T));
334 };
335
336 if (avail > 0) {
337 // Copy 0 to avail items into existing tail block
338 copy(headBlock, headStart, fAllocator.allocator(), std::min(headItemCount, avail));
339 }
340 if (headItemCount > avail) {
341 // Copy (head count - avail) into the extra reserved space
342 copy(headBlock, headStart + avail * sizeof(T),
343 fAllocator.allocator(), headItemCount - avail);
344 }
345 fAllocator->setMetadata(fAllocator->metadata() + headItemCount);
346 } else {
347 // Move every item over one at a time
348 for (int i = headStart; i < headEnd; i += sizeof(T)) {
349 T& toMove = GetItem(headBlock, i);
350 this->push_back(std::move(toMove));
351 // Anything of interest should have been moved, but run this since T isn't
352 // a trusted type.
353 toMove.~T(); // NOLINT(bugprone-use-after-move): calling dtor always allowed
354 }
355 }
356
357 other.fAllocator->releaseBlock(headBlock);
358 }
359
360 // other's head block must have been fully copied since it cannot be stolen
361 SkASSERT(other.fAllocator->headBlock()->metadata() == 0 &&
362 fAllocator->metadata() == oldCount + headItemCount);
363 fAllocator->stealHeapBlocks(other.fAllocator.allocator());
364 fAllocator->setMetadata(fAllocator->metadata() +
365 (other.fAllocator->metadata() - headItemCount));
366 other.fAllocator->setMetadata(0);
367 }
368
369 /**
370 * BlockIndexIterator provides a reusable iterator template for collections built on top of a
371 * SkBlockAllocator, where each item is of the same type, and the index to an item can be iterated
372 * over in a known manner. It supports const and non-const, and forward and reverse, assuming it's
373 * provided with proper functions for starting, ending, and advancing.
374 */
375 template <typename T, // The element type (including any modifiers)
376 bool Forward, // Are indices within a block increasing or decreasing with iteration?
377 bool Const, // Whether or not T is const
378 IndexFn Start, // Returns the index of the first valid item in a block
379 IndexFn End, // Returns the index of the last valid item (so it is inclusive)
380 NextFn Next, // Returns the next index given the current index
381 ItemFn<T, typename std::conditional<Const, const SkBlockAllocator::Block,
382 SkBlockAllocator::Block>::type> Resolve>
383 class BlockIndexIterator {
384 using BlockIter = typename SkBlockAllocator::BlockIter<Forward, Const>;
385 public:
BlockIndexIterator(BlockIter iter)386 BlockIndexIterator(BlockIter iter) : fBlockIter(iter) {}
387
388 class Item {
389 public:
390 bool operator!=(const Item& other) const {
391 return other.fBlock != fBlock || (SkToBool(*fBlock) && other.fIndex != fIndex);
392 }
393
394 T operator*() const {
395 SkASSERT(*fBlock);
396 return Resolve(*fBlock, fIndex);
397 }
398
399 Item& operator++() {
400 const auto* block = *fBlock;
401 SkASSERT(block && block->metadata() > 0);
402 SkASSERT((Forward && Next(block, fIndex) > fIndex) ||
403 (!Forward && Next(block, fIndex) < fIndex));
404 fIndex = Next(block, fIndex);
405 if ((Forward && fIndex > fEndIndex) || (!Forward && fIndex < fEndIndex)) {
406 ++fBlock;
407 this->setIndices();
408 }
409 return *this;
410 }
411
412 private:
413 friend BlockIndexIterator;
414 using BlockItem = typename BlockIter::Item;
415
Item(BlockItem block)416 Item(BlockItem block) : fBlock(block) {
417 this->setIndices();
418 }
419
setIndices()420 void setIndices() {
421 // Skip empty blocks
422 while(*fBlock && (*fBlock)->metadata() == 0) {
423 ++fBlock;
424 }
425 if (*fBlock) {
426 fIndex = Start(*fBlock);
427 fEndIndex = End(*fBlock);
428 } else {
429 fIndex = 0;
430 fEndIndex = 0;
431 }
432
433 SkASSERT((Forward && fIndex <= fEndIndex) || (!Forward && fIndex >= fEndIndex));
434 }
435
436 BlockItem fBlock;
437 int fIndex;
438 int fEndIndex;
439 };
440
begin()441 Item begin() const { return Item(fBlockIter.begin()); }
end()442 Item end() const { return Item(fBlockIter.end()); }
443
444 private:
445 BlockIter fBlockIter;
446 };
447
448 #endif
449