Flutter Engine
The Flutter Engine
SkTBlockList.h
Go to the documentation of this file.
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
15
16#include <algorithm>
17#include <cstring>
18#include <type_traits>
19#include <utility>
20
21// Forward declarations for the iterators used by SkTBlockList
24template<typename T, typename B> using ItemFn = T (*)(B*, int);
25template <typename T, bool Forward, bool Const, IndexFn Start, IndexFn End, NextFn Next,
26 ItemFn<T, typename std::conditional<Const, const SkBlockAllocator::Block,
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 */
55template <typename T, int StartingItems = 1>
57public:
58 /**
59 * Create an list that defaults to using StartingItems as heap increment and the
60 * kFixed growth policy (e.g. all allocations will match StartingItems).
61 */
63
64 /**
65 * Create an list that defaults to using StartingItems as the heap increment, but with
66 * the defined growth policy.
67 */
69 : SkTBlockList(StartingItems, policy) {}
70
71 /**
72 * Create an list.
73 *
74 * @param itemsPerBlock the number of items to allocate at once
75 * @param policy the growth policy for subsequent blocks of items
76 */
77 explicit SkTBlockList(int itemsPerBlock,
80 : fAllocator(policy,
81 SkBlockAllocator::BlockOverhead<alignof(T)>() + sizeof(T)*itemsPerBlock) {}
82
83 ~SkTBlockList() { this->reset(); }
84
85 /**
86 * Adds an item and returns it.
87 *
88 * @return the added item.
89 */
91 return *new (this->pushItem()) T;
92 }
93 T& push_back(const T& t) {
94 return *new (this->pushItem()) T(t);
95 }
96 T& push_back(T&& t) {
97 return *new (this->pushItem()) T(std::move(t));
98 }
99
100 template <typename... Args>
101 T& emplace_back(Args&&... args) {
102 return *new (this->pushItem()) T(std::forward<Args>(args)...);
103 }
104
105 /**
106 * Move all items from 'other' to the end of this collection. When this returns, 'other' will
107 * be empty. Items in 'other' may be moved as part of compacting the pre-allocated start of
108 * 'other' into this list (using T's move constructor or memcpy if T is trivially copyable), but
109 * this is O(StartingItems) and not O(N). All other items are concatenated in O(1).
110 */
111 template <int SI>
113
114 /**
115 * Allocate, if needed, space to hold N more Ts before another malloc will occur.
116 */
117 void reserve(int n) {
118 int avail = fAllocator->currentBlock()->template avail<alignof(T)>() / sizeof(T);
119 if (n > avail) {
120 int reserved = n - avail;
121 // Don't consider existing bytes since we've already determined how to split the N items
122 fAllocator->template reserve<alignof(T)>(
124 }
125 }
126
127 /**
128 * Remove the last item, only call if count() != 0
129 */
130 void pop_back() {
131 SkASSERT(this->count() > 0);
132
133 SkBlockAllocator::Block* block = fAllocator->currentBlock();
134
135 // Run dtor for the popped item
136 int releaseIndex = Last(block);
137 GetItem(block, releaseIndex).~T();
138
139 if (releaseIndex == First(block)) {
140 fAllocator->releaseBlock(block);
141 } else {
142 // Since this always follows LIFO, the block should always be able to release the memory
143 SkAssertResult(block->release(releaseIndex, releaseIndex + sizeof(T)));
144 block->setMetadata(Decrement(block, releaseIndex));
145 }
146
147 fAllocator->setMetadata(fAllocator->metadata() - 1);
148 }
149
150 /**
151 * Removes all added items.
152 */
153 void reset() {
154 // Invoke destructors in reverse order if not trivially destructible
156 for (T& t : this->ritems()) {
157 t.~T();
158 }
159 }
160
161 fAllocator->reset();
162 }
163
164 /**
165 * Returns the item count.
166 */
167 int count() const {
168#ifdef SK_DEBUG
169 // Confirm total count matches sum of block counts
170 int count = 0;
171 for (const auto* b :fAllocator->blocks()) {
172 if (b->metadata() == 0) {
173 continue; // skip empty
174 }
175 count += (sizeof(T) + Last(b) - First(b)) / sizeof(T);
176 }
177 SkASSERT(count == fAllocator->metadata());
178#endif
179 return fAllocator->metadata();
180 }
181
182 /**
183 * Is the count 0?
184 */
185 bool empty() const { return this->count() == 0; }
186
187 /**
188 * Access first item, only call if count() != 0
189 */
190 T& front() {
191 // This assumes that the head block actually have room to store the first item.
192 static_assert(StartingItems >= 1);
193 SkASSERT(this->count() > 0 && fAllocator->headBlock()->metadata() > 0);
194 return GetItem(fAllocator->headBlock(), First(fAllocator->headBlock()));
195 }
196 const T& front() const {
197 SkASSERT(this->count() > 0 && fAllocator->headBlock()->metadata() > 0);
198 return GetItem(fAllocator->headBlock(), First(fAllocator->headBlock()));
199 }
200
201 /**
202 * Access last item, only call if count() != 0
203 */
204 T& back() {
205 SkASSERT(this->count() > 0 && fAllocator->currentBlock()->metadata() > 0);
206 return GetItem(fAllocator->currentBlock(), Last(fAllocator->currentBlock()));
207 }
208 const T& back() const {
209 SkASSERT(this->count() > 0 && fAllocator->currentBlock()->metadata() > 0);
210 return GetItem(fAllocator->currentBlock(), Last(fAllocator->currentBlock()));
211 }
212
213 /**
214 * Access item by index. Not an operator[] since it should not be considered constant time.
215 * Use for-range loops by calling items() or ritems() instead to access all added items in order
216 */
217 T& item(int i) {
218 SkASSERT(i >= 0 && i < this->count());
219
220 // Iterate over blocks until we find the one that contains i.
221 for (auto* b : fAllocator->blocks()) {
222 if (b->metadata() == 0) {
223 continue; // skip empty
224 }
225
226 int start = First(b);
227 int end = Last(b) + sizeof(T); // exclusive
228 int index = start + i * sizeof(T);
229 if (index < end) {
230 return GetItem(b, index);
231 } else {
232 i -= (end - start) / sizeof(T);
233 }
234 }
236 }
237 const T& item(int i) const {
238 return const_cast<SkTBlockList*>(this)->item(i);
239 }
240
241private:
242 // Let other SkTBlockLists have access (only ever used when T and S are the same but you
243 // cannot have partial specializations declared as a friend...)
244 template<typename S, int N> friend class SkTBlockList;
245 friend class TBlockListTestAccess; // for fAllocator
246
247 inline static constexpr size_t StartingSize =
248 SkBlockAllocator::Overhead<alignof(T)>() + StartingItems * sizeof(T);
249
250 static T& GetItem(SkBlockAllocator::Block* block, int index) {
251 return *static_cast<T*>(block->ptr(index));
252 }
253 static const T& GetItem(const SkBlockAllocator::Block* block, int index) {
254 return *static_cast<const T*>(block->ptr(index));
255 }
256 static int First(const SkBlockAllocator::Block* b) {
257 return b->firstAlignedOffset<alignof(T)>();
258 }
259 static int Last(const SkBlockAllocator::Block* b) {
260 return b->metadata();
261 }
262 static int Increment(const SkBlockAllocator::Block* b, int index) {
263 return index + sizeof(T);
264 }
265 static int Decrement(const SkBlockAllocator::Block* b, int index) {
266 return index - sizeof(T);
267 }
268
269 void* pushItem() {
270 // 'template' required because fAllocator is a template, calling a template member
271 auto br = fAllocator->template allocate<alignof(T)>(sizeof(T));
272 SkASSERT(br.fStart == br.fAlignedOffset ||
273 br.fAlignedOffset == First(fAllocator->currentBlock()));
274 br.fBlock->setMetadata(br.fAlignedOffset);
275 fAllocator->setMetadata(fAllocator->metadata() + 1);
276 return br.fBlock->ptr(br.fAlignedOffset);
277 }
278
279 // N represents the number of items, whereas SkSBlockAllocator takes total bytes, so must
280 // account for the block allocator's size too.
281 //
282 // This class uses the SkBlockAllocator's metadata to track total count of items, and per-block
283 // metadata to track the index of the last allocated item within each block.
285
286public:
291
292 /**
293 * Iterate over all items in allocation order (oldest to newest) using a for-range loop:
294 *
295 * for (auto&& T : this->items()) {}
296 */
297 Iter items() { return Iter(fAllocator.allocator()); }
298 CIter items() const { return CIter(fAllocator.allocator()); }
299
300 // Iterate from newest to oldest using a for-range loop.
301 RIter ritems() { return RIter(fAllocator.allocator()); }
302 CRIter ritems() const { return CRIter(fAllocator.allocator()); }
303};
304
305template <typename T, int SI1>
306template <int SI2>
308 // Optimize the common case where the list to append only has a single item
309 if (other.empty()) {
310 return;
311 } else if (other.count() == 1) {
312 this->push_back(other.back());
313 other.pop_back();
314 return;
315 }
316
317 // Manually move all items in other's head block into this list; all heap blocks from 'other'
318 // will be appended to the block linked list (no per-item moves needed then).
319 int headItemCount = 0;
320 SkBlockAllocator::Block* headBlock = other.fAllocator->headBlock();
321 SkDEBUGCODE(int oldCount = this->count();)
322 if (headBlock->metadata() > 0) {
323 int headStart = First(headBlock);
324 int headEnd = Last(headBlock) + sizeof(T); // exclusive
325 headItemCount = (headEnd - headStart) / sizeof(T);
326 int avail = fAllocator->currentBlock()->template avail<alignof(T)>() / sizeof(T);
327 if (headItemCount > avail) {
328 // Make sure there is extra room for the items beyond what's already avail. Use the
329 // kIgnoreGrowthPolicy_Flag to make this reservation as tight as possible since
330 // 'other's heap blocks will be appended after it and any extra space is wasted.
331 fAllocator->template reserve<alignof(T)>((headItemCount - avail) * sizeof(T),
334 }
335
337 // memcpy all items at once (or twice between current and reserved space).
339 auto copy = [](SkBlockAllocator::Block* src, int start, SkBlockAllocator* dst, int n) {
340 auto target = dst->template allocate<alignof(T)>(n * sizeof(T));
341 memcpy(target.fBlock->ptr(target.fAlignedOffset), src->ptr(start), n * sizeof(T));
342 target.fBlock->setMetadata(target.fAlignedOffset + (n - 1) * sizeof(T));
343 };
344
345 if (avail > 0) {
346 // Copy 0 to avail items into existing tail block
347 copy(headBlock, headStart, fAllocator.allocator(), std::min(headItemCount, avail));
348 }
349 if (headItemCount > avail) {
350 // Copy (head count - avail) into the extra reserved space
351 copy(headBlock, headStart + avail * sizeof(T),
352 fAllocator.allocator(), headItemCount - avail);
353 }
354 fAllocator->setMetadata(fAllocator->metadata() + headItemCount);
355 } else {
356 // Move every item over one at a time
357 for (int i = headStart; i < headEnd; i += sizeof(T)) {
358 T& toMove = GetItem(headBlock, i);
359 this->push_back(std::move(toMove));
360 // Anything of interest should have been moved, but run this since T isn't
361 // a trusted type.
362 toMove.~T(); // NOLINT(bugprone-use-after-move): calling dtor always allowed
363 }
364 }
365
366 other.fAllocator->releaseBlock(headBlock);
367 }
368
369 // other's head block must have been fully copied since it cannot be stolen
370 SkASSERT(other.fAllocator->headBlock()->metadata() == 0 &&
371 fAllocator->metadata() == oldCount + headItemCount);
372 fAllocator->stealHeapBlocks(other.fAllocator.allocator());
373 fAllocator->setMetadata(fAllocator->metadata() +
374 (other.fAllocator->metadata() - headItemCount));
375 other.fAllocator->setMetadata(0);
376}
377
378/**
379 * BlockIndexIterator provides a reusable iterator template for collections built on top of a
380 * SkBlockAllocator, where each item is of the same type, and the index to an item can be iterated
381 * over in a known manner. It supports const and non-const, and forward and reverse, assuming it's
382 * provided with proper functions for starting, ending, and advancing.
383 */
384template <typename T, // The element type (including any modifiers)
385 bool Forward, // Are indices within a block increasing or decreasing with iteration?
386 bool Const, // Whether or not T is const
387 IndexFn Start, // Returns the index of the first valid item in a block
388 IndexFn End, // Returns the index of the last valid item (so it is inclusive)
389 NextFn Next, // Returns the next index given the current index
390 ItemFn<T, typename std::conditional<Const, const SkBlockAllocator::Block,
393 using BlockIter = typename SkBlockAllocator::BlockIter<Forward, Const>;
394public:
395 BlockIndexIterator(BlockIter iter) : fBlockIter(iter) {}
396
397 class Item {
398 public:
399 bool operator!=(const Item& other) const {
400 return other.fBlock != fBlock || (SkToBool(*fBlock) && other.fIndex != fIndex);
401 }
402
403 T operator*() const {
404 SkASSERT(*fBlock);
405 return Resolve(*fBlock, fIndex);
406 }
407
409 const auto* block = *fBlock;
410 SkASSERT(block && block->metadata() > 0);
411 SkASSERT((Forward && Next(block, fIndex) > fIndex) ||
412 (!Forward && Next(block, fIndex) < fIndex));
413 fIndex = Next(block, fIndex);
414 if ((Forward && fIndex > fEndIndex) || (!Forward && fIndex < fEndIndex)) {
415 ++fBlock;
416 this->setIndices();
417 }
418 return *this;
419 }
420
421 private:
422 friend BlockIndexIterator;
423 using BlockItem = typename BlockIter::Item;
424
425 Item(BlockItem block) : fBlock(block) {
426 this->setIndices();
427 }
428
429 void setIndices() {
430 // Skip empty blocks
431 while(*fBlock && (*fBlock)->metadata() == 0) {
432 ++fBlock;
433 }
434 if (*fBlock) {
435 fIndex = Start(*fBlock);
436 fEndIndex = End(*fBlock);
437 } else {
438 fIndex = 0;
439 fEndIndex = 0;
440 }
441
442 SkASSERT((Forward && fIndex <= fEndIndex) || (!Forward && fIndex >= fEndIndex));
443 }
444
445 BlockItem fBlock;
446 int fIndex;
447 int fEndIndex;
448 };
449
450 Item begin() const { return Item(fBlockIter.begin()); }
451 Item end() const { return Item(fBlockIter.end()); }
452
453private:
454 BlockIter fBlockIter;
455};
456
457#endif
SkAssertResult(font.textToGlyphs("Hello", 5, SkTextEncoding::kUTF8, glyphs, std::size(glyphs))==count)
int count
Definition: FontMgrTest.cpp:50
#define SkUNREACHABLE
Definition: SkAssert.h:135
#define SkASSERT(cond)
Definition: SkAssert.h:116
SkBlockAllocator::Block Block
static void copy(void *dst, const uint8_t *src, int width, int bpp, int deltaSrc, int offset, const SkPMColor ctable[])
Definition: SkSwizzler.cpp:31
T(*)(B *, int) ItemFn
Definition: SkTBlockList.h:24
int(*)(const SkBlockAllocator::Block *, int) NextFn
Definition: SkTBlockList.h:23
int(*)(const SkBlockAllocator::Block *) IndexFn
Definition: SkTBlockList.h:22
SkDEBUGCODE(SK_SPI) SkThreadID SkGetThreadID()
static constexpr bool SkToBool(const T &x)
Definition: SkTo.h:35
GLenum type
bool operator!=(const Item &other) const
Definition: SkTBlockList.h:399
Item begin() const
Definition: SkTBlockList.h:450
Item end() const
Definition: SkTBlockList.h:451
BlockIndexIterator(BlockIter iter)
Definition: SkTBlockList.h:395
bool release(int start, int end)
void * ptr(int offset)
void setMetadata(int value)
void setMetadata(int value)
void releaseBlock(Block *block)
int metadata() const
const Block * headBlock() const
BlockIter< true, false > blocks()
const Block * currentBlock() const
SkBlockAllocator * allocator()
CIter items() const
Definition: SkTBlockList.h:298
RIter ritems()
Definition: SkTBlockList.h:301
const T & item(int i) const
Definition: SkTBlockList.h:237
const T & back() const
Definition: SkTBlockList.h:208
SkTBlockList(int itemsPerBlock, SkBlockAllocator::GrowthPolicy policy=SkBlockAllocator::GrowthPolicy::kFixed)
Definition: SkTBlockList.h:77
T & item(int i)
Definition: SkTBlockList.h:217
T & push_back()
Definition: SkTBlockList.h:90
CRIter ritems() const
Definition: SkTBlockList.h:302
BlockIndexIterator< T &, true, false, &First, &Last, &Increment, &GetItem > Iter
Definition: SkTBlockList.h:287
const T & front() const
Definition: SkTBlockList.h:196
void pop_back()
Definition: SkTBlockList.h:130
int count() const
Definition: SkTBlockList.h:167
BlockIndexIterator< const T &, false, true, &Last, &First, &Decrement, &GetItem > CRIter
Definition: SkTBlockList.h:290
T & emplace_back(Args &&... args)
Definition: SkTBlockList.h:101
BlockIndexIterator< const T &, true, true, &First, &Last, &Increment, &GetItem > CIter
Definition: SkTBlockList.h:288
SkTBlockList(SkBlockAllocator::GrowthPolicy policy)
Definition: SkTBlockList.h:68
T & push_back(T &&t)
Definition: SkTBlockList.h:96
bool empty() const
Definition: SkTBlockList.h:185
void concat(SkTBlockList< T, SI > &&other)
void reserve(int n)
Definition: SkTBlockList.h:117
BlockIndexIterator< T &, false, false, &Last, &First, &Decrement, &GetItem > RIter
Definition: SkTBlockList.h:289
T & push_back(const T &t)
Definition: SkTBlockList.h:93
static bool b
glong glong end
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
uint8_t value
uint32_t * target
static float min(float r, float g, float b)
Definition: hsl.cpp:48
Definition: copy.py:1
static FunctionPtr Resolve(Thread *thread, Zone *zone, const GrowableArray< const Instance * > &caller_arguments, const Class &receiver_class, const String &name, const Array &descriptor)
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network policy
Definition: switches.h:248
dst
Definition: cp.py:12
#define T
Definition: precompiler.cc:65