From 65f982b78f2d54e1e954d805b5840af396f9eb5c Mon Sep 17 00:00:00 2001 From: Ash Date: Thu, 27 Jun 2024 12:32:48 +0200 Subject: [PATCH] inital commit --- LICENSE.txt | 21 ++++++ LICENSES/Unlicense.txt | 9 +++ Makefile | 64 +++++++++++++++++ README.md | 10 +++ src/allocator.c | 57 +++++++++++++++ src/allocator.h | 33 +++++++++ src/arena.c | 160 +++++++++++++++++++++++++++++++++++++++++ src/arena.h | 30 ++++++++ src/ashlib.h | 14 ++++ src/guarded.c | 69 ++++++++++++++++++ src/guarded.h | 8 +++ src/main.c | 41 +++++++++++ src/malloc.c | 33 +++++++++ src/malloc.h | 8 +++ src/slice.h | 13 ++++ 15 files changed, 570 insertions(+) create mode 100644 LICENSE.txt create mode 100644 LICENSES/Unlicense.txt create mode 100644 Makefile create mode 100644 README.md create mode 100644 src/allocator.c create mode 100644 src/allocator.h create mode 100644 src/arena.c create mode 100644 src/arena.h create mode 100644 src/ashlib.h create mode 100644 src/guarded.c create mode 100644 src/guarded.h create mode 100644 src/main.c create mode 100644 src/malloc.c create mode 100644 src/malloc.h create mode 100644 src/slice.h diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..fb210e1 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/LICENSES/Unlicense.txt b/LICENSES/Unlicense.txt new file mode 100644 index 0000000..ad81de3 --- /dev/null +++ b/LICENSES/Unlicense.txt @@ -0,0 +1,9 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. + +In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8ac78ef --- /dev/null +++ b/Makefile @@ -0,0 +1,64 @@ +.POSIX: + +TARGET = debug +OUTDIR = out +SRCDIR = src +BIN = main + +PREFIX = /usr +DESTDIR = + +ZIG = zig +AR = ${ZIG} ar +CC = ${ZIG} cc +CFLAGS_RELEASE = \ + -O3 -g0 -Werror -Wno-error=strict-prototypes -Wno-error=unused-variable +# NOTE: -fsanitize=address doesn't work with zig cc yet, add later +CFLAGS_DEBUG = -O0 -ggdb -g3 -DDEBUG -fno-omit-frame-pointer +CFLAGS = -Wall -Wextra -std=gnu18 -Isrc +EXTRA_CFLAGS = +LDFLAGS = +LDLIBS = + +ifeq (${TARGET}, debug) + CFLAGS += ${CFLAGS_DEBUG} +else ifeq (${TARGET}, release) + CFLAGS += ${CFLAGS_RELEASE} +else + ERR = $(error invalid TARGET '${TARGET}') +endif + +HEADERS := $(wildcard ${SRCDIR}/*.h) +SOURCES := $(wildcard ${SRCDIR}/*.c) +OBJECTS := $(patsubst ${SRCDIR}/%.c,${OUTDIR}/${TARGET}/obj/%.o,${SOURCES}) + +.SUFFIXES: +.PHONY: all clean err install +.PRECIOUS: ${OBJECTS} + +.DEFAULT_GOAL = all + +install: all + mkdir -p $(foreach d,include bin,${DESTDIR}${PREFIX}/${d}) + install -m644 -p ${OUTDIR}/${TARGET}/bin/${BIN} ${DESTDIR}${PREFIX}/bin + install -m644 -p source/*.h ${DESTDIR}${PREFIX}/include + +err: ${ERR} + +all: ${OUTDIR}/${TARGET}/bin/${BIN} + +${OUTDIR}/${TARGET}/bin/${BIN}: ${OBJECTS} + @mkdir -p $(dir $@) + ${CC} ${OBJECTS} ${LDLIBS} -o $@ + @# ${AR} rcs $@ $^ +ifeq (${TARGET}, release) + @# strip -g $@ + strip $@ +endif + +${OUTDIR}/${TARGET}/obj/%.o: ${SRCDIR}/%.c ${HEADERS} + @mkdir -p $(dir $@) + ${CC} ${CFLAGS} ${EXTRA_CFLAGS} -c $< ${LDFLAGS} ${LDLIBS} -o $@ + +clean: + -rm -rf ${OUTDIR} diff --git a/README.md b/README.md new file mode 100644 index 0000000..13f5467 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# C Allocator Experiments + +This repo contains a Zig like allocator API implementation in C. + +It lacks error handling and docs, and was only made for fun, but the logic +inside is sound and I plan to use this in my C projects. + +I do not encourage anyone to directly copy and paste this, but if you wanted to +do so the license is Unlicense and you are free to do whatever the fuck you +want with this. diff --git a/src/allocator.c b/src/allocator.c new file mode 100644 index 0000000..d993b30 --- /dev/null +++ b/src/allocator.c @@ -0,0 +1,57 @@ +#include "allocator.h" + +// FIX: error handling + +uintptr_t align_forward(uintptr_t address, size_t alignment) { + return ((address + (alignment - 1)) & ~(alignment - 1)); +} + +void* alloc_with(const Allocator* allocator, size_t size) { + ASHLIB_ASSERT(allocator->alloc); + ASHLIB_ASSERT(size > 0); + + return allocator->alloc(allocator->data, size); +} + +void* alloc_aligned_with( + const Allocator* allocator, + size_t alignment, + size_t size +) { + ASHLIB_ASSERT(allocator->alloc_aligned); + // NOTE: this check is not sufficient, alignment should also be power of 2 + ASHLIB_ASSERT(alignment % 2 == 0); + ASHLIB_ASSERT(size > 0); + + if (size < alignment) { + size = alignment; + } else { + size = align_forward(size, alignment); + } + + return allocator->alloc_aligned(allocator->data, alignment, size); +} + +void free_with(const Allocator* allocator, void* ptr) { + ASHLIB_ASSERT(allocator->free); + + if (ptr) { + allocator->free(allocator->data, ptr); + } +} + +void* realloc_with(const Allocator* allocator, void* ptr, size_t size) { + ASHLIB_ASSERT(allocator->realloc); + ASHLIB_ASSERT(ptr); + ASHLIB_ASSERT(size > 0); + + return allocator->realloc(allocator->data, ptr, size); +} + +void allocator_deinit(Allocator* allocator) { + ASHLIB_ASSERT(allocator); + + if (allocator->deinit) { + allocator->deinit(allocator->data); + } +} diff --git a/src/allocator.h b/src/allocator.h new file mode 100644 index 0000000..0456bc2 --- /dev/null +++ b/src/allocator.h @@ -0,0 +1,33 @@ +#ifndef ASHLIB_ALLOCATOR_H +#define ASHLIB_ALLOCATOR_H + +#include + +#include "ashlib.h" + +uintptr_t align_forward(uintptr_t address, size_t alignment); + +typedef struct { + void* (*alloc)(void*, size_t); + void* (*alloc_aligned)(void*, size_t, size_t); + void (*free)(void*, void*); + void* (*realloc)(void*, void*, size_t); + void (*deinit)(void* data); + void* data; +} Allocator; + +void* alloc_with(const Allocator* allocator, size_t size); + +void* alloc_aligned_with( + const Allocator* allocator, + size_t alignment, + size_t size +); + +void free_with(const Allocator* allocator, void* ptr); + +void* realloc_with(const Allocator* allocator, void* ptr, size_t size); + +void allocator_deinit(Allocator* allocator); + +#endif // !ASHLIB_ALLOCATOR_H diff --git a/src/arena.c b/src/arena.c new file mode 100644 index 0000000..f0c9db8 --- /dev/null +++ b/src/arena.c @@ -0,0 +1,160 @@ +#include "arena.h" + +#include "ashlib.h" + +struct ArenaRegion { + ArenaRegion* next; + size_t capacity; + void* ptr; + void* buf; +}; + +static ArenaRegion* +region_create(const Allocator* a, size_t alignment, size_t capacity) { + // NOTE: we cannot do this in single allocation because then the 0-th index + // of buf would be misaligned + ArenaRegion* region = alloc_with(a, sizeof(*region)); + + region->next = NULL; + region->capacity = capacity; + region->buf = alloc_aligned_with(a, alignment, capacity); + region->ptr = region->buf; + + return region; +} + +static void* +region_get_chunk(ArenaRegion* region, size_t alignment, size_t size) { + void* aligned_ptr = + (void*)align_forward((uintptr_t)region->ptr, alignment); + + if (aligned_ptr + size > region->ptr + region->capacity) { + return NULL; + } else { + region->ptr = aligned_ptr + size; + return aligned_ptr; + } +} + +Arena arena_init(Allocator allocator) { return (Arena){.inner = allocator}; } + +void* arena_alloc(Arena* arena, size_t alignment, size_t size) { + ASHLIB_ASSERT(arena); + ASHLIB_ASSERT(size > 0); + + if (!arena->end) { + arena->end = region_create(&arena->inner, alignment, size); + arena->end->ptr += size; + arena->start = arena->end; + + return arena->end->buf; + } + + void* result; + while (arena->end->next) { + result = region_get_chunk(arena->end, alignment, size); + if (result) { + return result; + } + + arena->end = arena->end->next; + } + + result = region_get_chunk(arena->end, alignment, size); + if (result) { + return result; + } else { + arena->end->next = region_create(&arena->inner, alignment, size); + arena->end = arena->end->next; + arena->end->ptr += size; + + return arena->end->buf; + } +} + +void arena_reserve(Arena* arena, size_t capacity) { + ASHLIB_ASSERT(arena); + + if (capacity == 0) { + return; + } + + ArenaRegion* region = + region_create(&arena->inner, ASHLIB_ARENA_DEFAULT_ALIGNMENT, capacity); + if (!arena->start) { + arena->start = region; + arena->end = arena->start; + } else { + region->next = arena->end->next; + arena->end->next = region; + } +} + +void arena_reset(Arena* arena) { + ASHLIB_ASSERT(arena); + + ArenaRegion* region = arena->start; + + while (region) { + region->ptr = region->buf; + region = region->next; + } + + arena->end = arena->start; +} + +void arena_free(Arena* arena) { + ASHLIB_ASSERT(arena); + ASHLIB_ASSERT(arena->start); + + ArenaRegion* region = arena->start; + while (region) { + region = region->next; + ArenaRegion* tmp = region; + + free_with(&arena->inner, tmp->buf); + free_with(&arena->inner, tmp); + } + + arena->start = NULL; + arena->end = NULL; +} + +static inline void* alloc(void* data, size_t size) { + // XXX: test different alignment sizes + return arena_alloc(data, ASHLIB_ARENA_DEFAULT_ALIGNMENT, size); +} + +static inline void* alloc_aligned(void* data, size_t alignment, size_t size) { + return arena_alloc(data, alignment, size); +} + +// dummy free +static inline void free(void* _data, void* _ptr) { + (void)_data; + (void)_ptr; +} + +// XXX: make it actually realloc if it fits +static inline void* realloc(void* data, void* _, size_t size) { + (void)_; + // FIX: handle errors + return alloc(data, size); +} + +static inline void deinit(void* data) { + Arena* arena = data; + arena_free(arena); + allocator_deinit(&arena->inner); +} + +Allocator allocator_arena(Arena* arena) { + return (Allocator){ + .alloc = alloc, + .alloc_aligned = alloc_aligned, + .free = free, + .realloc = realloc, + .deinit = deinit, + .data = arena, + }; +} diff --git a/src/arena.h b/src/arena.h new file mode 100644 index 0000000..d92800f --- /dev/null +++ b/src/arena.h @@ -0,0 +1,30 @@ +#ifndef ASHLIB_ARENA_H +#define ASHLIB_ARENA_H + +#ifndef ASHLIB_ARENA_DEFAULT_ALIGNMENT +#include +#define ASHLIB_ARENA_DEFAULT_ALIGNMENT alignof(max_align_t) +#endif // !ASHLIB_ARENA_DEFAULT_ALIGNMENT + +#include "allocator.h" + +typedef struct ArenaRegion ArenaRegion; + +typedef struct { + Allocator inner; + ArenaRegion *start, *end; +} Arena; + +Arena arena_init(Allocator a); + +void* arena_alloc(Arena* arena, size_t alignment, size_t size); + +void arena_reserve(Arena* arena, size_t capacity); + +void arena_reset(Arena* arena); + +void arena_free(Arena* arena); + +Allocator allocator_arena(Arena* arena); + +#endif // !ASHLIB_ARENA_H diff --git a/src/ashlib.h b/src/ashlib.h new file mode 100644 index 0000000..6f89912 --- /dev/null +++ b/src/ashlib.h @@ -0,0 +1,14 @@ +#ifndef ASHLIB_H +#define ASHLIB_H + +#ifdef ASHLIB_NO_ASSERT +#define ASHLIB_ASSERT(predicate) (void)(predicate) +#else +#include +#define ASHLIB_ASSERT(predicate) assert(predicate) +#endif // ASHLIB_NO_ASSERT + +// we should use the types from here everywhere anyway +#include + +#endif // !ASHLIB_H diff --git a/src/guarded.c b/src/guarded.c new file mode 100644 index 0000000..051502f --- /dev/null +++ b/src/guarded.c @@ -0,0 +1,69 @@ +#include "guarded.h" + +#include + +typedef struct { + Allocator inner; + mtx_t lock; +} AllocatorGuarded; + +// FIX: handle mutex errors +static void* alloc(void* data, size_t size) { + AllocatorGuarded* guarded = data; + + mtx_lock(&guarded->lock); + void* result = alloc_with(&guarded->inner, size); + mtx_unlock(&guarded->lock); + + return result; +} + +static void* alloc_aligned(void* data, size_t alignment, size_t size) { + AllocatorGuarded* guarded = data; + + mtx_lock(&guarded->lock); + void* result = alloc_aligned_with(&guarded->inner, alignment, size); + mtx_unlock(&guarded->lock); + + return result; +} + +static void free(void* data, void* ptr) { + AllocatorGuarded* guarded = data; + + mtx_lock(&guarded->lock); + free_with(&guarded->inner, ptr); + mtx_unlock(&guarded->lock); +} + +static void* realloc(void* data, void* ptr, size_t size) { + AllocatorGuarded* guarded = data; + + mtx_lock(&guarded->lock); + void* result = realloc_with(&guarded->inner, ptr, size); + mtx_unlock(&guarded->lock); + + return result; +} + +static void deinit(void* data) { + AllocatorGuarded* guarded = data; + mtx_destroy(&guarded->lock); + allocator_deinit(&guarded->inner); +} + +Allocator allocator_guarded(Allocator allocator) { + // NOTE: this cannot be stack allocated because mutex needs ptr to itself + AllocatorGuarded* guarded = alloc_with(&allocator, sizeof(*guarded)); + guarded->inner = allocator; + mtx_init(&guarded->lock, mtx_plain); + + return (Allocator){ + .alloc = alloc, + .alloc_aligned = alloc_aligned, + .free = free, + .realloc = realloc, + .deinit = deinit, + .data = guarded, + }; +} diff --git a/src/guarded.h b/src/guarded.h new file mode 100644 index 0000000..5931dad --- /dev/null +++ b/src/guarded.h @@ -0,0 +1,8 @@ +#ifndef ASHLIB_GUARDED_H +#define ASHLIB_GUARDED_H + +#include "allocator.h" + +Allocator allocator_guarded(Allocator allocator); + +#endif // !ASHLIB_GUARDED_H diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..596b206 --- /dev/null +++ b/src/main.c @@ -0,0 +1,41 @@ +#include +#include +#include + +#include "allocator.h" +#include "arena.h" +#include "guarded.h" +#include "malloc.h" + +int main(void) { + printf("%lu\n", ASHLIB_ARENA_DEFAULT_ALIGNMENT); + Arena arena = arena_init(allocator_malloc()); + Allocator a = allocator_guarded(allocator_arena(&arena)); + + arena_reserve(&arena, 2048); + + char* test = alloc_with(&a, 1024); + + (void)alloc_with(&a, 512); + (void)alloc_with(&a, 104); + + printf("%lu\n", (uintptr_t)alloc_aligned_with(&a, 4096, 1011) % 4096); + + arena_reset(&arena); + + test = alloc_with(&a, 1024); + + (void)alloc_with(&a, 512); + (void)alloc_with(&a, 104); + + printf("%lu\n", (uintptr_t)alloc_aligned_with(&a, 4096, 1011) % 4096); + + memset(test, 'a', 1024); + test[1023] = 0; + + printf("%s\n", test); + + allocator_deinit(&a); + + return 0; +} diff --git a/src/malloc.c b/src/malloc.c new file mode 100644 index 0000000..98b1177 --- /dev/null +++ b/src/malloc.c @@ -0,0 +1,33 @@ +#include "malloc.h" + +#include + +static void* alloc_impl(void* _, size_t size) { + (void)_; + return malloc(size); +} + +static void* +alloc_aligned_impl(void* _, size_t alignment, size_t size) { + (void)_; + return aligned_alloc(alignment, size); +} + +static void free_impl(void* _, void* ptr) { + (void)_; + free(ptr); +} + +static void* realloc_impl(void* _, void* ptr, size_t size) { + (void)_; + return realloc(ptr, size); +} + +Allocator allocator_malloc() { + return (Allocator){ + .alloc = alloc_impl, + .alloc_aligned = alloc_aligned_impl, + .free = free_impl, + .realloc = realloc_impl, + }; +} diff --git a/src/malloc.h b/src/malloc.h new file mode 100644 index 0000000..615f3b4 --- /dev/null +++ b/src/malloc.h @@ -0,0 +1,8 @@ +#ifndef ASHLIB_MALLOC_H +#define ASHLIB_MALLOC_H + +#include "allocator.h" + +Allocator allocator_malloc(); + +#endif // !ASHLIB_MALLOC_H diff --git a/src/slice.h b/src/slice.h new file mode 100644 index 0000000..127d2e9 --- /dev/null +++ b/src/slice.h @@ -0,0 +1,13 @@ +#ifndef ASHLIB_SLICE_H +#define ASHLIB_SLICE_H + +#define ASHLIB_SLICE_TYPE(T) \ + struct { \ + T* buf; \ + size_t len; \ + } + +#define ASHLIB_SLICE(b, l) \ + { .buf = b, .len = l } + +#endif // !ASHLIB_SLICE_H -- 2.45.2