Compare commits

...

10 Commits

Author SHA1 Message Date
Aryadev Chavali
c93d358b6a Add types.h for useful type aliases
So simple it's not got any functions.
2025-04-09 22:55:37 +01:00
Aryadev Chavali
4218fa3a2c Translate region_delete_rec while loop to for loop, minor edits 2025-04-09 22:52:58 +01:00
Aryadev Chavali
1dd0f8835c Fixed arena_realloc
So the real purpose of arena_realloc is to figure out if we can get
away with just adjusting the region that the pointer given resides in
so that we don't need to _actually_ allocate any new memory.

The previous implementation did this in the special case where the
pointer given _is_ the entire region.  But we can adjust the region's
size _if_ the pointer given is the last allocation on the region
i.e. it's on the tail end.
2025-04-09 22:49:21 +01:00
Aryadev Chavali
1b78493b19 Completely document arena's functions and make region public
If someone uses the arena functionality, they may as well get the
region functionality for free.  In particular, they may just want a
fixed bump allocator implementation which they can get here for free.
2024-11-01 08:16:03 +00:00
Aryadev Chavali
9427c3d324 Implement arena_reset 2024-11-01 08:15:46 +00:00
Aryadev Chavali
cf93eede6b region_delete->region_delete_rec and slightly rework arena_realloc
1) Better naming.

2) Use MIN macro for choosing the size to copy over from the old
buffer to the new one.  Also fix issue where we free the old region of
memory before copying it over to the new buffer.
2024-11-01 08:13:11 +00:00
Aryadev Chavali
46f62c5d2c Simple arena implementation using singly linked list
Manages individual allocations via a bump allocator (region), with
unfit allocations triggering a new region allocation that is added to
a linked list.  Any new region allocations will always be oversized
for the initially requested size, to amortize the cost of future
allocations.
2024-11-01 07:48:19 +00:00
Aryadev Chavali
f0f1e06e1e VEC_MULT may be set as user, so have a #if guard. 2024-10-24 03:17:54 +01:00
Aryadev Chavali
3a8f0c8d00 Use VEC_MULT in vec_ensure_remaining 2024-10-24 03:13:14 +01:00
Aryadev Chavali
14dea7c48c Clean up macros in vec.h 2024-10-24 03:13:03 +01:00
3 changed files with 277 additions and 7 deletions

241
arena.h Normal file
View File

@@ -0,0 +1,241 @@
/* Copyright (C) 2024 Aryadev Chavali
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Unlicense
* for details.
* You may distribute and modify this code under the terms of the
* Unlicense, which you should have received a copy of along with this
* program. If not, please go to <https://unlicense.org/>.
* Created: 2024-11-01
* Author: Aryadev Chavali
* Description: Arena allocator.
*/
#ifndef ARENA_H
#define ARENA_H
#include <stdint.h>
/**
@brief A single block of memory to be used by an arena.
@details Blocks of memory arranged in a singly linked list.
Each individual node is a bump allocator.
*/
typedef struct Region
{
struct Region *next;
uint32_t size, capacity;
uint8_t bytes[];
} region_t;
/**
@brief Allocate a new region on the heap with requested size and a pointer to
the next region.
@details If capacity is less than REGION_DEFAULT_SIZE, capacity is set to
REGION_DEFAULT_SIZE.
*/
region_t *region_make(uint32_t capacity, region_t *next);
/**
@brief Allocate memory of requested size on the region.
@details If the region cannot fit the requested size, then return NULL.
Otherwise return a pointer to the start of the allocated memory, incrementing
the region size appropriately.
*/
uint8_t *region_alloc_flat(region_t *region, uint32_t size);
/**
@brief Allocate memory of requested size on the region.
@details Iterates through the linked list of regions to find an appropriately
sized region for the requested size, allocating a new region if one cannot be
found. This new region will have capacity at least size *
REGION_CAPACITY_MULT.
Returns a pointer to the start of the allocated memory, incrementing the
appropriate region's size.
*/
uint8_t *region_alloc_rec(region_t *region, uint32_t size);
/**
@brief Delete a region, freeing its memory.
@details Will free all regions following it in the linked list.
*/
void region_delete_rec(region_t *region);
typedef struct
{
region_t *beg, *end;
} arena_t;
/**
@brief Allocate memory of requested size in the arena, returning a pointer to
the start of it.
@details Uses region_alloc_rec internally to allocate the memory required.
arena->beg and arena->end are set appropriately for this task.
*/
uint8_t *arena_alloc(arena_t *arena, uint32_t size);
/**
@brief Reallocate buffer of old_size to a buffer of new_size in the
arena, returning a pointer to the start of the new buffer.
@details If the pointer is not allocated in the arena, return NULL. If the
pointer and old_size cover a complete region reallocate the region itself to
fit the newly requested size, relinking it in the linked list. Otherwise,
allocate as per usual.
The contents of the old memory are copied into the new buffer. If old_size >
new_size, only new_size bytes will be copied from the old buffer into the new
one.
*/
uint8_t *arena_realloc(arena_t *arena, uint8_t *pointer, uint32_t old_size,
uint32_t new_size);
/**
@brief Reset the arena for reuse.
@details Sets all regions to default values, setting size to 0. No memory is
deleted in this operation.
*/
void arena_reset(arena_t *arena);
/**
@brief Free the memory associated with the arena.
@details Deletes all regions of memory associated in the arena.
*/
void arena_free(arena_t *arena);
#ifdef ARENA_IMPL
#include <malloc.h>
#include <string.h>
#ifndef REGION_DEFAULT_SIZE
#define REGION_DEFAULT_SIZE 512
#endif
#ifndef REGION_CAPACITY_MULT
#define REGION_CAPACITY_MULT 2
#endif
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#define MIN(A, B) ((A) < (B) ? (A) : (B))
region_t *region_make(uint32_t capacity, 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;
return region;
}
uint8_t *region_alloc_rec(region_t *region, uint32_t capacity)
{
if (!region)
return NULL;
for (; region->next && region->capacity - region->size < capacity;
region = region->next)
continue;
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;
}
uint8_t *start = region->bytes + region->size;
region->size += capacity;
return start;
}
void region_delete_rec(region_t *region)
{
for (region_t *next = NULL; region;
next = region->next, free(region), region = next)
continue;
}
uint8_t *arena_alloc(arena_t *arena, uint32_t size)
{
if (!arena->beg)
{
arena->beg = region_make(size, NULL);
arena->end = arena->beg;
}
uint8_t *start = 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)
{
if (!pointer)
// 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;
/*
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.
*/
if (ptr + oldsize == reg->bytes + reg->size &&
(reg->capacity - reg->size) > (newsize - oldsize))
{
reg->size += newsize - oldsize;
return ptr;
}
arena_realloc__allocate_new:
new_ptr = arena_alloc(arena, newsize);
memcpy(new_ptr, ptr, oldsize);
return new_ptr;
}
void arena_reset(arena_t *arena)
{
for (region_t *region = arena->beg; region; region = region->next)
{
region->size = 0;
memset(region->bytes, 0, region->capacity);
}
}
void arena_free(arena_t *arena)
{
region_delete_rec(arena->beg);
memset(arena, 0, sizeof(*arena));
}
#endif
#endif

30
types.h Normal file
View File

@@ -0,0 +1,30 @@
/* Copyright (C) 2025 Aryadev Chavali
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the Unlicense for details.
* You may distribute and modify this code under the terms of the Unlicense,
* which you should have received a copy of along with this program. If not,
* please go to <https://unlicense.org/>.
* Created: 2025-04-09
* Description: Some basic type definitions to make life easier.
*/
#ifndef TYPES_H
#define TYPES_H
#include <stdint.h>
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
#endif

13
vec.h
View File

@@ -36,19 +36,17 @@ void vec_append(void **ptr, void *data, uint32_t size);
void vec_clone(void **dest, void **src);
void vec_ensure_remaining(void **ptr, uint32_t space);
#define VEC_IMPL
#ifdef VEC_IMPL
#include <malloc.h>
#include <string.h>
#ifndef MAX
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#ifndef VEC_MULT
#define VEC_MULT 2
#endif
#ifndef MIN
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#endif
void vec_make(void **ptr, uint32_t size)
{
@@ -76,7 +74,7 @@ void vec_ensure_remaining(void **ptr, uint32_t space)
if (vec->capacity - vec->size < space)
{
void *new_vec = NULL;
vec_make(&new_vec, MAX(vec->capacity * 2, vec->size + space));
vec_make(&new_vec, MAX(vec->capacity * VEC_MULT, vec->size + space));
VEC_SIZE(new_vec) = vec->size;
memcpy(new_vec, *ptr, vec->size);
vec_free(ptr);
@@ -95,7 +93,7 @@ void vec_append(void **ptr, void *data, uint32_t size)
{
vec_ensure_remaining(ptr, size);
vec_t *vec = VEC_GET(*ptr);
memcpy(vec->bytes + vec->size, data, size);
memcpy(*ptr + vec->size, data, size);
vec->size += size;
}
@@ -107,6 +105,7 @@ void vec_clone(void **dest, void **src)
memcpy(*dest, *src, VEC_SIZE(*src));
VEC_SIZE(*dest) = VEC_SIZE(*src);
}
#endif
#endif