dqn: Add head ptr on arena blocks

This commit is contained in:
doyle 2023-06-07 22:11:57 +10:00
parent faa46b4921
commit d9116ae48e
2 changed files with 99 additions and 49 deletions

62
dqn.h
View File

@ -1447,10 +1447,6 @@ DQN_API int Dqn_VMem_Protect (void *ptr, Dqn_usize size, uint32_t page_flags);
// @desc Dump the stats of the given arena to the memory log-stream. // @desc Dump the stats of the given arena to the memory log-stream.
// @param[in] arena The arena to dump stats for // @param[in] arena The arena to dump stats for
#if !defined(DQN_ARENA_MIN_BLOCK_SIZE)
#define DQN_ARENA_MIN_BLOCK_SIZE DQN_VMEM_RESERVE_GRANULARITY
#endif
enum Dqn_ArenaBlockFlags enum Dqn_ArenaBlockFlags
{ {
Dqn_ArenaBlockFlags_Private = 1 << 0, ///< Private blocks can only allocate its memory when used in the 'FromBlock' API variants Dqn_ArenaBlockFlags_Private = 1 << 0, ///< Private blocks can only allocate its memory when used in the 'FromBlock' API variants
@ -1493,20 +1489,21 @@ struct Dqn_Arena
{ {
bool use_after_free_guard; bool use_after_free_guard;
Dqn_String8 label; ///< Optional label to describe the arena Dqn_String8 label; ///< Optional label to describe the arena
Dqn_usize min_block_size; Dqn_ArenaBlock *head; ///< Active block the arena is allocating from
Dqn_ArenaBlock *curr; ///< Active block the arena is allocating from Dqn_ArenaBlock *curr; ///< Active block the arena is allocating from
Dqn_ArenaBlock *tail; ///< Last block in the linked list of blocks Dqn_ArenaBlock *tail; ///< Last block in the linked list of blocks
Dqn_ArenaStat stats; ///< Current arena stats, reset when reset usage is invoked. Dqn_ArenaStat stats; ///< Current arena stats, reset when reset usage is invoked.
}; };
struct Dqn_ArenaTempMemory struct Dqn_ArenaTempMemory
{ {
Dqn_Arena *arena; ///< The arena the scope is for Dqn_Arena *arena; ///< Arena the scope is for
Dqn_ArenaBlock *curr; ///< The current block of the arena at the beginning of the scope Dqn_ArenaBlock *head; ///< Head block of the arena at the beginning of the scope
Dqn_ArenaBlock *tail; ///< The tail block of the arena at the beginning of the scope Dqn_ArenaBlock *curr; ///< Current block of the arena at the beginning of the scope
Dqn_usize curr_used; ///< The current used amount of the current block Dqn_ArenaBlock *tail; ///< Tail block of the arena at the beginning of the scope
Dqn_ArenaStat stats; ///< The stats of the arena at the beginning of the scope Dqn_usize curr_used; ///< Current used amount of the current block
Dqn_ArenaStat stats; ///< Stats of the arena at the beginning of the scope
}; };
// Automatically begin and end a temporary memory scope on object construction // Automatically begin and end a temporary memory scope on object construction
@ -6352,13 +6349,12 @@ DQN_API void Dqn_Arena_Reset(Dqn_Arena *arena, Dqn_ZeroMem zero_mem)
return; return;
// NOTE: Zero all the blocks until we reach the first block in the list // NOTE: Zero all the blocks until we reach the first block in the list
for (Dqn_ArenaBlock *block = arena->tail; block; block = block->prev) { for (Dqn_ArenaBlock *block = arena->head; block; block = block->next) {
if (!block->prev)
arena->curr = block;
Dqn_ArenaBlockResetInfo_ reset_info = {}; Dqn_ArenaBlockResetInfo_ reset_info = {};
Dqn_Arena_BlockReset_(DQN_LEAK_TRACE block, zero_mem, reset_info); Dqn_Arena_BlockReset_(DQN_LEAK_TRACE block, zero_mem, reset_info);
} }
arena->curr = arena->head;
arena->stats.used = 0; arena->stats.used = 0;
arena->stats.wasted = 0; arena->stats.wasted = 0;
} }
@ -6366,11 +6362,12 @@ DQN_API void Dqn_Arena_Reset(Dqn_Arena *arena, Dqn_ZeroMem zero_mem)
DQN_API Dqn_ArenaTempMemory Dqn_Arena_BeginTempMemory(Dqn_Arena *arena) DQN_API Dqn_ArenaTempMemory Dqn_Arena_BeginTempMemory(Dqn_Arena *arena)
{ {
Dqn_ArenaTempMemory result = {}; Dqn_ArenaTempMemory result = {};
result.arena = arena; result.arena = arena;
result.curr = arena->curr; result.head = arena->head;
result.tail = arena->tail; result.curr = arena->curr;
result.curr_used = (arena->curr) ? arena->curr->used : 0; result.tail = arena->tail;
result.stats = arena->stats; result.curr_used = (arena->curr) ? arena->curr->used : 0;
result.stats = arena->stats;
return result; return result;
} }
@ -6387,6 +6384,7 @@ DQN_API void Dqn_Arena_EndTempMemory_(DQN_LEAK_TRACE_FUNCTION Dqn_ArenaTempMemor
arena->stats.blocks = scope.stats.blocks; arena->stats.blocks = scope.stats.blocks;
// NOTE: Revert the current block to the scope's current block // NOTE: Revert the current block to the scope's current block
arena->head = scope.head;
arena->curr = scope.curr; arena->curr = scope.curr;
if (arena->curr) { if (arena->curr) {
Dqn_ArenaBlock *curr = arena->curr; Dqn_ArenaBlock *curr = arena->curr;
@ -6526,6 +6524,7 @@ DQN_API Dqn_ArenaBlock *Dqn_Arena_Grow_(DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena
} else { } else {
DQN_ASSERT(!arena->curr); DQN_ASSERT(!arena->curr);
arena->curr = result; arena->curr = result;
arena->head = result;
} }
arena->tail = result; arena->tail = result;
@ -6558,15 +6557,18 @@ DQN_API void *Dqn_Arena_Allocate_(DQN_LEAK_TRACE_FUNCTION Dqn_Arena *arena, Dqn_
while (arena->curr && (arena->curr->flags & Dqn_ArenaBlockFlags_Private)) while (arena->curr && (arena->curr->flags & Dqn_ArenaBlockFlags_Private))
arena->curr = arena->curr->next; arena->curr = arena->curr->next;
if (!arena->curr) {
Dqn_usize allocation_size = size + (align - 1);
if (!Dqn_Arena_Grow(DQN_LEAK_TRACE_ARG arena, allocation_size, allocation_size /*commit*/, 0 /*flags*/)) {
break;
}
arena->curr = arena->tail;
}
result = Dqn_Arena_AllocateFromBlock(arena->curr, size, align, zero_mem); result = Dqn_Arena_AllocateFromBlock(arena->curr, size, align, zero_mem);
if (!result) {
if (!arena->curr || arena->curr == arena->tail) {
Dqn_usize allocation_size = size + (align - 1);
if (!Dqn_Arena_Grow(DQN_LEAK_TRACE_ARG arena, allocation_size, allocation_size /*commit*/, 0 /*flags*/)) {
break;
}
}
if (arena->curr != arena->tail)
arena->curr = arena->curr->next;
}
} }
if (result) if (result)

View File

@ -31,34 +31,82 @@
#define DQN_TESTER_IMPLEMENTATION #define DQN_TESTER_IMPLEMENTATION
#include "dqn_tester.h" #include "dqn_tester.h"
enum Guard {
Guard_None,
Guard_UseAfterFree,
Guard_Count,
};
static Dqn_String8 ArenaGuardTestSuffix(uint32_t guard)
{
Dqn_String8 result = {};
switch (guard) {
case Guard_None: result = DQN_STRING8(" "); break;
case Guard_UseAfterFree: result = DQN_STRING8(" [UAF]"); break;
}
return result;
}
Dqn_Tester TestArena() Dqn_Tester TestArena()
{ {
Dqn_Tester test = {}; Dqn_Tester test = {};
DQN_TESTER_GROUP(test, "Dqn_Arena") { DQN_TESTER_GROUP(test, "Dqn_Arena") {
DQN_TESTER_TEST("Reused memory is zeroed out") { for (Dqn_usize guard = 0; guard < Guard_Count; guard++) {
Dqn_Arena arena = {}; Dqn_String8 test_suffix = ArenaGuardTestSuffix(guard);
DQN_TESTER_TEST("Reused memory is zeroed out%.*s", DQN_STRING_FMT(test_suffix)) {
Dqn_Arena arena = {};
arena.use_after_free_guard = guard == Guard_UseAfterFree;
// NOTE: Allocate 128 kilobytes, fill it with garbage, then reset the arena // NOTE: Allocate 128 kilobytes, fill it with garbage, then reset the arena
Dqn_usize size = DQN_KILOBYTES(128); Dqn_usize size = DQN_KILOBYTES(128);
uintptr_t first_ptr_address = 0; uintptr_t first_ptr_address = 0;
{ {
Dqn_ArenaTempMemory temp_mem = Dqn_Arena_BeginTempMemory(&arena); Dqn_ArenaTempMemory temp_mem = Dqn_Arena_BeginTempMemory(&arena);
void *ptr = Dqn_Arena_Allocate(&arena, size, 1, Dqn_ZeroMem_Yes); void *ptr = Dqn_Arena_Allocate(&arena, size, 1, Dqn_ZeroMem_Yes);
first_ptr_address = DQN_CAST(uintptr_t)ptr; first_ptr_address = DQN_CAST(uintptr_t)ptr;
DQN_MEMSET(ptr, 'z', size); DQN_MEMSET(ptr, 'z', size);
Dqn_Arena_EndTempMemory(temp_mem); Dqn_Arena_EndTempMemory(temp_mem);
}
// NOTE: Reallocate 128 kilobytes
char *ptr = DQN_CAST(char *)Dqn_Arena_Allocate(&arena, size, 1, Dqn_ZeroMem_Yes);
// NOTE: Double check we got the same pointer
DQN_TESTER_ASSERT(&test, first_ptr_address == DQN_CAST(uintptr_t)ptr);
// NOTE: Check that the bytes are set to 0
for (Dqn_usize i = 0; i < size; i++)
DQN_TESTER_ASSERT(&test, ptr[i] == 0);
Dqn_Arena_Free(&arena, Dqn_ZeroMem_No);
} }
}
// NOTE: Reallocate 128 kilobytes for (Dqn_usize guard = 0; guard < Guard_Count; guard++) {
char *ptr = DQN_CAST(char *)Dqn_Arena_Allocate(&arena, size, 1, Dqn_ZeroMem_Yes); Dqn_String8 test_suffix = ArenaGuardTestSuffix(guard);
DQN_TESTER_TEST("Test arena grows naturally, 1mb + 4mb%.*s", DQN_STRING_FMT(test_suffix)) {
Dqn_Arena arena = {};
arena.use_after_free_guard = guard == Guard_UseAfterFree;
// NOTE: Double check we got the same pointer // NOTE: Allocate 1mb, then 4mb, this should force the arena to grow
DQN_TESTER_ASSERT(&test, first_ptr_address == DQN_CAST(uintptr_t)ptr); char *ptr_1mb = DQN_CAST(char *)Dqn_Arena_Allocate(&arena, DQN_MEGABYTES(1), 1 /*align*/, Dqn_ZeroMem_Yes);
char *ptr_4mb = DQN_CAST(char *)Dqn_Arena_Allocate(&arena, DQN_MEGABYTES(4), 1 /*align*/, Dqn_ZeroMem_Yes);
DQN_TESTER_ASSERT(&test, ptr_1mb);
DQN_TESTER_ASSERT(&test, ptr_4mb);
// NOTE: Check that the bytes are set to 0 Dqn_ArenaBlock const *block_1mb = arena.head;
for (Dqn_usize i = 0; i < size; i++) char const *block_1mb_begin = DQN_CAST(char *)block_1mb->memory;
DQN_TESTER_ASSERT(&test, ptr[i] == 0); char const *block_1mb_end = DQN_CAST(char *)block_1mb->memory + block_1mb->size;
Dqn_Arena_Free(&arena, Dqn_ZeroMem_No);
Dqn_ArenaBlock const *block_4mb = arena.curr;
char const *block_4mb_begin = DQN_CAST(char *)block_4mb->memory;
char const *block_4mb_end = DQN_CAST(char *)block_4mb->memory + block_4mb->size;
DQN_TESTER_ASSERTF(&test, block_1mb != block_4mb, "New block should have been allocated and linked");
DQN_TESTER_ASSERTF(&test, ptr_1mb >= block_1mb_begin && ptr_1mb <= block_1mb_end, "Pointer was not allocated from correct memory block");
DQN_TESTER_ASSERTF(&test, ptr_4mb >= block_4mb_begin && ptr_4mb <= block_4mb_end, "Pointer was not allocated from correct memory block");
Dqn_Arena_Free(&arena, Dqn_ZeroMem_No);
}
} }
Dqn_usize sizes[] = {DQN_KILOBYTES(1), DQN_KILOBYTES(4), DQN_KILOBYTES(5)}; Dqn_usize sizes[] = {DQN_KILOBYTES(1), DQN_KILOBYTES(4), DQN_KILOBYTES(5)};