A => LICENSE.txt +21 -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 <https://unlicense.org/>
A => LICENSES/Unlicense.txt +9 -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 <https://unlicense.org/>
A => Makefile +64 -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}
A => README.md +10 -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.
A => src/allocator.c +57 -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);
+ }
+}
A => src/allocator.h +33 -0
@@ 1,33 @@
+#ifndef ASHLIB_ALLOCATOR_H
+#define ASHLIB_ALLOCATOR_H
+
+#include <stdint.h>
+
+#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
A => src/arena.c +160 -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,
+ };
+}
A => src/arena.h +30 -0
@@ 1,30 @@
+#ifndef ASHLIB_ARENA_H
+#define ASHLIB_ARENA_H
+
+#ifndef ASHLIB_ARENA_DEFAULT_ALIGNMENT
+#include <stdalign.h>
+#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
A => src/ashlib.h +14 -0
@@ 1,14 @@
+#ifndef ASHLIB_H
+#define ASHLIB_H
+
+#ifdef ASHLIB_NO_ASSERT
+#define ASHLIB_ASSERT(predicate) (void)(predicate)
+#else
+#include <assert.h>
+#define ASHLIB_ASSERT(predicate) assert(predicate)
+#endif // ASHLIB_NO_ASSERT
+
+// we should use the types from here everywhere anyway
+#include <stddef.h>
+
+#endif // !ASHLIB_H
A => src/guarded.c +69 -0
@@ 1,69 @@
+#include "guarded.h"
+
+#include <threads.h>
+
+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,
+ };
+}
A => src/guarded.h +8 -0
@@ 1,8 @@
+#ifndef ASHLIB_GUARDED_H
+#define ASHLIB_GUARDED_H
+
+#include "allocator.h"
+
+Allocator allocator_guarded(Allocator allocator);
+
+#endif // !ASHLIB_GUARDED_H
A => src/main.c +41 -0
@@ 1,41 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#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;
+}
A => src/malloc.c +33 -0
@@ 1,33 @@
+#include "malloc.h"
+
+#include <stdlib.h>
+
+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,
+ };
+}
A => src/malloc.h +8 -0
@@ 1,8 @@
+#ifndef ASHLIB_MALLOC_H
+#define ASHLIB_MALLOC_H
+
+#include "allocator.h"
+
+Allocator allocator_malloc();
+
+#endif // !ASHLIB_MALLOC_H
A => src/slice.h +13 -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