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 "./api-build_p.h"
8 #include "./support_p.h"
9 #include "./zoneallocator_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.
17 const BLZoneAllocator::Block BLZoneAllocator::_zeroBlock = { nullptr, nullptr, 0 };
18 
19 // ============================================================================
20 // [BLZoneAllocator - Init / Reset]
21 // ============================================================================
22 
_init(size_t blockSize,size_t blockAlignment,void * staticData,size_t staticSize)23 void 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 & blNonZeroLsbMask<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 
reset()46 void 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 
_alloc(size_t size,size_t alignment)89 void* 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 
allocZeroed(size_t size,size_t alignment)161 void* 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