• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 Google LLC
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 #include "src/core/SkBlockAllocator.h"
9 
10 #ifdef SK_DEBUG
11 #include <vector>
12 #endif
13 
SkBlockAllocator(GrowthPolicy policy,size_t blockIncrementBytes,size_t additionalPreallocBytes)14 SkBlockAllocator::SkBlockAllocator(GrowthPolicy policy, size_t blockIncrementBytes,
15                                    size_t additionalPreallocBytes)
16         : fTail(&fHead)
17         // Round up to the nearest max-aligned value, and then divide so that fBlockSizeIncrement
18         // can effectively fit higher byte counts in its 16 bits of storage
19         , fBlockIncrement(SkTo<uint16_t>(SkAlignTo(blockIncrementBytes, kAddressAlign)
20                                                 / kAddressAlign))
21         , fGrowthPolicy(static_cast<uint64_t>(policy))
22         , fN0((policy == GrowthPolicy::kLinear || policy == GrowthPolicy::kExponential) ? 1 : 0)
23         , fN1(1)
24         // The head block always fills remaining space from SkBlockAllocator's size, because it's
25         // inline, but can take over the specified number of bytes immediately after it.
26         , fHead(/*prev=*/nullptr, additionalPreallocBytes + BaseHeadBlockSize()) {
27     SkASSERT(fBlockIncrement >= 1);
28     SkASSERT(additionalPreallocBytes <= kMaxAllocationSize);
29 }
30 
Block(Block * prev,int allocationSize)31 SkBlockAllocator::Block::Block(Block* prev, int allocationSize)
32          : fNext(nullptr)
33          , fPrev(prev)
34          , fSize(allocationSize)
35          , fCursor(kDataStart)
36          , fMetadata(0)
37          , fAllocatorMetadata(0) {
38     SkASSERT(allocationSize >= (int) sizeof(Block));
39     SkDEBUGCODE(fSentinel = kAssignedMarker;)
40 
41     this->poisonRange(kDataStart, fSize);
42 }
43 
~Block()44 SkBlockAllocator::Block::~Block() {
45     this->unpoisonRange(kDataStart, fSize);
46 
47     SkASSERT(fSentinel == kAssignedMarker);
48     SkDEBUGCODE(fSentinel = kFreedMarker;) // FWIW
49 }
50 
totalSize() const51 size_t SkBlockAllocator::totalSize() const {
52     // Use size_t since the sum across all blocks could exceed 'int', even though each block won't
53     size_t size = offsetof(SkBlockAllocator, fHead) + this->scratchBlockSize();
54     for (const Block* b : this->blocks()) {
55         size += b->fSize;
56     }
57     SkASSERT(size >= this->preallocSize());
58     return size;
59 }
60 
totalUsableSpace() const61 size_t SkBlockAllocator::totalUsableSpace() const {
62     size_t size = this->scratchBlockSize();
63     if (size > 0) {
64         size -= kDataStart; // scratchBlockSize reports total block size, not usable size
65     }
66     for (const Block* b : this->blocks()) {
67         size += (b->fSize - kDataStart);
68     }
69     SkASSERT(size >= this->preallocUsableSpace());
70     return size;
71 }
72 
totalSpaceInUse() const73 size_t SkBlockAllocator::totalSpaceInUse() const {
74     size_t size = 0;
75     for (const Block* b : this->blocks()) {
76         size += (b->fCursor - kDataStart);
77     }
78     SkASSERT(size <= this->totalUsableSpace());
79     return size;
80 }
81 
findOwningBlock(const void * p)82 SkBlockAllocator::Block* SkBlockAllocator::findOwningBlock(const void* p) {
83     // When in doubt, search in reverse to find an overlapping block.
84     uintptr_t ptr = reinterpret_cast<uintptr_t>(p);
85     for (Block* b : this->rblocks()) {
86         uintptr_t lowerBound = reinterpret_cast<uintptr_t>(b) + kDataStart;
87         uintptr_t upperBound = reinterpret_cast<uintptr_t>(b) + b->fSize;
88         if (lowerBound <= ptr && ptr < upperBound) {
89             SkASSERT(b->fSentinel == kAssignedMarker);
90             return b;
91         }
92     }
93     return nullptr;
94 }
95 
releaseBlock(Block * block)96 void SkBlockAllocator::releaseBlock(Block* block) {
97      if (block == &fHead) {
98         // Reset the cursor of the head block so that it can be reused if it becomes the new tail
99         block->fCursor = kDataStart;
100         block->fMetadata = 0;
101         block->poisonRange(kDataStart, block->fSize);
102         // Unlike in reset(), we don't set the head's next block to null because there are
103         // potentially heap-allocated blocks that are still connected to it.
104     } else {
105         SkASSERT(block->fPrev);
106         block->fPrev->fNext = block->fNext;
107         if (block->fNext) {
108             SkASSERT(fTail != block);
109             block->fNext->fPrev = block->fPrev;
110         } else {
111             SkASSERT(fTail == block);
112             fTail = block->fPrev;
113         }
114 
115         // The released block becomes the new scratch block (if it's bigger), or delete it
116         if (this->scratchBlockSize() < block->fSize) {
117             SkASSERT(block != fHead.fPrev); // shouldn't already be the scratch block
118             if (fHead.fPrev) {
119                 delete fHead.fPrev;
120             }
121             block->markAsScratch();
122             fHead.fPrev = block;
123         } else {
124             delete block;
125         }
126     }
127 
128     // Decrement growth policy (opposite of addBlock()'s increment operations)
129     GrowthPolicy gp = static_cast<GrowthPolicy>(fGrowthPolicy);
130     if (fN0 > 0 && (fN1 > 1 || gp == GrowthPolicy::kFibonacci)) {
131         SkASSERT(gp != GrowthPolicy::kFixed); // fixed never needs undoing, fN0 always is 0
132         if (gp == GrowthPolicy::kLinear) {
133             fN1 = fN1 - fN0;
134         } else if (gp == GrowthPolicy::kFibonacci) {
135             // Subtract n0 from n1 to get the prior 2 terms in the fibonacci sequence
136             int temp = fN1 - fN0; // yields prior fN0
137             fN1 = fN1 - temp;     // yields prior fN1
138             fN0 = temp;
139         } else {
140             SkASSERT(gp == GrowthPolicy::kExponential);
141             // Divide by 2 to undo the 2N update from addBlock
142             fN1 = fN1 >> 1;
143             fN0 = fN1;
144         }
145     }
146 
147     SkASSERT(fN1 >= 1 && fN0 >= 0);
148 }
149 
stealHeapBlocks(SkBlockAllocator * other)150 void SkBlockAllocator::stealHeapBlocks(SkBlockAllocator* other) {
151     Block* toSteal = other->fHead.fNext;
152     if (toSteal) {
153         // The other's next block connects back to this allocator's current tail, and its new tail
154         // becomes the end of other's block linked list.
155         SkASSERT(other->fTail != &other->fHead);
156         toSteal->fPrev = fTail;
157         fTail->fNext = toSteal;
158         fTail = other->fTail;
159         // The other allocator becomes just its inline head block
160         other->fTail = &other->fHead;
161         other->fHead.fNext = nullptr;
162     } // else no block to steal
163 }
164 
reset()165 void SkBlockAllocator::reset() {
166     for (Block* b : this->rblocks()) {
167         if (b == &fHead) {
168             // Reset metadata and cursor, tail points to the head block again
169             fTail = b;
170             b->fNext = nullptr;
171             b->fCursor = kDataStart;
172             b->fMetadata = 0;
173             // For reset(), but NOT releaseBlock(), the head allocatorMetadata and scratch block
174             // are reset/destroyed.
175             b->fAllocatorMetadata = 0;
176             b->poisonRange(kDataStart, b->fSize);
177             this->resetScratchSpace();
178         } else {
179             delete b;
180         }
181     }
182     SkASSERT(fTail == &fHead && fHead.fNext == nullptr && fHead.fPrev == nullptr &&
183              fHead.metadata() == 0 && fHead.fCursor == kDataStart);
184 
185     GrowthPolicy gp = static_cast<GrowthPolicy>(fGrowthPolicy);
186     fN0 = (gp == GrowthPolicy::kLinear || gp == GrowthPolicy::kExponential) ? 1 : 0;
187     fN1 = 1;
188 }
189 
resetScratchSpace()190 void SkBlockAllocator::resetScratchSpace() {
191     if (fHead.fPrev) {
192         delete fHead.fPrev;
193         fHead.fPrev = nullptr;
194     }
195 }
196 
addBlock(int minSize,int maxSize)197 void SkBlockAllocator::addBlock(int minSize, int maxSize) {
198     SkASSERT(minSize > (int) sizeof(Block) && minSize <= maxSize);
199 
200     // Max positive value for uint:23 storage (decltype(fN0) picks up uint64_t, not uint:23).
201     static constexpr int kMaxN = (1 << 23) - 1;
202     static_assert(2 * kMaxN <= std::numeric_limits<int32_t>::max()); // Growth policy won't overflow
203 
204     auto alignAllocSize = [](int size) {
205         // Round to a nice boundary since the block isn't maxing out:
206         //   if allocSize > 32K, aligns on 4K boundary otherwise aligns on max_align_t, to play
207         //   nicely with jeMalloc (from SkArenaAlloc).
208         int mask = size > (1 << 15) ? ((1 << 12) - 1) : (kAddressAlign - 1);
209         return (size + mask) & ~mask;
210     };
211 
212     int allocSize;
213     void* mem = nullptr;
214     if (this->scratchBlockSize() >= minSize) {
215         // Activate the scratch block instead of making a new block
216         SkASSERT(fHead.fPrev->isScratch());
217         allocSize = fHead.fPrev->fSize;
218         mem = fHead.fPrev;
219         fHead.fPrev = nullptr;
220     } else if (minSize < maxSize) {
221         // Calculate the 'next' size per growth policy sequence
222         GrowthPolicy gp = static_cast<GrowthPolicy>(fGrowthPolicy);
223         int nextN1 = fN0 + fN1;
224         int nextN0;
225         if (gp == GrowthPolicy::kFixed || gp == GrowthPolicy::kLinear) {
226             nextN0 = fN0;
227         } else if (gp == GrowthPolicy::kFibonacci) {
228             nextN0 = fN1;
229         } else {
230             SkASSERT(gp == GrowthPolicy::kExponential);
231             nextN0 = nextN1;
232         }
233         fN0 = std::min(kMaxN, nextN0);
234         fN1 = std::min(kMaxN, nextN1);
235 
236         // However, must guard against overflow here, since all the size-based asserts prevented
237         // alignment/addition overflows, while multiplication requires 2x bits instead of x+1.
238         int sizeIncrement = fBlockIncrement * kAddressAlign;
239         if (maxSize / sizeIncrement < nextN1) {
240             // The growth policy would overflow, so use the max. We've already confirmed that
241             // maxSize will be sufficient for the requested minimumSize
242             allocSize = maxSize;
243         } else {
244             allocSize = std::min(alignAllocSize(std::max(minSize, sizeIncrement * nextN1)),
245                                  maxSize);
246         }
247     } else {
248         SkASSERT(minSize == maxSize);
249         // Still align on a nice boundary, no max clamping since that would just undo the alignment
250         allocSize = alignAllocSize(minSize);
251     }
252 
253     // Create new block and append to the linked list of blocks in this allocator
254     if (!mem) {
255         mem = operator new(allocSize);
256     }
257     fTail->fNext = new (mem) Block(fTail, allocSize);
258     fTail = fTail->fNext;
259 }
260 
261 #ifdef SK_DEBUG
validate() const262 void SkBlockAllocator::validate() const {
263     std::vector<const Block*> blocks;
264     const Block* prev = nullptr;
265     for (const Block* block : this->blocks()) {
266         blocks.push_back(block);
267 
268         SkASSERT(kAssignedMarker == block->fSentinel);
269         if (block == &fHead) {
270             // The head blocks' fPrev may be non-null if it holds a scratch block, but that's not
271             // considered part of the linked list
272             SkASSERT(!prev && (!fHead.fPrev || fHead.fPrev->isScratch()));
273         } else {
274             SkASSERT(prev == block->fPrev);
275         }
276         if (prev) {
277             SkASSERT(prev->fNext == block);
278         }
279 
280         SkASSERT(block->fSize >= (int) sizeof(Block));
281         SkASSERT(block->fCursor >= kDataStart);
282         SkASSERT(block->fCursor <= block->fSize);
283 
284         prev = block;
285     }
286     SkASSERT(prev == fTail);
287     SkASSERT(!blocks.empty());
288     SkASSERT(blocks[0] == &fHead);
289 
290     // Confirm reverse iteration matches forward iteration
291     size_t j = blocks.size();
292     for (const Block* b : this->rblocks()) {
293         SkASSERT(b == blocks[j - 1]);
294         j--;
295     }
296     SkASSERT(j == 0);
297 }
298 #endif
299