diff --git a/.clang-format b/.clang-format index cdcea7e..13b0a73 100644 --- a/.clang-format +++ b/.clang-format @@ -1,2 +1,10 @@ BasedOnStyle: WebKit + +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: true +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: false +PointerAlignment: Right diff --git a/.gitignore b/.gitignore index f779bd9..9f534f1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ test/ *.o *.a *.so +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e92ad78 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.16) + +project("ArenaAllocator" + VERSION 1.0 + LANGUAGES C + DESCRIPTION "Arena Allocator in C" +) + +add_subdirectory(src) + +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + message(STATUS "Building tests") + add_subdirectory(test) +endif() + diff --git a/Makefile b/Makefile deleted file mode 100644 index 6b283bb..0000000 --- a/Makefile +++ /dev/null @@ -1,72 +0,0 @@ - -all: - -BUILD ?= debug -COMPILER ?= gcc - -# ==== set compiler flags ==== -# credits Maxim Egorushkin: -# https://stackoverflow.com/questions/48791883/best-practice-for-building-a-make-file/48793058#48793058 - -CC := $(COMPILER) - -# -fsanitize={address,undefined} -CFLAGS.gcc.debug := -Og -ggdb -pg -fsanitize=address -fno-omit-frame-pointer -CFLAGS.gcc.release := -O3 -g -march=native -DNDEBUG -CFLAGS.gcc := -fanalyzer - -CFLAGS.clang.debug :=-O0 -g3 -DBACKTRACE -rdynamic -CFLAGS.clang.release :=-O3 -g -march=native -DNDEBUG -CFLAGS.clang := - -CFLAGS := ${CFLAGS.${COMPILER}} ${CFLAGS.${COMPILER}.${BUILD}} \ - -Iinclude \ - -std=c2x \ - -Wall -Wextra -Wpedantic -Werror - -BUILD_DIR := build -BIN_DIR := lib -TEST_DIR := test - -OBJS := $(BUILD_DIR)/arena.o -FPIC_OBJS := $(BUILD_DIR)/fpic/arena.o - -all : static - -static : $(BIN_DIR)/libarena.a - -dynamic : $(BIN_DIR)/libarena.so - -tests : test/test_arena - -$(TEST_DIR)/test_arena : src/test_arena.c $(BIN_DIR)/libarena.a | $(TEST_DIR) - $(CC) -o $@ $(CFLAGS) $^ - -$(BIN_DIR)/libarena.a : $(OBJS) | $(BIN_DIR) - ar cr $@ $^ - -$(BIN_DIR)/libarena.so : $(FPIC_OBJS) | $(BIN_DIR) - $(CC) -o $@ -shared $(CFLAGS) $^ - -$(BUILD_DIR)/%.o : src/%.c | $(BUILD_DIR) - $(CC) -o $@ -c $(CFLAGS) $< - -$(BUILD_DIR)/fpic/%.o : src/%.c | $(BUILD_DIR)/fpic - $(CC) -o $@ -c -fPIC $(CFLAGS) $< - -$(BUILD_DIR): - mkdir -p $@ - -$(BUILD_DIR)/fpic: - mkdir -p $@ - -$(BIN_DIR) : - mkdir -p $@ - -$(TEST_DIR) : - mkdir -p $@ - -clean: - rm -rf $(BUILD_DIR) $(BIN_DIR) $(TEST_DIR) - -.PHONY: clean $(BUILD_DIR) test bin all diff --git a/README.md b/README.md index cb2f7f3..d9b287f 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,38 @@ +## `arena_t arena_new()` + +Allocate a new arena. +The underlying memory is allocated with mmap. + -`arena_t* arena_new()` +## `void arena_delete(arena_t *a)` -Initializes and returns new arena +Delete memory mapped for arena. +Should only be used with arenas from arena\_new(). -`void arena_reset(arena_t *a)` -Free memory allocated in arena +## `arena_t arena_attach(void *ptr, size_t size)` -`void* arena_alloc(arena_t *a, size_t len)` +Attach an arena to an existing memory region. +The arena will not expand the region if space is exceeded. + + +## `void *arena_detatch(arena_t arena)` +Detach an arena from an existing memory region. + + +## `void arena_reset(arena_t *a)` +Reset an arena. + + + +## `void *arena_alloc(arena_t *a, size_t len)` +Allocate memory from an arena. +Returns NULL and sets errno on failure. + + + +## `void *arena_calloc(arena_t *a, size_t nmemb, size_t size)` +Allocate and zero memory from an arena. +Returns NULL and sets errno on failure. -Allocate new memory using arena diff --git a/include/arena.h b/include/arena.h index 68b5e2f..d24d51c 100644 --- a/include/arena.h +++ b/include/arena.h @@ -2,23 +2,60 @@ #ifndef ARENA_H #define ARENA_H -#include // ptrdiff_t -#include // uintptr_t +#include +#include // size_t -#define _WORD_SIZE (sizeof(intptr_t)) - -// linked list terminology: -// https://web.archive.org/web/20230604145332/https://i.stack.imgur.com/2FbXf.png typedef struct arena { - struct page *head, *last; -} __attribute__((aligned(_WORD_SIZE))) arena_t; + char *data; + size_t size; + size_t cap; + bool grow; +} arena_t; -arena_t arena_new(void); -void arena_reset(arena_t* a); -void* arena_alloc(arena_t* a, size_t len); -void* arena_calloc(arena_t* a, size_t nmemb, size_t size); -void* arena_realloc_tail(arena_t* a, size_t len); -void arena_delete(arena_t* a); -void arena_free(arena_t* a, void* p); +/** + * Allocate a new arena. + * The underlying memory is allocated with mmap. + */ +arena_t arena_new(); + +/** + * Delete memory mapped for arena. + * Should only be used with arenas from arena_new(). + */ +void arena_delete(arena_t *a); + +/** + * Attach an arena to an existing memory region. + * The arena will not expand the region if space is exceeded. + */ +static inline arena_t arena_attach(void *ptr, size_t size) +{ + return (arena_t) { .data = ptr, .size = 0, .cap = size, .grow = false }; +} + +/** + * Detach an arena from an existing memory region. + */ +static inline void *arena_detatch(arena_t arena) +{ + return arena.data; +} + +/** + * Reset an arena. + */ +void arena_reset(arena_t *a); + +/** + * Allocate memory from an arena. + * Returns NULL and sets errno on failure. + */ +void *arena_alloc(arena_t *a, size_t len); + +/** + * Allocate and zero memory from an arena. + * Returns NULL and sets errno on failure. + */ +void *arena_calloc(arena_t *a, size_t nmemb, size_t size); #endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..d9ca6e3 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,4 @@ + +add_library(arena_allocator arena.c) + +target_include_directories(arena_allocator PUBLIC ../include) diff --git a/src/arena.c b/src/arena.c index 5af820e..1e2952c 100644 --- a/src/arena.c +++ b/src/arena.c @@ -1,153 +1,81 @@ #include "arena.h" -#include -#include -#include -#include +#include // errno +#include +#include // fprintf +#include // strerror +#include #include -// (sizeof(intptr_t) isn't guaranteed to be the machine word size but on most -// compilers it is) -#define SYS_PAGE_SIZE ((size_t)sysconf(_SC_PAGE_SIZE)) +#define ARENA_ALIGN (sizeof(void *)) +#define ARENA_GROW_FACTOR 2UL -#define BIG_PAGE (SYS_PAGE_SIZE + 1) +#ifndef NDEBUG +#define arena_err(msg) \ + fprintf(stderr, "%s (%s:%d): %s\n", msg, __func__, __LINE__, strerror(errno)) +#else +#define arena_err(msg) +#endif -typedef unsigned char byte_t; - -struct page { - void* data; - size_t offset, prev_offset; - struct page* next; -}; - -// linked list terminology: -// https://web.archive.org/web/20230604145332/https://i.stack.imgur.com/2FbXf.png - -arena_t arena_new(void) +static bool arena_grow(struct arena *a) { - struct page* p = malloc(sizeof *p); + int ok = mprotect( + a->data + a->cap, + a->cap * ARENA_GROW_FACTOR, + PROT_READ | PROT_WRITE); + if (ok == -1) { + arena_err("mprotect"); + return false; + } + + a->cap *= ARENA_GROW_FACTOR; + + return true; +} + +inline void arena_reset(arena_t *a) +{ + a->size = 0; +} + +arena_t arena_new() +{ + size_t size = sysconf(_SC_PAGE_SIZE); + void *p = mmap(NULL, 1UL << 40UL, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + mprotect(p, size, PROT_READ | PROT_WRITE); + if (p == MAP_FAILED) + arena_err("mmap"); + + return arena_attach(p, size); +} + +void *arena_alloc(arena_t *a, size_t size) +{ + size = (size + ARENA_ALIGN - 1) & ~(ARENA_ALIGN - 1); // align + + void *p = a->data + a->size; + a->size += size; + if (a->size > a->cap) { + if (!arena_grow(a)) + return NULL; + } + return p; +} + +void *arena_calloc(arena_t *a, size_t nmemb, size_t size) +{ + void *p = arena_alloc(a, nmemb * size); if (p == NULL) - exit(errno); - - p->next = NULL; - p->offset = 0; - p->prev_offset = 0; - p->data = malloc(SYS_PAGE_SIZE); - - if (p->data == NULL) - exit(errno); - - arena_t a = { - .head = p, - .last = p, - }; - - return a; -} - -void _arena_new_page(arena_t* a, size_t size) -{ - /* potentially reuse page from previously - reset arena */ - if (a->head->next != NULL) { - a->head = a->head->next; - a->head->offset = 0; - a->head->prev_offset = 0; - return; - } - - void* tmp = calloc(1, sizeof *(a->head->next)); - - if (tmp == NULL) - exit(errno); - - a->head->next = tmp; - - a->head = a->head->next; - a->head->data = malloc(size); - - if (a->head->data == NULL) - exit(errno); -} - -void arena_reset(arena_t* a) -{ - a->head = a->last; - a->head->offset = 0; - a->head->prev_offset = 0; -} - -void* _arena_big_alloc(arena_t* a, size_t size) -{ - _arena_new_page(a, size); - a->head->offset = BIG_PAGE; - a->head->prev_offset = BIG_PAGE; - - return a->head->data; -} - -void* arena_alloc(arena_t* a, size_t size) -{ - if (size > SYS_PAGE_SIZE) - return _arena_big_alloc(a, size); - - // align size to machine word size - size = (size + _WORD_SIZE - 1) & ~(_WORD_SIZE - 1); - - if (a->head->offset > SYS_PAGE_SIZE - size) { - _arena_new_page(a, SYS_PAGE_SIZE); - return arena_alloc(a, size); - } - - a->head->prev_offset = a->head->offset; - a->head->offset += size; - - return (byte_t*)(a->head->data) + a->head->prev_offset; -} - -void* arena_calloc(arena_t* a, size_t nmemb, size_t size) -{ - void* p = arena_alloc(a, nmemb * size); + return p; memset(p, 0, nmemb * size); return p; } -void* arena_realloc_tail(arena_t* a, size_t len) +void arena_delete(struct arena *a) { - if (a->head->offset == BIG_PAGE) { - void* tmp = realloc(a->head->data, len); - - if (tmp == NULL) - exit(errno); - - a->head->data = tmp; - - return tmp; - } - - a->head->offset = a->head->prev_offset; - - return arena_alloc(a, len); -} - -void arena_delete(arena_t* a) -{ - struct page* p = a->last; - - while (p != NULL) { - struct page* next = p->next; - free(p->data); - free(p); - p = next; - } -} - -// included for completeness -void arena_free(arena_t* a, void* p) -{ - (void)p; - (void)a; - return; + munmap(a->data, a->cap); + a->cap = -1; + a->size = -1; } diff --git a/src/test_arena.c b/src/test_arena.c deleted file mode 100644 index 55d34fe..0000000 --- a/src/test_arena.c +++ /dev/null @@ -1,42 +0,0 @@ - - -// _start test_arena_alloc.c -#include "arena.h" - -#include -#include -#include -#include -#include - -int main() -{ - arena_t default_arena = arena_new(); - - printf("\nAttempting to allocate and write to 1 byte 1024 times"); - for (size_t i = 0; i < 1024; i++) { - char* c = arena_alloc(&default_arena, sizeof *c); - - if (c == NULL) - err(EXIT_FAILURE, "failed to allocate memory"); - - *c = i & 0xFF; - } - printf("\n OK!\n"); - - printf("\nAttempting to allocate and write to `_SC_PAGESIZE+1` bytes"); - size_t psz = sysconf(_SC_PAGESIZE) + 1; - char* c = arena_alloc(&default_arena, psz); - - if (c == NULL) - err(EXIT_FAILURE, "failed to allocate memory"); - - for (size_t i = 0; i < psz; i++) - *c = i & 0xFF; - - printf("\n OK!\n"); - - arena_delete(&default_arena); - - return 0; -}