From ad815adc9f2a75e3257c507f6021f389d4b51863 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Wed, 25 Oct 2023 19:35:13 +0700 Subject: [PATCH] Splitting things up and adding stuff to the libs --- Makefile | 6 +- lib.h | 132 ++++++-------------------- libs/arena.h | 118 +++++++++++++++++++++++ libs/arena/LICENSE | 20 ++++ libs/arena/README.md | 47 ++++++++++ libs/arena/arena.h | 217 +++++++++++++++++++++++++++++++++++++++++++ rendering.h | 113 ++++++++++++++++++++++ stack.h | 176 +++++++++++++++++++++++++++++++++++ 8 files changed, 720 insertions(+), 109 deletions(-) create mode 100644 libs/arena.h create mode 100644 libs/arena/LICENSE create mode 100644 libs/arena/README.md create mode 100644 libs/arena/arena.h create mode 100644 rendering.h create mode 100644 stack.h diff --git a/Makefile b/Makefile index b11ad57..e32bd9a 100644 --- a/Makefile +++ b/Makefile @@ -10,9 +10,9 @@ RM=rm -vf build: clean $(CC) $(CFLAGS) $(LDLIBS) $(INCLUDES) $(OBJECTS) $(P).c -o $(P) -.PHONY: texturepacker -texturepacker: - $(CC) $(CFLAGS) -lSDL2 -lm -lcyaml -L objs/ $(INCLUDES) tools/texturepacker.c -o texturepacker +.PHONY: texpack +texpack: + $(CC) $(CFLAGS) -lSDL2 -lm -lcyaml -L objs/ $(INCLUDES) tools/texturepacker.c -o texpack .PHONY: run run: build diff --git a/lib.h b/lib.h index 31744fa..0def033 100644 --- a/lib.h +++ b/lib.h @@ -2,6 +2,8 @@ #include #include #include +#define ARENA_IMPLEMENTATION +#include "libs/arena/arena.h" typedef uint8_t u8; // typedef char16_t c16; @@ -25,115 +27,33 @@ typedef struct Animation { u16 cols; u16 width; u16 height; -} SpriteSheet; +} Animation; -void checkCode(int code, char* errorMsg) { - if (code < 0) { - fprintf(stderr, "Application Error %i: %s\n", code, errorMsg); - exit(1); - } +typedef struct s8 { + u8* data; + usize size; +} s8; + +typedef struct s8_buf { + char* data; + usize size; + usize capcity; + Arena* arena; +} str_buf; + +// TODO: It would be cool to use abstract allocators +s8 cstr(char *cstr, Arena *arena) { + int size = strlen(cstr); + void *data = arena_alloc(arena, size); + memcpy(data, cstr, size); + return (s8) { (u8*)data, size }; } -void* checkPtr(void *ptr, char* errorMsg) { - if (ptr == NULL) { - fprintf(stderr, "Application Error: %s\n", errorMsg); - exit(1); +void print_str(s8 string, bool print_nl) { + for (usize i = 0; i < string.size; i++) { + printf("%c", string.data[i]); } - return ptr; -} - -void checkShader(unsigned int shader, int statusFlag, char* actionName) { - int success; - glGetShaderiv(shader, statusFlag, &success); - if (success < 0) { - fprintf(stderr, "%s Error %i\n", actionName, success); - exit(1); + if (print_nl) { + printf("\n"); } } - -char* loadText(char* path) { - char* buffer = NULL; - long length; - FILE* f = fopen(path, "rb"); - if (f) { - fseek(f, 0, SEEK_END); - length = ftell(f); - fseek(f, 0, SEEK_SET); - buffer = calloc(length, sizeof(char)); - if (buffer) { - fread(buffer, 1, length, f); - } - fclose(f); - } - return buffer; -} - -void checkCompileErrors(unsigned int object, char* type) -{ - int success; - char infoLog[1024]; - if (strcmp("PROGRAM", type)) - { - glGetShaderiv(object, GL_COMPILE_STATUS, &success); - if (!success) - { - glGetShaderInfoLog(object, 1024, NULL, infoLog); - fprintf(stderr, "| ERROR::SHADER: Compile-time error: Type: %s \n %s\n", type, infoLog); - } - } - else - { - glGetProgramiv(object, GL_LINK_STATUS, &success); - if (!success) - { - glGetProgramInfoLog(object, 1024, NULL, infoLog); - fprintf(stderr, "| ERROR::Shader: Link-time error: Type: %s \n %s\n", type, infoLog); - } - } -} - -unsigned int compileShaderProgram(char* vertSrcPath, char* fragSrcPath, char* geoSrcPath) { - unsigned int vertShader, fragShader, geoShader; - - char* vertSrc = loadText(vertSrcPath); - vertShader = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(vertShader, 1, (const GLchar* const*)&vertSrc, NULL); - glCompileShader(vertShader); - checkCompileErrors(vertShader, "VERTEX"); - - char* fragSrc = loadText(fragSrcPath); - - fragShader = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(fragShader, 1, (const GLchar* const*)&fragSrc, NULL); - glCompileShader(fragShader); - checkCompileErrors(fragShader, "FRAGMENT"); - - char* geoSrc = NULL; - if (geoSrcPath != NULL) { - geoSrc = loadText(geoSrcPath); - geoShader = glCreateShader(GL_GEOMETRY_SHADER); - glShaderSource(geoShader, 1, (const GLchar* const*)&geoSrc, NULL); - glCompileShader(geoShader); - checkCompileErrors(geoShader, "GEOMETRY"); - } - - unsigned int program = glCreateProgram(); - glAttachShader(program, vertShader); - glAttachShader(program, fragShader); - if (geoSrcPath != NULL) { - glAttachShader(program, geoShader); - } - - glLinkProgram(program); - checkCompileErrors(program, "PROGRAM"); - - glDeleteShader(vertShader); - glDeleteShader(fragShader); - free(vertSrc); - free(fragSrc); - if (geoSrc != NULL) { - free(geoSrc); - } - - return program; -} diff --git a/libs/arena.h b/libs/arena.h new file mode 100644 index 0000000..f2e5c99 --- /dev/null +++ b/libs/arena.h @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include +#include + +bool is_power_of_two(uintptr_t x) { + return (x & (x-1)) == 0; +} + +uintptr_t align_forward(uintptr_t ptr, size_t align) { + uintptr_t p, a, modulo; + + assert(is_power_of_two(align)); + + p = ptr; + a = (uintptr_t)align; + // Same as (p % a) but faster as 'a' is a power of two + modulo = p & (a-1); + + if (modulo != 0) { + // If 'p' address is not aligned, push the address to the + // next value which is aligned + p += a - modulo; + } + return p; +} + +#ifndef DEFAULT_ALIGNMENT +#define DEFAULT_ALIGNMENT (2*sizeof(void *)) +#endif + +typedef struct Arena Arena; +struct Arena { + unsigned char *buf; + size_t buf_len; + size_t prev_offset; // This will be useful for later on + size_t curr_offset; +}; + +void arena_init(Arena *a, void *backing_buffer, size_t backing_buffer_length) { + a->buf = (unsigned char *)backing_buffer; + a->buf_len = backing_buffer_length; + a->curr_offset = 0; + a->prev_offset = 0; +} + + +void *arena_alloc_align(Arena *a, size_t size, size_t align) { + // Align 'curr_offset' forward to the specified alignment + uintptr_t curr_ptr = (uintptr_t)a->buf + (uintptr_t)a->curr_offset; + uintptr_t offset = align_forward(curr_ptr, align); + offset -= (uintptr_t)a->buf; // Change to relative offset + + // Check to see if the backing memory has space left + if (offset+size <= a->buf_len) { + void *ptr = &a->buf[offset]; + a->prev_offset = offset; + a->curr_offset = offset+size; + + // Zero new memory by default + memset(ptr, 0, size); + return ptr; + } + // Return NULL if the arena is out of memory (or handle differently) + return NULL; +} + +// Because C doesn't have default parameters +void *arena_alloc(Arena *a, size_t size) { + return arena_alloc_align(a, size, DEFAULT_ALIGNMENT); +} + +// void arena_free(Arena *a, void *ptr) { +// // Do nothing +// } + +void *arena_resize_align(Arena *a, void *old_memory, size_t old_size, size_t new_size, size_t align) { + unsigned char *old_mem = (unsigned char *)old_memory; + + assert(is_power_of_two(align)); + + if (old_mem == NULL || old_size == 0) { + return arena_alloc_align(a, new_size, align); + } else if (a->buf <= old_mem && old_mem < a->buf+a->buf_len) { + if (a->buf+a->prev_offset == old_mem) { + a->curr_offset = a->prev_offset + new_size; + if (new_size > old_size) { + // Zero the new memory by default + memset(&a->buf[a->curr_offset], 0, new_size-old_size); + } + return old_memory; + } else { + void *new_memory = arena_alloc_align(a, new_size, align); + size_t copy_size = old_size < new_size ? old_size : new_size; + // Copy across old memory to the new memory + memmove(new_memory, old_memory, copy_size); + return new_memory; + } + + } else { + assert(0 && "Memory is out of bounds of the buffer in this arena"); + return NULL; + } + +} + +// Because C doesn't have default parameters +void *arena_resize(Arena *a, void *old_memory, size_t old_size, size_t new_size) { + return arena_resize_align(a, old_memory, old_size, new_size, DEFAULT_ALIGNMENT); +} + +void arena_free_all(Arena *a) { + a->curr_offset = 0; + a->prev_offset = 0; +} + diff --git a/libs/arena/LICENSE b/libs/arena/LICENSE new file mode 100644 index 0000000..696ed40 --- /dev/null +++ b/libs/arena/LICENSE @@ -0,0 +1,20 @@ +Copyright 2022 Alexey Kutepov + +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. \ No newline at end of file diff --git a/libs/arena/README.md b/libs/arena/README.md new file mode 100644 index 0000000..0a313cd --- /dev/null +++ b/libs/arena/README.md @@ -0,0 +1,47 @@ +# Arena Allocator + +[Arena Allocator](https://en.wikipedia.org/wiki/Region-based_memory_management) implementation in pure C as an [stb-style single-file library](https://github.com/nothings/stb). + +*I just caught myself implementing this over and over again in my projects, so I decided to turn it into a copy-pastable library similar to [sv](http://github.com/tsoding/sv)* + +## Quick Start + +> The truly reusable code is the one that you can simply copy-paste. + +The library itself does not require any special building. You can simple copy-paste [./arena.h](./arena.h) to your project and `#include` it. + +```c +#define ARENA_IMPLEMENTATION +#include "arena.h" + +static Arena default_arena = {0}; +static Arena temporary_arena = {0}; +static Arena *context_arena = &default_arena; + +void *context_alloc(size_t size) +{ + assert(context_arena); + return arena_alloc(context_arena, size); +} + +int main(void) +{ + // Allocate stuff in default_arena + context_alloc(64); + context_alloc(128); + context_alloc(256); + context_alloc(512); + + // Allocate stuff in temporary_arena; + context_arena = &temporary_arena; + context_alloc(64); + context_alloc(128); + context_alloc(256); + context_alloc(512); + + // Deallocate everything at once + arena_free(&default_arena); + arena_free(&temporary_arena); + return 0; +} +``` diff --git a/libs/arena/arena.h b/libs/arena/arena.h new file mode 100644 index 0000000..ac3e0ee --- /dev/null +++ b/libs/arena/arena.h @@ -0,0 +1,217 @@ +// Copyright 2022 Alexey Kutepov + +// 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. + +#ifndef ARENA_H_ +#define ARENA_H_ + +#include +#include + +#ifndef ARENA_ASSERT +#include +#define ARENA_ASSERT assert +#endif + +#define ARENA_BACKEND_LIBC_MALLOC 0 +#define ARENA_BACKEND_LINUX_MMAP 1 +#define ARENA_BACKEND_WIN32_VIRTUALALLOC 2 +#define ARENA_BACKEND_WASM_HEAPBASE 3 + +#ifndef ARENA_BACKEND +#define ARENA_BACKEND ARENA_BACKEND_LIBC_MALLOC +#endif // ARENA_BACKEND + +typedef struct Region Region; + +struct Region { + Region *next; + size_t count; + size_t capacity; + uintptr_t data[]; +}; + +typedef struct { + Region *begin, *end; +} Arena; + +#define REGION_DEFAULT_CAPACITY (8*1024) + +Region *new_region(size_t capacity); +void free_region(Region *r); + +// TODO: snapshot/rewind capability for the arena +// - Snapshot should be combination of a->end and a->end->count. +// - Rewinding should be restoring a->end and a->end->count from the snapshot and +// setting count-s of all the Region-s after the remembered a->end to 0. +void *arena_alloc(Arena *a, size_t size_bytes); +void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz); + +void arena_reset(Arena *a); +void arena_free(Arena *a); + +#endif // ARENA_H_ + +#ifdef ARENA_IMPLEMENTATION + +#if ARENA_BACKEND == ARENA_BACKEND_LIBC_MALLOC +#include + +// TODO: instead of accepting specific capacity new_region() should accept the size of the object we want to fit into the region +// It should be up to new_region() to decide the actual capacity to allocate +Region *new_region(size_t capacity) +{ + size_t size_bytes = sizeof(Region) + sizeof(uintptr_t)*capacity; + // TODO: it would be nice if we could guarantee that the regions are allocated by ARENA_BACKEND_LIBC_MALLOC are page aligned + Region *r = malloc(size_bytes); + ARENA_ASSERT(r); + r->next = NULL; + r->count = 0; + r->capacity = capacity; + return r; +} + +void free_region(Region *r) +{ + free(r); +} +#elif ARENA_BACKEND == ARENA_BACKEND_LINUX_MMAP +# error "TODO: Linux mmap backend is not implemented yet" +#elif ARENA_BACKEND == ARENA_BACKEND_WIN32_VIRTUALALLOC + +#if !defined(_WIN32) +# error "Current platform is not Windows" +#endif + +#define WIN32_LEAN_AND_MEAN +#include + +#define INV_HANDLE(x) (((x) == NULL) || ((x) == INVALID_HANDLE_VALUE)) + +Region *new_region(size_t capacity) +{ + SIZE_T size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity; + Region *r = VirtualAllocEx( + GetCurrentProcess(), /* Allocate in current process address space */ + NULL, /* Unknown position */ + size_bytes, /* Bytes to allocate */ + MEM_COMMIT | MEM_RESERVE, /* Reserve and commit allocated page */ + PAGE_READWRITE /* Permissions ( Read/Write )*/ + ); + if (INV_HANDLE(r)) + ARENA_ASSERT(0 && "VirtualAllocEx() failed."); + + r->next = NULL; + r->count = 0; + r->capacity = capacity; + return r; +} + +void free_region(Region *r) +{ + if (INV_HANDLE(r)) + return; + + BOOL free_result = VirtualFreeEx( + GetCurrentProcess(), /* Deallocate from current process address space */ + (LPVOID)r, /* Address to deallocate */ + 0, /* Bytes to deallocate ( Unknown, deallocate entire page ) */ + MEM_RELEASE /* Release the page ( And implicitly decommit it ) */ + ); + + if (FALSE == free_result) + ARENA_ASSERT(0 && "VirtualFreeEx() failed."); +} + +#elif ARENA_BACKEND == ARENA_BACKEND_WASM_HEAPBASE +# error "TODO: WASM __heap_base backend is not implemented yet" +#else +# error "Unknown Arena backend" +#endif + +// TODO: add debug statistic collection mode for arena +// Should collect things like: +// - How many times new_region was called +// - How many times existing region was skipped +// - How many times allocation exceeded REGION_DEFAULT_CAPACITY + +void *arena_alloc(Arena *a, size_t size_bytes) +{ + size_t size = (size_bytes + sizeof(uintptr_t) - 1)/sizeof(uintptr_t); + + if (a->end == NULL) { + ARENA_ASSERT(a->begin == NULL); + size_t capacity = REGION_DEFAULT_CAPACITY; + if (capacity < size) capacity = size; + a->end = new_region(capacity); + a->begin = a->end; + } + + while (a->end->count + size > a->end->capacity && a->end->next != NULL) { + a->end = a->end->next; + } + + if (a->end->count + size > a->end->capacity) { + ARENA_ASSERT(a->end->next == NULL); + size_t capacity = REGION_DEFAULT_CAPACITY; + if (capacity < size) capacity = size; + a->end->next = new_region(capacity); + a->end = a->end->next; + } + + void *result = &a->end->data[a->end->count]; + a->end->count += size; + return result; +} + +void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz) +{ + if (newsz <= oldsz) return oldptr; + void *newptr = arena_alloc(a, newsz); + char *newptr_char = newptr; + char *oldptr_char = oldptr; + for (size_t i = 0; i < oldsz; ++i) { + newptr_char[i] = oldptr_char[i]; + } + return newptr; +} + +void arena_reset(Arena *a) +{ + for (Region *r = a->begin; r != NULL; r = r->next) { + r->count = 0; + } + + a->end = a->begin; +} + +void arena_free(Arena *a) +{ + Region *r = a->begin; + while (r) { + Region *r0 = r; + r = r->next; + free_region(r0); + } + a->begin = NULL; + a->end = NULL; +} + +#endif // ARENA_IMPLEMENTATION diff --git a/rendering.h b/rendering.h new file mode 100644 index 0000000..9e93904 --- /dev/null +++ b/rendering.h @@ -0,0 +1,113 @@ +#pragma once +#include + +void checkCode(int code, char* errorMsg) { + if (code < 0) { + fprintf(stderr, "Application Error %i: %s\n", code, errorMsg); + exit(1); + } +} + +void* checkPtr(void *ptr, char* errorMsg) { + if (ptr == NULL) { + fprintf(stderr, "Application Error: %s\n", errorMsg); + exit(1); + } + return ptr; +} + +void checkShader(unsigned int shader, int statusFlag, char* actionName) { + int success; + glGetShaderiv(shader, statusFlag, &success); + if (success < 0) { + fprintf(stderr, "%s Error %i\n", actionName, success); + exit(1); + } +} + +char* loadText(char* path) { + char* buffer = NULL; + long length; + FILE* f = fopen(path, "rb"); + if (f) { + fseek(f, 0, SEEK_END); + length = ftell(f); + fseek(f, 0, SEEK_SET); + buffer = calloc(length, sizeof(char)); + if (buffer) { + fread(buffer, 1, length, f); + } + fclose(f); + } + return buffer; +} + +void checkCompileErrors(unsigned int object, char* type) +{ + int success; + char infoLog[1024]; + if (strcmp("PROGRAM", type)) + { + glGetShaderiv(object, GL_COMPILE_STATUS, &success); + if (!success) + { + glGetShaderInfoLog(object, 1024, NULL, infoLog); + fprintf(stderr, "| ERROR::SHADER: Compile-time error: Type: %s \n %s\n", type, infoLog); + } + } + else + { + glGetProgramiv(object, GL_LINK_STATUS, &success); + if (!success) + { + glGetProgramInfoLog(object, 1024, NULL, infoLog); + fprintf(stderr, "| ERROR::Shader: Link-time error: Type: %s \n %s\n", type, infoLog); + } + } +} + +unsigned int compileShaderProgram(char* vertSrcPath, char* fragSrcPath, char* geoSrcPath) { + unsigned int vertShader, fragShader, geoShader; + + char* vertSrc = loadText(vertSrcPath); + vertShader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertShader, 1, (const GLchar* const*)&vertSrc, NULL); + glCompileShader(vertShader); + checkCompileErrors(vertShader, "VERTEX"); + + char* fragSrc = loadText(fragSrcPath); + + fragShader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragShader, 1, (const GLchar* const*)&fragSrc, NULL); + glCompileShader(fragShader); + checkCompileErrors(fragShader, "FRAGMENT"); + + char* geoSrc = NULL; + if (geoSrcPath != NULL) { + geoSrc = loadText(geoSrcPath); + geoShader = glCreateShader(GL_GEOMETRY_SHADER); + glShaderSource(geoShader, 1, (const GLchar* const*)&geoSrc, NULL); + glCompileShader(geoShader); + checkCompileErrors(geoShader, "GEOMETRY"); + } + + unsigned int program = glCreateProgram(); + glAttachShader(program, vertShader); + glAttachShader(program, fragShader); + if (geoSrcPath != NULL) { + glAttachShader(program, geoShader); + } + + glLinkProgram(program); + checkCompileErrors(program, "PROGRAM"); + + glDeleteShader(vertShader); + glDeleteShader(fragShader); + free(vertSrc); + free(fragSrc); + if (geoSrc != NULL) { + free(geoSrc); + } + + return program; +} diff --git a/stack.h b/stack.h new file mode 100644 index 0000000..2679253 --- /dev/null +++ b/stack.h @@ -0,0 +1,176 @@ +#pragma once + +#ifndef DEFAULT_ALIGNMENT +#define DEFAULT_ALIGNMENT (2*sizeof(void *)) +#endif + +bool is_power_of_two(uintptr_t x) { + return (x & (x-1)) == 0; +} + +typedef struct Stack { + unsigned char *buf; + size_t buf_len; + size_t prev_offset; + size_t curr_offset; +} Stack; + +typedef struct Stack_Allocation_Header { + size_t prev_offset; + size_t padding; +} Stack_Allocation_Header; + +void stack_init(Stack *s, void *backing_buffer, size_t backing_buffer_length) { + s->buf = (unsigned char *)backing_buffer; + s->buf_len = backing_buffer_length; + s->prev_offset = 0; + s->curr_offset = 0; +} +size_t calc_padding_with_header(uintptr_t ptr, uintptr_t alignment, size_t header_size) { + uintptr_t p, a, modulo, padding, needed_space; + + assert(is_power_of_two(alignment)); + + p = ptr; + a = alignment; + modulo = p & (a-1); // (p % a) as it assumes alignment is a power of two + + padding = 0; + needed_space = 0; + + if (modulo != 0) { // Same logic as 'align_forward' + padding = a - modulo; + } + + needed_space = (uintptr_t)header_size; + + if (padding < needed_space) { + needed_space -= padding; + + if ((needed_space & (a-1)) != 0) { + padding += a * (1+(needed_space/a)); + } else { + padding += a * (needed_space/a); + } + } + + return (size_t)padding; +} + +void *stack_alloc_align(Stack *s, size_t size, size_t alignment) { + uintptr_t curr_addr, next_addr; + size_t padding; + Stack_Allocation_Header *header; + + + assert(is_power_of_two(alignment)); + + if (alignment > 128) { + // As the padding is 8 bits (1 byte), the largest alignment that can + // be used is 128 bytes + alignment = 128; + } + + curr_addr = (uintptr_t)s->buf + (uintptr_t)s->curr_offset; + usize sah_size = sizeof(Stack_Allocation_Header); + padding = calc_padding_with_header(curr_addr, (uintptr_t)alignment, sah_size); + if (s->curr_offset + padding + size > s->buf_len) { + // Stack allocator is out of memory + return NULL; + } + + s->prev_offset = s->curr_offset; // Store the previous offset + s->curr_offset += padding; + + next_addr = curr_addr + (uintptr_t)padding; + header = (Stack_Allocation_Header *)(next_addr - sizeof(Stack_Allocation_Header)); + header->padding = padding; + header->prev_offset = s->prev_offset; // store the previous offset in the header + + s->curr_offset += size; + + return memset((void *)next_addr, 0, size); +} + +// Because C does not have default parameters +void *stack_alloc(Stack *s, size_t size) { + return stack_alloc_align(s, size, DEFAULT_ALIGNMENT); +} + +void stack_free(Stack *s, void *ptr) { + if (ptr != NULL) { + uintptr_t start, end, curr_addr; + Stack_Allocation_Header *header; + size_t prev_offset; + + start = (uintptr_t)s->buf; + end = start + (uintptr_t)s->buf_len; + curr_addr = (uintptr_t)ptr; + + if (!(start <= curr_addr && curr_addr < end)) { + assert(0 && "Out of bounds memory address passed to stack allocator (free)"); + return; + } + + if curr_addr >= start+(uintptr_t)s->offset { + // Allow double frees + return; + } + + header = (Stack_Allocation_Header *)(curr_addr - sizeof(Stack_Allocation_Header)); + + // Calculate previous offset from the header and its address + prev_offset = (size_t)(curr_addr - (uintptr_t)header->padding - start); + + if (prev_offset != header->prev_offset) { + assert(0 && "Out of order stack allocator free"); + return; + } + + // Reset the offsets to the previous allocation + s->curr_offset = s->prev_offset; + s->prev_offset = header->prev_offset; + } +} + +void *stack_resize_align(Stack *s, void *ptr, size_t old_size, size_t new_size, size_t alignment) { + if (ptr == NULL) { + return stack_alloc_align(s, new_size, alignment); + } else if (new_size == 0) { + stack_free(s, ptr); + return NULL; + } else { + uintptr_t start, end, curr_addr; + size_t min_size = old_size < new_size ? old_size : new_size; + void *new_ptr; + + start = (uintptr_t)s->buf; + end = start + (uintptr_t)s->buf_len; + curr_addr = (uintptr_t)ptr; + if (!(start <= curr_addr && curr_addr < end)) { + assert(0 && "Out of bounds memory address passed to stack allocator (resize)"); + return NULL; + } + + if (curr_addr >= start + (uintptr_t)s->offset) { + // Treat as a double free + return NULL; + } + + if old_size == size { + return ptr; + } + + new_ptr = stack_alloc_align(s, new_size, alignment); + memmove(new_ptr, ptr, min_size); + return new_ptr; + } +} + +void *stack_resize(Stack *s, void *ptr, size_t old_size, size_t new_size) { + return stack_resize_align(s, ptr, old_size, new_size, DEFAULT_ALIGNMENT); +} + +void stack_free_all(Stack *s) { + s->offset = 0; +}