From e6a978d36b899bb34010c2754482d654f75d4c88 Mon Sep 17 00:00:00 2001 From: Randy Jordan Date: Sat, 25 Oct 2025 19:15:56 -0500 Subject: [PATCH] Initial commit --- LICENSE | 21 ++++++++++ Makefile | 76 +++++++++++++++++++++++++++++++++++ README.md | 23 +++++++++++ include/arena.h | 20 ++++++++++ include/except.h | 81 ++++++++++++++++++++++++++++++++++++++ include/mem.h | 35 ++++++++++++++++ src/arena.c | 38 ++++++++++++++++++ src/except.c | 30 ++++++++++++++ src/mem.c | 65 ++++++++++++++++++++++++++++++ tests/01_arena_new.c | 14 +++++++ tests/02_arena_nozero.c | 14 +++++++ tests/03_arena_softfail.c | 12 ++++++ tests/04_arena_exception.c | 30 ++++++++++++++ 13 files changed, 459 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 include/arena.h create mode 100644 include/except.h create mode 100644 include/mem.h create mode 100644 src/arena.c create mode 100644 src/except.c create mode 100644 src/mem.c create mode 100644 tests/01_arena_new.c create mode 100644 tests/02_arena_nozero.c create mode 100644 tests/03_arena_softfail.c create mode 100644 tests/04_arena_exception.c diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8aa2645 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) [year] [fullname] + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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 OR COPYRIGHT HOLDERS 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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..205f21a --- /dev/null +++ b/Makefile @@ -0,0 +1,76 @@ +# Compiler Flags +CC := gcc +CFLAGS := -g -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined -fno-omit-frame-pointer +export ASAN_OPTIONS = allocator_may_return_null=1 +# Directory variables +LIBDIR := lib +OBJ := obj +INC := include +SRC := src +TEST := tests + +# Filepath Pattern Matching +LIB := $(LIBDIR)/lib.a +SRCS := $(wildcard $(SRC)/*.c) +OBJS := $(patsubst $(SRC)/%.c, $(OBJ)/%.o, $(SRCS)) +TESTS := $(wildcard $(TEST)/*.c) +TESTBINS := $(patsubst $(TEST)/%.c, $(TEST)/bin/%, $(TESTS)) + +# Commands must be labeled PHONY +.PHONY: all release clean test + +# Compiler Release Flags +release: CFLAGS := -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined -fno-omit-frame-pointer -O2 -DNDEBUG +release: clean $(LIB) + +# Target for compilation. +all: $(LIB) + +# Target / Dependencies +$(LIB): $(OBJS) | $(LIBDIR) + $(RM) $(LIB) + ar -cvrs $@ $^ + +$(OBJ)/%.o: $(SRC)/%.c $(SRC)/%.h | $(OBJ) + $(CC) $(CFLAGS) -c $< -o $@ + +$(OBJ)/%.o: $(SRC)/%.c | $(OBJ) + $(CC) $(CFLAGS) -c $< -o $@ + +$(TEST)/bin/%: $(TEST)/%.c $(LIB) | $(TEST)/bin + $(CC) $(CFLAGS) $< $(LIB) -o $@ + +# Make directories if none. +$(LIBDIR): + mkdir $@ + +$(INC): + mkdir $@ + +$(OBJ): + mkdir $@ + +$(TEST)/bin: + mkdir $@ + +# Run the tests in the bin folder and track results +test: $(LIB) $(TEST)/bin $(TESTBINS) + @SUCCESS_COUNT=0; FAILURE_COUNT=0; \ + for test in $(TESTBINS); do \ + ./$$test; \ + EXIT_CODE=$$?; \ + TEST_NAME=$(notdir $$test); \ + if [ $$EXIT_CODE -eq 0 ]; then \ + echo "\033[0;32m$$TEST_NAME: EXIT CODE: $$EXIT_CODE (SUCCESS)\033[0m"; \ + SUCCESS_COUNT=$$((SUCCESS_COUNT + 1)); \ + else \ + echo "\033[0;31m$$TEST_NAME: EXIT CODE: $$EXIT_CODE (FAILURE)\033[0m"; \ + FAILURE_COUNT=$$((FAILURE_COUNT + 1)); \ + fi; \ + done; \ + echo "\n\nTests completed"; \ + echo "SUCCESS: $$SUCCESS_COUNT"; \ + echo "FAILURE: $$FAILURE_COUNT"; + +clean: + $(RM) -r $(LIBDIR) $(OBJ) $(TEST)/bin/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..9b72265 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# arena + +## Description +Arena based memory allocation. + +## Table of Contents + +- [Description](#description) +- [Features](#features) +- [Usage](#usage) +- [Credits / Resources](#credits--resources) +- [License](#license) + +## Features / TODOS + +## Usage + +## Credits / Resources +[C Interfaces and Implementations - David Hanson](https://github.com/drh/cii)
+[Arena Allocator Tips and Tricks - Chris Wellons](https://nullprogram.com/blog/2023/09/27/)
+[Memory Allocation Strategies - Ginger Bill](https://www.gingerbill.org/series/memory-allocation-strategies/)
+## License +This project is licensed under MIT - see the [LICENSE](LICENSE) file for details. diff --git a/include/arena.h b/include/arena.h new file mode 100644 index 0000000..4f1e19f --- /dev/null +++ b/include/arena.h @@ -0,0 +1,20 @@ +#ifndef ARENA_INCLUDED +#define ARENA_INCLUDED + +struct Arena { + unsigned char *beg; + unsigned char *end; +}; +typedef struct Arena Arena; + +extern void *arena_alloc(int flags, struct Arena *a, size_t nbytes, + size_t align, size_t count, const char *file, int line); + +extern struct Arena arena_new(int flags, size_t cap, const char *file, int line); +extern void arena_back(struct Arena *a, void *buf, size_t len); + +#define ARENA(flags, cap) arena_new((flags), (cap), __FILE__, __LINE__) +#define ARENA_BACK(a, buf, len) arena_back((a), (buf), (len)) +#define ARENA_ALLOC(flags, a, nbytes, align, count) \ + arena_alloc((flags), (a), (nbytes), (align), (count), __FILE__, __LINE__) +#endif diff --git a/include/except.h b/include/except.h new file mode 100644 index 0000000..dd3712d --- /dev/null +++ b/include/except.h @@ -0,0 +1,81 @@ +#ifndef EXCEPT_INCLUDED +#define EXCEPT_INCLUDED + +#include +#define UNUSED(x) (void)(x) + +struct Exception { + const char *reason; +}; +typedef struct Exception Exception; + +struct ExceptFrame { + struct ExceptFrame *prev; // Exception Stack + jmp_buf env; // Enviroment Buffer + const char *file; // Exception File + int line; // Exception Line + const Exception *exception; // Exception Reason +}; +typedef struct ExceptFrame ExceptFrame; + +// Exception States +enum { EXCEPT_ENTERED=0, EXCEPT_RAISED, EXCEPT_HANDLED, EXCEPT_FINALIZED}; + +void except_raise(const Exception *e, const char *file,int line); // Raise exceptions + +// External declarations +extern ExceptFrame *except_stack; // Global exception stack +extern const Exception assert_failed; // Forward declaration for assert. +extern void asserted(int e); + +#ifdef NDEBUG +#define ASSERTED(e) ((void)0) +#else +#define ASSERTED(e) ((void)((e)||(RAISE(assert_failed),0))) +#endif + +// Raise an Exception. +#define RAISE(e) except_raise(&(e), __FILE__, __LINE__) + +// Reraise the currect exception. +#define RERAISE except_raise(except_frame.exception, \ + except_frame.file, except_frame.line) + +// Switch to the previous exception frame and return. +#define RETURN switch (except_stack = except_stack->prev,0) default: return + +// Start a try block. +#define TRY do { \ + volatile int except_flag; \ + ExceptFrame except_frame; \ + except_frame.prev = except_stack; \ + except_stack = &except_frame; \ + except_flag = setjmp(except_frame.env); \ + if (except_flag == EXCEPT_ENTERED) { + +// Handle specific example. +#define EXCEPT(e) \ + if (except_flag == EXCEPT_ENTERED) except_stack = except_stack->prev; \ + } else if (except_frame.exception == &(e)) { \ + except_flag = EXCEPT_HANDLED; + +// Catch all other exceptions. +#define ELSE \ + if (except_flag == EXCEPT_ENTERED) except_stack = except_stack->prev; \ + } else { \ + except_flag = EXCEPT_HANDLED; + +// Execute finalization code. +#define FINALLY \ + if (except_flag == EXCEPT_ENTERED) except_stack = except_stack->prev; \ + } { \ + if (except_flag == EXCEPT_ENTERED) \ + except_flag = EXCEPT_FINALIZED; + +// End Try block. +#define END_TRY \ + if (except_flag == EXCEPT_ENTERED) except_stack = except_stack->prev; \ + } if (except_flag == EXCEPT_RAISED) RERAISE; \ +} while (0) + +#endif diff --git a/include/mem.h b/include/mem.h new file mode 100644 index 0000000..241741d --- /dev/null +++ b/include/mem.h @@ -0,0 +1,35 @@ +#ifndef MEM_INCLUDED +#define MEM_INCLUDED + +#include "except.h" // Exceptions +#include // size_t + +/* General Macros*/ +#define MEM_KB(x) ((size_t)(x) * 1024ULL) +#define MEM_MB(x) ((size_t)(x) * 1024ULL * 1024ULL) +#define MEM_GB(x) ((size_t)(x) * 1024ULL * 1024ULL * 1024ULL) +#define MEM_SIZE(x) (ptrdiff_t)sizeof(x) +#define MEM_COUNT(a) (MEM_SIZE(a) / MEM_SIZE(*(a))) +#define MEM_LEN(s) (MEM_COUNT(s) - 1) + +/* Default behavior is to throw exceptions and zero out memory*/ +enum MemFlags { + NOZERO = 1 << 0, /* 0001 */ + SOFT_FAIL = 1 << 1, /* 0010 */ + HARD_FAIL = 1 << 2, /* 0100 */ +}; + +extern const Exception OOM; // Out of memory + +extern void *mem_alloc (int flags, size_t nbytes, const char *file, int line); +extern void mem_free(void *ptr, const char *file, int line); +extern void *mem_resize(int flags, void *ptr, size_t nbytes, const char *file, int line); +extern int mem_is_zero(const void *ptr, size_t nbytes); + + +#define ALLOC(flags, nbytes) mem_alloc((flags), (nbytes), __FILE__, __LINE__) +#define FREE(ptr) ((void)(mem_free((ptr), __FILE__, __LINE__), (ptr) = 0)) +#define RESIZE(flags, ptr, nbytes) ((ptr) = mem_resize((flags), (ptr), \ + (nbytes), __FILE__, __LINE__)) + +#endif diff --git a/src/arena.c b/src/arena.c new file mode 100644 index 0000000..03ada8c --- /dev/null +++ b/src/arena.c @@ -0,0 +1,38 @@ +#include "../include/mem.h" // Flags +#include "../include/arena.h" // Arena +#include +#include +#include +struct Arena arena_new(int flags, size_t cap, const char *file, int line){ + struct Arena a = {0}; + a.beg = mem_alloc(flags, cap, file, line); + a.end = a.beg ? a.beg+cap : 0; + return a; +} +void arena_back(struct Arena *a, void *buf, size_t len){ + ASSERTED(a != NULL); + ASSERTED(buf != NULL); + ASSERTED(len > 0); + a->beg = (unsigned char *)buf; + a->end = a->beg+len; +} +void *arena_alloc(int flags, struct Arena *a, size_t nbytes, size_t align, + size_t count, const char *file, int line){ + ptrdiff_t padding = -(uintptr_t)a->beg & (align - 1); + ptrdiff_t available = a->end - a->beg - padding; + if (available < 0 || count > available/nbytes) { + if(flags & SOFT_FAIL) return NULL; + if(flags & HARD_FAIL) exit(12); // ENOMEM + // Raise Exception + if (file == NULL) // Wasn't called by macro + RAISE(OOM); // Out of Memory Exception + else // Called by macro + except_raise(&OOM, file, line); + + } + + void *p = a->beg + padding; + a->beg += padding + count*nbytes; + // Check flags to see if it gets zeroed out + return flags&NOZERO ? p : memset(p, 0, count*nbytes); +} diff --git a/src/except.c b/src/except.c new file mode 100644 index 0000000..ecadde7 --- /dev/null +++ b/src/except.c @@ -0,0 +1,30 @@ +#include "../include/except.h" +#include +#include + +ExceptFrame *except_stack = NULL; // Global exception stack. +const Exception assert_failed = { "Assertion Failure!" }; // If ASSERT fails. + +void except_raise(const Exception *e, const char *file,int line) { + // An exception was raised, grab the exception stack. + ExceptFrame *p = except_stack; + asserted(e != NULL); // Ensure exception pointer is not NULL + if (p == NULL) { // Uncaught Exception + const char *msg = e->reason ? e->reason : "Uncaught Exception!"; + fprintf(stderr,"\033[31m%s | Address: 0x%p | Raised at %s@%d \033[0m" ,msg,(void *)e,file,line); + fflush(stderr); + abort(); + } + // Set the exception details to the current frame. + p->exception = e; // Exception reason + p->file = file; + p->line = line; + // Move to the previous frame in the stack. + except_stack = except_stack->prev; + // Jump to the saved context environment. + longjmp(p->env, EXCEPT_RAISED); +} +void asserted(int e) { + ASSERTED(e); + UNUSED(e); +} diff --git a/src/mem.c b/src/mem.c new file mode 100644 index 0000000..3ddc622 --- /dev/null +++ b/src/mem.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include "../include/except.h" +#include "../include/mem.h" + +const Exception OOM = { "Out of Memory" };/* Out of memory Exception */ +#define MEM_ENOMEM 12 /* Out of memory errno */ + +void *mem_alloc(int flags, size_t nbytes, const char *file, int line){ + void *ptr; + ASSERTED(nbytes > 0); + ptr = malloc(nbytes); + if (ptr == NULL){ + if (flags & SOFT_FAIL) return 0; + if (flags & HARD_FAIL) exit(MEM_ENOMEM); + + if (file == NULL) // Wasn't called by macro + RAISE(OOM); // Out of Memory Exception + else // Called by macro + except_raise(&OOM, file, line); + } + return flags&NOZERO ? ptr : memset(ptr, 0, nbytes); +} + +void mem_free(void *ptr, const char *file, int line) { + UNUSED(file); + UNUSED(line); + if (ptr){ + free(ptr); + } +} + +void *mem_resize(int flags, void *ptr, size_t nbytes,const char *file, int line) { + ASSERTED(ptr); + ASSERTED(nbytes > 0); + ptr = realloc(ptr, nbytes); + if (ptr == NULL){ + if(flags & SOFT_FAIL) return 0; + if(flags & HARD_FAIL) exit(MEM_ENOMEM); + + if (file == NULL) + RAISE(OOM); // Out of memory exception + else // Called by macro + except_raise(&OOM, file, line); + } + return ptr; +} + +int mem_is_zero(const void *ptr, size_t nbytes) { + ASSERTED(ptr); + ASSERTED(nbytes > 0); + static const unsigned char zero_block[1024] = {0}; /* reused */ + while (nbytes >= sizeof(zero_block)) { + if (memcmp(ptr, zero_block, sizeof(zero_block)) != 0) + return 0; + ptr = (const unsigned char *)ptr + sizeof(zero_block); + nbytes -= sizeof(zero_block); + } + if (nbytes > 0 && memcmp(ptr, zero_block, nbytes) != 0) + return 0; + return 1; +} + + diff --git a/tests/01_arena_new.c b/tests/01_arena_new.c new file mode 100644 index 0000000..7f72400 --- /dev/null +++ b/tests/01_arena_new.c @@ -0,0 +1,14 @@ +#define DEBUG +#include +#include +#include "../include/arena.h" +#include "../include/mem.h" + +int main(void){ + size_t nbytes = 20; + Arena a = ARENA(0,nbytes); + assert(a.beg != NULL); + assert( mem_is_zero(a.beg, nbytes)); + free(a.beg); + return EXIT_SUCCESS; +} diff --git a/tests/02_arena_nozero.c b/tests/02_arena_nozero.c new file mode 100644 index 0000000..58b6528 --- /dev/null +++ b/tests/02_arena_nozero.c @@ -0,0 +1,14 @@ +#define DEBUG +#include +#include +#include "../include/arena.h" +#include "../include/mem.h" + +int main(void){ + size_t nbytes = 20; + Arena a = ARENA(NOZERO,nbytes); + assert(a.beg != NULL); + assert( !mem_is_zero(a.beg, nbytes)); + free(a.beg); + return EXIT_SUCCESS; +} diff --git a/tests/03_arena_softfail.c b/tests/03_arena_softfail.c new file mode 100644 index 0000000..ebf89a1 --- /dev/null +++ b/tests/03_arena_softfail.c @@ -0,0 +1,12 @@ +#define DEBUG +#include +#include +#include +#include "../include/arena.h" +#include "../include/mem.h" +int main(void){ + size_t nbytes = MEM_GB(1024); + Arena a = ARENA(SOFT_FAIL, nbytes); + assert(a.beg == NULL); + return EXIT_SUCCESS; +} diff --git a/tests/04_arena_exception.c b/tests/04_arena_exception.c new file mode 100644 index 0000000..d6e1bb3 --- /dev/null +++ b/tests/04_arena_exception.c @@ -0,0 +1,30 @@ +#define DEBUG +#include +#include +#include +#include +#include "../include/arena.h" + +#include "../include/mem.h" +int main(void){ + size_t nbytes = MEM_GB(1024); + Arena a; + TRY { + a = ARENA(0,nbytes); /* try 1GB */ + if (!a.beg) + RAISE(OOM); + free(a.beg); + } + EXCEPT(OOM) { + /* handle memory failure gracefully */ + fprintf(stderr, "Caught: %s\n", OOM.reason); + return EXIT_SUCCESS; /* requested behavior */ + } + FINALLY { + /* cleanup if needed, runs always */ + if (a.beg) free(a.beg); + } + END_TRY; + + return EXIT_FAILURE; /* shouldn't reach here for this example */ + }