From 65c424530f955055a97f57640caae6404cdaac92 Mon Sep 17 00:00:00 2001 From: Aryadev Chavali Date: Tue, 17 Mar 2026 20:56:00 +0000 Subject: [PATCH] prick_arena: major bug fixes and namespacing --- prick_arena.h | 144 +++++++++++++++++++++++++++----------------------- 1 file changed, 79 insertions(+), 65 deletions(-) diff --git a/prick_arena.h b/prick_arena.h index 08f6901..fc41301 100644 --- a/prick_arena.h +++ b/prick_arena.h @@ -25,28 +25,30 @@ #include -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); - region->next = next; - region->size = 0; - region->capacity = capacity; + capacity = MAX(capacity, REGION_DEFAULT_SIZE); + 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,9 +94,10 @@ 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); - region->next = new; - region = new; + prick_arena_region_t *new = + prick_region_make(capacity * REGION_CAPACITY_MULT, NULL); + region->next = new; + region = new; } uint8_t *start = region->bytes + region->size; @@ -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)); }