~ashie/c-allocator-experiments

65f982b78f2d54e1e954d805b5840af396f9eb5c — Ash 4 months ago
inital commit
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