Add pool allocator

This commit is contained in:
2026-07-26 17:37:09 +01:00
parent cc2d0cea6e
commit 4bbd7dfe01
2 changed files with 176 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
// vim:fileencoding=utf-8:foldmethod=marker
#include "pr_pool_allocator.h"
#include "../../vendor/wapp/os/mem/mem_os.h"
#include <string.h>
// ---------------------------------------------------------------------------
// 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;
}
+63
View File
@@ -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