Add allocator interface

This commit is contained in:
Nils O. Selåsdal
2026-06-21 23:41:27 +02:00
parent 1010541db7
commit e52051b859
7 changed files with 218 additions and 82 deletions
-55
View File
@@ -174,58 +174,3 @@ void arena_reset(Arena *arena)
arena_reset_to(arena, 0); arena_reset_to(arena, 0);
} }
// Pool
APool *apool_create_ex(Arena *arena, U32 n_elements, U32 element_size, U32 element_align)
{
Assert(IsPow2(element_align));
U32 align = element_align;
if (element_align < alignof(APoolElement)) {
element_align = alignof(APoolElement);
}
U32 size = AlignUp(element_size, element_align);
APool *pool = arena_push_type(arena, APool);
Assert(pool != NULL);
pool->align = align;
pool->element_size = size;
pool->arena = arena;
pool->n_elements = n_elements;
pool->free_list = NULL;
pool->start = NULL;
if (size != APOOL_AUTOGROW) {
U32 total_size = n_elements * size; // Note(nos): guard against unreasonable sizes
U8 * start = arena_push(arena, total_size, align);
for (U32 i = 0; i < total_size; i += size) {
APoolElement *element = (APoolElement *)&start[i];
element->next = pool->free_list;
pool->free_list = element;
}
pool->start = start;
}
return pool;
}
void *apool_alloc(APool *pool)
{
APoolElement *element = pool->free_list;
if (element != NULL) {
pool->free_list = element->next;
} else if(pool->n_elements == APOOL_AUTOGROW) {
element = arena_push(pool->arena, pool->element_size, pool->align);
}
return element;
}
void apool_free(APool *pool, void *ptr)
{
if (ptr != NULL) {
APoolElement *element = (APoolElement *)ptr;
element->next = pool->free_list;
pool->free_list = element;
}
}
+23 -21
View File
@@ -73,26 +73,28 @@ static INLINE U64 arena_used(const Arena *arena)
} }
typedef union APoolElement APoolElement; static INLINE void *arena_allocator_func(Allocator *allocator, AllocOperation operation , void *memory, U32 size, U32 align)
union APoolElement { {
APoolElement *next; // offset from start to next free block (when not allocated) switch (operation) {
}; case Alloc_Op_Aligned:
return arena_push(allocator->data, size, align);
typedef struct APool APool; case Alloc_Op_Free: // can't free from an marena
struct APool { return NULL;
APoolElement *free_list; // free list
U8 *start; // start of user buffer
Arena *arena;
U32 element_size; // aligned size of each block
U32 n_elements; // total number of blocks
U32 align; // alignment
};
enum {
APOOL_AUTOGROW = 0
};
#define apool_create(arena, n_elements, type) \ case Alloc_Op_Free_All:
apool_create_ex(arena, n_elements, sizeof(type), alignof(type)); arena_free(allocator->data);
APool *apool_create_ex(Arena *arena, U32 n_elements, U32 element_size, uint32_t element_align); return NULL;
void *apool_alloc(APool *pool); }
void apool_free(APool *pool, void *element);
Assert(false);
}
static INLINE Allocator arena_allocator(Arena *arena)
{
Allocator allocator = {};
allocator.op_func_ptr = arena_allocator_func;
allocator.data = arena;
return allocator;
}
+40
View File
@@ -344,6 +344,46 @@ static INLINE U64 SaturatingMult64(U64 a, U64 b)
return SaturatingMult(a, b, U64, U64_Max); return SaturatingMult(a, b, U64, U64_Max);
} }
// Allocators
typedef enum AllocOperation AllocOperation;
enum AllocOperation {
Alloc_Op_Aligned = 1,
Alloc_Op_Free,
Alloc_Op_Free_All,
};
typedef struct Allocator Allocator;
typedef void *(*AllocOpFunc)(Allocator *allocator, AllocOperation operation, void *memory, U32 size, U32 align);
struct Allocator {
AllocOpFunc op_func_ptr;
void *data;
};
static INLINE void *allocator_alloc_aligned(Allocator *allocator, U32 size, U32 align)
{
return allocator->op_func_ptr(allocator, Alloc_Op_Aligned, NULL, size, align);
}
static INLINE void allocator_free(Allocator *allocator, void *memory)
{
allocator->op_func_ptr(allocator, Alloc_Op_Free, memory, 0, 0);
}
static INLINE void allocator_free_all(Allocator *allocator)
{
allocator->op_func_ptr(allocator, Alloc_Op_Free_All, NULL, 0, 0);
}
#define allocator_push_type(allocator, type_)\
(allocator)->op_func_ptr((allocator), Alloc_Op_Aligned, NULL, sizeof(type_), _Alignof(type_))
#define allocator_push_array(allocator, type_, nr_elements)\
(allocator)->op_func_ptr((allocator), Alloc_Op_Aligned, NULL, sizeof(type_) * (nr_elements), _Alignof(type_) )
#define allocator_push(allocator, size, align)\
(allocator)->op_func_ptr((allocator), Alloc_Op_Aligned, NULL, size, align)
// address-sanitizer helpers (-fsanitize=address) // address-sanitizer helpers (-fsanitize=address)
#ifdef USE_ASAN #ifdef USE_ASAN
#include <sanitizer/asan_interface.h> #include <sanitizer/asan_interface.h>
+49 -5
View File
@@ -2,6 +2,7 @@
#include "arena.h" #include "arena.h"
#include "mini_arena.h" #include "mini_arena.h"
#include "platform.h" #include "platform.h"
#include "pool.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -163,6 +164,40 @@ static void arena_test_4(void)
arena_free(&arena); arena_free(&arena);
} }
static void arena_test_allocator_interface(void)
{
TRACEF("\n");
U32 page_size = platform_page_size();
ArenaOptions arena_params = {};
U32 arena_size = MB(1) + MemoryBlock_Header_Size;
U32 commit_size = KB(64);
arena_params.size = arena_size;
arena_params.commit_size = commit_size;
Arena arena;
arena_init(&arena, &arena_params);
Allocator allocator = arena_allocator(&arena);
// block_size is the page-aligned arena_size. Request nearly the whole block
// but with page alignment, so the alignment gap (a full page) forces a grow.
U32 block_size = arena.block_size;
U32 push_size = block_size - MemoryBlock_Header_Size;
void *ptr = allocator_push(&allocator, push_size, page_size);
Assert(IsAlignedTo((uintptr_t)ptr, page_size));
MemoryZero(ptr, push_size);
printf("test_4: allocated %u bytes page-aligned at %p\n", push_size, ptr);
arena_dump(&arena);
// A second page-aligned push to make sure the cursor math still holds.
ptr = allocator_push(&allocator, page_size, page_size);
Assert(IsAlignedTo((uintptr_t)ptr, page_size));
MemoryZero(ptr, page_size);
printf("test_4: allocated %u bytes page-aligned at %p\n", page_size, ptr);
arena_free(&arena);
}
static void arena_test_decommit(void) static void arena_test_decommit(void)
{ {
TRACEF("\n"); TRACEF("\n");
@@ -192,6 +227,8 @@ static void arena_test_decommit(void)
void miniarena_test1(void) void miniarena_test1(void)
{ {
TRACEF("\n");
#define arena_size 4096 #define arena_size 4096
MARENA_DEFINE(arena, arena_size, 8); MARENA_DEFINE(arena, arena_size, 8);
int i; int i;
@@ -228,9 +265,12 @@ struct PoolTest{
void apool_test(void) void apool_test(void)
{ {
TRACEF("\n");
Arena arena; Arena arena;
arena_init(&arena, ArenaOptions_Default); arena_init(&arena, ArenaOptions_Default);
APool *pool = apool_create_ex(&arena, 16, sizeof(PoolTest), alignof(PoolTest)); Allocator allocator = arena_allocator(&arena);
APool *pool = apool_create_ex(&allocator, 16, sizeof(PoolTest), alignof(PoolTest));
PoolTest *list = NULL; PoolTest *list = NULL;
for (U32 i = 0; i < U32_Max; i++) { for (U32 i = 0; i < U32_Max; i++) {
PoolTest *pt = apool_alloc(pool); PoolTest *pt = apool_alloc(pool);
@@ -271,9 +311,13 @@ void apool_test(void)
void apool_test_autogrow(void) void apool_test_autogrow(void)
{ {
TRACEF("\n");
Arena arena; Arena arena;
arena_init(&arena, ArenaOptions_Default); arena_init(&arena, ArenaOptions_Default);
APool *pool = apool_create(&arena, APOOL_AUTOGROW, PoolTest); Allocator allocator = arena_allocator(&arena);
APool *pool = apool_create(&allocator, APOOL_AUTOGROW, PoolTest);
PoolTest *list = NULL; PoolTest *list = NULL;
for (U32 i = 0; i < 4; i++) { for (U32 i = 0; i < 4; i++) {
PoolTest *pt = apool_alloc(pool); PoolTest *pt = apool_alloc(pool);
@@ -319,9 +363,9 @@ int main(int argc, char *argv[])
// arena_test_3(); // arena_test_3();
// arena_test_4(); // arena_test_4();
arena_test_decommit(); arena_test_decommit();
arena_test_allocator_interface();
// miniarena_test1(); // miniarena_test1();
// apool_test(); apool_test();
// apool_test_autogrow(); apool_test_autogrow();
} }
+26 -1
View File
@@ -71,7 +71,7 @@ static INLINE void *marena_push(MArena *arena, U32 size)
} }
[[gnu::alloc_align(3)]] [[gnu::alloc_align(3)]]
static INLINE void *maren_push_aligned(MArena *arena, U32 size, U32 align) static INLINE void *marena_push_aligned(MArena *arena, U32 size, U32 align)
{ {
Assert(IsPow2(align)); Assert(IsPow2(align));
@@ -94,3 +94,28 @@ static INLINE void marena_reset(MArena *arena)
{ {
arena->first_free = 0; arena->first_free = 0;
} }
static INLINE void *marena_allocator_func(Allocator *allocator, AllocOperation operation , void *memory, U32 size, U32 align)
{
switch (operation) {
case Alloc_Op_Aligned:
return marena_push_aligned(allocator->data, size, align);
case Alloc_Op_Free: // can't free/Free_all from an marena
return NULL;
case Alloc_Op_Free_All:
return NULL;
}
Assert(false);
}
static INLINE Allocator marena_allocator(MArena *arena)
{
Allocator allocator = {};
allocator.op_func_ptr = marena_allocator_func;
allocator.data = arena;
return allocator;
}
+54
View File
@@ -0,0 +1,54 @@
#include "pool.h"
APool *apool_create_ex(Allocator *allocator, U32 n_elements, U32 element_size, U32 element_align)
{
Assert(IsPow2(element_align));
U32 align = element_align;
if (element_align < alignof(APoolElement)) {
element_align = alignof(APoolElement);
}
U32 size = AlignUp(element_size, element_align);
APool *pool = allocator_push_type(allocator, APool);
Assert(pool != NULL);
pool->align = align;
pool->element_size = size;
pool->allocator = allocator;
pool->n_elements = n_elements;
pool->free_list = NULL;
pool->start = NULL;
if (size != APOOL_AUTOGROW) {
U32 total_size = n_elements * size; // Note(nos): guard against unreasonable sizes
U8 * start = allocator_push(allocator, total_size, align);
for (U32 i = 0; i < total_size; i += size) {
APoolElement *element = (APoolElement *)&start[i];
element->next = pool->free_list;
pool->free_list = element;
}
pool->start = start;
}
return pool;
}
void *apool_alloc(APool *pool)
{
APoolElement *element = pool->free_list;
if (element != NULL) {
pool->free_list = element->next;
} else if(pool->n_elements == APOOL_AUTOGROW) {
element = allocator_push(pool->allocator, pool->element_size, pool->align);
}
return element;
}
void apool_free(APool *pool, void *ptr)
{
if (ptr != NULL) {
APoolElement *element = (APoolElement *)ptr;
element->next = pool->free_list;
pool->free_list = element;
}
}
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include "base_core.h"
typedef union APoolElement APoolElement;
union APoolElement {
APoolElement *next; // offset from start to next free block (when not allocated)
};
typedef struct APool APool;
struct APool {
APoolElement *free_list; // free list
U8 *start; // start of user buffer
Allocator *allocator;
U32 element_size; // aligned size of each block
U32 n_elements; // total number of blocks
U32 align; // alignment
};
enum {
APOOL_AUTOGROW = 0
};
#define apool_create(arena, n_elements, type) \
apool_create_ex(arena, n_elements, sizeof(type), alignof(type));
APool *apool_create_ex(Allocator *allocator, U32 n_elements, U32 element_size, uint32_t element_align);
void *apool_alloc(APool *pool);
void apool_free(APool *pool, void *element);