• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 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 SkArenaAlloc_DEFINED
9 #define SkArenaAlloc_DEFINED
10 
11 #include "include/core/SkTypes.h"
12 #include "include/private/SkTFitsIn.h"
13 #include "include/private/SkTo.h"
14 
15 #include <array>
16 #include <cassert>
17 #include <cstddef>
18 #include <cstdint>
19 #include <cstdlib>
20 #include <cstring>
21 #include <limits>
22 #include <new>
23 #include <type_traits>
24 #include <utility>
25 #include <vector>
26 
27 // We found allocating strictly doubling amounts of memory from the heap left too
28 // much unused slop, particularly on Android.  Instead we'll follow a Fibonacci-like
29 // progression.
30 
31 // SkFibonacci47 is the first 47 Fibonacci numbers. Fib(47) is the largest value less than 2 ^ 32.
32 extern std::array<const uint32_t, 47> SkFibonacci47;
33 template<uint32_t kMaxSize>
34 class SkFibBlockSizes {
35 public:
36     // staticBlockSize, and firstAllocationSize are parameters describing the initial memory
37     // layout. staticBlockSize describes the size of the inlined memory, and firstAllocationSize
38     // describes the size of the first block to be allocated if the static block is exhausted. By
39     // convention, firstAllocationSize is the first choice for the block unit size followed by
40     // staticBlockSize followed by the default of 1024 bytes.
SkFibBlockSizes(uint32_t staticBlockSize,uint32_t firstAllocationSize)41     SkFibBlockSizes(uint32_t staticBlockSize, uint32_t firstAllocationSize) : fIndex{0} {
42         fBlockUnitSize = firstAllocationSize > 0 ? firstAllocationSize :
43                          staticBlockSize     > 0 ? staticBlockSize     : 1024;
44 
45         SkASSERT_RELEASE(0 < fBlockUnitSize);
46         SkASSERT_RELEASE(fBlockUnitSize < std::min(kMaxSize, (1u << 26) - 1));
47     }
48 
nextBlockSize()49     uint32_t nextBlockSize() {
50         uint32_t result = SkFibonacci47[fIndex] * fBlockUnitSize;
51 
52         if (SkTo<size_t>(fIndex + 1) < SkFibonacci47.size() &&
53             SkFibonacci47[fIndex + 1] < kMaxSize / fBlockUnitSize)
54         {
55             fIndex += 1;
56         }
57 
58         return result;
59     }
60 
61 private:
62     uint32_t fIndex : 6;
63     uint32_t fBlockUnitSize : 26;
64 };
65 
66 // SkArenaAlloc allocates object and destroys the allocated objects when destroyed. It's designed
67 // to minimize the number of underlying block allocations. SkArenaAlloc allocates first out of an
68 // (optional) user-provided block of memory, and when that's exhausted it allocates on the heap,
69 // starting with an allocation of firstHeapAllocation bytes.  If your data (plus a small overhead)
70 // fits in the user-provided block, SkArenaAlloc never uses the heap, and if it fits in
71 // firstHeapAllocation bytes, it'll use the heap only once. If 0 is specified for
72 // firstHeapAllocation, then blockSize is used unless that too is 0, then 1024 is used.
73 //
74 // Examples:
75 //
76 //   char block[mostCasesSize];
77 //   SkArenaAlloc arena(block, mostCasesSize);
78 //
79 // If mostCasesSize is too large for the stack, you can use the following pattern.
80 //
81 //   std::unique_ptr<char[]> block{new char[mostCasesSize]};
82 //   SkArenaAlloc arena(block.get(), mostCasesSize, almostAllCasesSize);
83 //
84 // If the program only sometimes allocates memory, use the following pattern.
85 //
86 //   SkArenaAlloc arena(nullptr, 0, almostAllCasesSize);
87 //
88 // The storage does not necessarily need to be on the stack. Embedding the storage in a class also
89 // works.
90 //
91 //   class Foo {
92 //       char storage[mostCasesSize];
93 //       SkArenaAlloc arena (storage, mostCasesSize);
94 //   };
95 //
96 // In addition, the system is optimized to handle POD data including arrays of PODs (where
97 // POD is really data with no destructors). For POD data it has zero overhead per item, and a
98 // typical per block overhead of 8 bytes. For non-POD objects there is a per item overhead of 4
99 // bytes. For arrays of non-POD objects there is a per array overhead of typically 8 bytes. There
100 // is an addition overhead when switching from POD data to non-POD data of typically 8 bytes.
101 //
102 // If additional blocks are needed they are increased exponentially. This strategy bounds the
103 // recursion of the RunDtorsOnBlock to be limited to O(log size-of-memory). Block size grow using
104 // the Fibonacci sequence which means that for 2^32 memory there are 48 allocations, and for 2^48
105 // there are 71 allocations.
106 class SkArenaAlloc {
107 public:
108     SkArenaAlloc(char* block, size_t blockSize, size_t firstHeapAllocation);
109 
SkArenaAlloc(size_t firstHeapAllocation)110     explicit SkArenaAlloc(size_t firstHeapAllocation)
111         : SkArenaAlloc(nullptr, 0, firstHeapAllocation) {}
112 
113     SkArenaAlloc(const SkArenaAlloc&) = delete;
114     SkArenaAlloc& operator=(const SkArenaAlloc&) = delete;
115     SkArenaAlloc(SkArenaAlloc&&) = delete;
116     SkArenaAlloc& operator=(SkArenaAlloc&&) = delete;
117 
118     ~SkArenaAlloc();
119 
120     template <typename Ctor>
121     auto make(Ctor&& ctor) -> decltype(ctor(nullptr)) {
122         using T = std::remove_pointer_t<decltype(ctor(nullptr))>;
123 
124         uint32_t size      = ToU32(sizeof(T));
125         uint32_t alignment = ToU32(alignof(T));
126         char* objStart;
127         if (std::is_trivially_destructible<T>::value) {
128             objStart = this->allocObject(size, alignment);
129             fCursor = objStart + size;
130         } else {
131             objStart = this->allocObjectWithFooter(size + sizeof(Footer), alignment);
132             // Can never be UB because max value is alignof(T).
133             uint32_t padding = ToU32(objStart - fCursor);
134 
135             // Advance to end of object to install footer.
136             fCursor = objStart + size;
137             FooterAction* releaser = [](char* objEnd) {
138                 char* objStart = objEnd - (sizeof(T) + sizeof(Footer));
139                 ((T*)objStart)->~T();
140                 return objStart;
141             };
142             this->installFooter(releaser, padding);
143         }
144 
145         // This must be last to make objects with nested use of this allocator work.
146         return ctor(objStart);
147     }
148 
149     template <typename T, typename... Args>
make(Args &&...args)150     T* make(Args&&... args) {
151         return this->make([&](void* objStart) {
152             return new(objStart) T(std::forward<Args>(args)...);
153         });
154     }
155 
156     template <typename T>
makeArrayDefault(size_t count)157     T* makeArrayDefault(size_t count) {
158         T* array = this->allocUninitializedArray<T>(count);
159         for (size_t i = 0; i < count; i++) {
160             // Default initialization: if T is primitive then the value is left uninitialized.
161             new (&array[i]) T;
162         }
163         return array;
164     }
165 
166     template <typename T>
makeArray(size_t count)167     T* makeArray(size_t count) {
168         T* array = this->allocUninitializedArray<T>(count);
169         for (size_t i = 0; i < count; i++) {
170             // Value initialization: if T is primitive then the value is zero-initialized.
171             new (&array[i]) T();
172         }
173         return array;
174     }
175 
176     template <typename T, typename Initializer>
makeInitializedArray(size_t count,Initializer initializer)177     T* makeInitializedArray(size_t count, Initializer initializer) {
178         T* array = this->allocUninitializedArray<T>(count);
179         for (size_t i = 0; i < count; i++) {
180             new (&array[i]) T(initializer(i));
181         }
182         return array;
183     }
184 
185     // Only use makeBytesAlignedTo if none of the typed variants are impractical to use.
makeBytesAlignedTo(size_t size,size_t align)186     void* makeBytesAlignedTo(size_t size, size_t align) {
187         AssertRelease(SkTFitsIn<uint32_t>(size));
188         auto objStart = this->allocObject(ToU32(size), ToU32(align));
189         fCursor = objStart + size;
190         return objStart;
191     }
192 
193 private:
AssertRelease(bool cond)194     static void AssertRelease(bool cond) { if (!cond) { ::abort(); } }
ToU32(size_t v)195     static uint32_t ToU32(size_t v) {
196         assert(SkTFitsIn<uint32_t>(v));
197         return (uint32_t)v;
198     }
199 
200     using FooterAction = char* (char*);
201     struct Footer {
202         uint8_t unaligned_action[sizeof(FooterAction*)];
203         uint8_t padding;
204     };
205 
206     static char* SkipPod(char* footerEnd);
207     static void RunDtorsOnBlock(char* footerEnd);
208     static char* NextBlock(char* footerEnd);
209 
210     template <typename T>
installRaw(const T & val)211     void installRaw(const T& val) {
212         memcpy(fCursor, &val, sizeof(val));
213         fCursor += sizeof(val);
214     }
215     void installFooter(FooterAction* releaser, uint32_t padding);
216 
217     void ensureSpace(uint32_t size, uint32_t alignment);
218 
allocObject(uint32_t size,uint32_t alignment)219     char* allocObject(uint32_t size, uint32_t alignment) {
220         uintptr_t mask = alignment - 1;
221         uintptr_t alignedOffset = (~reinterpret_cast<uintptr_t>(fCursor) + 1) & mask;
222         uintptr_t totalSize = size + alignedOffset;
223         AssertRelease(totalSize >= size);
224         if (totalSize > static_cast<uintptr_t>(fEnd - fCursor)) {
225             this->ensureSpace(size, alignment);
226             alignedOffset = (~reinterpret_cast<uintptr_t>(fCursor) + 1) & mask;
227         }
228 
229         char* object = fCursor + alignedOffset;
230 
231         SkASSERT((reinterpret_cast<uintptr_t>(object) & (alignment - 1)) == 0);
232         SkASSERT(object + size <= fEnd);
233 
234         return object;
235     }
236 
237     char* allocObjectWithFooter(uint32_t sizeIncludingFooter, uint32_t alignment);
238 
239     template <typename T>
allocUninitializedArray(size_t countZ)240     T* allocUninitializedArray(size_t countZ) {
241         AssertRelease(SkTFitsIn<uint32_t>(countZ));
242         uint32_t count = ToU32(countZ);
243 
244         char* objStart;
245         AssertRelease(count <= std::numeric_limits<uint32_t>::max() / sizeof(T));
246         uint32_t arraySize = ToU32(count * sizeof(T));
247         uint32_t alignment = ToU32(alignof(T));
248 
249         if (std::is_trivially_destructible<T>::value) {
250             objStart = this->allocObject(arraySize, alignment);
251             fCursor = objStart + arraySize;
252         } else {
253             constexpr uint32_t overhead = sizeof(Footer) + sizeof(uint32_t);
254             AssertRelease(arraySize <= std::numeric_limits<uint32_t>::max() - overhead);
255             uint32_t totalSize = arraySize + overhead;
256             objStart = this->allocObjectWithFooter(totalSize, alignment);
257 
258             // Can never be UB because max value is alignof(T).
259             uint32_t padding = ToU32(objStart - fCursor);
260 
261             // Advance to end of array to install footer.?
262             fCursor = objStart + arraySize;
263             this->installRaw(ToU32(count));
264             this->installFooter(
265                 [](char* footerEnd) {
266                     char* objEnd = footerEnd - (sizeof(Footer) + sizeof(uint32_t));
267                     uint32_t count;
268                     memmove(&count, objEnd, sizeof(uint32_t));
269                     char* objStart = objEnd - count * sizeof(T);
270                     T* array = (T*) objStart;
271                     for (uint32_t i = 0; i < count; i++) {
272                         array[i].~T();
273                     }
274                     return objStart;
275                 },
276                 padding);
277         }
278 
279         return (T*)objStart;
280     }
281 
282     char*          fDtorCursor;
283     char*          fCursor;
284     char*          fEnd;
285 
286     SkFibBlockSizes<std::numeric_limits<uint32_t>::max()> fFibonacciProgression;
287 };
288 
289 class SkArenaAllocWithReset : public SkArenaAlloc {
290 public:
291     SkArenaAllocWithReset(char* block, size_t blockSize, size_t firstHeapAllocation);
292 
SkArenaAllocWithReset(size_t firstHeapAllocation)293     explicit SkArenaAllocWithReset(size_t firstHeapAllocation)
294             : SkArenaAllocWithReset(nullptr, 0, firstHeapAllocation) {}
295 
296     // Destroy all allocated objects, free any heap allocations.
297     void reset();
298 
299 private:
300     char* const    fFirstBlock;
301     const uint32_t fFirstSize;
302     const uint32_t fFirstHeapAllocationSize;
303 };
304 
305 // Helper for defining allocators with inline/reserved storage.
306 // For argument declarations, stick to the base type (SkArenaAlloc).
307 // Note: Inheriting from the storage first means the storage will outlive the
308 // SkArenaAlloc, letting ~SkArenaAlloc read it as it calls destructors.
309 // (This is mostly only relevant for strict tools like MSAN.)
310 template <size_t InlineStorageSize>
311 class SkSTArenaAlloc : private std::array<char, InlineStorageSize>, public SkArenaAlloc {
312 public:
313     explicit SkSTArenaAlloc(size_t firstHeapAllocation = InlineStorageSize)
314         : SkArenaAlloc{this->data(), this->size(), firstHeapAllocation} {}
315 };
316 
317 template <size_t InlineStorageSize>
318 class SkSTArenaAllocWithReset
319         : private std::array<char, InlineStorageSize>, public SkArenaAllocWithReset {
320 public:
321     explicit SkSTArenaAllocWithReset(size_t firstHeapAllocation = InlineStorageSize)
322             : SkArenaAllocWithReset{this->data(), this->size(), firstHeapAllocation} {}
323 };
324 
325 #endif  // SkArenaAlloc_DEFINED
326