From 4bbd7dfe012ed3a0d4e16ed0538bc1f1f59ef1f9 Mon Sep 17 00:00:00 2001 From: Abdelrahman Date: Sun, 26 Jul 2026 17:37:09 +0100 Subject: [PATCH] Add pool allocator --- src/prism/allocators/pr_pool_allocator.c | 113 +++++++++++++++++++++++ src/prism/allocators/pr_pool_allocator.h | 63 +++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 src/prism/allocators/pr_pool_allocator.c create mode 100644 src/prism/allocators/pr_pool_allocator.h diff --git a/src/prism/allocators/pr_pool_allocator.c b/src/prism/allocators/pr_pool_allocator.c new file mode 100644 index 0000000..baf81b9 --- /dev/null +++ b/src/prism/allocators/pr_pool_allocator.c @@ -0,0 +1,113 @@ +// vim:fileencoding=utf-8:foldmethod=marker + +#include "pr_pool_allocator.h" +#include "../../vendor/wapp/os/mem/mem_os.h" +#include + +// --------------------------------------------------------------------------- +// Intrusive free list node — reuses the first sizeof(void*) bytes of each slot +// --------------------------------------------------------------------------- + +typedef struct PrPoolFreeNode PrPoolFreeNode; +struct PrPoolFreeNode { + PrPoolFreeNode *next; +}; + +// --------------------------------------------------------------------------- +// Internal: allocate a new block and carve it into the free list +// --------------------------------------------------------------------------- + +wp_intern b8 _prPoolGrow(PrPool *pool) { + // Grow blocks array if full + if (pool->block_count == pool->block_cap) { + u64 new_cap = pool->block_cap ? pool->block_cap * 2 : 4; + u64 new_size = sizeof(void *) * new_cap; + void **new_arr = (void **)wpOsMemAlloc(NULL, new_size, WP_MEM_ACCESS_READ_WRITE, + WP_MEM_ALLOC_RESERVE, WP_MEM_INIT_UNINITIALISED); + if (!new_arr) { return false; } + + if (pool->blocks) { + memcpy(new_arr, pool->blocks, sizeof(void *) * pool->block_count); + wpOsMemFree(pool->blocks, sizeof(void *) * pool->block_cap); + } + + pool->blocks = new_arr; + pool->block_cap = new_cap; + } + + // Allocate the block + u64 block_bytes = pool->alloc_size * pool->block_slots; + void *block = wpOsMemAlloc(NULL, block_bytes, WP_MEM_ACCESS_READ_WRITE, + WP_MEM_ALLOC_RESERVE, WP_MEM_INIT_UNINITIALISED); + if (!block) { return false; } + + pool->blocks[pool->block_count++] = block; + pool->total += pool->block_slots; + + // Carve into free list (in reverse so the first slot ends up on top) + u8 *bytes = (u8 *)block; + for (u64 i = pool->block_slots; i > 0; --i) { + PrPoolFreeNode *node = (PrPoolFreeNode *)bytes; + node->next = (PrPoolFreeNode *)pool->free_list; + pool->free_list = node; + bytes += pool->alloc_size; + } + + return true; +} + +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- + +void prPoolInit(PrPool *pool, u64 slot_size, u64 initial_slots) { + memset(pool, 0, sizeof(*pool)); + pool->slot_size = slot_size; + pool->alloc_size = slot_size < sizeof(PrPoolFreeNode) ? sizeof(PrPoolFreeNode) : slot_size; + + // Enforce minimum block size of 4096 bytes + u64 min_slots = (4096 + pool->alloc_size - 1) / pool->alloc_size; + pool->block_slots = initial_slots > min_slots ? initial_slots : min_slots; + + _prPoolGrow(pool); +} + +void *prPoolAlloc(PrPool *pool) { + // Grow if free list is empty + if (!pool->free_list) { + if (!_prPoolGrow(pool)) { return NULL; } + } + + PrPoolFreeNode *node = (PrPoolFreeNode *)pool->free_list; + pool->free_list = node->next; + pool->active++; + return node; +} + +void prPoolFree(PrPool *pool, void *slot) { + if (!slot) { return; } + + PrPoolFreeNode *node = (PrPoolFreeNode *)slot; + node->next = (PrPoolFreeNode *)pool->free_list; + pool->free_list = node; + pool->active--; +} + +void prPoolDestroy(PrPool *pool) { + u64 block_bytes = pool->alloc_size * pool->block_slots; + for (u64 i = 0; i < pool->block_count; ++i) { + wpOsMemFree(pool->blocks[i], block_bytes); + } + if (pool->blocks) { + wpOsMemFree(pool->blocks, sizeof(void *) * pool->block_cap); + } + memset(pool, 0, sizeof(*pool)); +} + +u64 prPoolTotalSlots(const PrPool *pool) { + return pool->total; +} + +u64 prPoolActiveSlots(const PrPool *pool) { + return pool->active; +} diff --git a/src/prism/allocators/pr_pool_allocator.h b/src/prism/allocators/pr_pool_allocator.h new file mode 100644 index 0000000..189ecf1 --- /dev/null +++ b/src/prism/allocators/pr_pool_allocator.h @@ -0,0 +1,63 @@ +// vim:fileencoding=utf-8:foldmethod=marker + +#ifndef PR_POOL_ALLOCATOR_H +#define PR_POOL_ALLOCATOR_H + +#include "../../vendor/wapp/common/aliases/aliases.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// --------------------------------------------------------------------------- +// Pool allocator — fixed-size slot allocator with self-managed growth +// +// Manages fixed-size slots arranged in contiguous blocks. The pool owns its +// memory (via wapp OS allocation) and grows on demand when the free list is empty. +// +// Usage: +// PrPool pool; +// prPoolInit(&pool, sizeof(Edge), 64); +// Edge *e = prPoolAlloc(&pool); +// prPoolFree(&pool, e); +// prPoolDestroy(&pool); +// --------------------------------------------------------------------------- + +typedef struct PrPool PrPool; + +struct PrPool { + void **blocks; // array of allocated block pointers (for destroy) + u64 block_count; // number of allocated blocks + u64 block_cap; // capacity of blocks array + void *free_list; // intrusive free list head + u64 slot_size; // user-requested slot size + u64 alloc_size; // actual slot size used internally (>= slot_size, >= sizeof(void*)) + u64 block_slots; // slots per block + u64 total; // total slots ever allocated (diagnostics) + u64 active; // currently in use (diagnostics) +}; + +// Initialise a pool. +// slot_size: fixed size of each slot +// initial_slots: starting capacity in slots (also used as block size) +void prPoolInit(PrPool *pool, u64 slot_size, u64 initial_slots); + +// Allocate one slot. Grows by a new block if the free list is empty. +// Returns NULL only on allocation failure. +void *prPoolAlloc(PrPool *pool); + +// Return a slot to the pool's free list. Safe no-op on NULL. +void prPoolFree(PrPool *pool, void *slot); + +// Free all blocks and zero the pool. +void prPoolDestroy(PrPool *pool); + +// Diagnostics +u64 prPoolTotalSlots(const PrPool *pool); +u64 prPoolActiveSlots(const PrPool *pool); + +#ifdef __cplusplus +} +#endif + +#endif // !PR_POOL_ALLOCATOR_H