prick_arena: major bug fixes and namespacing

This commit is contained in:
2026-03-17 20:56:00 +00:00
parent 8a3ae735dc
commit 65c424530f

View File

@@ -25,28 +25,30 @@
#include <stdint.h>
typedef struct Region
// A region is a fixed sized allocation on the heap. Allocations against a
// region will depend on the difference between `size` and `capacity` and may
// fail if there isn't enough free space. Regions are arranged in a linked list
// to allow for recursion on failure to allocate.
typedef struct PrickArenaRegion
{
struct Region *next;
struct PrickArenaRegion *next;
uint32_t size, capacity;
uint8_t bytes[];
} region_t;
region_t *region_make(uint32_t capacity, region_t *next);
uint8_t *region_alloc_flat(region_t *region, uint32_t size);
uint8_t *region_alloc_rec(region_t *region, uint32_t size);
void region_delete_rec(region_t *region);
} prick_arena_region_t;
// An arena is simply a linked list of regions.
typedef struct
{
region_t *beg, *end;
} arena_t;
prick_arena_region_t *beg, *end;
} prick_arena_t;
uint8_t *arena_alloc(arena_t *arena, uint32_t size);
uint8_t *arena_realloc(arena_t *arena, uint8_t *pointer, uint32_t old_size,
uint32_t new_size);
void arena_reset(arena_t *arena);
void arena_free(arena_t *arena);
uint8_t *prick_arena_alloc(prick_arena_t *arena, uint32_t size);
uint8_t *prick_arena_realloc(prick_arena_t *arena, uint8_t *pointer,
uint32_t old_size, uint32_t new_size);
// Reset the arena but do not delete any regions, allowing for fresh allocations
// to take place.
void prick_arena_reset(prick_arena_t *arena);
void prick_arena_free(prick_arena_t *arena);
#ifdef PRICK_ARENA_IMPL
@@ -61,20 +63,26 @@ void arena_free(arena_t *arena);
#define REGION_CAPACITY_MULT 2
#endif
#ifndef MAX
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#endif
region_t *region_make(uint32_t capacity, region_t *next)
#ifndef MIN
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#endif
prick_arena_region_t *prick_region_make(uint32_t capacity,
prick_arena_region_t *next)
{
capacity = MAX(capacity, REGION_DEFAULT_SIZE);
region_t *region = calloc(1, sizeof(*region) + capacity);
prick_arena_region_t *region = calloc(1, sizeof(*region) + capacity);
region->next = next;
region->size = 0;
region->capacity = capacity;
return region;
}
uint8_t *region_alloc_rec(region_t *region, uint32_t capacity)
uint8_t *prick_region_alloc_rec(prick_arena_region_t *region, uint32_t capacity)
{
if (!region)
return NULL;
@@ -86,7 +94,8 @@ uint8_t *region_alloc_rec(region_t *region, uint32_t capacity)
if (region->capacity - region->size < capacity)
{
// no region->next, so make a new region that can fit the capacity required.
region_t *new = region_make(capacity * REGION_CAPACITY_MULT, NULL);
prick_arena_region_t *new =
prick_region_make(capacity * REGION_CAPACITY_MULT, NULL);
region->next = new;
region = new;
}
@@ -96,81 +105,86 @@ uint8_t *region_alloc_rec(region_t *region, uint32_t capacity)
return start;
}
void region_delete_rec(region_t *region)
void prick_region_delete_rec(prick_arena_region_t *region)
{
for (region_t *next = NULL; region;
for (prick_arena_region_t *next = NULL; region;
next = region->next, free(region), region = next)
continue;
}
uint8_t *arena_alloc(arena_t *arena, uint32_t size)
uint8_t *prick_arena_alloc(prick_arena_t *arena, uint32_t size)
{
if (!arena->beg)
{
arena->beg = region_make(size, NULL);
arena->beg = prick_region_make(size, NULL);
arena->end = arena->beg;
}
uint8_t *start = region_alloc_rec(arena->beg, size);
uint8_t *start = prick_region_alloc_rec(arena->beg, size);
// if we've attached a new region, end needs to be at that region
if (arena->end->next)
arena->end = arena->end->next;
return start;
}
uint8_t *arena_realloc(arena_t *arena, uint8_t *pointer, uint32_t old_size,
uint32_t new_size)
uint8_t *prick_arena_realloc(prick_arena_t *arena, uint8_t *ptr,
uint32_t old_size, uint32_t new_size)
{
if (!pointer)
if (!ptr)
{
// Basically the same as allocating at this point
return arena_alloc(arena, newsize);
// Firstly find the region the pointer exists in
region_t *prev, *reg;
for (prev = NULL, reg = arena->beg;
reg &&
!(reg->bytes <= pointer && reg->bytes + reg->size >= pointer + old_size);
prev = reg, reg = reg->next)
continue;
uint8_t *new_ptr = NULL;
// pointer isn't allocated in the arena, just allocate a new pointer
if (!reg)
goto arena_realloc__allocate_new;
return prick_arena_alloc(arena, new_size);
}
/*
If `ptr` is the latest allocation in `reg` and `reg` has enough capacity to
handle newsize, then we can adjust `reg` and not have to do any further
allocation work.
This check is not UB because ptr is confirmed to be in reg.
There are two options for reallocation strategy depending on where `ptr`
lies in its parent region (NOTE: certainly there can only be one parent
region):
1) If `ptr` lies at the end of the allocation and there is enough space to
fit the new_size requirement, just bump the parent region's size and
return the pointer (nearly "for free")
2) Otherwise, we must allocate a new block of memory and copy data over.
*/
if (ptr + oldsize == reg->bytes + reg->size &&
(reg->capacity - reg->size) > (newsize - oldsize))
// Linear search through our regions until we find parent region for `ptr`.
// FIXME: I'm pretty sure this is technically UB since we will almost
// certainly be comparing different batch allocations. Shame!
prick_arena_region_t *parent = NULL;
for (parent = arena->beg;
parent && !(parent->bytes <= ptr &&
parent->bytes + parent->size >= ptr + old_size);
parent = parent->next)
{
reg->size += newsize - oldsize;
continue;
}
// If `parent` is not NULL, then it is the region hosting `ptr`. Check if it
// satisfies the "lies at the end" condition.
if (parent && (ptr + old_size == parent->bytes + parent->size) &&
((parent->capacity - parent->size) > (new_size - old_size)))
{
parent->size += new_size - old_size;
return ptr;
}
arena_realloc__allocate_new:
new_ptr = arena_alloc(arena, newsize);
memcpy(new_ptr, ptr, oldsize);
// Otherwise, we'll need to reallocate.
uint8_t *new_ptr = prick_arena_alloc(arena, new_size);
memcpy(new_ptr, ptr, old_size);
return new_ptr;
}
void arena_reset(arena_t *arena)
void prick_arena_reset(prick_arena_t *arena)
{
for (region_t *region = arena->beg; region; region = region->next)
for (prick_arena_region_t *region = arena->beg; region; region = region->next)
{
region->size = 0;
memset(region->bytes, 0, region->capacity);
memset(region->bytes, 0, region->size);
}
}
void arena_free(arena_t *arena)
void prick_arena_free(prick_arena_t *arena)
{
region_delete_rec(arena->beg);
prick_region_delete_rec(arena->beg);
memset(arena, 0, sizeof(*arena));
}