Dqn/dqn_memory.h
2023-08-25 20:35:04 +10:00

287 lines
14 KiB
C

// NOTE: [$ALLO] Dqn_Allocator =====================================================================
typedef void *Dqn_Allocator_AllocProc(size_t size, uint8_t align, Dqn_ZeroMem zero_mem, void *user_context);
typedef void Dqn_Allocator_DeallocProc(void *ptr, size_t size, void *user_context);
struct Dqn_Allocator
{
void *user_context; // User assigned pointer that is passed into the allocator functions
Dqn_Allocator_AllocProc *alloc; // Memory allocating routine
Dqn_Allocator_DeallocProc *dealloc; // Memory deallocating routine
};
// NOTE: Macros ====================================================================================
#define Dqn_Allocator_NewArray(allocator, Type, count, zero_mem) (Type *)Dqn_Allocator_Alloc(allocator, sizeof(Type) * count, alignof(Type), zero_mem)
#define Dqn_Allocator_New(allocator, Type, zero_mem) (Type *)Dqn_Allocator_Alloc(allocator, sizeof(Type), alignof(Type), zero_mem)
// NOTE: API =======================================================================================
void *Dqn_Allocator_Alloc (Dqn_Allocator allocator, size_t size, uint8_t align, Dqn_ZeroMem zero_mem);
void Dqn_Allocator_Dealloc(Dqn_Allocator allocator, void *ptr, size_t size);
// NOTE: [$VMEM] Dqn_VMem ==========================================================================
enum Dqn_VMemCommit
{
Dqn_VMemCommit_No,
Dqn_VMemCommit_Yes,
};
enum Dqn_VMemPage
{
// Exception on read/write with a page. This flag overrides the read/write
// access.
Dqn_VMemPage_NoAccess = 1 << 0,
// Only read permitted on the page.
Dqn_VMemPage_Read = 1 << 1,
// Only write permitted on the page. On Windows this is not supported and
// will be promoted to read+write permissions.
Dqn_VMemPage_Write = 1 << 2,
Dqn_VMemPage_ReadWrite = Dqn_VMemPage_Read | Dqn_VMemPage_Write,
// Modifier used in conjunction with previous flags. Raises exception on
// first access to the page, then, the underlying protection flags are
// active. This is supported on Windows, on other OS's using this flag will
// set the OS equivalent of Dqn_VMemPage_NoAccess.
// This flag must only be used in Dqn_VMem_Protect
Dqn_VMemPage_Guard = 1 << 3,
// If leak tracing is enabled, this flag will allow the allocation recorded
// from the reserve call to be leaked, e.g. not printed when leaks are
// dumped to the console.
Dqn_VMemPage_AllocRecordLeakPermitted = 1 << 2,
};
DQN_API void *Dqn_VMem_Reserve (Dqn_usize size, Dqn_VMemCommit commit, uint32_t page_flags);
DQN_API bool Dqn_VMem_Commit (void *ptr, Dqn_usize size, uint32_t page_flags);
DQN_API void Dqn_VMem_Decommit(void *ptr, Dqn_usize size);
DQN_API void Dqn_VMem_Release (void *ptr, Dqn_usize size);
DQN_API int Dqn_VMem_Protect (void *ptr, Dqn_usize size, uint32_t page_flags);
// NOTE: [$MEMB] Dqn_MemBlock ======================================================================
enum Dqn_MemBlockFlag
{
Dqn_MemBlockFlag_PageGuard = 1 << 0,
Dqn_MemBlockFlag_ArenaPrivate = 1 << 1,
// If leak tracing is enabled, this flag will allow the allocation recorded
// from the reserve call to be leaked, e.g. not printed when leaks are
// dumped to the console.
Dqn_MemBlockFlag_AllocRecordLeakPermitted = 1 << 2,
Dqn_MemBlockFlag_All = Dqn_MemBlockFlag_PageGuard |
Dqn_MemBlockFlag_ArenaPrivate |
Dqn_MemBlockFlag_AllocRecordLeakPermitted,
};
struct Dqn_MemBlock
{
void *data;
Dqn_usize used;
Dqn_usize size;
Dqn_usize commit;
Dqn_MemBlock *next;
Dqn_MemBlock *prev;
uint8_t flags;
};
DQN_API Dqn_usize Dqn_MemBlock_MetadataSize(uint8_t flags);
DQN_API Dqn_MemBlock *Dqn_MemBlock_Init (Dqn_usize reserve, Dqn_usize commit, uint8_t flags);
DQN_API void *Dqn_MemBlock_Alloc (Dqn_MemBlock *block, Dqn_usize size, uint8_t alignment, Dqn_ZeroMem zero_mem);
DQN_API void Dqn_MemBlock_Free (Dqn_MemBlock *block);
#define Dqn_MemBlock_New(block, Type, zero_mem) (Type *)Dqn_MemBlock_Alloc(block, sizeof(Type), alignof(Type), zero_mem)
#define Dqn_MemBlock_NewArray(block, Type, count, zero_mem) (Type *)Dqn_MemBlock_Alloc(block, sizeof(Type) * count, alignof(Type), zero_mem)
// NOTE: [$AREN] Dqn_Arena =========================================================================
// A bump-allocator that can grow dynamically by chaining blocks of memory
// together. The arena's memory is backed by virtual memory allowing the
// allocator to reserve and commit physical pages as memory is given from
// the block of memory.
//
// Arena's allow grouping of multiple allocations into one lifetime that is
// bound to the arena. Allocation involves a simple 'bump' of the pointer in the
// memory block. Freeing involves resetting the pointer to the start of the
// block and/or releasing the single pointer to the entire block of memory.
//
// This allocator reserves memory blocks at a 64k granularity as per the minimum
// granularity reserve size of VirtualAlloc on Windows. Memory is commit at
// a 4k granularity for similar reasons. On 64 bit platforms you have access
// to 48 bits of address space for applications, this is 256TB of address space
// you can reserve. The typical usage for this style of arena is to reserve
// as much space as you possibly need, ever, for the lifetime of the arena (e.g.
// 64GB) since the arena only commits as much as needed.
//
// NOTE: API
// @proc Dqn_Arena_Grow
// @desc Grow the arena's capacity by allocating a block of memory with the
// requested size. The requested size is rounded up to the nearest 64k
// boundary as that is the minimum reserve granularity (atleast on Windows)
// for virtual memory.
// @param size[in] The size in bytes to expand the capacity of the arena
// @param commit[in] The amount of bytes to request to be physically backed by
// pages from the OS.
// @param flags[in] Bit flags from 'Dqn_ArenaBlockFlags', set to 0 if none
// @return The block of memory that
// @proc Dqn_Arena_Alloc, Dqn_Arena_New, Dqn_Arena_NewArray,
// Dqn_Arena_NewArrayWithBlock,
// @desc Alloc byte/objects
// `Alloc` allocates bytes
// `New` allocates an object
// `NewArray` allocates an array of objects
// `NewArrayWithBlock` allocates an array of objects from the given memory 'block'
// @return A pointer to the allocated bytes/object. Null pointer on failure
// @proc Dqn_Arena_Copy, Dqn_Arena_CopyZ
// @desc Alloc a copy of an object's bytes. The 'Z' variant adds
// a null-terminating byte at the end of the stream.
// @return A pointer to the allocated object. Null pointer on failure.
// @proc Dqn_Arena_Reset
// @desc Set the arena's current block to the first block in the linked list
// of blocks and mark all blocks free.
// @param[in] zero_mem When yes, the memory is cleared using DQN_MEMSET with the
// value of DQN_MEMSET_BYTE
// @proc Dqn_Arena_Free
// @desc Free the arena returning all memory back to the OS
// @param[in] zero_mem: When true, the memory is cleared using DQN_MEMSET with
// the value of DQN_MEMSET_BYTE
// @proc Dqn_Arena_BeginTempMemory
// @desc Begin an allocation scope where all allocations between begin and end
// calls will be reverted. Useful for short-lived or highly defined lifetime
// allocations. An allocation scope is invalidated if the arena is freed
// between the begin and end call.
// @proc Dqn_Arena_EndTempMemory
// @desc End an allocation scope previously begun by calling begin scope.
// @proc Dqn_Arena_StatString
// @desc Dump the stats of the given arena to a string
// @param[in] arena The arena to dump stats for
// @return A stack allocated string containing the stats of the arena
// @proc Dqn_Arena_LogStats
// @desc Dump the stats of the given arena to the memory log-stream.
// @param[in] arena The arena to dump stats for
struct Dqn_ArenaStat
{
Dqn_usize capacity; // Total allocating capacity of the arena in bytes
Dqn_usize used; // Total amount of bytes used in the arena
Dqn_usize wasted; // Orphaned space in blocks due to allocations requiring more space than available in the active block
uint32_t blocks; // Number of memory blocks in the arena
Dqn_usize syscalls; // Number of memory allocation syscalls into the OS
Dqn_usize capacity_hwm; // High-water mark for 'capacity'
Dqn_usize used_hwm; // High-water mark for 'used'
Dqn_usize wasted_hwm; // High-water mark for 'wasted'
uint32_t blocks_hwm; // High-water mark for 'blocks'
};
struct Dqn_ArenaBlock
{
struct Dqn_Arena *arena; // Arena that owns this block
void *memory; // Backing memory of the block
Dqn_usize size; // Size of the block
Dqn_usize used; // Number of bytes used up in the block. Always less than the commit amount.
Dqn_usize hwm_used;// High-water mark for 'used' bytes in this block
Dqn_usize commit; // Number of bytes in the block physically backed by pages
Dqn_ArenaBlock *prev; // Previous linked block
Dqn_ArenaBlock *next; // Next linked block
uint8_t flags; // Bit field for 'Dqn_ArenaBlockFlags'
};
struct Dqn_ArenaStatString
{
char data[256];
uint16_t size;
};
struct Dqn_Arena
{
bool allocs_are_allowed_to_leak;
Dqn_String8 label; // Optional label to describe the arena
Dqn_MemBlock *head; // Active block the arena is allocating from
Dqn_MemBlock *curr; // Active block the arena is allocating from
Dqn_MemBlock *tail; // Last block in the linked list of blocks
uint64_t blocks;
};
struct Dqn_ArenaTempMemory
{
Dqn_Arena *arena; // Arena the scope is for
Dqn_MemBlock *head; // Head block of the arena at the beginning of the scope
Dqn_MemBlock *curr; // Current block of the arena at the beginning of the scope
Dqn_MemBlock *tail; // Tail block of the arena at the beginning of the scope
Dqn_usize blocks;
Dqn_usize curr_used; // Current used amount of the current block
};
// Automatically begin and end a temporary memory scope on object construction
// and destruction respectively.
#define Dqn_Arena_TempMemoryScope(arena) Dqn_ArenaTempMemoryScope DQN_UNIQUE_NAME(temp_memory_) = Dqn_ArenaTempMemoryScope(arena)
struct Dqn_ArenaTempMemoryScope
{
Dqn_ArenaTempMemoryScope(Dqn_Arena *arena);
~Dqn_ArenaTempMemoryScope();
Dqn_ArenaTempMemory temp_memory;
bool cancel = false;
#if defined(DQN_LEAK_TRACING)
Dqn_CallSite leak_site_;
#endif
};
enum Dqn_ArenaCommit
{
// Commit the pages to ensure the block has the requested commit amount.
// No-op if the block has sufficient commit space already.
Dqn_ArenaCommit_EnsureSpace,
Dqn_ArenaCommit_GetNewPages, // Grow the block by the requested commit amount
};
// NOTE: Allocation ================================================================================
#define Dqn_Arena_New(arena, Type, zero_mem) (Type *)Dqn_Arena_Alloc(arena, sizeof(Type), alignof(Type), zero_mem)
#define Dqn_Arena_NewCopy(arena, Type, src) (Type *)Dqn_Arena_Copy(arena, src, sizeof(*src), alignof(Type))
#define Dqn_Arena_NewCopyZ(arena, Type, src) (Type *)Dqn_Arena_Copy(arena, src, sizeof(*src), alignof(Type))
#define Dqn_Arena_NewArray(arena, Type, count, zero_mem) (Type *)Dqn_Arena_Alloc(arena, sizeof(Type) * count, alignof(Type), zero_mem)
#define Dqn_Arena_NewArrayCopy(arena, Type, src, count) (Type *)Dqn_Arena_Copy(arena, src, sizeof(*src) * count, alignof(Type))
#define Dqn_Arena_NewArrayCopyZ(arena, Type, src, count) (Type *)Dqn_Arena_CopyZ(arena, src, sizeof(*src) * count, alignof(Type))
DQN_API Dqn_Allocator Dqn_Arena_Allocator (Dqn_Arena *arena);
DQN_API Dqn_MemBlock * Dqn_Arena_Grow (Dqn_Arena *arena, Dqn_usize size, Dqn_usize commit, uint8_t flags);
DQN_API void * Dqn_Arena_Alloc (Dqn_Arena *arena, Dqn_usize size, uint8_t align, Dqn_ZeroMem zero_mem);
DQN_API void * Dqn_Arena_Copy (Dqn_Arena *arena, void *src, Dqn_usize size, uint8_t alignment);
DQN_API void * Dqn_Arena_CopyZ (Dqn_Arena *arena, void *src, Dqn_usize size, uint8_t alignment);
DQN_API void Dqn_Arena_Free (Dqn_Arena *arena, Dqn_ZeroMem zero_mem);
// NOTE: Temp Memory ===============================================================================
DQN_API Dqn_ArenaTempMemory Dqn_Arena_BeginTempMemory(Dqn_Arena *arena);
DQN_API void Dqn_Arena_EndTempMemory (Dqn_ArenaTempMemory temp_memory, bool cancel);
// NOTE: Arena Stats ===============================================================================
DQN_API Dqn_ArenaStatString Dqn_Arena_StatString (Dqn_ArenaStat const *stat);
DQN_API void Dqn_Arena_LogStats (Dqn_Arena const *arena);
// NOTE: [$ACAT] Dqn_ArenaCatalog ==================================================================
struct Dqn_ArenaCatalogItem
{
Dqn_Arena *arena;
Dqn_ArenaCatalogItem *next;
Dqn_ArenaCatalogItem *prev;
};
struct Dqn_ArenaCatalog
{
Dqn_TicketMutex ticket_mutex; // Mutex for adding to the linked list of arenas
Dqn_Arena *arena;
Dqn_ArenaCatalogItem sentinel;
uint16_t arena_count;
};
DQN_API void Dqn_ArenaCatalog_Init (Dqn_ArenaCatalog *catalog, Dqn_Arena *arena);
DQN_API void Dqn_ArenaCatalog_Add (Dqn_ArenaCatalog *catalog, Dqn_Arena *arena);
DQN_API Dqn_Arena *Dqn_ArenaCatalog_Alloc (Dqn_ArenaCatalog *catalog, Dqn_usize byte_size, Dqn_usize commit);
DQN_API Dqn_Arena *Dqn_ArenaCatalog_AllocFV(Dqn_ArenaCatalog *catalog, Dqn_usize byte_size, Dqn_usize commit, char const *fmt, va_list args);
DQN_API Dqn_Arena *Dqn_ArenaCatalog_AllocF (Dqn_ArenaCatalog *catalog, Dqn_usize byte_size, Dqn_usize commit, char const *fmt, ...);