yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Ellie Hermaszewskaformatf65d756bf

master
12.2 KiB469 linesraw
1
2#include "slang-memory-arena.h"
3
4namespace Slang
5{
6
7MemoryArena::MemoryArena()
8{
9    // Mark as invalid so any alloc call will fail
10    m_blockAlignment = 0;
11    m_blockAllocSize = 0;
12
13    // Set up as empty
14    m_usedBlocks = nullptr;
15    m_availableBlocks = nullptr;
16
17    _resetCurrentBlock();
18
19    m_blockFreeList.init(sizeof(Block), sizeof(void*), 16);
20}
21
22MemoryArena::~MemoryArena()
23{
24    reset();
25}
26
27
28MemoryArena::MemoryArena(size_t blockPayloadSize, size_t blockAlignment)
29{
30    _initialize(blockPayloadSize, blockAlignment);
31}
32
33void MemoryArena::init(size_t blockPayloadSize, size_t blockAlignment)
34{
35    reset();
36    _initialize(blockPayloadSize, blockAlignment);
37}
38
39void MemoryArena::_initialize(size_t blockPayloadSize, size_t alignment)
40{
41    // Alignment must be a power of 2
42    assert(((alignment - 1) & alignment) == 0);
43
44    // Ensure it's alignment is at least kMinAlignment
45    alignment = (alignment < kMinAlignment) ? kMinAlignment : alignment;
46
47    const size_t alignMask = alignment - 1;
48
49    // Make sure the payload is rounded up to the alignment
50    blockPayloadSize = (blockPayloadSize + alignMask) & ~alignMask;
51
52    m_blockPayloadSize = blockPayloadSize;
53
54    // If alignment required is larger then the backing allocators then
55    // make larger to ensure when alignment correction takes place it will be aligned
56    const size_t blockAllocSize =
57        (alignment > kMinAlignment) ? (blockPayloadSize + alignment) : blockPayloadSize;
58
59    m_blockAllocSize = blockAllocSize;
60    m_blockAlignment = alignment;
61    m_availableBlocks = nullptr;
62
63    m_blockFreeList.init(sizeof(Block), sizeof(void*), 16);
64
65    _resetCurrentBlock();
66}
67
68void MemoryArena::swapWith(ThisType& rhs)
69{
70    Swap(m_start, rhs.m_start);
71    Swap(m_end, rhs.m_end);
72    Swap(m_current, rhs.m_current);
73
74    Swap(m_blockPayloadSize, rhs.m_blockPayloadSize);
75    Swap(m_blockAllocSize, rhs.m_blockAllocSize);
76    Swap(m_blockAlignment, rhs.m_blockAlignment);
77
78    Swap(m_availableBlocks, rhs.m_availableBlocks);
79    Swap(m_usedBlocks, rhs.m_usedBlocks);
80
81    m_blockFreeList.swapWith(rhs.m_blockFreeList);
82}
83
84void MemoryArena::_resetCurrentBlock()
85{
86    m_start = nullptr;
87    m_end = nullptr;
88    m_current = nullptr;
89
90    m_usedBlocks = nullptr;
91}
92
93void MemoryArena::_addCurrentBlock(Block* block)
94{
95    // Set up for allocation from
96    m_end = block->m_end;
97    m_start = block->m_start;
98    m_current = m_start;
99
100    // Add to linked list of used block, making it the top used block
101    block->m_next = m_usedBlocks;
102    m_usedBlocks = block;
103}
104
105void MemoryArena::_setCurrentBlock(Block* block)
106{
107    // Set up for allocation from
108    m_end = block->m_end;
109    m_start = block->m_start;
110    m_current = m_start;
111
112    assert(m_usedBlocks == block);
113}
114
115void MemoryArena::_deallocateBlocksPayload(Block* start)
116{
117    Block* cur = start;
118    while (cur)
119    {
120        // Deallocate the block
121        ::free(cur->m_alloc);
122        cur = cur->m_next;
123    }
124}
125
126void MemoryArena::_deallocateBlocks(Block* start)
127{
128    Block* cur = start;
129    while (cur)
130    {
131        Block* next = cur->m_next;
132        // Deallocate the block
133        ::free(cur->m_alloc);
134
135        m_blockFreeList.deallocate(cur);
136        cur = next;
137    }
138}
139
140bool MemoryArena::_isNormalBlock(Block* block)
141{
142    // The size of the block in total is from m_alloc to the m_end (ie the size that is passed into
143    // _newBlock)
144    const size_t blockSize = size_t(block->m_end - block->m_alloc);
145    return (blockSize == m_blockAllocSize) &&
146           ((size_t(block->m_start) & (m_blockAlignment - 1)) == 0);
147}
148
149void MemoryArena::_deallocateBlock(Block* block)
150{
151    // If it's a normal block then make it available
152    if (_isNormalBlock(block))
153    {
154        block->m_next = m_availableBlocks;
155        m_availableBlocks = block;
156    }
157    else
158    {
159        // Must be odd sized so free it
160        ::free(block->m_alloc);
161        // Free it in the block list
162        m_blockFreeList.deallocate(block);
163    }
164}
165
166void MemoryArena::deallocateAll()
167{
168    // we need to rewind through m_usedBlocks -> seeing it the are normal sized or not
169    Block* block = m_usedBlocks;
170    while (block)
171    {
172        Block* next = block->m_next;
173        _deallocateBlock(block);
174        block = next;
175    }
176
177    // Reset current block
178    _resetCurrentBlock();
179}
180
181void MemoryArena::reset()
182{
183    _deallocateBlocksPayload(m_usedBlocks);
184    _deallocateBlocksPayload(m_availableBlocks);
185
186    m_blockFreeList.reset();
187
188    m_availableBlocks = nullptr;
189
190    _resetCurrentBlock();
191}
192
193MemoryArena::Block* MemoryArena::_findNonCurrent(const void* data, size_t size) const
194{
195    return m_usedBlocks ? _findInBlocks(m_usedBlocks->m_next, data, size) : nullptr;
196}
197
198MemoryArena::Block* MemoryArena::_findNonCurrent(const void* data) const
199{
200    return m_usedBlocks ? _findInBlocks(m_usedBlocks->m_next, data) : nullptr;
201}
202
203MemoryArena::Block* MemoryArena::_findInBlocks(Block* block, const void* data, size_t size) const
204{
205    const uint8_t* ptr = (const uint8_t*)data;
206    while (block)
207    {
208        if (ptr >= block->m_start && ptr + size <= block->m_end)
209        {
210            return block;
211        }
212        block = block->m_next;
213    }
214    return nullptr;
215}
216
217MemoryArena::Block* MemoryArena::_findInBlocks(Block* block, const void* data) const
218{
219    const uint8_t* ptr = (const uint8_t*)data;
220    while (block)
221    {
222        if (ptr >= block->m_start && ptr <= block->m_end)
223        {
224            return block;
225        }
226        block = block->m_next;
227    }
228    return nullptr;
229}
230
231MemoryArena::Block* MemoryArena::_newNormalBlock()
232{
233    if (m_availableBlocks)
234    {
235        // We have an available block..
236        Block* block = m_availableBlocks;
237        m_availableBlocks = block->m_next;
238        return block;
239    }
240
241    Block* block = _newBlock(m_blockAllocSize, m_blockAlignment);
242    // Check that every normal block has m_blockPayloadSize space
243    assert(size_t(block->m_end - block->m_start) >= m_blockPayloadSize);
244    return block;
245}
246
247MemoryArena::Block* MemoryArena::_newBlock(size_t allocSize, size_t alignment)
248{
249    assert(alignment >= m_blockAlignment);
250    // Alignment must be a power of 2
251    assert(((alignment - 1) & alignment) == 0);
252
253    // Allocate block
254    Block* block = (Block*)m_blockFreeList.allocate();
255    if (!block)
256    {
257        return nullptr;
258    }
259
260    // Allocate the memory
261    uint8_t* alloc = (uint8_t*)::malloc(allocSize);
262    if (!alloc)
263    {
264        m_blockFreeList.deallocate(block);
265        return nullptr;
266    }
267
268    const size_t alignMask = alignment - 1;
269
270    // Do the alignment on the allocation
271    uint8_t* const start = (uint8_t*)((size_t(alloc) + alignMask) & ~alignMask);
272
273    // Setup the block
274    block->m_alloc = alloc;
275    block->m_start = start;
276    block->m_end = alloc + allocSize;
277    block->m_next = nullptr;
278
279    return block;
280}
281
282void MemoryArena::addExternalBlock(void* inData, size_t size)
283{
284    // Allocate block
285    Block* block = (Block*)m_blockFreeList.allocate();
286    if (!block)
287    {
288        return;
289    }
290
291    uint8_t* alloc = (uint8_t*)inData;
292
293    const size_t alignMask = m_blockAlignment - 1;
294
295    // Do the alignment on the allocation
296    uint8_t* const start = (uint8_t*)((size_t(alloc) + alignMask) & ~alignMask);
297
298    // Setup the block
299    block->m_alloc = alloc;
300    block->m_start = start;
301    block->m_end = alloc + size;
302    block->m_next = nullptr;
303
304    // We don't want to place at start, if there is any used blocks - as that is the one
305    // that is being split from and can be rewound. So we place just behind in that case
306    if (m_usedBlocks)
307    {
308        block->m_next = m_usedBlocks->m_next;
309        m_usedBlocks->m_next = block;
310    }
311    else
312    {
313        // There aren't any blocks, so just place at the front
314        m_usedBlocks = block;
315    }
316}
317
318void* MemoryArena::_allocateAlignedFromNewBlockAndZero(size_t sizeInBytes, size_t alignment)
319{
320    void* mem = _allocateAlignedFromNewBlock(sizeInBytes, alignment);
321    if (mem)
322    {
323        ::memset(mem, 0, sizeInBytes);
324    }
325    return mem;
326}
327
328void* MemoryArena::_allocateAlignedFromNewBlock(size_t size, size_t alignment)
329{
330    // Make sure init has been called (or has been set up in parameterized constructor)
331    assert(m_blockAllocSize > 0);
332    // Alignment must be a power of 2
333    assert(((alignment - 1) & alignment) == 0);
334
335    // Alignment must at a minimum be block alignment (such if reused the constraints hold)
336    alignment = (alignment < m_blockAlignment) ? m_blockAlignment : alignment;
337
338    const size_t alignMask = alignment - 1;
339
340    // The size of the block must be at least large enough to take into account alignment
341    size_t allocSize = (alignment <= kMinAlignment) ? size : (size + alignment);
342
343    Block* block;
344
345    // There are two scenarios
346    // a) Allocate a new normal block and make current
347    // b) Allocate a new 'odd-sized' block and make current
348    //
349    // That by always allocating a new block if odd-sized, we lose more efficiency in terms of
350    // storage (the previous block may not have been used much). BUT doing so makes it easy to
351    // rewind - as the blocks are always in order of allocation.
352    //
353    // An improvement might be to have some abstraction that sits on top that can do this tracking
354    // (or have the blocks themselves record if they alias over a previously used block - but we
355    // don't bother with this here. If the alignment is greater than regular alignment we need to
356    // handle specially
357    if (allocSize > m_blockPayloadSize ||
358        (alignment > m_blockAlignment && allocSize + alignment > m_blockPayloadSize))
359    {
360        // This is an odd-sized block so just allocate the whole thing.
361        block = _newBlock(allocSize, alignment);
362    }
363    else
364    {
365        // Must be allocatable within a normal block
366        assert(allocSize <= m_blockAllocSize);
367        block = _newNormalBlock();
368    }
369
370    // If not allocated we are done
371    if (!block)
372    {
373        return nullptr;
374    }
375
376    // Make the current block
377    _addCurrentBlock(block);
378
379    // Align the memory
380    uint8_t* memory = (uint8_t*)((size_t(m_current) + alignMask) & ~alignMask);
381
382    // It must be aligned
383    assert((size_t(memory) & alignMask) == 0);
384
385    // Do the aligned allocation (which must fit) by aligning the pointer
386    // It must fit if the previous code is correct...
387    assert(memory + size <= m_end);
388    // Move the current pointer
389    m_current = memory + size;
390    return memory;
391}
392
393size_t MemoryArena::_calcBlocksUsedMemory(const Block* block) const
394{
395    size_t total = 0;
396    while (block)
397    {
398        total += size_t(block->m_end - block->m_start);
399        block = block->m_next;
400    }
401    return total;
402}
403
404size_t MemoryArena::_calcBlocksAllocatedMemory(const Block* block) const
405{
406    size_t total = 0;
407    while (block)
408    {
409        total += size_t(block->m_end - block->m_alloc);
410        block = block->m_next;
411    }
412    return total;
413}
414
415void MemoryArena::_rewindToCursor(const void* cursorIn)
416{
417    // If it's nullptr, then there are no allocation so free all
418    if (cursorIn == nullptr)
419    {
420        deallocateAll();
421        return;
422    }
423
424    // Find the block that contains the allocation
425    Block* cursorBlock = _findNonCurrent(cursorIn);
426    assert(cursorBlock);
427    if (!cursorBlock)
428    {
429        // If not found it means this address is NOT part any of the active used heap!
430        // Probably an invalid cursor
431        return;
432    }
433
434    // Deallocate all of the blocks up to the cursor block
435    {
436        Block* block = m_usedBlocks;
437        while (block != cursorBlock)
438        {
439            Block* next = block->m_next;
440            _deallocateBlock(block);
441            block = next;
442        }
443    }
444
445    // The cursor block is now the current block
446    m_usedBlocks = cursorBlock;
447    _setCurrentBlock(cursorBlock);
448
449    const uint8_t* cursor = (const uint8_t*)cursorIn;
450    // Must be in the range of the currently set block
451    assert(cursor >= m_start && cursor <= m_end);
452
453    // Set the current position where the cursor is
454    m_current = const_cast<uint8_t*>(cursor);
455}
456
457size_t MemoryArena::calcTotalMemoryUsed() const
458{
459    return (m_usedBlocks ? _calcBlocksUsedMemory(m_usedBlocks->m_next) : 0) +
460           size_t(m_current - m_start);
461}
462
463size_t MemoryArena::calcTotalMemoryAllocated() const
464{
465    return _calcBlocksAllocatedMemory(m_usedBlocks) + _calcBlocksAllocatedMemory(m_availableBlocks);
466}
467
468
469} // namespace Slang