1// [Blend2D]
2// 2D Vector Graphics Powered by a JIT Compiler.
3//
4// [License]
5// Zlib - See LICENSE.md file in the package.
6
7#include "./blapi-build_p.h"
8#include "./blsupport_p.h"
9#include "./blzoneallocator_p.h"
10
11// ============================================================================
12// [BLZoneAllocator - Statics]
13// ============================================================================
14
15// Zero size block used by `BLZoneAllocator` that doesn't have any memory allocated.
16// Should be allocated in read-only memory and should never be modified.
17const BLZoneAllocator::Block BLZoneAllocator::_zeroBlock = { nullptr, nullptr, 0 };
18
19// ============================================================================
20// [BLZoneAllocator - Init / Reset]
21// ============================================================================
22
23void BLZoneAllocator::_init(size_t blockSize, size_t blockAlignment, void* staticData, size_t staticSize) noexcept {
24 BL_ASSERT(blockSize >= kMinBlockSize);
25 BL_ASSERT(blockSize <= kMaxBlockSize);
26 BL_ASSERT(blockAlignment <= 64);
27
28 _assignZeroBlock();
29 _blockSize = blockSize & blTrailingBitMask<size_t>(blBitSizeOf<size_t>() - 4);
30 _hasStaticBlock = staticData != nullptr;
31 _blockAlignmentShift = blBitCtz(blockAlignment) & 0x7;
32
33 // Setup the first [temporary] block, if necessary.
34 if (staticData) {
35 Block* block = static_cast<Block*>(staticData);
36 block->prev = nullptr;
37 block->next = nullptr;
38
39 BL_ASSERT(staticSize >= kBlockSize);
40 block->size = staticSize - kBlockSize;
41
42 _assignBlock(block);
43 }
44}
45
46void BLZoneAllocator::reset() noexcept {
47 // Can't be altered.
48 Block* cur = _block;
49 if (cur == &_zeroBlock)
50 return;
51
52 Block* initial = const_cast<BLZoneAllocator::Block*>(&_zeroBlock);
53 _ptr = initial->data();
54 _end = initial->data();
55 _block = initial;
56
57 // Since cur can be in the middle of the double-linked list, we have to
58 // traverse both directions (`prev` and `next`) separately to visit all.
59 Block* next = cur->next;
60 do {
61 Block* prev = cur->prev;
62
63 // If this is the first block and this BLZoneAllocatorTmp is temporary then
64 // the first block is statically allocated. We cannot free it and it makes
65 // sense to keep it even when this is hard reset.
66 if (prev == nullptr && _hasStaticBlock) {
67 cur->prev = nullptr;
68 cur->next = nullptr;
69 _assignBlock(cur);
70 break;
71 }
72
73 free(cur);
74 cur = prev;
75 } while (cur);
76
77 cur = next;
78 while (cur) {
79 next = cur->next;
80 free(cur);
81 cur = next;
82 }
83}
84
85// ============================================================================
86// [BLZoneAllocator - Alloc]
87// ============================================================================
88
89void* BLZoneAllocator::_alloc(size_t size, size_t alignment) noexcept {
90 Block* curBlock = _block;
91 Block* next = curBlock->next;
92
93 size_t rawBlockAlignment = blockAlignment();
94 size_t minimumAlignment = blMax<size_t>(alignment, rawBlockAlignment);
95
96 // If the `BLZoneAllocator` has been cleared the current block doesn't have to be the
97 // last one. Check if there is a block that can be used instead of allocating
98 // a new one. If there is a `next` block it's completely unused, we don't have
99 // to check for remaining bytes in that case.
100 if (next) {
101 uint8_t* ptr = blAlignUp(next->data(), minimumAlignment);
102 uint8_t* end = blAlignDown(next->data() + next->size, rawBlockAlignment);
103
104 if (size <= (size_t)(end - ptr)) {
105 _block = next;
106 _ptr = ptr + size;
107 _end = blAlignDown(next->data() + next->size, rawBlockAlignment);
108 return static_cast<void*>(ptr);
109 }
110 }
111
112 size_t blockAlignmentOverhead = alignment - blMin<size_t>(alignment, BL_ALLOC_ALIGNMENT);
113 size_t newSize = blMax(blockSize(), size);
114
115 // Prevent arithmetic overflow.
116 if (BL_UNLIKELY(newSize > SIZE_MAX - kBlockSize - blockAlignmentOverhead))
117 return nullptr;
118
119 // Allocate new block - we add alignment overhead to `newSize`, which becomes the
120 // new block size, and we also add `kBlockOverhead` to the allocator as it includes
121 // members of `BLZoneAllocator::Block` structure.
122 newSize += blockAlignmentOverhead;
123 Block* newBlock = static_cast<Block*>(malloc(newSize + kBlockSize));
124
125 if (BL_UNLIKELY(!newBlock))
126 return nullptr;
127
128 // Align the pointer to `minimumAlignment` and adjust the size of this block
129 // accordingly. It's the same as using `minimumAlignment - blAlignUpDiff()`,
130 // just written differently.
131 {
132 newBlock->prev = nullptr;
133 newBlock->next = nullptr;
134 newBlock->size = newSize;
135
136 if (curBlock != &_zeroBlock) {
137 newBlock->prev = curBlock;
138 curBlock->next = newBlock;
139
140 // Does only happen if there is a next block, but the requested memory
141 // can't fit into it. In this case a new buffer is allocated and inserted
142 // between the current block and the next one.
143 if (next) {
144 newBlock->next = next;
145 next->prev = newBlock;
146 }
147 }
148
149 uint8_t* ptr = blAlignUp(newBlock->data(), minimumAlignment);
150 uint8_t* end = blAlignDown(newBlock->data() + newSize, rawBlockAlignment);
151
152 _ptr = ptr + size;
153 _end = end;
154 _block = newBlock;
155
156 BL_ASSERT(_ptr <= _end);
157 return static_cast<void*>(ptr);
158 }
159}
160
161void* BLZoneAllocator::allocZeroed(size_t size, size_t alignment) noexcept {
162 void* p = alloc(size, alignment);
163 if (BL_UNLIKELY(!p))
164 return p;
165 return memset(p, 0, size);
166}
167