Add allocator-asan-example.h

This commit is contained in:
peterino 2026-06-18 21:43:09 +00:00
commit 9d34f13246
1 changed files with 855 additions and 0 deletions

855
allocator-asan-example.h Normal file
View File

@ -0,0 +1,855 @@
#ifndef __UH_ALLOCATOR
#define __UH_ALLOCATOR
#include <windows.h>
#include "std.h"
#include "errors.h"
#include "synchronization.h"
#include "timers.h"
UHEXTERN_C_BEGIN
#ifndef UH_USE_ASAN
#define UH_USE_ASAN 0
#endif
#if UH_USE_ASAN
void __asan_poison_memory_region(void const volatile* addr, usize size);
void __asan_unpoison_memory_region(void const volatile* addr, usize size);
#define UH_ASAN_POISON(addr, size) __asan_poison_memory_region((addr), (size))
#define UH_ASAN_UNPOISON(addr, size) __asan_unpoison_memory_region((addr), (size))
#else
#define UH_ASAN_POISON(addr, size) ((void) 0)
#define UH_ASAN_UNPOISON(addr, size) ((void) 0)
#endif
// it uses a linear allocation strategy. when the initial 8k
// page is used up, it allocates new 64k pages by default from the global allocator, or exact size pages for new allocations
// binned allocations in pages for individual blocks of allocations
typedef struct uha_block_bin {
void** pages; // 64K Pages
i32 page_count; // 64K Pages
i32 page_cap; // 64K Pages
i32 block_size; // 8, 16, 64, 512, 2048, 8192, 16 * 1024, 32 * 1024
i32 cap;
i32 next;
i32* free_list;
i32 free_count;
} uha_block_bin;
typedef struct uh_page_header {
i32 kind;
i32 bin_index;
u32 payload_offset;
u32 rsvd;
} uh_page_header;
typedef struct uh_large_page_node uh_large_page_node;
typedef struct uh_large_page_node {
uh_large_page_node* next;
uh_large_page_node* prev;
void* alloc_base;
void* page;
i64 size;
i64 free_time;
} uh_large_page_node;
#define UH_WPSIZEINIT 8 * 1024
#define UH_BINPAGESIZE 64 * 1024
#define UH_SLABMAX 32752
#define UH_ALIGN16(x) (((x) + 15) & ~((usize) 15))
#define UH_PAGE_KIND_SLAB 0
#define UH_PAGE_KIND_LARGE -1
#define UH_SLAB_OFFSET 16
#define UH_LARGE_TRIM_NS 1000000000LL
static const usize uh_bins_size_list[] = {
8, 16, 64, 512,
2 * 1024,
// these 3 will use tiled blocks strategy
8176,
16368,
UH_SLABMAX,
};
#define UH_BINSCOUNT sizeof(uh_bins_size_list) / sizeof(uh_bins_size_list[0])
// allocations up to 8KB in size.
// small arenas, we do not pre-create any bins until we hit an allocation
// but they are pre-slotted
typedef struct uh_allocator {
uh_mutex mutex;
void* work_page; // 4k small work page for all internal memory allocation functions
i32 wp_size;
i32 wp_allocator;
uha_block_bin* bins;
i32 bin_count;
void** large;
uh_large_page_node* lpn;
uh_large_page_node* lpn_free;
uh_large_page_node* lpn_free_tail;
i64 commit_amount; // bytes requested through VirtualAlloc
i64 alloc_amount; // bytes handed out to subAllocations
i64 alloc_count; // bytes handed out to subAllocations
// slab overhead = commit_amount - alloc_amount
} uh_allocator;
static b8 uh_allocator_init(uh_allocator* allocator);
static void uh_allocator_deinit(uh_allocator* a);
static void uh_allocator_trim_large_pages(uh_allocator* a, i64 now);
static void uha_free_unlocked(uh_allocator* a, void* ptr);
static void* uha_alloc_unlocked(uh_allocator* a, usize size);
extern uh_allocator* g_allocator;
uh_allocator* g_allocator;
static uh_allocator* uh_get_global_allocator(void)
{
return g_allocator;
}
static b8 uh_init_global_allocator(void)
{
g_allocator = VirtualAlloc(NULL, sizeof(uh_allocator), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
UH_ASAN_UNPOISON(g_allocator, sizeof(uh_allocator));
uh_logf("arena allocator init.");
return uh_allocator_init(uh_get_global_allocator());
}
static void uh_deinit_global_allocator(void)
{
uh_allocator_deinit(uh_get_global_allocator());
VirtualFree(uh_get_global_allocator(), 0, MEM_RELEASE);
}
static void uh_allocator_deinit(uh_allocator* a)
{
uh_mutex_lock(&a->mutex);
if(a->commit_amount || a->alloc_amount || a->alloc_count)
{
const i64 overhead_amount = a->commit_amount - a->alloc_amount;
uh_logf("==== LEAKED MEMORY report ====");
uh_logf("live alloc_amount=%d", a->alloc_amount);
uh_logf("live alloc_count=%d", a->alloc_count);
uh_logf("committed bytes=%d", a->commit_amount);
uh_logf("allocator overhead=%d", overhead_amount);
}
for (i32 i = 0; i < a->bin_count; i += 1)
{
uha_block_bin* bin = &a->bins[i];
for(i32 j = 0; j < bin->page_count; j += 1)
{
VirtualFree(bin->pages[j], 0, MEM_RELEASE);
}
if (bin->free_list != NULL)
{
VirtualFree(bin->free_list, 0, MEM_RELEASE);
bin->free_list = NULL;
bin->free_count = 0;
}
}
uh_large_page_node* lpn_dead = a->lpn;
while(lpn_dead != NULL)
{
uh_large_page_node* lpn_next = lpn_dead->next;
VirtualFree(lpn_dead->alloc_base, 0, MEM_RELEASE);
lpn_dead = lpn_next;
}
lpn_dead = a->lpn_free;
while(lpn_dead != NULL)
{
uh_large_page_node* lpn_next = lpn_dead->next;
VirtualFree(lpn_dead->alloc_base, 0, MEM_RELEASE);
lpn_dead = lpn_next;
}
VirtualFree(a->work_page, 0, MEM_RELEASE);
uh_mutex_unlock(&a->mutex);
uh_mutex_deinit(&a->mutex);
}
// allocate from the workpage linear allocator
static void* uha_wp_alloc(uh_allocator* a, i64 size)
{
void* p = (void*) ((usize) a->work_page + a->wp_allocator);
a->wp_allocator += size;
if (a->wp_allocator > a->wp_size)
{
uh_panic("Workpage Size exceeded, adding additional workpages... (not implemented yet)");
}
return p;
}
static b8 uh_allocator_init(uh_allocator* a)
{
uh_mutex_init(&a->mutex);
a->work_page = VirtualAlloc(NULL, UH_WPSIZEINIT, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
UH_ASAN_UNPOISON(a->work_page, UH_WPSIZEINIT);
a->wp_size = UH_WPSIZEINIT;
a->wp_allocator = 0;
a->lpn = NULL;
a->lpn_free = NULL;
a->lpn_free_tail = NULL;
a->commit_amount = UH_WPSIZEINIT;
a->alloc_amount = 0;
a->alloc_count = 0;
// initialize bins
a->bin_count = (i32) UH_BINSCOUNT;
a->bins = (uha_block_bin*) uha_wp_alloc(a, sizeof(uha_block_bin) * a->bin_count);
// bins initialization
for (i32 i = 0; i < a->bin_count; i += 1)
{
uha_block_bin* bin = a->bins + i;
bin->pages = (void**) uha_wp_alloc(a, sizeof(void*) * 32);
bin->page_cap = 32;
bin->page_count = 0;
bin->block_size = uh_bins_size_list[i];
bin->cap = 0;
bin->next = 0;
bin->free_list = NULL;
bin->free_count = 0;
}
return 1;
}
static void uh_allocator_trim_large_pages(uh_allocator* a, i64 now)
{
while (a->lpn_free_tail != NULL)
{
uh_large_page_node* node = a->lpn_free_tail;
if (now - node->free_time <= UH_LARGE_TRIM_NS)
{
break;
}
a->lpn_free_tail = node->prev;
if (a->lpn_free_tail != NULL)
{
a->lpn_free_tail->next = NULL;
}
else
{
a->lpn_free = NULL;
}
a->commit_amount -= (i64) (((usize) node->page - (usize) node->alloc_base) + (usize) node->size);
VirtualFree(node->alloc_base, 0, MEM_RELEASE);
}
}
static void uha_free_unlocked(uh_allocator* a, void* ptr)
{
uh_page_header* header;
if (ptr == NULL)
{
return;
}
header = (uh_page_header*) ((usize) ptr & ~((usize) UH_BINPAGESIZE - 1));
if (header->kind == UH_PAGE_KIND_LARGE)
{
uh_large_page_node* node = (uh_large_page_node*) ((usize) header + UH_ALIGN16(sizeof(uh_page_header)));
if (node->prev != NULL)
{
node->prev->next = node->next;
}
else
{
a->lpn = node->next;
}
if (node->next != NULL)
{
node->next->prev = node->prev;
}
else
{
a->lpn_free_tail = node->prev;
}
node->prev = NULL;
node->next = a->lpn_free;
if (a->lpn_free != NULL)
{
a->lpn_free->prev = node;
}
else
{
a->lpn_free_tail = node;
}
a->lpn_free = node;
node->free_time = uh_time();
UH_ASAN_POISON(node->page, (usize) node->size);
a->alloc_amount -= node->size;
a->alloc_count -= 1;
uh_allocator_trim_large_pages(a, node->free_time);
return;
}
if (header->kind == UH_PAGE_KIND_SLAB)
{
uha_block_bin* bin = &a->bins[header->bin_index];
const i32 slots_per_page = (UH_BINPAGESIZE - UH_SLAB_OFFSET) / bin->block_size;
const usize offset = (usize) ptr - ((usize) header + UH_SLAB_OFFSET);
const i32 slot_index = (i32) (offset / (usize) bin->block_size);
const i32 page_index = (i32) header->rsvd;
const usize free_list_size = sizeof(i32) * (usize) (slots_per_page * bin->page_cap);
if (bin->free_list == NULL)
{
bin->free_list = (i32*) VirtualAlloc(NULL, free_list_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (bin->free_list == NULL)
{
uh_panic("failed to alloc slab free_list");
}
UH_ASAN_UNPOISON(bin->free_list, free_list_size);
a->commit_amount += (i64) free_list_size;
}
bin->free_list[bin->free_count] = page_index * slots_per_page + slot_index;
bin->free_count += 1;
UH_ASAN_POISON(ptr, (usize) bin->block_size);
a->alloc_amount -= bin->block_size;
a->alloc_count -= 1;
return;
}
uh_panic("invalid page kind in uha_free");
}
static void uha_free(uh_allocator* a, void* ptr)
{
uh_mutex_lock(&a->mutex);
uha_free_unlocked(a, ptr);
uh_mutex_unlock(&a->mutex);
}
static void* uha_alloc_linkedlist(uh_allocator* a, usize size)
{
uh_page_header* header;
uh_large_page_node* node;
uh_large_page_node* free_node;
usize node_offset;
usize payload_offset;
usize total_size;
free_node = a->lpn_free;
while (free_node != NULL)
{
if ((usize) free_node->size >= size)
{
if (free_node->prev != NULL)
{
free_node->prev->next = free_node->next;
}
else
{
a->lpn_free = free_node->next;
}
if (free_node->next != NULL)
{
free_node->next->prev = free_node->prev;
}
else
{
a->lpn_free_tail = free_node->prev;
}
free_node->prev = NULL;
free_node->next = a->lpn;
if (a->lpn != NULL)
{
a->lpn->prev = free_node;
}
a->lpn = free_node;
free_node->size = (i64) size;
UH_ASAN_UNPOISON(free_node->page, size);
a->alloc_amount += (i64) size;
a->alloc_count += 1;
return free_node->page;
}
free_node = free_node->next;
}
node_offset = UH_ALIGN16(sizeof(uh_page_header));
payload_offset = UH_ALIGN16(node_offset + sizeof(uh_large_page_node));
total_size = payload_offset + size;
header = (uh_page_header*) VirtualAlloc(NULL, total_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (header == NULL)
{
return NULL;
}
UH_ASAN_UNPOISON(header, total_size);
header->kind = UH_PAGE_KIND_LARGE;
header->bin_index = -1;
header->payload_offset = (u32) payload_offset;
header->rsvd = 0;
node = (uh_large_page_node*) ((usize) header + node_offset);
node->alloc_base = header;
node->page = (void*) ((usize) header + payload_offset);
node->size = (i64) size;
node->free_time = 0;
node->next = a->lpn;
node->prev = NULL;
if (a->lpn != NULL)
{
a->lpn->prev = node;
}
a->lpn = node;
a->commit_amount += (i64) total_size;
a->alloc_amount += (i64) size;
a->alloc_count += 1;
UH_ASAN_POISON(node->page, size);
UH_ASAN_UNPOISON(node->page, size);
return node->page;
}
static uha_block_bin* uha_ensure_block(uh_allocator* a, usize block_index)
{
// for a given block index, ensure that there is a new page allocated if need be
uha_block_bin* bin = &a->bins[block_index];
const i32 slots_per_page = (UH_BINPAGESIZE - UH_SLAB_OFFSET) / bin->block_size;
const i32 total_slots = bin->page_count * slots_per_page;
if (!bin)
{
uh_panic("invalid bin setup");
}
// early out if something is available in the free_count
if (bin->free_count > 0)
{
return bin;
}
if (bin->next >= total_slots)
{
// append a new page
if(bin->page_count + 1 > bin->page_cap)
{
uh_panic("pages exceeded TODO, implement");
}
bin->pages[bin->page_count] = VirtualAlloc(NULL, UH_BINPAGESIZE, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if(!bin->pages[bin->page_count])
{
uh_panic("failed to alloc page");
}
UH_ASAN_UNPOISON(bin->pages[bin->page_count], UH_BINPAGESIZE);
((uh_page_header*) bin->pages[bin->page_count])->kind = UH_PAGE_KIND_SLAB;
((uh_page_header*) bin->pages[bin->page_count])->bin_index = (i32) block_index;
((uh_page_header*) bin->pages[bin->page_count])->payload_offset = UH_SLAB_OFFSET;
((uh_page_header*) bin->pages[bin->page_count])->rsvd = (u32) bin->page_count;
UH_ASAN_POISON(
(void*) ((usize) bin->pages[bin->page_count] + UH_SLAB_OFFSET),
(usize) slots_per_page * (usize) bin->block_size);
bin->page_count += 1;
bin->cap += slots_per_page;
a->commit_amount += UH_BINPAGESIZE;
}
return bin;
}
static void* uha_block_alloc(uh_allocator* a, uha_block_bin* bin)
{
a->alloc_count += 1;
a->alloc_amount += bin->block_size;
i32 index = -1;
if (bin->free_count > 0)
{
bin->free_count -= 1;
index = bin->free_list[bin->free_count];
}
if (index == -1)
{
index = bin->next;
bin->next += 1;
}
if (index == -1)
{
uh_panic("unable to assign index?");
return NULL;
}
const i32 slots_per_page = (UH_BINPAGESIZE - UH_SLAB_OFFSET) / bin->block_size;
const i32 page_index = index / slots_per_page;
const i32 slot_index = index % slots_per_page;
void* ptr = (void*) ((usize)(bin->pages[page_index]) + UH_SLAB_OFFSET + (slot_index * bin->block_size));
UH_ASAN_UNPOISON(ptr, (usize) bin->block_size);
return ptr;
}
static void* uha_alloc_unlocked(uh_allocator* a, usize size)
{
if (size > UH_SLABMAX)
{
// appends an allocation to the linkedlist
return uha_alloc_linkedlist(a, size);
}
void* rv = NULL;
for (i32 i = 0; i < (i32)(UH_BINSCOUNT); i += 1)
{
if(size <= uh_bins_size_list[i])
{
// bin found calculate slot index
uha_block_bin* bin = uha_ensure_block(a, i);
// grab the latest slot from the bin and return that
rv = uha_block_alloc(a, bin);
break;
}
}
return rv;
}
static void* uha_alloc(uh_allocator* a, usize size)
{
void* rv;
uh_mutex_lock(&a->mutex);
rv = uha_alloc_unlocked(a, size);
uh_mutex_unlock(&a->mutex);
return rv;
}
// a linear allocator that uha can hand to you whenever.
// starts at 8k and hands out 64k pages after the first page is exhausted
#define UH_LA_PAGESIZE0 8176
#define UH_LA_PAGESIZE 64 * 1024
#define UH_LA_INITIAL_PAGECOUNT 8
typedef struct uh_linear_arena {
uh_allocator* backing;
void** pages;
i32 page_count;
i32 page_cap;
i32 current_page;
usize current_offset;
uh_large_page_node* lpn;
uh_large_page_node* free_list;
i64 amount; // amount allocated, not amount in page
i64 commit; // total bytes requested from the backing allocator
}uh_linear_arena;
static usize uh_linear_arena_page_size_for_index(i32 page_index)
{
return page_index == 0 ? UH_LA_PAGESIZE0 : UH_LA_PAGESIZE;
}
static usize uh_linear_arena_align_forward(usize value, usize align)
{
const usize mask = align - 1;
return (value + mask) & ~mask;
}
static void uh_linear_arena_reset(uh_linear_arena* arena)
{
arena->amount = 0;
arena->current_page = 0;
arena->current_offset = 0;
if (arena->free_list != NULL)
{
uh_large_page_node* tail = arena->free_list;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = arena->lpn;
if (arena->lpn != NULL)
{
arena->lpn->prev = tail;
}
}
else
{
arena->free_list = arena->lpn;
}
if (arena->lpn != NULL)
{
arena->lpn->prev = NULL;
}
arena->lpn = NULL;
}
static b8 uh_linear_arena_push_page(uh_linear_arena* arena)
{
if (arena->page_count + 1 > arena->page_cap)
{
i32 new_cap = arena->page_cap * 2;
void** new_pages = uha_alloc(arena->backing, sizeof(void*) * new_cap);
if (!new_pages)
{
return BAD;
}
for (i32 i = 0; i < arena->page_count; i += 1)
{
new_pages[i] = arena->pages[i];
}
uha_free(arena->backing, arena->pages);
arena->pages = new_pages;
arena->page_cap = new_cap;
}
usize page_size = uh_linear_arena_page_size_for_index(arena->page_count);
void* page = uha_alloc(arena->backing, page_size);
if (!page)
{
return BAD;
}
arena->pages[arena->page_count] = page;
arena->page_count += 1;
arena->commit += page_size;
return OK;
}
static void* uh_linear_arena_alloc_large(uh_linear_arena* arena, usize size)
{
uh_large_page_node* node = arena->free_list;
while (node != NULL)
{
if ((usize) node->size >= size)
{
if (node->prev != NULL)
{
node->prev->next = node->next;
}
else
{
arena->free_list = node->next;
}
if (node->next != NULL)
{
node->next->prev = node->prev;
}
node->prev = NULL;
node->next = arena->lpn;
if (arena->lpn != NULL)
{
arena->lpn->prev = node;
}
arena->lpn = node;
arena->amount += (i64) size;
return node->page;
}
node = node->next;
}
node = (uh_large_page_node*) uha_alloc(arena->backing, sizeof(uh_large_page_node));
if (node == NULL)
{
return NULL;
}
node->page = uha_alloc(arena->backing, size);
if (node->page == NULL)
{
uha_free(arena->backing, node);
return NULL;
}
node->alloc_base = node->page;
node->size = (i64) size;
node->prev = NULL;
node->next = arena->lpn;
if (arena->lpn != NULL)
{
arena->lpn->prev = node;
}
arena->lpn = node;
arena->amount += (i64) size;
arena->commit += (i64) size + (i64) sizeof(uh_large_page_node);
return node->page;
}
static void* uh_linear_arena_alloc(uh_linear_arena* arena, usize size, usize align)
{
usize offset;
usize page_size;
void* page;
if (align == 0)
{
align = 1;
}
if ((align & (align - 1)) != 0)
{
uh_panic("uh_linear_arena_alloc requires power-of-two alignment");
}
if (size > UH_LA_PAGESIZE)
{
return uh_linear_arena_alloc_large(arena, size);
}
if (arena->page_count == 0)
{
if (!uh_linear_arena_push_page(arena))
{
return NULL;
}
}
for (;;)
{
page_size = uh_linear_arena_page_size_for_index(arena->current_page);
offset = uh_linear_arena_align_forward(arena->current_offset, align);
if (offset + size <= page_size)
{
page = arena->pages[arena->current_page];
arena->current_offset = offset + size;
arena->amount += (i64) size;
return (void*) ((usize) page + offset);
}
arena->current_page += 1;
arena->current_offset = 0;
if (arena->current_page >= arena->page_count)
{
if (!uh_linear_arena_push_page(arena))
{
return NULL;
}
}
}
}
static b8 uh_new_arena(uh_allocator* backing, uh_linear_arena* arena)
{
arena->backing = backing;
arena->pages = uha_alloc(backing, UH_LA_INITIAL_PAGECOUNT * sizeof(void*)); // init 8 page slots first
arena->page_cap = UH_LA_INITIAL_PAGECOUNT;
arena->page_count = 0;
arena->current_page = 0;
arena->current_offset = 0;
arena->amount = 0;
arena->commit = 0;
arena->lpn = NULL;
arena->free_list = NULL;
if(!arena->pages)
{
uh_panic("unable to init arena");
}
arena->commit += (i64) (UH_LA_INITIAL_PAGECOUNT * sizeof(void*));
if (!uh_linear_arena_push_page(arena))
{
uh_panic("unable to push initial arena page");
}
return OK;
}
static void uh_destroy_arena(uh_linear_arena* arena)
{
uh_allocator* a = arena->backing;
i32 i;
uh_large_page_node* lpn_dead;
for (i = 0; i < arena->page_count; i += 1)
{
uha_free(a, arena->pages[i]);
}
lpn_dead = arena->lpn;
while(lpn_dead != NULL)
{
uh_large_page_node* lpn_next = lpn_dead->next;
uha_free(a, lpn_dead->page);
uha_free(a, lpn_dead);
lpn_dead = lpn_next;
}
lpn_dead = arena->free_list;
while(lpn_dead != NULL)
{
uh_large_page_node* lpn_next = lpn_dead->next;
uha_free(a, lpn_dead->page);
uha_free(a, lpn_dead);
lpn_dead = lpn_next;
}
uha_free(a, arena->pages);
}
UHEXTERN_C_END
#endif // __UH_ALLOCATOR