654 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			654 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
////////////////////////////////////////////////////////////////////////////////////////////////////
 | 
						|
//
 | 
						|
//    $$$$$$\  $$\       $$\       $$$$$$\   $$$$$$\   $$$$$$\ $$$$$$$$\  $$$$$$\  $$$$$$$\
 | 
						|
//   $$  __$$\ $$ |      $$ |     $$  __$$\ $$  __$$\ $$  __$$\\__$$  __|$$  __$$\ $$  __$$\
 | 
						|
//   $$ /  $$ |$$ |      $$ |     $$ /  $$ |$$ /  \__|$$ /  $$ |  $$ |   $$ /  $$ |$$ |  $$ |
 | 
						|
//   $$$$$$$$ |$$ |      $$ |     $$ |  $$ |$$ |      $$$$$$$$ |  $$ |   $$ |  $$ |$$$$$$$  |
 | 
						|
//   $$  __$$ |$$ |      $$ |     $$ |  $$ |$$ |      $$  __$$ |  $$ |   $$ |  $$ |$$  __$$<
 | 
						|
//   $$ |  $$ |$$ |      $$ |     $$ |  $$ |$$ |  $$\ $$ |  $$ |  $$ |   $$ |  $$ |$$ |  $$ |
 | 
						|
//   $$ |  $$ |$$$$$$$$\ $$$$$$$$\ $$$$$$  |\$$$$$$  |$$ |  $$ |  $$ |    $$$$$$  |$$ |  $$ |
 | 
						|
//   \__|  \__|\________|\________|\______/  \______/ \__|  \__|  \__|    \______/ \__|  \__|
 | 
						|
//
 | 
						|
//   dqn_allocator.cpp
 | 
						|
//
 | 
						|
////////////////////////////////////////////////////////////////////////////////////////////////////
 | 
						|
*/
 | 
						|
 | 
						|
// NOTE: [$AREN] DN_Arena /////////////////////////////////////////////////////////////////////////
 | 
						|
DN_API DN_ArenaBlock *DN_Arena_BlockInit(DN_U64 reserve, DN_U64 commit, bool track_alloc, bool alloc_can_leak)
 | 
						|
{
 | 
						|
    DN_USize const page_size    = g_dn_core->os_page_size;
 | 
						|
    DN_U64         real_reserve = reserve ? reserve : DN_ARENA_RESERVE_SIZE;
 | 
						|
    DN_U64         real_commit  = commit  ? commit  : DN_ARENA_COMMIT_SIZE;
 | 
						|
    real_reserve                = DN_AlignUpPowerOfTwo(real_reserve, page_size);
 | 
						|
    real_commit                 = DN_MIN(DN_AlignUpPowerOfTwo(real_commit, page_size), real_reserve);
 | 
						|
 | 
						|
    DN_ASSERTF(DN_ARENA_HEADER_SIZE < real_commit && real_commit <= real_reserve, "%I64u < %I64u <= %I64u", DN_ARENA_HEADER_SIZE, real_commit, real_reserve);
 | 
						|
    DN_ASSERTF(page_size, "Call DN_Library_Init() to initialise the known page size");
 | 
						|
 | 
						|
    DN_OSMemCommit  mem_commit = real_reserve == real_commit ? DN_OSMemCommit_Yes : DN_OSMemCommit_No;
 | 
						|
    DN_ArenaBlock *result      = DN_CAST(DN_ArenaBlock *)DN_OS_MemReserve(real_reserve, mem_commit, DN_OSMemPage_ReadWrite);
 | 
						|
    if (!result)
 | 
						|
        return result;
 | 
						|
 | 
						|
    if (mem_commit == DN_OSMemCommit_No && !DN_OS_MemCommit(result, real_commit, DN_OSMemPage_ReadWrite)) {
 | 
						|
        DN_OS_MemRelease(result, real_reserve);
 | 
						|
        return result;
 | 
						|
    }
 | 
						|
 | 
						|
    result->used    = DN_ARENA_HEADER_SIZE;
 | 
						|
    result->commit  = real_commit;
 | 
						|
    result->reserve = real_reserve;
 | 
						|
 | 
						|
    if (track_alloc)
 | 
						|
        DN_Debug_TrackAlloc(result, result->reserve, alloc_can_leak);
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
DN_API DN_ArenaBlock *DN_Arena_BlockInitFlags(DN_U64 reserve, DN_U64 commit, DN_ArenaFlags flags)
 | 
						|
{
 | 
						|
    bool            track_alloc    = (flags & DN_ArenaFlags_NoAllocTrack) == 0;
 | 
						|
    bool            alloc_can_leak = flags & DN_ArenaFlags_AllocCanLeak;
 | 
						|
    DN_ArenaBlock *result          = DN_Arena_BlockInit(reserve, commit, track_alloc, alloc_can_leak);
 | 
						|
    if (result && ((flags & DN_ArenaFlags_NoPoison) == 0))
 | 
						|
        DN_ASAN_PoisonMemoryRegion(DN_CAST(char *)result + DN_ARENA_HEADER_SIZE, result->commit - DN_ARENA_HEADER_SIZE);
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
static void DN_Arena_UpdateStatsOnNewBlock_(DN_Arena *arena, DN_ArenaBlock const *block)
 | 
						|
{
 | 
						|
    DN_ASSERT(arena);
 | 
						|
    if (block) {
 | 
						|
        arena->stats.info.used     += block->used;
 | 
						|
        arena->stats.info.commit   += block->commit;
 | 
						|
        arena->stats.info.reserve  += block->reserve;
 | 
						|
        arena->stats.info.blocks   += 1;
 | 
						|
 | 
						|
        arena->stats.hwm.used       = DN_MAX(arena->stats.hwm.used,    arena->stats.info.used);
 | 
						|
        arena->stats.hwm.commit     = DN_MAX(arena->stats.hwm.commit,  arena->stats.info.commit);
 | 
						|
        arena->stats.hwm.reserve    = DN_MAX(arena->stats.hwm.reserve, arena->stats.info.reserve);
 | 
						|
        arena->stats.hwm.blocks     = DN_MAX(arena->stats.hwm.blocks,  arena->stats.info.blocks);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
DN_API DN_Arena DN_Arena_InitBuffer(void *buffer, DN_USize size, DN_ArenaFlags flags)
 | 
						|
{
 | 
						|
    DN_ASSERT(buffer);
 | 
						|
    DN_ASSERTF(DN_ARENA_HEADER_SIZE < size, "Buffer (%zu bytes) too small, need atleast %zu bytes to store arena metadata", size, DN_ARENA_HEADER_SIZE);
 | 
						|
    DN_ASSERTF(DN_IsPowerOfTwo(size), "Buffer (%zu bytes) must be a power-of-two", size);
 | 
						|
 | 
						|
    // NOTE: Init block
 | 
						|
    DN_ArenaBlock *block = DN_CAST(DN_ArenaBlock *) buffer;
 | 
						|
    block->commit         = size;
 | 
						|
    block->reserve        = size;
 | 
						|
    block->used           = DN_ARENA_HEADER_SIZE;
 | 
						|
    if (block && ((flags & DN_ArenaFlags_NoPoison) == 0)) {
 | 
						|
        DN_ASAN_PoisonMemoryRegion(DN_CAST(char *)block + DN_ARENA_HEADER_SIZE, block->commit - DN_ARENA_HEADER_SIZE);
 | 
						|
    }
 | 
						|
 | 
						|
    DN_Arena result = {};
 | 
						|
    result.flags     = flags | DN_ArenaFlags_NoGrow | DN_ArenaFlags_NoAllocTrack | DN_ArenaFlags_AllocCanLeak | DN_ArenaFlags_UserBuffer;
 | 
						|
    result.curr      = block;
 | 
						|
    DN_Arena_UpdateStatsOnNewBlock_(&result, result.curr);
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
DN_API DN_Arena DN_Arena_InitSize(DN_U64 reserve, DN_U64 commit, DN_ArenaFlags flags)
 | 
						|
{
 | 
						|
    DN_Arena result = {};
 | 
						|
    result.flags     = flags;
 | 
						|
    result.curr      = DN_Arena_BlockInitFlags(reserve, commit, flags);
 | 
						|
    DN_Arena_UpdateStatsOnNewBlock_(&result, result.curr);
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
static void DN_Arena_BlockDeinit_(DN_Arena const *arena, DN_ArenaBlock *block)
 | 
						|
{
 | 
						|
    DN_USize release_size = block->reserve;
 | 
						|
    if (DN_Bit_IsNotSet(arena->flags, DN_ArenaFlags_NoAllocTrack))
 | 
						|
        DN_Debug_TrackDealloc(block);
 | 
						|
    DN_ASAN_UnpoisonMemoryRegion(block, block->commit);
 | 
						|
    DN_OS_MemRelease(block, release_size);
 | 
						|
}
 | 
						|
 | 
						|
DN_API void DN_Arena_Deinit(DN_Arena *arena)
 | 
						|
{
 | 
						|
    for (DN_ArenaBlock *block = arena ? arena->curr : nullptr; block; ) {
 | 
						|
        DN_ArenaBlock *block_to_free = block;
 | 
						|
        block                         = block->prev;
 | 
						|
        DN_Arena_BlockDeinit_(arena, block_to_free);
 | 
						|
    }
 | 
						|
    if (arena)
 | 
						|
        *arena = {};
 | 
						|
}
 | 
						|
 | 
						|
DN_API bool DN_Arena_CommitTo(DN_Arena *arena, DN_U64 pos)
 | 
						|
{
 | 
						|
    if (!arena || !arena->curr)
 | 
						|
        return false;
 | 
						|
 | 
						|
    DN_ArenaBlock *curr = arena->curr;
 | 
						|
    if (pos <= curr->commit)
 | 
						|
        return true;
 | 
						|
 | 
						|
    DN_U64 real_pos = pos;
 | 
						|
    if (!DN_CHECK(pos <= curr->reserve))
 | 
						|
        real_pos = curr->reserve;
 | 
						|
 | 
						|
    DN_USize end_commit  = DN_AlignUpPowerOfTwo(real_pos, g_dn_core->os_page_size);
 | 
						|
    DN_USize commit_size = end_commit - curr->commit;
 | 
						|
    char    *commit_ptr  = DN_CAST(char *) curr + curr->commit;
 | 
						|
    if (!DN_OS_MemCommit(commit_ptr, commit_size, DN_OSMemPage_ReadWrite))
 | 
						|
        return false;
 | 
						|
 | 
						|
    bool poison = DN_ASAN_POISON && ((arena->flags & DN_ArenaFlags_NoPoison) == 0);
 | 
						|
    if (poison)
 | 
						|
        DN_ASAN_PoisonMemoryRegion(commit_ptr, commit_size);
 | 
						|
 | 
						|
    curr->commit = end_commit;
 | 
						|
    return true;
 | 
						|
}
 | 
						|
 | 
						|
DN_API bool DN_Arena_Commit(DN_Arena *arena, DN_U64 size)
 | 
						|
{
 | 
						|
    if (!arena || !arena->curr)
 | 
						|
        return false;
 | 
						|
    DN_U64 pos    = arena->curr->commit + size;
 | 
						|
    bool     result = DN_Arena_CommitTo(arena, pos);
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
DN_API bool DN_Arena_Grow(DN_Arena *arena, DN_U64 reserve, DN_U64 commit)
 | 
						|
{
 | 
						|
    if (arena->flags & (DN_ArenaFlags_NoGrow | DN_ArenaFlags_UserBuffer))
 | 
						|
        return false;
 | 
						|
 | 
						|
    DN_ArenaBlock *new_block = DN_Arena_BlockInitFlags(reserve, commit, arena->flags);
 | 
						|
    if (new_block) {
 | 
						|
        new_block->prev        = arena->curr;
 | 
						|
        arena->curr            = new_block;
 | 
						|
        new_block->reserve_sum = new_block->prev->reserve_sum + new_block->prev->reserve;
 | 
						|
        DN_Arena_UpdateStatsOnNewBlock_(arena, arena->curr);
 | 
						|
    }
 | 
						|
    bool result = new_block;
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
DN_API void *DN_Arena_Alloc(DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZeroMem zero_mem)
 | 
						|
{
 | 
						|
    if (!arena)
 | 
						|
        return nullptr;
 | 
						|
 | 
						|
    if (!arena->curr) {
 | 
						|
        arena->curr = DN_Arena_BlockInitFlags(DN_ARENA_RESERVE_SIZE, DN_ARENA_COMMIT_SIZE, arena->flags);
 | 
						|
        DN_Arena_UpdateStatsOnNewBlock_(arena, arena->curr);
 | 
						|
    }
 | 
						|
 | 
						|
    if (!arena->curr)
 | 
						|
        return nullptr;
 | 
						|
 | 
						|
    try_alloc_again:
 | 
						|
    DN_ArenaBlock *curr       = arena->curr;
 | 
						|
    bool           poison     = DN_ASAN_POISON && ((arena->flags & DN_ArenaFlags_NoPoison) == 0);
 | 
						|
    uint8_t        real_align = poison ? DN_MAX(align, DN_ASAN_POISON_ALIGNMENT) : align;
 | 
						|
    DN_U64         offset_pos = DN_AlignUpPowerOfTwo(curr->used, real_align) + (poison ? DN_ASAN_POISON_GUARD_SIZE : 0);
 | 
						|
    DN_U64         end_pos    = offset_pos + size;
 | 
						|
    DN_U64         alloc_size = end_pos - curr->used;
 | 
						|
 | 
						|
    if (end_pos > curr->reserve) {
 | 
						|
        if (arena->flags & (DN_ArenaFlags_NoGrow | DN_ArenaFlags_UserBuffer))
 | 
						|
            return nullptr;
 | 
						|
        DN_USize new_reserve = DN_MAX(DN_ARENA_HEADER_SIZE + alloc_size, DN_ARENA_RESERVE_SIZE);
 | 
						|
        DN_USize new_commit  = DN_MAX(DN_ARENA_HEADER_SIZE + alloc_size, DN_ARENA_COMMIT_SIZE);
 | 
						|
        if (!DN_Arena_Grow(arena, new_reserve, new_commit))
 | 
						|
            return nullptr;
 | 
						|
        goto try_alloc_again;
 | 
						|
    }
 | 
						|
 | 
						|
    DN_USize prev_arena_commit = curr->commit;
 | 
						|
    if (end_pos > curr->commit) {
 | 
						|
        DN_ASSERT((arena->flags & DN_ArenaFlags_UserBuffer) == 0);
 | 
						|
        DN_USize end_commit  = DN_AlignUpPowerOfTwo(end_pos, g_dn_core->os_page_size);
 | 
						|
        DN_USize commit_size = end_commit - curr->commit;
 | 
						|
        char    *commit_ptr  = DN_CAST(char *) curr + curr->commit;
 | 
						|
        if (!DN_OS_MemCommit(commit_ptr, commit_size, DN_OSMemPage_ReadWrite))
 | 
						|
            return nullptr;
 | 
						|
        if (poison)
 | 
						|
            DN_ASAN_PoisonMemoryRegion(commit_ptr, commit_size);
 | 
						|
        curr->commit              = end_commit;
 | 
						|
        arena->stats.info.commit += commit_size;
 | 
						|
        arena->stats.hwm.commit   = DN_MAX(arena->stats.hwm.commit, arena->stats.info.commit);
 | 
						|
    }
 | 
						|
 | 
						|
    void *result            = DN_CAST(char *) curr + offset_pos;
 | 
						|
    curr->used             += alloc_size;
 | 
						|
    arena->stats.info.used += alloc_size;
 | 
						|
    arena->stats.hwm.used   = DN_MAX(arena->stats.hwm.used, arena->stats.info.used);
 | 
						|
    DN_ASAN_UnpoisonMemoryRegion(result, size);
 | 
						|
 | 
						|
    if (zero_mem == DN_ZeroMem_Yes) {
 | 
						|
        DN_USize reused_bytes = DN_MIN(prev_arena_commit - offset_pos, size);
 | 
						|
        DN_MEMSET(result, 0, reused_bytes);
 | 
						|
    }
 | 
						|
 | 
						|
    DN_ASSERT(arena->stats.hwm.used    >= arena->stats.info.used);
 | 
						|
    DN_ASSERT(arena->stats.hwm.commit  >= arena->stats.info.commit);
 | 
						|
    DN_ASSERT(arena->stats.hwm.reserve >= arena->stats.info.reserve);
 | 
						|
    DN_ASSERT(arena->stats.hwm.blocks  >= arena->stats.info.blocks);
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
DN_API void *DN_Arena_AllocContiguous(DN_Arena *arena, DN_U64 size, uint8_t align, DN_ZeroMem zero_mem)
 | 
						|
{
 | 
						|
    DN_ArenaFlags prev_flags  = arena->flags;
 | 
						|
    arena->flags             |= (DN_ArenaFlags_NoGrow | DN_ArenaFlags_NoPoison);
 | 
						|
    void *memory              = DN_Arena_Alloc(arena, size, align, zero_mem);
 | 
						|
    arena->flags              = prev_flags;
 | 
						|
    return memory;
 | 
						|
}
 | 
						|
 | 
						|
DN_API void *DN_Arena_Copy(DN_Arena *arena, void const *data, DN_U64 size, uint8_t align)
 | 
						|
{
 | 
						|
    if (!arena || !data || size == 0)
 | 
						|
        return nullptr;
 | 
						|
    void *result = DN_Arena_Alloc(arena, size, align, DN_ZeroMem_No);
 | 
						|
    if (result)
 | 
						|
        DN_MEMCPY(result, data, size);
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
DN_API void DN_Arena_PopTo(DN_Arena *arena, DN_U64 init_used)
 | 
						|
{
 | 
						|
    if (!arena || !arena->curr)
 | 
						|
        return;
 | 
						|
    DN_U64         used = DN_MAX(DN_ARENA_HEADER_SIZE, init_used);
 | 
						|
    DN_ArenaBlock *curr = arena->curr;
 | 
						|
    while (curr->reserve_sum >= used) {
 | 
						|
        DN_ArenaBlock *block_to_free  = curr;
 | 
						|
        arena->stats.info.used       -= block_to_free->used;
 | 
						|
        arena->stats.info.commit     -= block_to_free->commit;
 | 
						|
        arena->stats.info.reserve    -= block_to_free->reserve;
 | 
						|
        arena->stats.info.blocks     -= 1;
 | 
						|
        if (arena->flags & DN_ArenaFlags_UserBuffer)
 | 
						|
            break;
 | 
						|
        curr                          = curr->prev;
 | 
						|
        DN_Arena_BlockDeinit_(arena, block_to_free);
 | 
						|
    }
 | 
						|
 | 
						|
    arena->stats.info.used -= curr->used;
 | 
						|
    arena->curr             = curr;
 | 
						|
    curr->used              = used - curr->reserve_sum;
 | 
						|
    char     *poison_ptr    = (char *)curr + DN_AlignUpPowerOfTwo(curr->used, DN_ASAN_POISON_ALIGNMENT);
 | 
						|
    DN_USize poison_size    = ((char *)curr + curr->commit) - poison_ptr;
 | 
						|
    DN_ASAN_PoisonMemoryRegion(poison_ptr, poison_size);
 | 
						|
    arena->stats.info.used += curr->used;
 | 
						|
}
 | 
						|
 | 
						|
DN_API void DN_Arena_Pop(DN_Arena *arena, DN_U64 amount)
 | 
						|
{
 | 
						|
    DN_ArenaBlock *curr     = arena->curr;
 | 
						|
    DN_USize       used_sum = curr->reserve_sum + curr->used;
 | 
						|
    if (!DN_CHECK(amount <= used_sum))
 | 
						|
        amount = used_sum;
 | 
						|
    DN_USize pop_to = used_sum - amount;
 | 
						|
    DN_Arena_PopTo(arena, pop_to);
 | 
						|
}
 | 
						|
 | 
						|
DN_API DN_U64 DN_Arena_Pos(DN_Arena const *arena)
 | 
						|
{
 | 
						|
    DN_U64 result = (arena && arena->curr) ? arena->curr->reserve_sum + arena->curr->used : 0;
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
DN_API void DN_Arena_Clear(DN_Arena *arena)
 | 
						|
{
 | 
						|
    DN_Arena_PopTo(arena, 0);
 | 
						|
}
 | 
						|
 | 
						|
DN_API bool DN_Arena_OwnsPtr(DN_Arena const *arena, void *ptr)
 | 
						|
{
 | 
						|
    bool result = false;
 | 
						|
    uintptr_t uint_ptr = DN_CAST(uintptr_t)ptr;
 | 
						|
    for (DN_ArenaBlock const *block = arena ? arena->curr : nullptr; !result && block; block = block->prev) {
 | 
						|
        uintptr_t begin = DN_CAST(uintptr_t) block + DN_ARENA_HEADER_SIZE;
 | 
						|
        uintptr_t end   = begin + block->reserve;
 | 
						|
        result          = uint_ptr >= begin && uint_ptr <= end;
 | 
						|
    }
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
DN_API DN_ArenaStats DN_Arena_SumStatsArray(DN_ArenaStats const *array, DN_USize size)
 | 
						|
{
 | 
						|
    DN_ArenaStats result = {};
 | 
						|
    DN_FOR_UINDEX(index, size) {
 | 
						|
        DN_ArenaStats stats  = array[index];
 | 
						|
        result.info.used     += stats.info.used;
 | 
						|
        result.info.commit   += stats.info.commit;
 | 
						|
        result.info.reserve  += stats.info.reserve;
 | 
						|
        result.info.blocks   += stats.info.blocks;
 | 
						|
 | 
						|
        result.hwm.used       = DN_MAX(result.hwm.used,    result.info.used);
 | 
						|
        result.hwm.commit     = DN_MAX(result.hwm.commit,  result.info.commit);
 | 
						|
        result.hwm.reserve    = DN_MAX(result.hwm.reserve, result.info.reserve);
 | 
						|
        result.hwm.blocks     = DN_MAX(result.hwm.blocks,  result.info.blocks);
 | 
						|
    }
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
DN_API DN_ArenaStats DN_Arena_SumStats(DN_ArenaStats lhs, DN_ArenaStats rhs)
 | 
						|
{
 | 
						|
    DN_ArenaStats array[] = {lhs, rhs};
 | 
						|
    DN_ArenaStats result  = DN_Arena_SumStatsArray(array, DN_ARRAY_UCOUNT(array));
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
DN_API DN_ArenaStats DN_Arena_SumArenaArrayToStats(DN_Arena const *array, DN_USize size)
 | 
						|
{
 | 
						|
    DN_ArenaStats result = {};
 | 
						|
    for (DN_USize index = 0; index < size; index++) {
 | 
						|
        DN_Arena const *arena = array + index;
 | 
						|
        result                = DN_Arena_SumStats(result, arena->stats);
 | 
						|
    }
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
DN_API DN_ArenaTempMem DN_Arena_TempMemBegin(DN_Arena *arena)
 | 
						|
{
 | 
						|
    DN_ArenaTempMem result = {};
 | 
						|
    if (arena) {
 | 
						|
        DN_ArenaBlock *curr = arena->curr;
 | 
						|
        result              = {arena, curr ? curr->reserve_sum + curr->used : 0};
 | 
						|
    }
 | 
						|
    return result;
 | 
						|
};
 | 
						|
 | 
						|
DN_API void DN_Arena_TempMemEnd(DN_ArenaTempMem mem)
 | 
						|
{
 | 
						|
    DN_Arena_PopTo(mem.arena, mem.used_sum);
 | 
						|
};
 | 
						|
 | 
						|
DN_ArenaTempMemScope::DN_ArenaTempMemScope(DN_Arena *arena)
 | 
						|
{
 | 
						|
    mem = DN_Arena_TempMemBegin(arena);
 | 
						|
}
 | 
						|
 | 
						|
DN_ArenaTempMemScope::~DN_ArenaTempMemScope()
 | 
						|
{
 | 
						|
    DN_Arena_TempMemEnd(mem);
 | 
						|
}
 | 
						|
 | 
						|
// NOTE: [$POOL] DN_Pool //////////////////////////////////////////////////////////////////////////
 | 
						|
DN_API DN_Pool DN_Pool_Init(DN_Arena *arena, uint8_t align)
 | 
						|
{
 | 
						|
    DN_Pool result = {};
 | 
						|
    if (arena) {
 | 
						|
        result.arena = arena;
 | 
						|
        result.align = align ? align : DN_POOL_DEFAULT_ALIGN;
 | 
						|
    }
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
DN_API bool DN_Pool_IsValid(DN_Pool const *pool)
 | 
						|
{
 | 
						|
    bool result = pool && pool->arena && pool->align;
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
DN_API void *DN_Pool_Alloc(DN_Pool *pool, DN_USize size)
 | 
						|
{
 | 
						|
    void *result = nullptr;
 | 
						|
    if (!DN_Pool_IsValid(pool))
 | 
						|
        return result;
 | 
						|
 | 
						|
    DN_USize const required_size       = sizeof(DN_PoolSlot) + pool->align + size;
 | 
						|
    DN_USize const size_to_slot_offset = 5; // __lzcnt64(32) e.g. DN_PoolSlotSize_32B
 | 
						|
    DN_USize       slot_index          = 0;
 | 
						|
    if (required_size > 32) {
 | 
						|
        // NOTE: Round up if not PoT as the low bits are set.
 | 
						|
        DN_USize dist_to_next_msb = DN_CountLeadingZerosU64(required_size) + 1;
 | 
						|
        dist_to_next_msb -= DN_CAST(DN_USize)(!DN_IsPowerOfTwo(required_size));
 | 
						|
 | 
						|
        DN_USize const register_size = sizeof(DN_USize) * 8;
 | 
						|
        DN_ASSERT(register_size >= dist_to_next_msb + size_to_slot_offset);
 | 
						|
        slot_index = register_size - dist_to_next_msb - size_to_slot_offset;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!DN_CHECKF(slot_index < DN_PoolSlotSize_Count, "Chunk pool does not support the requested allocation size"))
 | 
						|
        return result;
 | 
						|
 | 
						|
    DN_USize slot_size_in_bytes = 1ULL << (slot_index + size_to_slot_offset);
 | 
						|
    DN_ASSERT(required_size <= (slot_size_in_bytes << 0));
 | 
						|
    DN_ASSERT(required_size >= (slot_size_in_bytes >> 1));
 | 
						|
 | 
						|
    DN_PoolSlot *slot = nullptr;
 | 
						|
    if (pool->slots[slot_index]) {
 | 
						|
        slot                    = pool->slots[slot_index];
 | 
						|
        pool->slots[slot_index] = slot->next;
 | 
						|
        DN_MEMSET(slot->data, 0, size);
 | 
						|
        DN_ASSERT(DN_IsPowerOfTwoAligned(slot->data, pool->align));
 | 
						|
    } else {
 | 
						|
        void *bytes = DN_Arena_Alloc(pool->arena, slot_size_in_bytes, alignof(DN_PoolSlot), DN_ZeroMem_Yes);
 | 
						|
        slot        = DN_CAST(DN_PoolSlot *) bytes;
 | 
						|
 | 
						|
        // NOTE: The raw pointer is round up to the next 'pool->align'-ed
 | 
						|
        // address ensuring at least 1 byte of padding between the raw pointer
 | 
						|
        // and the pointer given to the user and that the user pointer is
 | 
						|
        // aligned to the pool's alignment.
 | 
						|
        //
 | 
						|
        // This allows us to smuggle 1 byte behind the user pointer that has
 | 
						|
        // the offset to the original pointer.
 | 
						|
        slot->data  = DN_CAST(void *)DN_AlignDownPowerOfTwo(DN_CAST(uintptr_t)slot + sizeof(DN_PoolSlot) + pool->align, pool->align);
 | 
						|
 | 
						|
        uintptr_t offset_to_original_ptr = DN_CAST(uintptr_t)slot->data - DN_CAST(uintptr_t)bytes;
 | 
						|
        DN_ASSERT(slot->data > bytes);
 | 
						|
        DN_ASSERT(offset_to_original_ptr <= sizeof(DN_PoolSlot) + pool->align);
 | 
						|
 | 
						|
        // NOTE: Store the offset to the original pointer behind the user's
 | 
						|
        // pointer.
 | 
						|
        char *offset_to_original_storage = DN_CAST(char *)slot->data - 1;
 | 
						|
        DN_MEMCPY(offset_to_original_storage, &offset_to_original_ptr, 1);
 | 
						|
    }
 | 
						|
 | 
						|
    // NOTE: Smuggle the slot type in the next pointer so that we know, when the
 | 
						|
    // pointer gets returned which free list to return the pointer to.
 | 
						|
    result     = slot->data;
 | 
						|
    slot->next = DN_CAST(DN_PoolSlot *)slot_index;
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
DN_API DN_Str8 DN_Pool_AllocStr8FV(DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, va_list args)
 | 
						|
{
 | 
						|
    DN_Str8 result = {};
 | 
						|
    if (!DN_Pool_IsValid(pool))
 | 
						|
        return result;
 | 
						|
 | 
						|
    DN_USize size_required = DN_CStr8_FVSize(fmt, args);
 | 
						|
    result.data             = DN_CAST(char *) DN_Pool_Alloc(pool, size_required + 1);
 | 
						|
    if (result.data) {
 | 
						|
        result.size = size_required;
 | 
						|
        DN_VSNPRINTF(result.data, DN_CAST(int)(result.size + 1), fmt, args);
 | 
						|
    }
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
DN_API DN_Str8 DN_Pool_AllocStr8F(DN_Pool *pool, DN_FMT_ATTRIB char const *fmt, ...)
 | 
						|
{
 | 
						|
    va_list args;
 | 
						|
    va_start(args, fmt);
 | 
						|
    DN_Str8 result = DN_Pool_AllocStr8FV(pool, fmt, args);
 | 
						|
    va_end(args);
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
DN_API DN_Str8 DN_Pool_AllocStr8Copy(DN_Pool *pool, DN_Str8 string)
 | 
						|
{
 | 
						|
    DN_Str8 result = {};
 | 
						|
    if (!DN_Pool_IsValid(pool))
 | 
						|
        return result;
 | 
						|
 | 
						|
    if (!DN_Str8_HasData(string))
 | 
						|
        return result;
 | 
						|
 | 
						|
    char *data = DN_CAST(char *)DN_Pool_Alloc(pool, string.size + 1);
 | 
						|
    if (!data)
 | 
						|
        return result;
 | 
						|
 | 
						|
    DN_MEMCPY(data, string.data, string.size);
 | 
						|
    data[string.size] = 0;
 | 
						|
    result            = DN_Str8_Init(data, string.size);
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
DN_API void DN_Pool_Dealloc(DN_Pool *pool, void *ptr)
 | 
						|
{
 | 
						|
    if (!DN_Pool_IsValid(pool) || !ptr)
 | 
						|
        return;
 | 
						|
 | 
						|
    DN_ASSERT(DN_Arena_OwnsPtr(pool->arena, ptr));
 | 
						|
 | 
						|
    char const *one_byte_behind_ptr    = DN_CAST(char *) ptr - 1;
 | 
						|
    DN_USize    offset_to_original_ptr = 0;
 | 
						|
    DN_MEMCPY(&offset_to_original_ptr, one_byte_behind_ptr, 1);
 | 
						|
    DN_ASSERT(offset_to_original_ptr <= sizeof(DN_PoolSlot) + pool->align);
 | 
						|
 | 
						|
    char           *original_ptr = DN_CAST(char *) ptr - offset_to_original_ptr;
 | 
						|
    DN_PoolSlot    *slot         = DN_CAST(DN_PoolSlot *) original_ptr;
 | 
						|
    DN_PoolSlotSize slot_index   = DN_CAST(DN_PoolSlotSize)(DN_CAST(uintptr_t) slot->next);
 | 
						|
    DN_ASSERT(slot_index < DN_PoolSlotSize_Count);
 | 
						|
 | 
						|
    slot->next              = pool->slots[slot_index];
 | 
						|
    pool->slots[slot_index] = slot;
 | 
						|
}
 | 
						|
 | 
						|
DN_API void *DN_Pool_Copy(DN_Pool *pool, void const *data, DN_U64 size, uint8_t align)
 | 
						|
{
 | 
						|
    if (!pool || !data || size == 0)
 | 
						|
        return nullptr;
 | 
						|
 | 
						|
    // TODO: Hmm should align be part of the alloc interface in general? I'm not going to worry
 | 
						|
    // about this until we crash because of misalignment.
 | 
						|
    DN_ASSERT(pool->align >= align);
 | 
						|
 | 
						|
    void *result = DN_Pool_Alloc(pool, size);
 | 
						|
    if (result)
 | 
						|
        DN_MEMCPY(result, data, size);
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
// NOTE: [$ACAT] DN_ArenaCatalog //////////////////////////////////////////////////////////////////
 | 
						|
DN_API void DN_ArenaCatalog_Init(DN_ArenaCatalog *catalog, DN_Pool *pool)
 | 
						|
{
 | 
						|
    catalog->pool          = pool;
 | 
						|
    catalog->sentinel.next = &catalog->sentinel;
 | 
						|
    catalog->sentinel.prev = &catalog->sentinel;
 | 
						|
}
 | 
						|
 | 
						|
DN_API DN_ArenaCatalogItem *DN_ArenaCatalog_Find(DN_ArenaCatalog *catalog, DN_Str8 label)
 | 
						|
{
 | 
						|
    DN_TicketMutex_Begin(&catalog->ticket_mutex);
 | 
						|
    DN_ArenaCatalogItem *result = &catalog->sentinel;
 | 
						|
    for (DN_ArenaCatalogItem *item = catalog->sentinel.next; item != &catalog->sentinel; item = item->next) {
 | 
						|
        if (item->label == label) {
 | 
						|
            result = item;
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    DN_TicketMutex_End(&catalog->ticket_mutex);
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
static void DN_ArenaCatalog_AddInternal_(DN_ArenaCatalog *catalog, DN_Arena *arena, DN_Str8 label, bool arena_pool_allocated)
 | 
						|
{
 | 
						|
    // NOTE: We could use an atomic for appending to the sentinel but it is such
 | 
						|
    // a rare operation to append to the catalog that we don't bother.
 | 
						|
    DN_TicketMutex_Begin(&catalog->ticket_mutex);
 | 
						|
 | 
						|
    // NOTE: Create item in the catalog
 | 
						|
    DN_ArenaCatalogItem *result = DN_Pool_New(catalog->pool, DN_ArenaCatalogItem);
 | 
						|
    if (result) {
 | 
						|
        result->arena                = arena;
 | 
						|
        result->label                = label;
 | 
						|
        result->arena_pool_allocated = arena_pool_allocated;
 | 
						|
 | 
						|
        // NOTE: Add to the catalog (linked list)
 | 
						|
        DN_ArenaCatalogItem *sentinel = &catalog->sentinel;
 | 
						|
        result->next                   = sentinel;
 | 
						|
        result->prev                   = sentinel->prev;
 | 
						|
        result->next->prev             = result;
 | 
						|
        result->prev->next             = result;
 | 
						|
        DN_Atomic_AddU32(&catalog->arena_count, 1);
 | 
						|
    }
 | 
						|
    DN_TicketMutex_End(&catalog->ticket_mutex);
 | 
						|
}
 | 
						|
 | 
						|
DN_API void DN_ArenaCatalog_AddF(DN_ArenaCatalog *catalog, DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, ...)
 | 
						|
{
 | 
						|
    va_list args;
 | 
						|
    va_start(args, fmt);
 | 
						|
    DN_TicketMutex_Begin(&catalog->ticket_mutex);
 | 
						|
    DN_Str8 label = DN_Pool_AllocStr8FV(catalog->pool, fmt, args);
 | 
						|
    DN_TicketMutex_End(&catalog->ticket_mutex);
 | 
						|
    va_end(args);
 | 
						|
    DN_ArenaCatalog_AddInternal_(catalog, arena, label, false /*arena_pool_allocated*/);
 | 
						|
}
 | 
						|
 | 
						|
DN_API void DN_ArenaCatalog_AddFV(DN_ArenaCatalog *catalog, DN_Arena *arena, DN_FMT_ATTRIB char const *fmt, va_list args)
 | 
						|
{
 | 
						|
    DN_TicketMutex_Begin(&catalog->ticket_mutex);
 | 
						|
    DN_Str8 label = DN_Pool_AllocStr8FV(catalog->pool, fmt, args);
 | 
						|
    DN_TicketMutex_End(&catalog->ticket_mutex);
 | 
						|
    DN_ArenaCatalog_AddInternal_(catalog, arena, label, false /*arena_pool_allocated*/);
 | 
						|
}
 | 
						|
 | 
						|
DN_API DN_Arena *DN_ArenaCatalog_AllocFV(DN_ArenaCatalog *catalog, DN_USize reserve, DN_USize commit, uint8_t arena_flags, DN_FMT_ATTRIB char const *fmt, va_list args)
 | 
						|
{
 | 
						|
    DN_TicketMutex_Begin(&catalog->ticket_mutex);
 | 
						|
    DN_Str8   label  = DN_Pool_AllocStr8FV(catalog->pool, fmt, args);
 | 
						|
    DN_Arena *result = DN_Pool_New(catalog->pool, DN_Arena);
 | 
						|
    DN_TicketMutex_End(&catalog->ticket_mutex);
 | 
						|
 | 
						|
    *result = DN_Arena_InitSize(reserve, commit, arena_flags);
 | 
						|
    DN_ArenaCatalog_AddInternal_(catalog, result, label, true /*arena_pool_allocated*/);
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
DN_API DN_Arena *DN_ArenaCatalog_AllocF(DN_ArenaCatalog *catalog, DN_USize reserve, DN_USize commit, uint8_t arena_flags, DN_FMT_ATTRIB char const *fmt, ...)
 | 
						|
{
 | 
						|
    va_list args;
 | 
						|
    va_start(args, fmt);
 | 
						|
    DN_TicketMutex_Begin(&catalog->ticket_mutex);
 | 
						|
    DN_Str8   label  = DN_Pool_AllocStr8FV(catalog->pool, fmt, args);
 | 
						|
    DN_Arena *result = DN_Pool_New(catalog->pool, DN_Arena);
 | 
						|
    DN_TicketMutex_End(&catalog->ticket_mutex);
 | 
						|
    va_end(args);
 | 
						|
 | 
						|
    *result = DN_Arena_InitSize(reserve, commit, arena_flags);
 | 
						|
    DN_ArenaCatalog_AddInternal_(catalog, result, label, true /*arena_pool_allocated*/);
 | 
						|
    return result;
 | 
						|
}
 | 
						|
 | 
						|
DN_API bool DN_ArenaCatalog_Erase(DN_ArenaCatalog *catalog, DN_Arena *arena, DN_ArenaCatalogFreeArena free_arena)
 | 
						|
{
 | 
						|
    bool result = false;
 | 
						|
    DN_TicketMutex_Begin(&catalog->ticket_mutex);
 | 
						|
    for (DN_ArenaCatalogItem *item = catalog->sentinel.next; item != &catalog->sentinel; item = item->next) {
 | 
						|
        if (item->arena == arena) {
 | 
						|
            item->next->prev = item->prev;
 | 
						|
            item->prev->next = item->next;
 | 
						|
            if (item->arena_pool_allocated) {
 | 
						|
                if (free_arena == DN_ArenaCatalogFreeArena_Yes)
 | 
						|
                    DN_Arena_Deinit(item->arena);
 | 
						|
                DN_Pool_Dealloc(catalog->pool, item->arena);
 | 
						|
            }
 | 
						|
            DN_Pool_Dealloc(catalog->pool, item->label.data);
 | 
						|
            DN_Pool_Dealloc(catalog->pool, item);
 | 
						|
            result = true;
 | 
						|
            break;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    DN_TicketMutex_End(&catalog->ticket_mutex);
 | 
						|
    return result;
 | 
						|
}
 |