BlockArena, a bit of allocator rework

This commit is contained in:
Krzosa Karol
2025-11-30 19:54:46 +01:00
parent c19c60fe22
commit c2f4686bc4
15 changed files with 337 additions and 224 deletions

View File

@@ -10,6 +10,7 @@
#define MA_ASAN_UNPOISON_MEMORY_REGION(addr, size) ASAN_UNPOISON_MEMORY_REGION(addr, size)
#endif
#include <stdlib.h>
#if OS_WINDOWS
#ifndef NOMINMAX
@@ -20,44 +21,44 @@
#endif
#include <windows.h>
void *VReserve(size_t size) {
API void *VReserve(size_t size) {
void *result = (uint8_t *)VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE);
return result;
}
bool VCommit(void *p, size_t size) {
API bool VCommit(void *p, size_t size) {
void *result = VirtualAlloc(p, size, MEM_COMMIT, PAGE_READWRITE);
return result ? true : false;
}
bool VRelease(void *p, size_t size) {
API bool VRelease(void *p, size_t size) {
BOOL result = VirtualFree(p, 0, MEM_RELEASE);
return result ? true : false;
}
bool VDecommit(void *p, size_t size) {
API bool VDecommit(void *p, size_t size) {
BOOL result = VirtualFree(p, size, MEM_DECOMMIT);
return result ? true : false;
}
#elif OS_LINUX || OS_MAC
void *VReserve(size_t size) {
API void *VReserve(size_t size) {
void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, (off_t)0);
return result == (void *)-1 ? 0 : result;
}
bool VCommit(void *p, size_t size) {
API bool VCommit(void *p, size_t size) {
int result = mprotect(p, size, PROT_READ | PROT_WRITE);
return result == 0;
}
bool VRelease(void *p, size_t size) {
API bool VRelease(void *p, size_t size) {
int result = munmap(p, size);
return result == 0;
}
bool VDecommit(void *p, size_t size) {
API bool VDecommit(void *p, size_t size) {
mprotect(p, size, PROT_NONE);
madvise(p, size, MADV_DONTNEED);
return true;
@@ -65,29 +66,102 @@ bool VDecommit(void *p, size_t size) {
#else
void *VReserve(size_t size) {
API void *VReserve(size_t size) {
InvalidCodepath();
return NULL;
}
bool VCommit(void *p, size_t size) {
API bool VCommit(void *p, size_t size) {
InvalidCodepath();
return false;
}
bool VRelease(void *p, size_t size) {
API bool VRelease(void *p, size_t size) {
InvalidCodepath();
return false;
}
bool VDecommit(void *p, size_t size) {
API bool VDecommit(void *p, size_t size) {
InvalidCodepath();
return false;
}
#endif
void InitArena(Arena *arena, size_t reserve) {
API void *SystemAllocatorProc(void *object, int kind, void *p, size_t size) {
void *result = NULL;
if (kind == AllocatorKind_Allocate) {
result = malloc(size);
Assert(result);
} else if (kind == AllocatorKind_Deallocate) {
free(p);
} else {
InvalidCodepath();
}
return result;
}
API Allocator GetSystemAllocator() {
Allocator result = {SystemAllocatorProc};
return result;
}
API void *_AllocSize_(Allocator alo, size_t size) {
void *result = alo.proc(alo.object, AllocatorKind_Allocate, NULL, size);
memset(result, 0, size);
return result;
}
struct MemoryRecord {
size_t size;
void *addr;
char *file;
int line;
};
thread_local Array<MemoryRecord> MemoryTrackingRecord;
API void *TrackingAllocatorProc(void *object, int kind, void *p, size_t size) {
void *result = NULL;
if (kind == AllocatorKind_Allocate) {
result = malloc(size);
Add(&MemoryTrackingRecord, {size, result, LocationTraceO.file, LocationTraceO.line});
Assert(result);
} else if (kind == AllocatorKind_Deallocate) {
free(p);
bool found = false;
For(IterateInReverse(&MemoryTrackingRecord)) {
if (it.addr == p) {
found = true;
UnorderedRemove(&MemoryTrackingRecord, it);
break;
}
}
Assert(found);
} else {
InvalidCodepath();
}
return result;
}
API void TrackingAllocatorCheck() {
// For (MemoryTrackingRecord) {
// ReportConsolef("%s(%d): error: memory leak");
// }
Assert(MemoryTrackingRecord.len == 0);
}
API Allocator GetTrackingAllocator() {
Allocator result = {TrackingAllocatorProc};
return result;
}
///////////////////////////////
// Virtual Arena
API void InitArena(VirtualArena *arena, size_t reserve) {
reserve = AlignUp(reserve, PAGE_SIZE);
arena->align = DEFAULT_ALIGNMENT;
arena->data = (uint8_t *)VReserve(reserve);
@@ -96,8 +170,8 @@ void InitArena(Arena *arena, size_t reserve) {
}
}
Arena *AllocArena(Allocator allocator, size_t size) {
Arena *result = AllocType(allocator, Arena);
API VirtualArena *AllocArena(Allocator allocator, size_t size) {
VirtualArena *result = AllocType(allocator, VirtualArena);
result->data = (uint8_t *)AllocSize(allocator, size);
result->reserve = size;
result->commit = size;
@@ -105,8 +179,8 @@ Arena *AllocArena(Allocator allocator, size_t size) {
return result;
}
Arena *AllocArena(size_t reserve) {
Arena *result = NULL;
API VirtualArena *AllocArena(size_t reserve) {
VirtualArena *result = NULL;
void *data = VReserve(reserve);
if (!data) return result;
@@ -117,16 +191,16 @@ Arena *AllocArena(size_t reserve) {
return result;
}
result = (Arena *)data;
result = (VirtualArena *)data;
result->data = (uint8_t *)data;
result->reserve = reserve;
result->commit = PAGE_SIZE;
result->len = result->base_len = sizeof(Arena);
result->len = result->base_len = sizeof(VirtualArena);
result->align = DEFAULT_ALIGNMENT;
return result;
}
void *PushSize(Arena *arena, size_t size) {
API void *PushSize(VirtualArena *arena, size_t size) {
// base_len is used for bootstraping arenas, it denotes the
// space occupied by the arena. If len is smaller then base_len then
// we start to overwrite the arena itself - pure barbarism.
@@ -145,7 +219,7 @@ void *PushSize(Arena *arena, size_t size) {
if (to_commit_clamped > 0) {
bool success = VCommit(arena->data + arena->commit, to_commit_clamped);
if (success) {
MA_ASAN_UNPOISON_MEMORY_REGION(arena->data + arena->commit, to_commit_clamped);
MA_ASAN_POISON_MEMORY_REGION(arena->data + arena->commit, to_commit_clamped);
arena->commit += to_commit_clamped;
}
}
@@ -159,14 +233,14 @@ void *PushSize(Arena *arena, size_t size) {
return (void *)result;
}
void Release(Arena *arena) {
API void Release(VirtualArena *arena) {
if (arena == NULL || arena->data == NULL) return;
bool zero_memory = (uint8_t *)arena != arena->data;
VRelease(arena->data, arena->reserve);
if (zero_memory) MemoryZero(arena, sizeof(Arena));
if (zero_memory) MemoryZero(arena, sizeof(VirtualArena));
}
void PopToPos(Arena *arena, size_t pos) {
API void PopToPos(VirtualArena *arena, size_t pos) {
// base_len is used for bootstraping arenas, it denotes the
// space occupied by the arena. If len is smaller then base_len then
// we start to overwrite the arena itself - pure barbarism.
@@ -178,9 +252,9 @@ void PopToPos(Arena *arena, size_t pos) {
MA_ASAN_POISON_MEMORY_REGION(arena->data + arena->len, size);
}
void *ArenaAllocatorProc(void *object, int kind, void *p, size_t size) {
API void *ArenaAllocatorProc(void *object, int kind, void *p, size_t size) {
if (kind == AllocatorKind_Allocate) {
return PushSize((Arena *)object, size);
return PushSize((VirtualArena *)object, size);
} else if (AllocatorKind_Deallocate) {
} else {
Assert(!"invalid codepath");
@@ -188,106 +262,123 @@ void *ArenaAllocatorProc(void *object, int kind, void *p, size_t size) {
return NULL;
}
thread_local Arena *ScratchArenaPool[4];
///////////////////////////////
// Block Arena
#if OS_WASM
void InitScratch() {
Allocator sys_allocator = GetSystemAllocator();
ScratchArenaPool[0] = AllocArena(sys_allocator, MiB(16));
ScratchArenaPool[1] = AllocArena(sys_allocator, MiB(8));
ScratchArenaPool[3] = AllocArena(sys_allocator, MiB(2));
ScratchArenaPool[3] = AllocArena(sys_allocator, MiB(1));
}
#else
void InitScratch() {
for (int i = 0; i < Lengthof(ScratchArenaPool); i += 1) {
ScratchArenaPool[i] = AllocArena();
}
}
#endif
TempArena GetScratchEx(Arena **conflicts, int conflict_count) {
Arena *unoccupied = 0;
for (int i = 0; i < Lengthof(ScratchArenaPool); i += 1) {
Arena *from_pool = ScratchArenaPool[i];
unoccupied = from_pool;
for (int conflict_i = 0; conflict_i < conflict_count; conflict_i += 1) {
Arena *from_conflict = conflicts[conflict_i];
if (from_pool == from_conflict) {
unoccupied = 0;
break;
}
API void *PushSize(BlockArena *arena, size_t size) {
if (size > (size_t)(arena->end - arena->start)) {
size_t block_size = MiB(1);
if (size > block_size) {
block_size = size;
}
if (arena->allocator.proc == NULL) {
arena->allocator = GetSystemAllocator();
}
BlockArenaNode *new_block = (BlockArenaNode *)AllocSize(arena->allocator, block_size + sizeof(BlockArenaNode));
Assert(GetAlignOffset((size_t)new_block->start, DEFAULT_ALIGNMENT) == 0);
arena->start = new_block->start;
new_block->end = arena->end = new_block->start + block_size;
SLL_STACK_ADD(arena->blocks, new_block);
}
U8 *result = arena->start;
Assert(GetAlignOffset((size_t)result, DEFAULT_ALIGNMENT) == 0);
arena->start = (U8 *)AlignUp((size_t)(arena->start + size), DEFAULT_ALIGNMENT);
return result;
}
if (unoccupied) {
API void Release(BlockArena *arena) {
for (BlockArenaNode *it = arena->blocks, *next = NULL; it; it = next) {
next = it->next;
Dealloc(arena->allocator, it);
}
MemoryZero(arena, sizeof(BlockArena));
}
API void Unwind(BlockArena *arena, U8 *pos) {
bool contains = false;
for (BlockArenaNode *it = arena->blocks, *next = NULL; it; it = next) {
next = it->next;
if ((pos >= it->start) && (pos < it->end)) {
contains = true;
break;
} else {
arena->blocks = arena->blocks->next;
Dealloc(arena->allocator, it);
}
}
// Failed to get free scratch memory, this is a fatal error, this shouldnt happen
Assert(unoccupied);
TempArena result = BeginTemp(unoccupied);
return result;
Assert(contains || pos == NULL);
arena->start = pos;
}
#include <stdlib.h>
void *SystemAllocator_Alloc(void *object, int kind, void *p, size_t size) {
void *result = NULL;
API void *BlockArenaAllocatorProc(void *object, int kind, void *p, size_t size) {
BlockArena *arena = (BlockArena *)object;
if (kind == AllocatorKind_Allocate) {
result = malloc(size);
Assert(result);
} else if (kind == AllocatorKind_Deallocate) {
free(p);
return PushSize(arena, size);
} else if (AllocatorKind_Deallocate) {
} else {
InvalidCodepath();
Assert(!"invalid codepath");
}
return result;
return NULL;
}
Allocator GetSystemAllocator() {
Allocator result = {SystemAllocator_Alloc};
return result;
}
struct MemoryRecord {
size_t size;
void *addr;
bool deallocated;
};
Array<MemoryRecord> MemoryTrackingRecord;
void *TrackingAllocatorProc(void *object, int kind, void *p, size_t size) {
void *result = NULL;
if (kind == AllocatorKind_Allocate) {
result = malloc(size);
Add(&MemoryTrackingRecord, {size, result});
Assert(result);
} else if (kind == AllocatorKind_Deallocate) {
free(p);
bool found = false;
For(MemoryTrackingRecord){
if (it.addr == p) {
it.deallocated = true;
found = true;
void TestArena() {
Allocator memory_tracking_allocator = GetTrackingAllocator();
{
BlockArena arena = {};
arena.allocator = memory_tracking_allocator;
for (int i = 0; i < 10000; i += 1) {
int *vals = (int *)PushSize(&arena, sizeof(int)*i);
for (int j = 0; j < i; j += 1) {
vals[j] = j;
}
}
Assert(found);
} else {
InvalidCodepath();
Release(&arena);
TrackingAllocatorCheck();
}
return result;
}
void TrackingAllocatorCheck() {
For (MemoryTrackingRecord) {
Assert(it.deallocated);
{
BlockArena arena = {};
U8 *start = arena.start;
arena.allocator = memory_tracking_allocator;
int *vals = (int *)PushSize(&arena, sizeof(int) * 32);
for (int i = 0; i < 32; i += 1) vals[i] = i;
Unwind(&arena, (U8 *)vals);
Assert(arena.blocks);
Assert(arena.blocks->next == NULL);
Assert(arena.start == (U8 *)vals);
Assert(arena.blocks[0].start == (U8 *)vals);
Unwind(&arena, NULL);
Dealloc(arena.allocator, arena.blocks);
TrackingAllocatorCheck();
}
{
BlockArena arena = {};
arena.allocator = memory_tracking_allocator;
int *vals = (int *)PushSize(&arena, sizeof(int) * 32);
for (int i = 0; i < 32; i += 1) vals[i] = i;
U8 *p = arena.start;
U8 *a = (U8 *)PushSize(&arena, KiB(32));
U8 *b = (U8 *)PushSize(&arena, KiB(1000));
Assert(arena.blocks);
Assert(arena.blocks->next);
Assert(arena.blocks->next->next == NULL);
Unwind(&arena, a);
Assert(arena.blocks);
Assert(arena.blocks->next == NULL);
Assert(arena.start == p);
Release(&arena);
TrackingAllocatorCheck();
}
{
BlockArena arena = {};
arena.allocator = memory_tracking_allocator;
U8 *a = (U8 *)PushSize(&arena, KiB(2000));
Assert((size_t)(arena.blocks[0].end - arena.blocks[0].start) == KiB(2000));
Release(&arena);
TrackingAllocatorCheck();
}
}
Allocator GetTrackingAllocator() {
Allocator result = {TrackingAllocatorProc};
return result;
}