2 * Copyright 2021 Google LLC
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
8 #ifndef sktext_gpu_SubRunAllocator_DEFINED
9 #define sktext_gpu_SubRunAllocator_DEFINED
11 #include "include/core/SkMath.h"
12 #include "include/core/SkSpan.h"
13 #include "include/private/SkTemplates.h"
14 #include "src/core/SkArenaAlloc.h"
22 namespace sktext::gpu {
24 // BagOfBytes parcels out bytes with a given size and alignment.
27 BagOfBytes(char* block, size_t blockSize, size_t firstHeapAllocation);
28 explicit BagOfBytes(size_t firstHeapAllocation = 0);
29 BagOfBytes(const BagOfBytes&) = delete;
30 BagOfBytes& operator=(const BagOfBytes&) = delete;
31 BagOfBytes(BagOfBytes&& that)
32 : fEndByte{std::exchange(that.fEndByte, nullptr)}
33 , fCapacity{that.fCapacity}
34 , fFibProgression{that.fFibProgression} {}
35 BagOfBytes& operator=(BagOfBytes&& that) {
37 new (this) BagOfBytes{std::move(that)};
43 // Given a requestedSize round up to the smallest size that accounts for all the per block
44 // overhead and alignment. It crashes if requestedSize is negative or too big.
45 static constexpr int PlatformMinimumSizeWithOverhead(int requestedSize, int assumedAlignment) {
46 return MinimumSizeWithOverhead(
47 requestedSize, assumedAlignment, sizeof(Block), kMaxAlignment);
50 static constexpr int MinimumSizeWithOverhead(
51 int requestedSize, int assumedAlignment, int blockSize, int maxAlignment) {
52 SkASSERT_RELEASE(0 <= requestedSize && requestedSize < kMaxByteSize);
53 SkASSERT_RELEASE(SkIsPow2(assumedAlignment) && SkIsPow2(maxAlignment));
55 const int minAlignment = std::min(maxAlignment, assumedAlignment);
56 // There are two cases, one easy and one subtle. The easy case is when minAlignment ==
57 // maxAlignment. When that happens, the term maxAlignment - minAlignment is zero, and the
58 // block will be placed at the proper alignment because alignUp is properly
60 // The subtle case is where minAlignment < maxAlignment. Because
61 // minAlignment < maxAlignment, alignUp(requestedSize, minAlignment) + blockSize does not
62 // guarantee that block can be placed at a maxAlignment address. Block can be placed at
63 // maxAlignment/minAlignment different address to achieve alignment, so we need
64 // to add memory to allow the block to be placed on a maxAlignment address.
65 // For example, if assumedAlignment = 4 and maxAlignment = 16 then block can be placed at
66 // the following address offsets at the end of minimumSize bytes.
67 // 0 * minAlignment = 0
68 // 1 * minAlignment = 4
69 // 2 * minAlignment = 8
70 // 3 * minAlignment = 12
71 // Following this logic, the equation for the additional bytes is
72 // (maxAlignment/minAlignment - 1) * minAlignment
73 // = maxAlignment - minAlignment.
74 int minimumSize = AlignUp(requestedSize, minAlignment)
76 + maxAlignment - minAlignment;
78 // If minimumSize is > 32k then round to a 4K boundary unless it is too close to the
79 // maximum int. The > 32K heuristic is from the JEMalloc behavior.
80 constexpr int k32K = (1 << 15);
81 if (minimumSize >= k32K && minimumSize < std::numeric_limits<int>::max() - k4K) {
82 minimumSize = AlignUp(minimumSize, k4K);
89 using Storage = std::array<char, PlatformMinimumSizeWithOverhead(size, 1)>;
91 // Returns a pointer to memory suitable for holding n Ts.
92 template <typename T> char* allocateBytesFor(int n = 1) {
93 static_assert(alignof(T) <= kMaxAlignment, "Alignment is too big for arena");
94 static_assert(sizeof(T) < kMaxByteSize, "Size is too big for arena");
95 constexpr int kMaxN = kMaxByteSize / sizeof(T);
96 SkASSERT_RELEASE(0 <= n && n < kMaxN);
98 int size = n ? n * sizeof(T) : 1;
99 return this->allocateBytes(size, alignof(T));
102 void* alignedBytes(int unsafeSize, int unsafeAlignment);
105 // The maximum alignment supported by GrBagOfBytes. 16 seems to be a good number for alignment.
106 // If a use case for larger alignments is found, we can turn this into a template parameter.
107 inline static constexpr int kMaxAlignment = std::max(16, (int)alignof(std::max_align_t));
108 // The largest size that can be allocated. In larger sizes, the block is rounded up to 4K
109 // chunks. Leave a 4K of slop.
110 inline static constexpr int k4K = (1 << 12);
111 // This should never overflow with the calculations done on the code.
112 inline static constexpr int kMaxByteSize = std::numeric_limits<int>::max() - k4K;
113 // The assumed alignment of new char[] given the platform.
114 // There is a bug in Emscripten's allocator that make alignment different than max_align_t.
115 // kAllocationAlignment accounts for this difference. For more information see:
116 // https://github.com/emscripten-core/emscripten/issues/10072
117 #if !defined(SK_FORCE_8_BYTE_ALIGNMENT)
118 inline static constexpr int kAllocationAlignment = alignof(std::max_align_t);
120 inline static constexpr int kAllocationAlignment = 8;
123 static constexpr size_t AlignUp(int size, int alignment) {
124 return (size + (alignment - 1)) & -alignment;
127 // The Block starts at the location pointed to by fEndByte.
128 // Beware. Order is important here. The destructor for fPrevious must be called first because
129 // the Block is embedded in fBlockStart. Destructors are run in reverse order.
131 Block(char* previous, char* startOfBlock);
132 // The start of the originally allocated bytes. This is the thing that must be deleted.
133 char* const fBlockStart;
134 Block* const fPrevious;
137 // Note: fCapacity is the number of bytes remaining, and is subtracted from fEndByte to
138 // generate the location of the object.
139 char* allocateBytes(int size, int alignment) {
140 fCapacity = fCapacity & -alignment;
141 if (fCapacity < size) {
142 this->needMoreBytes(size, alignment);
144 char* const ptr = fEndByte - fCapacity;
145 SkASSERT(((intptr_t)ptr & (alignment - 1)) == 0);
146 SkASSERT(fCapacity >= size);
151 // Adjust fEndByte and fCapacity give a new block starting at bytes with size.
152 void setupBytesAndCapacity(char* bytes, int size);
154 // Adjust fEndByte and fCapacity to satisfy the size and alignment request.
155 void needMoreBytes(int size, int alignment);
157 // This points to the highest kMaxAlignment address in the allocated block. The address of
158 // the current end of allocated data is given by fEndByte - fCapacity. While the negative side
159 // of this pointer are the bytes to be allocated. The positive side points to the Block for
160 // this memory. In other words, the pointer to the Block structure for these bytes is
161 // reinterpret_cast<Block*>(fEndByte).
162 char* fEndByte{nullptr};
164 // The number of bytes remaining in this block.
167 SkFibBlockSizes<kMaxByteSize> fFibProgression;
170 template <typename T>
171 class SubRunInitializer {
173 SubRunInitializer(void* memory) : fMemory{memory} { SkASSERT(memory != nullptr); }
174 template <typename... Args>
175 T* initialize(Args&&... args) {
176 // Warn on more than one initialization.
177 SkASSERT(fMemory != nullptr);
178 return new (std::exchange(fMemory, nullptr)) T(std::forward<Args>(args)...);
185 // GrSubRunAllocator provides fast allocation where the user takes care of calling the destructors
186 // of the returned pointers, and GrSubRunAllocator takes care of deleting the storage. The
187 // unique_ptrs returned, are to assist in assuring the object's destructor is called.
188 // A note on zero length arrays: according to the standard a pointer must be returned, and it
189 // can't be a nullptr. In such a case, SkArena allocates one byte, but does not initialize it.
190 class SubRunAllocator {
193 template <typename T>
194 void operator()(T* ptr) { ptr->~T(); }
197 struct ArrayDestroyer {
199 template <typename T>
200 void operator()(T* ptr) {
201 for (int i = 0; i < n; i++) { ptr[i].~T(); }
206 inline static constexpr bool HasNoDestructor = std::is_trivially_destructible<T>::value;
208 SubRunAllocator(char* block, int blockSize, int firstHeapAllocation);
209 explicit SubRunAllocator(int firstHeapAllocation = 0);
210 SubRunAllocator(const SubRunAllocator&) = delete;
211 SubRunAllocator& operator=(const SubRunAllocator&) = delete;
212 SubRunAllocator(SubRunAllocator&&) = default;
213 SubRunAllocator& operator=(SubRunAllocator&&) = default;
215 template <typename T>
216 static std::tuple<SubRunInitializer<T>, int, SubRunAllocator>
217 AllocateClassMemoryAndArena(int allocSizeHint) {
218 SkASSERT_RELEASE(allocSizeHint >= 0);
219 // Round the size after the object the optimal amount.
220 int extraSize = BagOfBytes::PlatformMinimumSizeWithOverhead(allocSizeHint, alignof(T));
222 // Don't overflow or die.
223 SkASSERT_RELEASE(INT_MAX - SkTo<int>(sizeof(T)) > extraSize);
224 int totalMemorySize = sizeof(T) + extraSize;
226 void* memory = ::operator new (totalMemorySize);
227 SubRunAllocator alloc{SkTAddOffset<char>(memory, sizeof(T)), extraSize, extraSize/2};
228 return {memory, totalMemorySize, std::move(alloc)};
231 template <typename T, typename... Args> T* makePOD(Args&&... args) {
232 static_assert(HasNoDestructor<T>, "This is not POD. Use makeUnique.");
233 char* bytes = fAlloc.template allocateBytesFor<T>();
234 return new (bytes) T(std::forward<Args>(args)...);
237 template <typename T, typename... Args>
238 std::unique_ptr<T, Destroyer> makeUnique(Args&&... args) {
239 static_assert(!HasNoDestructor<T>, "This is POD. Use makePOD.");
240 char* bytes = fAlloc.template allocateBytesFor<T>();
241 return std::unique_ptr<T, Destroyer>{new (bytes) T(std::forward<Args>(args)...)};
244 template<typename T> T* makePODArray(int n) {
245 static_assert(HasNoDestructor<T>, "This is not POD. Use makeUniqueArray.");
246 return reinterpret_cast<T*>(fAlloc.template allocateBytesFor<T>(n));
249 template<typename T, typename Src, typename Map>
250 SkSpan<T> makePODArray(const Src& src, Map map) {
251 static_assert(HasNoDestructor<T>, "This is not POD. Use makeUniqueArray.");
252 int size = SkTo<int>(src.size());
253 T* result = this->template makePODArray<T>(size);
254 for (int i = 0; i < size; i++) {
255 new (&result[i]) T(map(src[i]));
257 return {result, src.size()};
261 std::unique_ptr<T[], ArrayDestroyer> makeUniqueArray(int n) {
262 static_assert(!HasNoDestructor<T>, "This is POD. Use makePODArray.");
263 T* array = reinterpret_cast<T*>(fAlloc.template allocateBytesFor<T>(n));
264 for (int i = 0; i < n; i++) {
267 return std::unique_ptr<T[], ArrayDestroyer>{array, ArrayDestroyer{n}};
270 template<typename T, typename I>
271 std::unique_ptr<T[], ArrayDestroyer> makeUniqueArray(int n, I initializer) {
272 static_assert(!HasNoDestructor<T>, "This is POD. Use makePODArray.");
273 T* array = reinterpret_cast<T*>(fAlloc.template allocateBytesFor<T>(n));
274 for (int i = 0; i < n; i++) {
275 new (&array[i]) T(initializer(i));
277 return std::unique_ptr<T[], ArrayDestroyer>{array, ArrayDestroyer{n}};
280 void* alignedBytes(int size, int alignment);
286 } // namespace sktext::gpu
288 #endif // sktext_gpu_SubRunAllocator_DEFINED