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.
// @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
{
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;
Dqn_String8 label; ///< Optional label to describe the arena
Dqn_usize min_block_size;
Dqn_ArenaBlock *curr; ///< Active block the arena is allocating from
Dqn_ArenaBlock *tail; ///< Last block in the linked list of blocks
Dqn_ArenaStat stats; ///< Current arena stats, reset when reset usage is invoked.
Dqn_String8 label; ///< Optional label to describe the arena
Dqn_ArenaBlock *head; ///< 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_ArenaStat stats; ///< Current arena stats, reset when reset usage is invoked.
};
struct Dqn_ArenaTempMemory
{
Dqn_Arena *arena; ///< The arena the scope is for
Dqn_ArenaBlock *curr; ///< The current 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_usize curr_used; ///< The current used amount of the current block
Dqn_ArenaStat stats; ///< The stats of the arena at the beginning of the scope
Dqn_Arena *arena; ///< Arena the scope is for
Dqn_ArenaBlock *head; ///< Head 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_ArenaBlock *tail; ///< Tail block 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
@ -6352,13 +6349,12 @@ DQN_API void Dqn_Arena_Reset(Dqn_Arena *arena, Dqn_ZeroMem zero_mem)
return;
// NOTE: Zero all the blocks until we reach the first block in the list
for (Dqn_ArenaBlock *block = arena->tail; block; block = block->prev) {
if (!block->prev)
arena->curr = block;
for (Dqn_ArenaBlock *block = arena->head; block; block = block->next) {
Dqn_ArenaBlockResetInfo_ reset_info = {};
Dqn_Arena_BlockReset_(DQN_LEAK_TRACE block, zero_mem, reset_info);
}
arena->curr = arena->head;
arena->stats.used = 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_ArenaTempMemory result = {};
result.arena = arena;
result.curr = arena->curr;
result.tail = arena->tail;
result.curr_used = (arena->curr) ? arena->curr->used : 0;
result.stats = arena->stats;
result.arena = arena;
result.head = arena->head;
result.curr = arena->curr;
result.tail = arena->tail;
result.curr_used = (arena->curr) ? arena->curr->used : 0;
result.stats = arena->stats;
return result;
}
@ -6387,6 +6384,7 @@ DQN_API void Dqn_Arena_EndTempMemory_(DQN_LEAK_TRACE_FUNCTION Dqn_ArenaTempMemor
arena->stats.blocks = scope.stats.blocks;
// NOTE: Revert the current block to the scope's current block
arena->head = scope.head;
arena->curr = scope.curr;
if (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 {
DQN_ASSERT(!arena->curr);
arena->curr = result;
arena->head = 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))
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);
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)

View File

@ -31,34 +31,82 @@
#define DQN_TESTER_IMPLEMENTATION
#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 test = {};
DQN_TESTER_GROUP(test, "Dqn_Arena") {
DQN_TESTER_TEST("Reused memory is zeroed out") {
Dqn_Arena arena = {};
for (Dqn_usize guard = 0; guard < Guard_Count; guard++) {
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
Dqn_usize size = DQN_KILOBYTES(128);
uintptr_t first_ptr_address = 0;
{
Dqn_ArenaTempMemory temp_mem = Dqn_Arena_BeginTempMemory(&arena);
void *ptr = Dqn_Arena_Allocate(&arena, size, 1, Dqn_ZeroMem_Yes);
first_ptr_address = DQN_CAST(uintptr_t)ptr;
DQN_MEMSET(ptr, 'z', size);
Dqn_Arena_EndTempMemory(temp_mem);
// NOTE: Allocate 128 kilobytes, fill it with garbage, then reset the arena
Dqn_usize size = DQN_KILOBYTES(128);
uintptr_t first_ptr_address = 0;
{
Dqn_ArenaTempMemory temp_mem = Dqn_Arena_BeginTempMemory(&arena);
void *ptr = Dqn_Arena_Allocate(&arena, size, 1, Dqn_ZeroMem_Yes);
first_ptr_address = DQN_CAST(uintptr_t)ptr;
DQN_MEMSET(ptr, 'z', size);
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
char *ptr = DQN_CAST(char *)Dqn_Arena_Allocate(&arena, size, 1, Dqn_ZeroMem_Yes);
for (Dqn_usize guard = 0; guard < Guard_Count; guard++) {
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
DQN_TESTER_ASSERT(&test, first_ptr_address == DQN_CAST(uintptr_t)ptr);
// NOTE: Allocate 1mb, then 4mb, this should force the arena to grow
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
for (Dqn_usize i = 0; i < size; i++)
DQN_TESTER_ASSERT(&test, ptr[i] == 0);
Dqn_Arena_Free(&arena, Dqn_ZeroMem_No);
Dqn_ArenaBlock const *block_1mb = arena.head;
char const *block_1mb_begin = DQN_CAST(char *)block_1mb->memory;
char const *block_1mb_end = DQN_CAST(char *)block_1mb->memory + block_1mb->size;
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)};