diff --git a/docs/memory.md b/docs/memory.md index 95e4709..4cf1f9b 100644 --- a/docs/memory.md +++ b/docs/memory.md @@ -2,7 +2,7 @@ Custom allocators and memory functions -# Arena Allocator +## Arena Allocator A simple arena-style allocator @@ -11,7 +11,7 @@ A simple arena-style allocator ### ArenaAllocator Represents an arena allocator. `ArenaAllocator` holds its own buffer, but managing its size is left to the user. Like -most structs in `libflint`, it must be malloced first before being passed to `arena_init()`. +most structs in `libflint`, it must be malloc'd first before being passed to `arena_init()`. ```c typedef struct { @@ -96,4 +96,99 @@ Convenience macro for getting the size of the arena's buffer ```c #define arena_sz(a) (a)->buf_sz +``` + +## Pool Allocator + +A pool-based allocator for chunks of the same size. Useful for quickly allocating and using memory of the same size +without worrying about order; most operations are `O(1)` + +## Structs + +### PoolAllocator + +Represents a pool allocator. `PoolAllocator` holds its own buffer, but managing its size is left to the user. Like +most structs in `libflint`, it must be malloc'd first before being passed to `pool_init()`. + +```c +typedef struct { + unsigned char *buf; + size_t buf_sz; + size_t chunk_size; + + List *free_list; +} PoolAllocator; +``` + +## Functions + +### pool_init + +Initializes the `PoolAllocator`. The struct must first be created by the user using `malloc()`, see the example below. + +- `buf_sz`: Size of the underlying buffer in bytes +- `chunk_sz`: Size of each chunk in bytes +- `chunk_align`: The alignment of the chunks. The`LF_DEFAULT_ALIGNMENT` macro is available as a good default for basic types + +```c +void pool_init(PoolAllocator *allocator, size_t buf_sz, size_t chunk_sz, size_t chunk_align); + +/* Usage */ +PoolAllocator *pool = malloc(sizeof(PoolAllocator)); +pool_init(pool, 64, 16, LF_DEFAULT_ALIGNMENT); +``` + +### pool_free + +Return a single chunk back to the pool. `ptr` is a pointer to the allocated chunk. + +```c +void pool_free(PoolAllocator *allocator, void *ptr); +``` + +### pool_free_all + +Returns all chunks back to the pool. Any pointers received before this call are now invalid and must be reasigned with +`pool_alloc` or set to `NULL`. + +```c +void pool_free_all(PoolAllocator *allocator); +``` + +### pool_alloc + +Allocate a chunk from the pool. Returns `NULL` on failure. + +```c +void *pool_alloc(PoolAllocator *allocator); +``` + +### pool_destroy + +Destroys the underlying buffer and `List` in `allocator`, then frees it. User is responsible for setting `allocator` to +`NULL` afterwards. + +```c +void pool_destroy(PoolAllocator *allocator); +``` + +## Macros + +### pool_count_available + +Returns the amount of chunks left available in the pool + +```c +#define pool_count_available(x) (x)->free_list->size +``` + +### LF_DEFAULT_ALIGNMENT + +The default alignment for allocators. Use this as a simple default (defaults to 16 bytes on amd64 systems) when storing +basic types + +```c +#ifndef DEFAULT_ALIGNMENT +#define LF_DEFAULT_ALIGNMENT (2*sizeof(void*)) +#endif // DEFAULT_ALIGNMENT ``` \ No newline at end of file diff --git a/include/lfmemory.h b/include/lfmemory.h index 0aac0c3..9bcb2fd 100644 --- a/include/lfmemory.h +++ b/include/lfmemory.h @@ -3,6 +3,12 @@ #include +#include "lflinkedlist.h" + +#ifndef DEFAULT_ALIGNMENT +#define LF_DEFAULT_ALIGNMENT (2*sizeof(void*)) +#endif // DEFAULT_ALIGNMENT + typedef struct { unsigned char* buf; size_t buf_sz; @@ -17,4 +23,20 @@ void arena_resize_buf(ArenaAllocator *allocator, size_t new_sz); void *arena_resize(ArenaAllocator *allocator, void *mem, size_t old_sz, size_t new_sz); void arena_clear(ArenaAllocator *allocator); +typedef struct { + unsigned char *buf; + size_t buf_sz; + size_t chunk_size; + + List *free_list; +} PoolAllocator; + +void pool_init(PoolAllocator *allocator, size_t buf_sz, size_t chunk_sz, size_t chunk_align); +void pool_free(PoolAllocator *allocator, void *ptr); +void pool_free_all(PoolAllocator *allocator); +void *pool_alloc(PoolAllocator *allocator); +void pool_destroy(PoolAllocator *allocator); + +#define pool_count_available(x) (x)->free_list->size + #endif // LIBFLINT_H_MEMORY diff --git a/mkdocs.yml b/mkdocs.yml index 2debcc0..92300fd 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,6 +12,7 @@ nav: - 'Linked List': 'linkedlist.md' - 'Math': 'math.md' - 'macOS': 'macos.md' + - 'Memory': 'memory.md' - 'Network': 'network.md' - 'Parsing': 'parsing.md' - 'Set': 'set.md' diff --git a/src/memory.c b/src/memory.c index f49db3b..488ad06 100644 --- a/src/memory.c +++ b/src/memory.c @@ -28,26 +28,37 @@ void arena_clear(ArenaAllocator *allocator) { allocator->offset_prev = 0; } -static uintptr_t align_forward(uintptr_t ptr, size_t align) { - uintptr_t p, a, m; +static uintptr_t align_forward_uintptr(const uintptr_t ptr, const uintptr_t align) { if (!is_power_of_two(align)) { // TODO: Error } - p = ptr; - a = (uintptr_t)align; - m = p & (a - 1); + uintptr_t p = ptr; + const uintptr_t m = p & (align - 1); if (m != 0) { - p += a - m; + p += align - m; } return p; } -static void *arena_malloc_align(ArenaAllocator *allocator, size_t size, size_t align) { - uintptr_t cur_ptr = (uintptr_t)allocator->buf + (uintptr_t)allocator->offset_cur; +static uintptr_t align_forward_size(const size_t ptr, const size_t align) { + if (!is_power_of_two(align)) { + // TODO: Error + } + uintptr_t p = ptr; + const uintptr_t m = p & (align - 1); + + if (m != 0) { + p += align - m; + } + return p; +} + +static void *arena_malloc_align(ArenaAllocator *allocator, const size_t size, size_t align) { + uintptr_t cur_ptr = (uintptr_t)allocator->buf + allocator->offset_cur; // Push forward to align, then change to relative offset - uintptr_t offset = align_forward(cur_ptr, align); + uintptr_t offset = align_forward_uintptr(cur_ptr, align); offset -= (uintptr_t)allocator->buf; if (offset + size <= allocator->buf_sz) { @@ -62,8 +73,8 @@ static void *arena_malloc_align(ArenaAllocator *allocator, size_t size, size_t a return NULL; } -static void *arena_resize_align(ArenaAllocator *allocator, void *mem, size_t old_sz, size_t new_sz, size_t align) { - unsigned char *old_mem = (unsigned char *)mem; +static void *arena_resize_align(ArenaAllocator *allocator, void *mem, const size_t old_sz, const size_t new_sz, size_t align) { + unsigned char *old_mem = mem; if (!is_power_of_two(align)) { // TODO: Error handling } @@ -91,19 +102,79 @@ static void *arena_resize_align(ArenaAllocator *allocator, void *mem, size_t old return NULL; } -void arena_resize_buf(ArenaAllocator *allocator, size_t new_sz) { +void arena_resize_buf(ArenaAllocator *allocator, const size_t new_sz) { allocator->buf = realloc(allocator->buf, sizeof(unsigned char) * new_sz); } -#ifndef DEFAULT_ALIGNMENT -#define DEFAULT_ALIGNMENT (2*sizeof(void*)) -#endif // DEFAULT_ALIGNMENT - -void *arena_malloc(ArenaAllocator *allocator, size_t size) { - return arena_malloc_align(allocator, size, DEFAULT_ALIGNMENT); +void *arena_malloc(ArenaAllocator *allocator, const size_t size) { + return arena_malloc_align(allocator, size, LF_DEFAULT_ALIGNMENT); } -void *arena_resize(ArenaAllocator *allocator, void *mem, size_t old_sz, size_t new_sz) { - return arena_resize_align(allocator, mem, old_sz, new_sz, DEFAULT_ALIGNMENT); +void *arena_resize(ArenaAllocator *allocator, void *mem, const size_t old_sz, const size_t new_sz) { + return arena_resize_align(allocator, mem, old_sz, new_sz, LF_DEFAULT_ALIGNMENT); } +void pool_init(PoolAllocator *allocator, size_t buf_sz, size_t chunk_sz, size_t chunk_align) { + if (allocator == NULL) { + return; + } + + allocator->buf = malloc(sizeof(unsigned char) * buf_sz); + uintptr_t istart = (uintptr_t)allocator->buf; + uintptr_t start = align_forward_uintptr(istart, chunk_align); + allocator->buf_sz = buf_sz - (start - istart); + + allocator->chunk_size = align_forward_size(chunk_sz, chunk_align); + if (allocator->chunk_size < sizeof(void *) || allocator->buf_sz < allocator->chunk_size) { + //TODO: Handle error better + return; + } + + allocator->free_list = malloc(sizeof(List)); + ll_init(allocator->free_list, NULL); + + pool_free_all(allocator); +} + +void pool_free(PoolAllocator *allocator, void *ptr) { + ListNode *node = NULL; + const void *start = allocator->buf; + const void *end = &allocator->buf[allocator->buf_sz]; + + if (ptr == NULL) { + return; + } + + if (!(start <= ptr && ptr < end)) { + // TODO: Handle error better + return; + } + + ll_ins_next(allocator->free_list, allocator->free_list->tail, ptr); +} + +void pool_free_all(PoolAllocator *allocator) { + size_t chunk_count = allocator->buf_sz / allocator->chunk_size; + for (size_t i = 0; i < chunk_count; ++i) { + ll_ins_next(allocator->free_list, allocator->free_list->head, &allocator->buf[i * allocator->chunk_size]); + } +} + +void *pool_alloc(PoolAllocator *allocator) { + ListNode *node = allocator->free_list->head; + if (node == NULL) { + // TODO: Handle error better + return NULL; + } + + void *tmp; + ll_remove(allocator->free_list, allocator->free_list->head, &tmp); + return memset(tmp, 0, allocator->chunk_size); +} + +void pool_destroy(PoolAllocator *allocator) { + ll_destroy(allocator->free_list); + free(allocator->free_list); + free(allocator->buf); + free(allocator); +} diff --git a/src/vector.c b/src/vector.c index bab701f..dd8d303 100644 --- a/src/vector.c +++ b/src/vector.c @@ -113,7 +113,7 @@ int vec_shrink(Vector *vec) { vec->capacity = vec_len(vec); -#ifdef __OpenBSD__ +#if !defined(__OpenBSD__) || defined(__linux) vec->elements = reallocarray(vec->elements, vec->capacity, sizeof(void *)); #else vec->elements = reallocf(vec->elements, sizeof(void *) * vec->capacity); diff --git a/tests/tests.c b/tests/tests.c index 7258357..ed45525 100644 --- a/tests/tests.c +++ b/tests/tests.c @@ -471,11 +471,11 @@ void test_macos() { void test_memory() { printf("\n--- MEMORY TEST ---\n"); - ArenaAllocator *a = malloc(sizeof(ArenaAllocator)); - arena_init(a, 1024); + ArenaAllocator *arena = malloc(sizeof(ArenaAllocator)); + arena_init(arena, 1024); - int *i1 = arena_malloc(a, sizeof(int)); - int *i2 = arena_malloc(a, sizeof(int)); + int *i1 = arena_malloc(arena, sizeof(int)); + int *i2 = arena_malloc(arena, sizeof(int)); *i1 = 1; *i2 = 2; @@ -483,13 +483,33 @@ void test_memory() { assert(i1 < i2); assert(*i1 < *i2); - long *l = arena_resize(a, i1, sizeof(int), sizeof(long)); + long *l = arena_resize(arena, i1, sizeof(int), sizeof(long)); assert(*l == 1); - unsigned char *c = arena_resize(a, i2, sizeof(int), sizeof(unsigned char)); - assert(*c == 2); + unsigned char *char_test = arena_resize(arena, i2, sizeof(int), sizeof(unsigned char)); + assert(*char_test == 2); - arena_free(a); + arena_free(arena); + arena = NULL; + + PoolAllocator *pool = malloc(sizeof(PoolAllocator)); + pool_init(pool, 64, 16, LF_DEFAULT_ALIGNMENT); + void *a = pool_alloc(pool); + void *b = pool_alloc(pool); + void *c = pool_alloc(pool); + void *d = pool_alloc(pool); + + assert(a != NULL); + assert(b != NULL); + assert(c != NULL); + assert(d != NULL); + + assert(pool_count_available(pool) == 0); + pool_free(pool, d); + d = NULL; + assert(pool_count_available(pool) == 1); + + pool_destroy(pool); printf("Passes all memory tests\n"); }