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