yum-mirror/slang

Making it easier to work with shaders

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

Julius IkkalaReplace SLANG_ALIGN_OF with C++11 alignof (#7523)551d0c365

master
17.8 KiB488 linesraw
1#ifndef SLANG_CORE_MEMORY_ARENA_H
2#define SLANG_CORE_MEMORY_ARENA_H
3
4#include "slang-free-list.h"
5#include "slang.h"
6
7#include <stdlib.h>
8#include <string.h>
9#include <type_traits>
10
11namespace Slang
12{
13
14/** MemoryArena provides provides very fast allocation of small blocks, by aggregating many small
15allocations over smaller amount of larger blocks. A typical small unaligned allocation is a pointer
16bump.
17
18Allocations are made contiguously from the current block. If there is no space in the current block,
19the next block (which is unused) if available is checked. If that works, an allocation is made from
20the next block. If not a new block is allocated that can hold at least the allocation with required
21alignment.
22
23All memory allocated can be deallocated very quickly and without a client having to track any
24memory. All memory allocated will be freed on destruction - or with reset.
25
26A memory arena can have requests larger than the block size. When that happens they will just be
27allocated from the heap. As such 'odd blocks' are seen as unusual and potentially wasteful so they
28are deallocated when deallocateAll is called, whereas regular size blocks will remain allocated for
29fast subsequent allocation.
30
31It is intentional that blocks information is stored separately from the allocations that store the
32user data. This is so that alignment permitting, block allocations sizes can be passed directly to
33underlying allocator. For large power of 2 backing allocations this might mean a page/pages directly
34allocated by the OS for example. Also means better cache coherency when traversing blocks -> as
35generally they will be contiguous in memory.
36
37Note that allocateUnaligned can be used for slightly faster aligned allocations. All blocks
38allocated internally are aligned to the blockAlignment passed to the constructor. If subsequent
39allocations (of any type) sizes are of that alignment or larger then no alignment fixing is required
40(because allocations are contiguous) and so 'allocateUnaligned' will return allocations of
41blockAlignment alignment.
42
43If many 'odd' allocations occur it probably means that the block size should be increased.
44*/
45class MemoryArena
46{
47public:
48    typedef MemoryArena ThisType;
49
50    /** The minimum alignment of the backing memory allocator.
51    NOTE! That this should not be greater than the alignment of the underlying allocator, and should
52    never be less than sizeof(void*).
53    */
54    static const size_t kMinAlignment = sizeof(void*);
55    /** Determines if an allocation is consistent with an allocation from this arena.
56
57    The test cannot say definitively if this was such an allocation, because the exact details
58    of each allocation are not kept.
59    @param alloc The start of the allocation
60    @param sizeInBytes The size of the allocation in bytes
61    @return true if allocation could have been from this Arena */
62    bool isValid(const void* alloc, size_t sizeInBytes) const;
63
64    /** Initialize the arena with specified block size and alignment
65    If the arena has been previously initialized will free and deallocate all memory */
66    void init(size_t blockSizeInBytes, size_t blockAlignment = kMinAlignment);
67
68    /** Allocate some memory of at least size bytes without having any specific alignment.
69
70     Can be used for slightly faster *aligned* allocations if caveats in class description are met.
71     Alignment is kMinAlignment or better.
72
73     @param size The size of the allocation requested (in bytes and must be > 0).
74     @return The allocation. Can be nullptr if backing allocator was not able to request required
75     memory */
76    void* allocate(size_t sizeInBytes);
77
78    /** Same as allocate, but zeros memory before returning
79    @param size The size of the allocation requested (in bytes and must be > 0).
80    @return The allocation. Can be nullptr if backing allocator was not able to request required
81    memory */
82    void* allocateAndZero(size_t sizeInBytes);
83
84    /** Allocate some aligned memory of at least size bytes
85     @param size Size of allocation wanted (must be > 0).
86     @param alignment Alignment of allocation - must be a power of 2.
87     @return The allocation (or nullptr if unable to allocate). Will be at least 'alignment'
88     alignment or better. */
89    void* allocateAligned(size_t sizeInBytes, size_t alignment);
90
91    /** Allocate some aligned memory of at least size bytes
92    @param sizeInBytes Size of allocation wanted (must be > 0).
93    @return The allocation (or nullptr if unable to allocate).  */
94    void* allocateUnaligned(size_t sizeInBytes);
95
96    /** Allocate some aligned memory of at least size bytes, without alignment, and only from
97    current block.
98    @param sizeInBytes Size of allocation wanted.
99    @return The allocation (or nullptr if unable to allocate in current block).  */
100    void* allocateCurrentUnaligned(size_t sizeInBytes);
101
102    /** Allocates a null terminated string.
103
104    NOTE, it is not possible to rewind to a zero length string allocation (because such a strings
105    memory is not held on the arena)
106
107    @param str A null-terminated string
108    @return A copy of the string held on the arena */
109    const char* allocateString(const char* str);
110
111    /** Allocates a null terminated string.
112     @param chars Pointer to first character
113     @param charCount The amount of characters NOT including terminating 0.
114     @return A copy of the string held on the arena. */
115    const char* allocateString(const char* chars, size_t numChars);
116
117    /// Allocate space for the specified type, with appropriate alignment. Note: Constructor for
118    /// type is *NOT* executed.
119    template<typename T>
120    T* allocate();
121
122    /// Allocate an array of a specified type. NOTE Constructor of T is *NOT* executed.
123    template<typename T>
124    T* allocateArray(size_t numElems);
125
126    /// Allocate an array of a specified type, and copy array passed into it.
127    template<typename T>
128    T* allocateAndCopyArray(const T* src, size_t numElems);
129
130    /// Allocate an array of a specified type, and zero it.
131    template<typename T>
132    T* allocateAndZeroArray(size_t numElems);
133
134    /** Deallocates all allocated memory. That backing memory will generally not be released so
135     subsequent allocation will be fast, and from the same memory. Note though that 'odd' blocks
136     will be deallocated. */
137    void deallocateAll();
138
139    /// Resets to the initial state when constructed (and all backing memory will be deallocated)
140    void reset();
141    /// Adjusts such that the next allocate will be at least to the block alignment.
142    void adjustToBlockAlignment();
143
144    /// Gets the block alignment that is passed at initialization otherwise 0 an invalid block
145    /// alignment.
146    size_t getBlockAlignment() const { return m_blockAlignment; }
147
148    /// Get the default block payload size
149    size_t getBlockPayloadSize() const { return m_blockPayloadSize; }
150
151    /// Estimate of total amount of memory used in bytes. The number can never be smaller than
152    /// actual used memory but may be larger
153    size_t calcTotalMemoryUsed() const;
154    /// Total memory allocated in bytes
155    size_t calcTotalMemoryAllocated() const;
156
157    /// Get the current allocation cursor (memory address where subsequent allocations will be
158    /// placed if space within the current block) The address of an allocated block can be used as a
159    /// cursor to rewind to, such that it and all subsequent allocations will be deallocated
160    void* getCursor() const { return m_current; }
161    /// Rewind (and effectively deallocate) all allocations *after* the cursor
162    void rewindToCursor(const void* cursor);
163
164    /// Add a block such that it will be freed when everything else is freed.
165    void addExternalBlock(void* data, size_t size);
166
167    // Swap this with rhs
168    void swapWith(ThisType& rhs);
169
170    /// Default Ctor
171    MemoryArena();
172    /// Construct with block size and alignment. Block alignment must be a power of 2.
173    MemoryArena(size_t blockPayloadSize, size_t blockAlignment = kMinAlignment);
174
175    /// Dtor
176    ~MemoryArena();
177
178protected:
179    struct Block
180    {
181        Block* m_next;    ///< Singly linked list of blocks
182        uint8_t* m_alloc; ///< Allocation start (ie what to free)
183        uint8_t* m_start; ///< Start of payload (takes into account alignment)
184        uint8_t* m_end;   ///< End of payload (m_start to m_end defines payload)
185    };
186
187    void _initialize(size_t blockPayloadSize, size_t blockAlignment);
188
189    /// Delete the linked list of blocks specified by start
190    void _deallocateBlocks(Block* start);
191    /// Delete the linked list of blocks payloads specified by start
192    void _deallocateBlocksPayload(Block* start);
193
194    void _resetCurrentBlock();
195    void _addCurrentBlock(Block* block);
196    void _setCurrentBlock(Block* block);
197
198    void _deallocateBlock(Block* block);
199
200    /// Create a new block with regular block alignment
201    Block* _newNormalBlock();
202    /// Allocates a new block with allocSize and alignment
203    Block* _newBlock(size_t allocSizeInBytes, size_t alignment);
204
205    void* _allocateAlignedFromNewBlock(size_t sizeInBytes, size_t alignment);
206    void* _allocateAlignedFromNewBlockAndZero(size_t sizeInBytes, size_t alignment);
207
208    /// Find block that contains data/size that is _NOT_ current (ie not first block in
209    /// m_usedBlocks)
210    Block* _findNonCurrent(const void* data, size_t sizeInBytes) const;
211    Block* _findNonCurrent(const void* data) const;
212
213    /// Find a block that contains data starting from block. Returns null ptr if not found
214    Block* _findInBlocks(Block* block, const void* data) const;
215    Block* _findInBlocks(Block* block, const void* data, size_t sizeInBytes) const;
216
217    size_t _calcBlocksUsedMemory(const Block* block) const;
218    size_t _calcBlocksAllocatedMemory(const Block* block) const;
219    /// Returns true if block can be classed as normal (right size and same or better alignment)
220    bool _isNormalBlock(Block* block);
221
222    /// Handles the rewinding of the cursor for the more complicated cases
223    void _rewindToCursor(const void* cursor);
224
225    uint8_t* m_start;   ///< The start of the current block (pointed to by m_usedBlocks)
226    uint8_t* m_end;     ///< The end of the current block
227    uint8_t* m_current; ///< The current position in current block
228
229    size_t m_blockPayloadSize; ///< The size of the payload of a block
230    size_t m_blockAllocSize;   ///< The size of a block allocation (must be the same size or bigger
231                               ///< than m_blockPayloadSize)
232    size_t m_blockAlignment;   ///< The alignment applied to used blocks
233
234    Block* m_availableBlocks; ///< Standard sized blocks that are available
235    Block*
236        m_usedBlocks; ///< Singly linked list of used blocks. The first one is the 'current block'
237                      ///< and m_next is the previously allocated blocks. nullptr terminated.
238
239    FreeList m_blockFreeList; ///< Holds all of the blocks for fast allocation/free
240
241private:
242    // Disable
243    MemoryArena(const ThisType& rhs) = delete;
244    void operator=(const ThisType& rhs) = delete;
245};
246
247// --------------------------------------------------------------------------
248SLANG_FORCE_INLINE bool MemoryArena::isValid(const void* data, size_t size) const
249{
250    assert(size);
251    uint8_t* ptr = (uint8_t*)data;
252    return (ptr >= m_start && ptr + size <= m_current) || _findNonCurrent(data, size) != nullptr;
253}
254
255// --------------------------------------------------------------------------
256SLANG_FORCE_INLINE void* MemoryArena::allocateUnaligned(size_t sizeInBytes)
257{
258    assert(sizeInBytes > 0);
259    // Align with the minimum alignment
260    uint8_t* mem = m_current;
261    uint8_t* end = mem + sizeInBytes;
262    if (end <= m_end)
263    {
264        m_current = end;
265        return mem;
266    }
267    else
268    {
269        return _allocateAlignedFromNewBlock(sizeInBytes, kMinAlignment);
270    }
271}
272
273// --------------------------------------------------------------------------
274SLANG_FORCE_INLINE void* MemoryArena::allocateCurrentUnaligned(size_t sizeInBytes)
275{
276    // Align with the minimum alignment
277    uint8_t* mem = m_current;
278    uint8_t* end = mem + sizeInBytes;
279    if (end <= m_end)
280    {
281        m_current = end;
282        return mem;
283    }
284    else
285    {
286        return nullptr;
287    }
288}
289
290// --------------------------------------------------------------------------
291SLANG_FORCE_INLINE void* MemoryArena::allocate(size_t sizeInBytes)
292{
293    assert(sizeInBytes > 0);
294    // Align with the minimum alignment
295    const size_t alignMask = kMinAlignment - 1;
296    uint8_t* mem = (uint8_t*)((size_t(m_current) + alignMask) & ~alignMask);
297
298    if (mem + sizeInBytes <= m_end)
299    {
300        m_current = mem + sizeInBytes;
301        return mem;
302    }
303    else
304    {
305        return _allocateAlignedFromNewBlock(sizeInBytes, kMinAlignment);
306    }
307}
308
309// --------------------------------------------------------------------------
310SLANG_FORCE_INLINE void* MemoryArena::allocateAndZero(size_t sizeInBytes)
311{
312    assert(sizeInBytes > 0);
313    // Align with the minimum alignment
314    const size_t alignMask = kMinAlignment - 1;
315    // Implement without calling ::allocate, because in most common case we don't need to test for
316    // null.
317    uint8_t* mem = (uint8_t*)((size_t(m_current) + alignMask) & ~alignMask);
318    uint8_t* end = mem + sizeInBytes;
319    if (end <= m_end)
320    {
321        ::memset(mem, 0, sizeInBytes);
322        m_current = end;
323        return mem;
324    }
325    else
326    {
327        return _allocateAlignedFromNewBlockAndZero(sizeInBytes, kMinAlignment);
328    }
329}
330
331// --------------------------------------------------------------------------
332SLANG_FORCE_INLINE void* MemoryArena::allocateAligned(size_t sizeInBytes, size_t alignment)
333{
334    assert(sizeInBytes > 0);
335    // Alignment must be a power of 2
336    assert(((alignment - 1) & alignment) == 0);
337
338    // Align the pointer
339    const size_t alignMask = alignment - 1;
340    uint8_t* memory = (uint8_t*)((size_t(m_current) + alignMask) & ~alignMask);
341
342    if (memory + sizeInBytes <= m_end)
343    {
344        m_current = memory + sizeInBytes;
345        return memory;
346    }
347    else
348    {
349        return _allocateAlignedFromNewBlock(sizeInBytes, alignment);
350    }
351}
352
353// --------------------------------------------------------------------------
354SLANG_FORCE_INLINE const char* MemoryArena::allocateString(const char* str)
355{
356    size_t size = ::strlen(str);
357    if (size == 0)
358    {
359        return "";
360    }
361    char* dst = (char*)allocateUnaligned(size + 1);
362    ::memcpy(dst, str, size + 1);
363    return dst;
364}
365
366// --------------------------------------------------------------------------
367inline const char* MemoryArena::allocateString(const char* chars, size_t numChars)
368{
369    if (numChars == 0)
370    {
371        return "";
372    }
373    char* dst = (char*)allocateUnaligned(numChars + 1);
374    ::memcpy(dst, chars, numChars);
375
376    // Add null-terminating zero
377    dst[numChars] = 0;
378    return dst;
379}
380
381// --------------------------------------------------------------------------
382template<typename T>
383SLANG_FORCE_INLINE T* MemoryArena::allocate()
384{
385    void* mem = (alignof(T) <= kMinAlignment) ? allocate(sizeof(T))
386                                              : allocateAligned(sizeof(T), alignof(T));
387    return reinterpret_cast<T*>(mem);
388}
389
390// --------------------------------------------------------------------------
391template<typename T>
392SLANG_FORCE_INLINE T* MemoryArena::allocateArray(size_t numElems)
393{
394    return (numElems > 0) ? reinterpret_cast<T*>(allocateAligned(sizeof(T) * numElems, alignof(T)))
395                          : nullptr;
396}
397
398// --------------------------------------------------------------------------
399template<typename T>
400SLANG_FORCE_INLINE T* MemoryArena::allocateAndCopyArray(const T* arr, size_t numElems)
401{
402    static_assert(std::is_trivially_copyable_v<T>);
403    if (numElems > 0)
404    {
405        const size_t totalSize = sizeof(T) * numElems;
406        void* ptr = allocateAligned(totalSize, alignof(T));
407        ::memcpy(ptr, arr, totalSize);
408        return reinterpret_cast<T*>(ptr);
409    }
410    return nullptr;
411}
412
413// ---------------------------------------------------------------------------
414template<typename T>
415SLANG_FORCE_INLINE T* MemoryArena::allocateAndZeroArray(size_t numElems)
416{
417    if (numElems > 0)
418    {
419        const size_t totalSize = sizeof(T) * numElems;
420        void* ptr = allocateAligned(totalSize, alignof(T));
421        ::memset(ptr, 0, totalSize);
422        return reinterpret_cast<T*>(ptr);
423    }
424    return nullptr;
425}
426
427// --------------------------------------------------------------------------
428inline void MemoryArena::adjustToBlockAlignment()
429{
430    const size_t alignMask = m_blockAlignment - 1;
431    uint8_t* ptr = (uint8_t*)((size_t(m_current) + alignMask) & ~alignMask);
432
433    // Alignment might push beyond end of block... if so allocate a new block
434    // This test could be avoided if we aligned m_end, but depending on block alignment that might
435    // waste some space
436    if (ptr > m_end)
437    {
438        // We'll need a new block to make this alignment. Allocate a byte, and then rewind it.
439        _allocateAlignedFromNewBlock(1, 1);
440        m_current = m_usedBlocks->m_start;
441    }
442    else
443    {
444        // Set the position
445        m_current = ptr;
446    }
447    assert(size_t(m_current) & alignMask);
448}
449// --------------------------------------------------------------------------
450SLANG_FORCE_INLINE void MemoryArena::rewindToCursor(const void* cursor)
451{
452    // Is it in the current block?
453    {
454        const uint8_t* cur = (const uint8_t*)cursor;
455        if (cur >= m_start && cur <= m_current)
456        {
457            m_current = const_cast<uint8_t*>(cur);
458            return;
459        }
460    }
461    _rewindToCursor(cursor);
462}
463
464} // namespace Slang
465
466SLANG_FORCE_INLINE void* operator new(size_t size, Slang::MemoryArena& arena)
467{
468    return arena.allocate(size);
469}
470
471SLANG_FORCE_INLINE void operator delete(void* memory, Slang::MemoryArena& arena)
472{
473    SLANG_UNUSED(memory);
474    SLANG_UNUSED(arena);
475}
476
477SLANG_FORCE_INLINE void* operator new[](size_t size, Slang::MemoryArena& arena)
478{
479    return arena.allocate(size);
480}
481
482SLANG_FORCE_INLINE void operator delete[](void* memory, Slang::MemoryArena& arena)
483{
484    SLANG_UNUSED(memory);
485    SLANG_UNUSED(arena);
486}
487
488#endif // SLANG_MEMORY_ARENA_H