dqn: Add ASAN poisoning

This commit is contained in:
doyle 2023-08-27 18:07:13 +10:00
parent faf229c17f
commit 2740987956
9 changed files with 231 additions and 61 deletions

View File

@ -14,6 +14,7 @@
#define STBSP__ASAN __declspec(no_sanitize_address) #define STBSP__ASAN __declspec(no_sanitize_address)
#endif #endif
#define DQN_ASAN_POISON 1
#define DQN_NO_CHECK_BREAK #define DQN_NO_CHECK_BREAK
#define DQN_IMPLEMENTATION #define DQN_IMPLEMENTATION
#include "dqn.h" #include "dqn.h"
@ -40,28 +41,30 @@ static Dqn_UTest Dqn_Test_Arena()
DQN_UTEST_TEST("Reused memory is zeroed out") { DQN_UTEST_TEST("Reused memory is zeroed out") {
Dqn_usize size = DQN_KILOBYTES(128); uint8_t alignment = 1;
Dqn_usize alloc_size = DQN_KILOBYTES(128);
Dqn_MemBlockSizeRequiredResult size_required = Dqn_MemBlock_SizeRequired(nullptr, alloc_size, alignment, Dqn_MemBlockFlag_Nil);
Dqn_Arena arena = {}; Dqn_Arena arena = {};
Dqn_Arena_Grow(&arena, size, /*commit*/ size, /*flags*/ 0); Dqn_Arena_Grow(&arena, size_required.block_size, /*commit*/ size_required.block_size, /*flags*/ 0);
// NOTE: Allocate 128 kilobytes, fill it with garbage, then reset the arena // NOTE: Allocate 128 kilobytes, fill it with garbage, then reset the arena
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_Alloc(&arena, size, 1, Dqn_ZeroMem_Yes); void *ptr = Dqn_Arena_Alloc(&arena, alloc_size, alignment, 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', alloc_size);
Dqn_Arena_EndTempMemory(temp_mem, false /*cancel*/); Dqn_Arena_EndTempMemory(temp_mem, false /*cancel*/);
} }
// NOTE: Reallocate 128 kilobytes // NOTE: Reallocate 128 kilobytes
char *ptr = DQN_CAST(char *)Dqn_Arena_Alloc(&arena, size, 1, Dqn_ZeroMem_Yes); char *ptr = DQN_CAST(char *)Dqn_Arena_Alloc(&arena, alloc_size, alignment, Dqn_ZeroMem_Yes);
// NOTE: Double check we got the same pointer // NOTE: Double check we got the same pointer
DQN_UTEST_ASSERT(&test, first_ptr_address == DQN_CAST(uintptr_t)ptr); DQN_UTEST_ASSERT(&test, first_ptr_address == DQN_CAST(uintptr_t)ptr);
// NOTE: Check that the bytes are set to 0 // NOTE: Check that the bytes are set to 0
for (Dqn_usize i = 0; i < size; i++) for (Dqn_usize i = 0; i < alloc_size; i++)
DQN_UTEST_ASSERT(&test, ptr[i] == 0); DQN_UTEST_ASSERT(&test, ptr[i] == 0);
Dqn_Arena_Free(&arena); Dqn_Arena_Free(&arena);
} }
@ -1647,6 +1650,15 @@ static Dqn_UTest Dqn_Test_VArray()
DQN_UTEST_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal)); DQN_UTEST_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(array_literal));
DQN_UTEST_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0); DQN_UTEST_ASSERT(&test, DQN_MEMCMP(array.data, array_literal, DQN_ARRAY_UCOUNT(array_literal) * sizeof(array_literal[0])) == 0);
} }
DQN_UTEST_TEST("Test adding an array of items after erase") {
uint32_t array_literal[] = {0, 1, 2, 3};
Dqn_VArray_Add<uint32_t>(&array, array_literal, DQN_ARRAY_UCOUNT(array_literal));
uint32_t expected_literal[] = {14, 6, 0, 1, 2, 3};
DQN_UTEST_ASSERT(&test, array.size == DQN_ARRAY_UCOUNT(expected_literal));
DQN_UTEST_ASSERT(&test, DQN_MEMCMP(array.data, expected_literal, DQN_ARRAY_UCOUNT(expected_literal) * sizeof(expected_literal[0])) == 0);
}
} }
DQN_UTEST_TEST("Array of unaligned objects are contiguously laid out in memory") { DQN_UTEST_TEST("Array of unaligned objects are contiguously laid out in memory") {

View File

@ -50,4 +50,6 @@ pushd Build
REM set zig_cmd=zig c++ %zig_compile_flags% REM set zig_cmd=zig c++ %zig_compile_flags%
REM powershell -Command "$time = Measure-Command { !zig_cmd! | Out-Default }; Write-Host '[BUILD] zig:'$time.TotalSeconds's'; exit $LASTEXITCODE" || exit /b 1 REM powershell -Command "$time = Measure-Command { !zig_cmd! | Out-Default }; Write-Host '[BUILD] zig:'$time.TotalSeconds's'; exit $LASTEXITCODE" || exit /b 1
REM ) REM )
exit /b 1
popd popd

8
dqn.h
View File

@ -256,6 +256,14 @@
// [$TCTX] Dqn_ThreadContext | | Per-thread data structure e.g. temp arenas // [$TCTX] Dqn_ThreadContext | | Per-thread data structure e.g. temp arenas
// NOTE: Additional Configuration // NOTE: Additional Configuration
// - When compiling with ASAN, set this macro to `1` to enable poisoning of the
// memory allocated and freed by memory arenas in the library. By default this
// is set to `0`. By enabling this, all allocations will be guarded by a page,
// before and after the returned pointer. All allocations will be aligned to
// and padded to atleast DQN_ASAN_POISON_ALIGNMENT (e.g. 8 bytes).
//
// DQN_ASAN_POISON 1
//
// - Define this macro to record allocation stats for arenas used in the // - Define this macro to record allocation stats for arenas used in the
// thread context. The thread context arena stats can be printed by using // thread context. The thread context arena stats can be printed by using
// Dqn_Library_DumpThreadContextArenaStat. // Dqn_Library_DumpThreadContextArenaStat.

View File

@ -389,7 +389,7 @@ template <typename T> T * Dqn_List_Add (Dqn_List<T> *list, T const &v
DQN_API template <typename T> Dqn_VArray<T> Dqn_VArray_InitByteSize(Dqn_Arena *arena, Dqn_usize byte_size) DQN_API template <typename T> Dqn_VArray<T> Dqn_VArray_InitByteSize(Dqn_Arena *arena, Dqn_usize byte_size)
{ {
Dqn_VArray<T> result = {}; Dqn_VArray<T> result = {};
result.block = Dqn_Arena_Grow(arena, byte_size, 0 /*commit*/, Dqn_MemBlockFlag_ArenaPrivate); result.block = Dqn_Arena_Grow(arena, byte_size, 0 /*commit*/, Dqn_MemBlockFlag_ArenaPrivate | Dqn_MemBlockFlag_AllocsAreContiguous);
result.data = DQN_CAST(T *)Dqn_MemBlock_Alloc(result.block, /*size*/ 0, alignof(T), Dqn_ZeroMem_No); result.data = DQN_CAST(T *)Dqn_MemBlock_Alloc(result.block, /*size*/ 0, alignof(T), Dqn_ZeroMem_No);
result.max = (result.block->size - result.block->used) / sizeof(T); result.max = (result.block->size - result.block->used) / sizeof(T);
return result; return result;
@ -474,6 +474,7 @@ DQN_API template <typename T> void Dqn_VArray_EraseRange(Dqn_VArray<T> *array, D
DQN_MEMCPY(dest, src, (end - src) * sizeof(T)); DQN_MEMCPY(dest, src, (end - src) * sizeof(T));
} }
array->size -= erase_count; array->size -= erase_count;
Dqn_MemBlock_Pop(array->block, erase_count * sizeof(T));
} }
} }

View File

@ -30,6 +30,15 @@
} while (0) } while (0)
#endif #endif
#if !defined(DQN_ASAN_POISON)
#define DQN_ASAN_POISON 0
#endif
#if !defined(DQN_ASAN_POISON_ALIGNMENT)
#define DQN_ASAN_POISON_ALIGNMENT 8
#endif
// NOTE: [$CALL] Dqn_CallSite ====================================================================== // NOTE: [$CALL] Dqn_CallSite ======================================================================
struct Dqn_CallSite struct Dqn_CallSite
{ {

View File

@ -10,6 +10,8 @@
#include "b_stacktrace.h" #include "b_stacktrace.h"
// NOTE: [$OS_H] OS Headers ======================================================================== // NOTE: [$OS_H] OS Headers ========================================================================
#include <sanitizer/asan_interface.h>
#if defined(DQN_OS_WIN32) #if defined(DQN_OS_WIN32)
#pragma comment(lib, "bcrypt") #pragma comment(lib, "bcrypt")
#pragma comment(lib, "wininet") #pragma comment(lib, "wininet")

View File

@ -1,4 +1,6 @@
// NOTE: [$ALLO] Dqn_Allocator ===================================================================== // NOTE: [$ALLO] Dqn_Allocator =====================================================================
#include <ios>
#include <sanitizer/asan_interface.h>
DQN_API void *Dqn_Allocator_Alloc(Dqn_Allocator allocator, size_t size, uint8_t align, Dqn_ZeroMem zero_mem) DQN_API void *Dqn_Allocator_Alloc(Dqn_Allocator allocator, size_t size, uint8_t align, Dqn_ZeroMem zero_mem)
{ {
void *result = NULL; void *result = NULL;
@ -165,26 +167,58 @@ DQN_API int Dqn_VMem_Protect(void *ptr, Dqn_usize size, uint32_t page_flags)
} }
// NOTE: [$MEMB] Dqn_MemBlock ====================================================================== // NOTE: [$MEMB] Dqn_MemBlock ======================================================================
DQN_API Dqn_usize Dqn_MemBlock_MetadataSize(uint8_t flags) DQN_API Dqn_MemBlockSizeRequiredResult Dqn_MemBlock_SizeRequired(Dqn_MemBlock const *block, Dqn_usize size, uint8_t alignment, uint32_t flags)
{ {
Dqn_usize result = sizeof(Dqn_MemBlock); DQN_ASSERT(alignment > 0 && Dqn_IsPowerOfTwo(alignment));
if (flags & Dqn_MemBlockFlag_PageGuard)
result = g_dqn_library->os_page_size; Dqn_MemBlockSizeRequiredResult result = {};
result.alloc_size = size;
Dqn_MemBlockFlag block_flags = DQN_CAST(Dqn_MemBlockFlag)((block ? block->flags : 0) | flags);
uint8_t ptr_alignment = alignment;
if (DQN_ASAN_POISON) {
// NOTE: Guard a page after with poisoned memory. The first allocation
// is always guarded with poison-ed memory to prevent read/writes behind
// the block of memory.
if ((block_flags & Dqn_MemBlockFlag_AllocsAreContiguous) == 0) {
result.alloc_size = Dqn_AlignUpPowerOfTwo(size + g_dqn_library->os_page_size, DQN_ASAN_POISON_ALIGNMENT);
}
ptr_alignment = DQN_MAX(alignment, DQN_ASAN_POISON_ALIGNMENT);
}
if (block) {
uintptr_t address = DQN_CAST(uintptr_t)block->data + block->used;
uintptr_t next_address = Dqn_AlignUpPowerOfTwo(address, ptr_alignment);
result.data_offset = next_address - DQN_CAST(uintptr_t)block->data;
Dqn_usize new_used = result.data_offset + result.alloc_size;
result.block_size = new_used - block->used;
} else {
result.block_size = result.alloc_size + (ptr_alignment - 1);
}
return result; return result;
} }
DQN_API Dqn_MemBlock *Dqn_MemBlock_Init(Dqn_usize reserve, Dqn_usize commit, uint8_t flags) Dqn_usize Dqn_MemBlock_MetadataSize()
{ {
DQN_ASSERTF(g_dqn_library->os_page_size, "Library needs to be initialised by called Dqn_Library_Init()"); Dqn_usize init_poison_page = DQN_ASAN_POISON ? g_dqn_library->os_page_size : 0;
Dqn_usize poison_alignment = DQN_ASAN_POISON ? DQN_ASAN_POISON_ALIGNMENT : 0;
Dqn_usize result = Dqn_AlignUpPowerOfTwo(sizeof(Dqn_MemBlock), poison_alignment) + init_poison_page;
return result;
}
DQN_API Dqn_MemBlock *Dqn_MemBlock_Init(Dqn_usize reserve, Dqn_usize commit, uint32_t flags)
{
DQN_ASSERTF(g_dqn_library->os_page_size, "Library needs to be initialised by calling Dqn_Library_Init()");
DQN_ASSERTF(Dqn_IsPowerOfTwo(g_dqn_library->os_page_size), "Invalid page size"); DQN_ASSERTF(Dqn_IsPowerOfTwo(g_dqn_library->os_page_size), "Invalid page size");
DQN_ASSERTF((flags & ~Dqn_MemBlockFlag_All) == 0, "Invalid flag combination, must adhere to Dqn_MemBlockFlags"); DQN_ASSERTF((flags & ~Dqn_MemBlockFlag_All) == 0, "Invalid flag combination, must adhere to Dqn_MemBlockFlags");
if (reserve == 0) if (reserve == 0)
return nullptr; return nullptr;
Dqn_usize metadata_size = Dqn_MemBlock_MetadataSize(flags); Dqn_usize metadata_size = Dqn_MemBlock_MetadataSize();
Dqn_usize reserve_aligned = Dqn_AlignUpPowerOfTwo(reserve + metadata_size, g_dqn_library->os_page_size); Dqn_usize reserve_aligned = Dqn_AlignUpPowerOfTwo(metadata_size + reserve, g_dqn_library->os_page_size);
Dqn_usize commit_aligned = Dqn_AlignUpPowerOfTwo(commit + metadata_size, g_dqn_library->os_page_size); Dqn_usize commit_aligned = Dqn_AlignUpPowerOfTwo(metadata_size + commit, g_dqn_library->os_page_size);
commit_aligned = DQN_MIN(commit_aligned, reserve_aligned); commit_aligned = DQN_MIN(commit_aligned, reserve_aligned);
// NOTE: Avoid 1 syscall by committing on reserve if amounts are equal // NOTE: Avoid 1 syscall by committing on reserve if amounts are equal
@ -198,55 +232,90 @@ DQN_API Dqn_MemBlock *Dqn_MemBlock_Init(Dqn_usize reserve, Dqn_usize commit, uin
result->data = DQN_CAST(uint8_t *)result + metadata_size; result->data = DQN_CAST(uint8_t *)result + metadata_size;
result->size = reserve_aligned - metadata_size; result->size = reserve_aligned - metadata_size;
result->commit = commit_aligned - metadata_size; result->commit = commit_aligned - metadata_size;
result->flags = flags; result->flags = DQN_CAST(uint8_t)flags;
// NOTE: Guard pages if (DQN_ASAN_POISON) { // NOTE: Poison (guard page + entire block), we unpoison as we allocate
if (flags & Dqn_MemBlockFlag_PageGuard) DQN_ASSERT(Dqn_IsPowerOfTwoAligned(result->data, DQN_ASAN_POISON_ALIGNMENT));
Dqn_VMem_Protect(result->data, commit_aligned, Dqn_VMemPage_ReadWrite | Dqn_VMemPage_Guard); DQN_ASSERT(Dqn_IsPowerOfTwoAligned(result->size, DQN_ASAN_POISON_ALIGNMENT));
void *poison_ptr = DQN_CAST(void *)Dqn_AlignUpPowerOfTwo(DQN_CAST(char *)result + sizeof(Dqn_MemBlock), DQN_ASAN_POISON_ALIGNMENT);
ASAN_POISON_MEMORY_REGION(poison_ptr, g_dqn_library->os_page_size + result->size);
}
} }
return result; return result;
} }
DQN_API void *Dqn_MemBlock_Alloc(Dqn_MemBlock *block, Dqn_usize size, uint8_t alignment, Dqn_ZeroMem zero_mem) DQN_API void *Dqn_MemBlock_Alloc(Dqn_MemBlock *block, Dqn_usize size, uint8_t alignment, Dqn_ZeroMem zero_mem)
{ {
DQN_ASSERT(zero_mem == Dqn_ZeroMem_Yes || zero_mem == Dqn_ZeroMem_No);
void *result = nullptr; void *result = nullptr;
if (!block) if (!block)
return result; return result;
DQN_ASSERT(Dqn_IsPowerOfTwo(alignment)); Dqn_MemBlockSizeRequiredResult size_required = Dqn_MemBlock_SizeRequired(block, size, alignment, Dqn_MemBlockFlag_Nil);
Dqn_usize aligned_used = Dqn_AlignUpPowerOfTwo(block->used, alignment); Dqn_usize new_used = size_required.data_offset + size_required.alloc_size;
Dqn_usize new_used = aligned_used + size;
if (new_used > block->size) if (new_used > block->size)
return result; return result;
result = DQN_CAST(char *)block->data + aligned_used; result = DQN_CAST(char *)block->data + size_required.data_offset;
block->used = new_used; block->used = new_used;
DQN_ASSERT(Dqn_IsPowerOfTwoAligned(result, alignment));
if (DQN_ASAN_POISON) {
ASAN_UNPOISON_MEMORY_REGION(result, size);
}
if (zero_mem == Dqn_ZeroMem_Yes) { if (zero_mem == Dqn_ZeroMem_Yes) {
Dqn_usize reused_bytes = DQN_MIN(block->commit - aligned_used, size); Dqn_usize reused_bytes = DQN_MIN(block->commit - size_required.data_offset, size);
DQN_MEMSET(result, DQN_MEMSET_BYTE, reused_bytes); DQN_MEMSET(result, DQN_MEMSET_BYTE, reused_bytes);
} }
if (block->commit < block->used) { if (block->commit < block->used) {
Dqn_usize commit_size = Dqn_AlignUpPowerOfTwo(block->used - block->commit, g_dqn_library->os_page_size); Dqn_usize commit_size = Dqn_AlignUpPowerOfTwo(block->used - block->commit, g_dqn_library->os_page_size);
void *commit_ptr = (void *)Dqn_AlignUpPowerOfTwo((char *)block->data + block->commit, g_dqn_library->os_page_size); void *commit_ptr = (void *)Dqn_AlignUpPowerOfTwo((char *)block->data + block->commit, g_dqn_library->os_page_size);
block->commit += commit_size - Dqn_MemBlock_MetadataSize(block->flags); block->commit += commit_size;
Dqn_VMem_Commit(commit_ptr, commit_size, Dqn_VMemPage_ReadWrite); Dqn_VMem_Commit(commit_ptr, commit_size, Dqn_VMemPage_ReadWrite);
DQN_ASSERT(block->commit <= block->size); DQN_ASSERT(block->commit <= block->size);
} }
if (block->flags & Dqn_MemBlockFlag_PageGuard)
Dqn_VMem_Protect(result, size, Dqn_VMemPage_ReadWrite);
return result; return result;
} }
DQN_API void Dqn_MemBlock_Free(Dqn_MemBlock *block) DQN_API void Dqn_MemBlock_Free(Dqn_MemBlock *block)
{ {
if (block) { if (!block)
Dqn_usize release_size = block->size + Dqn_MemBlock_MetadataSize(block->flags); return;
Dqn_VMem_Release(block, release_size); Dqn_usize release_size = block->size + Dqn_MemBlock_MetadataSize();
if (DQN_ASAN_POISON) {
ASAN_UNPOISON_MEMORY_REGION(block, release_size);
} }
Dqn_VMem_Release(block, release_size);
}
DQN_API void Dqn_MemBlock_Pop(Dqn_MemBlock *block, Dqn_usize size)
{
if (!block)
return;
Dqn_usize size_adjusted = DQN_MIN(size, block->used);
Dqn_usize to = block->used - size_adjusted;
Dqn_MemBlock_PopTo(block, to);
}
DQN_API void Dqn_MemBlock_PopTo(Dqn_MemBlock *block, Dqn_usize to)
{
if (!block || to >= block->used)
return;
if (DQN_ASAN_POISON) {
// TODO(doyle): The poison API takes addresses that are 8 byte aligned
// so there are gaps here if we are dealing with objects that aren't 8
// byte aligned unfortunately.
void *poison_ptr = DQN_CAST(void *)Dqn_AlignUpPowerOfTwo(DQN_CAST(char *)block->data + to, DQN_ASAN_POISON_ALIGNMENT);
void *end_ptr = DQN_CAST(char *)block->data + block->used;
uintptr_t bytes_to_poison = DQN_CAST(uintptr_t)end_ptr - DQN_CAST(uintptr_t)poison_ptr;
ASAN_POISON_MEMORY_REGION(poison_ptr, bytes_to_poison);
}
block->used = to;
} }
// NOTE: [$AREN] Dqn_Arena ========================================================================= // NOTE: [$AREN] Dqn_Arena =========================================================================
@ -318,7 +387,9 @@ DQN_API void *Dqn_Arena_Alloc(Dqn_Arena *arena, Dqn_usize size, uint8_t align, D
arena->curr = arena->curr->next; arena->curr = arena->curr->next;
if (!arena->curr) { if (!arena->curr) {
if (!Dqn_Arena_Grow(arena, size, size /*commit*/, 0 /*flags*/)) Dqn_MemBlockSizeRequiredResult size_required = Dqn_MemBlock_SizeRequired(nullptr, size, align, Dqn_MemBlockFlag_Nil);
Dqn_usize block_size = size_required.block_size;
if (!Dqn_Arena_Grow(arena, block_size, block_size, Dqn_MemBlockFlag_Nil))
return result; return result;
DQN_ASSERT(arena->curr); DQN_ASSERT(arena->curr);
} }
@ -393,10 +464,7 @@ DQN_API void Dqn_Arena_EndTempMemory(Dqn_ArenaTempMemory temp_memory, bool cance
arena->blocks = temp_memory.blocks; arena->blocks = temp_memory.blocks;
arena->head = temp_memory.head; arena->head = temp_memory.head;
arena->curr = temp_memory.curr; arena->curr = temp_memory.curr;
if (arena->curr) { Dqn_MemBlock_PopTo(arena->curr, temp_memory.curr_used);
Dqn_MemBlock *curr = arena->curr;
curr->used = temp_memory.curr_used;
}
// NOTE: Free the tail blocks until we reach the temp_memory's tail block // NOTE: Free the tail blocks until we reach the temp_memory's tail block
while (arena->tail != temp_memory.tail) { while (arena->tail != temp_memory.tail) {
@ -411,7 +479,7 @@ DQN_API void Dqn_Arena_EndTempMemory(Dqn_ArenaTempMemory temp_memory, bool cance
// NOTE: Reset the usage of all the blocks between the tail and current block's // NOTE: Reset the usage of all the blocks between the tail and current block's
for (Dqn_MemBlock *block = arena->tail; block && (block != arena->curr); block = block->prev) for (Dqn_MemBlock *block = arena->tail; block && (block != arena->curr); block = block->prev)
block->used = 0; Dqn_MemBlock_PopTo(block, 0);
} }
Dqn_ArenaTempMemoryScope::Dqn_ArenaTempMemoryScope(Dqn_Arena *arena) Dqn_ArenaTempMemoryScope::Dqn_ArenaTempMemoryScope(Dqn_Arena *arena)

View File

@ -59,17 +59,64 @@ 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); DQN_API int Dqn_VMem_Protect (void *ptr, Dqn_usize size, uint32_t page_flags);
// NOTE: [$MEMB] Dqn_MemBlock ====================================================================== // NOTE: [$MEMB] Dqn_MemBlock ======================================================================
// Encapsulates allocation of objects from a raw block of memory by bumping a
// a pointer in the block. Some examples include our memory arenas are
// implemented as light wrappers over chained memory blocks and our arrays
// backed by virtual memory take memory blocks.
//
// One pattern we take advantage of under this design is that our virtual arrays
// can ask an arena for a memory block and sub-allocate its contiguous items
// from it. Since the arena created the memory block, the array's lifetime is
// bound to the arena which could also be managing a bunch of other allocations
// with the same lifetime.
//
// This provides an advantage over creating a specific arena for that array that
// is configured not to grow or chain (in order to adhere to the contiguous
// layout requirement) thus limiting the arena to that 1 specific usecase.
//
// NOTE: API =======================================================================================
// @proc Dqn_MemBlockSizeRequiredResult
// @desc Calculate various size metrics about how many bytes it'd take to
// allocate a pointer from the given block. The size of the allocation is
// treated as one object and the padding and page-guards are applied
// accordingly to the one object
//
// If you are trying to determine how many bytes are required for `N` distinct
// objects then you must multiple the result of this function by `N` to
// account for the per-item page-guard paddding.
//
// @param `block` Pass in the block you wish to allocate from to calculate
// size metrics for. You may pass in `null` to calculate how many bytes are
// needed to `Dqn_MemBlock_Init` a fresh block capable of allocating the size
// requested.
//
// @param `flags` The `Dqn_MemBlockFlag`s to apply in the calculation. Various
// features may influence the sizes required for allocating the requested
// amount of bytes. If `block` is passed in, the flags will be OR'ed together
// to determine the flags to account for.
enum Dqn_MemBlockFlag enum Dqn_MemBlockFlag
{ {
Dqn_MemBlockFlag_PageGuard = 1 << 0, Dqn_MemBlockFlag_Nil = 0,
Dqn_MemBlockFlag_ArenaPrivate = 1 << 1, Dqn_MemBlockFlag_ArenaPrivate = 1 << 0,
// Enforce that adjacent allocations from this block are contiguous in
// memory (as long as the alignment used between allocations are
/// consistent).
//
// This flag is currently only used when ASAN memory poison-ing is enabled
// via `DQN_ASAN_POISON`. In this mode all allocations are sandwiched with a
// page's worth of poison-ed memory breaking the contiguous requirement of
// arrays. Passing this flag will stop the block from padding pointers with
// poison.
Dqn_MemBlockFlag_AllocsAreContiguous = 1 << 1,
// If leak tracing is enabled, this flag will allow the allocation recorded // 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 // from the reserve call to be leaked, e.g. not printed when leaks are
// dumped to the console. // dumped to the console.
Dqn_MemBlockFlag_AllocRecordLeakPermitted = 1 << 2, Dqn_MemBlockFlag_AllocRecordLeakPermitted = 1 << 2,
Dqn_MemBlockFlag_All = Dqn_MemBlockFlag_PageGuard | Dqn_MemBlockFlag_All = Dqn_MemBlockFlag_ArenaPrivate |
Dqn_MemBlockFlag_ArenaPrivate | Dqn_MemBlockFlag_AllocsAreContiguous |
Dqn_MemBlockFlag_AllocRecordLeakPermitted, Dqn_MemBlockFlag_AllocRecordLeakPermitted,
}; };
@ -84,10 +131,31 @@ struct Dqn_MemBlock
uint8_t flags; uint8_t flags;
}; };
struct Dqn_MemBlockSizeRequiredResult
{
// Offset from the block's data pointer that the allocation will start at
// If `block` was null then this is always set to 0.
Dqn_usize data_offset;
// How many bytes will be allocated for the amount requested by the user.
// This is usually the same as the number requested except when ASAN
// poison-ing is enabled. In that case, the pointer will be padded at the
// end with a page's worth of poison-ed memory.
Dqn_usize alloc_size;
// How many bytes of space is needed in a block for allocating the requested
// pointer. This may differ from the allocation size depending on additional
// alignment requirements *and* whether or not ASAN poison-ing is required.
Dqn_usize block_size;
};
DQN_API Dqn_usize Dqn_MemBlock_MetadataSize(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 Dqn_MemBlockSizeRequiredResult Dqn_MemBlock_SizeRequired(Dqn_MemBlock const *block, Dqn_usize size, uint8_t alignment, uint32_t flags);
DQN_API void *Dqn_MemBlock_Alloc (Dqn_MemBlock *block, Dqn_usize size, uint8_t alignment, Dqn_ZeroMem zero_mem); DQN_API Dqn_MemBlock * Dqn_MemBlock_Init (Dqn_usize reserve, Dqn_usize commit, uint32_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); DQN_API void Dqn_MemBlock_Free (Dqn_MemBlock *block);
DQN_API void Dqn_MemBlock_Pop (Dqn_MemBlock *block, Dqn_usize size);
DQN_API void Dqn_MemBlock_PopTo (Dqn_MemBlock *block, Dqn_usize to);
#define Dqn_MemBlock_New(block, Type, zero_mem) (Type *)Dqn_MemBlock_Alloc(block, sizeof(Type), alignof(Type), zero_mem) #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) #define Dqn_MemBlock_NewArray(block, Type, count, zero_mem) (Type *)Dqn_MemBlock_Alloc(block, sizeof(Type) * count, alignof(Type), zero_mem)
@ -242,11 +310,11 @@ enum Dqn_ArenaCommit
// NOTE: Allocation ================================================================================ // NOTE: Allocation ================================================================================
#define Dqn_Arena_New(arena, Type, zero_mem) (Type *)Dqn_Arena_Alloc(arena, sizeof(Type), alignof(Type), zero_mem) #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_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_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_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_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)) #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_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 Dqn_MemBlock * Dqn_Arena_Grow (Dqn_Arena *arena, Dqn_usize size, Dqn_usize commit, uint8_t flags);

View File

@ -3,10 +3,10 @@
#error "Filesystem APIs requires Windows API, DQN_NO_WIN must not be defined" #error "Filesystem APIs requires Windows API, DQN_NO_WIN must not be defined"
#endif #endif
// NOTE: [$FSYS] Dqn_Fs ============================================================================ // NOTE: [$FSYS] Dqn_Fs ============================================================================
// NOTE: FS Manipulation ======================================================= // NOTE: FS Manipulation ===========================================================================
// TODO(dqn): We should have a Dqn_String8 interface and a CString interface // TODO(dqn): We should have a Dqn_String8 interface and a CString interface
// //
// NOTE: API =================================================================== // NOTE: API =======================================================================================
// @proc Dqn_FsDelete // @proc Dqn_FsDelete
// @desc Delete the item specified at the path. This function *CAN* not delete directories unless // @desc Delete the item specified at the path. This function *CAN* not delete directories unless
// the directory is empty. // the directory is empty.