diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e3f88f..a8ecfae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,12 @@ add_executable(arena_main src/main.c) target_link_libraries(arena_main arena_lib) +function(enable_sanitizers target) + target_compile_options(${target} PRIVATE -fsanitize=address -fno-omit-frame-pointer) + target_link_options(${target} PRIVATE -fsanitize=address) +endfunction() + + # ------------------------ # Testing # ------------------------ diff --git a/include/arena.h b/include/arena.h index 5b743fd..ac9bb1b 100644 --- a/include/arena.h +++ b/include/arena.h @@ -53,10 +53,11 @@ ArenaPointer arena_alloc(Arena *arena, size_t size, size_t alignment); ArenaPointer arena_push(Arena *arena, void *data, size_t size, size_t alignment); ArenaErr arena_realloc(Arena *arena, size_t new_capacity); -SizeResult align_arena_offset(Arena *arena, size_t alignment); - +SizeResult get_arena_align_padding(Arena *arena, size_t alignment); +ArenaErr arena_ensure_capacity(Arena *arena, size_t size, size_t alignment); // Should be moved to something like general utilities, // i should make one for all my c projects bool mul_size_t_safe(size_t a, size_t b, size_t *out); + #endif // !ARENA_H diff --git a/src/arena.c b/src/arena.c index 9bd16b3..36c430a 100644 --- a/src/arena.c +++ b/src/arena.c @@ -6,6 +6,13 @@ #include ArenaResult arena_init(size_t capacity) { + if (capacity < 1) { + ArenaResult err = { + .is_valid = false, + .err = ARENA_INVALID_SIZE, + }; + return err; + } void *buffer = malloc(capacity); if (buffer == NULL) { @@ -16,14 +23,6 @@ ArenaResult arena_init(size_t capacity) { return err; } - if (capacity < 1) { - ArenaResult err = { - .is_valid = false, - .err = ARENA_INVALID_SIZE, - }; - return err; - } - Arena new_arena = { .buffer = buffer, .capacity = capacity, @@ -38,13 +37,10 @@ ArenaResult arena_init(size_t capacity) { } void arena_destroy(Arena *arena) { - if (arena == NULL) { - return; - } - + free(arena->buffer); + arena->buffer = NULL; arena->offset = 0; arena->capacity = 0; - free(arena->buffer); } ArenaPointer arena_alloc(Arena *arena, size_t size, size_t alignment) { @@ -56,16 +52,17 @@ ArenaPointer arena_alloc(Arena *arena, size_t size, size_t alignment) { return err; } - SizeResult new_offset = align_arena_offset(arena, alignment); - if (!new_offset.is_valid) { + SizeResult padding = get_arena_align_padding(arena, alignment); + if (!padding.is_valid) { ArenaPointer err = { .is_valid = false, - .err = new_offset.err, + .err = padding.err, }; return err; } - if (new_offset.val + size >= arena->capacity) { + + if (arena->offset + padding.val >= arena->capacity) { ArenaPointer err = { .is_valid = false, .err = ARENA_OUT_OF_SPACE, @@ -73,15 +70,57 @@ ArenaPointer arena_alloc(Arena *arena, size_t size, size_t alignment) { return err; } - arena->offset = new_offset.val; + if (arena->offset > SIZE_MAX - padding.val - size) { + ArenaPointer err = { + .is_valid = false, + .err = ARENA_CAPACITY_OVERFLOW, + }; + return err; + } + size_t aligned_offset = arena->offset + padding.val; + + arena->offset = aligned_offset + size; ArenaPointer val = { .is_valid = true, - .address = arena->buffer + arena->offset, + .address = arena->buffer + aligned_offset, }; return val; } +ArenaErr arena_ensure_capacity(Arena *arena, size_t size, size_t alignment) { + if (arena == NULL) { + return ARENA_NULL_ARG; + } + + while (true) { + SizeResult padding = get_arena_align_padding(arena, alignment); + if (!padding.is_valid) { + return padding.err; + } + + if (arena->offset > SIZE_MAX - padding.val - size) { + return ARENA_CAPACITY_OVERFLOW; + } + + size_t required = arena->offset + padding.val + size; + + if (required <= arena->capacity) { + return ARENA_OK; + } + + size_t new_capacity; + if (mul_size_t_safe(arena->capacity, 2, &new_capacity)) { + return ARENA_CAPACITY_OVERFLOW; + } + + ArenaErr err = arena_realloc(arena, new_capacity); + if (err != ARENA_OK) { + return err; + } + } +} + ArenaPointer arena_push(Arena *arena, void *data, size_t size, size_t alignment) { if (arena == NULL || data == NULL) { ArenaPointer bad_pointer = { @@ -91,35 +130,20 @@ ArenaPointer arena_push(Arena *arena, void *data, size_t size, size_t alignment) return bad_pointer; } - ArenaPointer pointer = arena_alloc(arena, size, alignment); + ArenaErr err = arena_ensure_capacity(arena, size, alignment); + if (err != ARENA_OK) { + ArenaPointer bad_pointer = { + .is_valid = false, + .err = err, + }; + return bad_pointer; + } + ArenaPointer pointer = arena_alloc(arena, size, alignment); if (!pointer.is_valid) { return pointer; } - while (!pointer.is_valid && pointer.err == ARENA_OUT_OF_SPACE) { - size_t new_capacity; - - bool is_overflow = mul_size_t_safe(arena->capacity, 2, &new_capacity); - if (is_overflow) { - ArenaPointer bad_pointer = { - .is_valid = false, - .err = ARENA_CAPACITY_OVERFLOW, - }; - return bad_pointer; - } - - ArenaErr err = arena_realloc(arena, new_capacity); - if (err != ARENA_OK) { - ArenaPointer bad_pointer = { - .is_valid = false, - .err = err, - }; - return bad_pointer; - } - - pointer = arena_alloc(arena, size, alignment); - } - + memcpy( pointer.address, data, @@ -143,7 +167,7 @@ ArenaErr arena_realloc(Arena *arena, size_t new_capacity) { return ARENA_OK; } -SizeResult align_arena_offset(Arena *arena, size_t alignment) { +SizeResult get_arena_align_padding(Arena *arena, size_t alignment) { if (arena == NULL) { SizeResult err = { .is_valid = false, @@ -160,11 +184,11 @@ SizeResult align_arena_offset(Arena *arena, size_t alignment) { return err; } + uintptr_t current_address = (uintptr_t) (arena->buffer + arena->offset); + uintptr_t aligned = ((current_address + (alignment - 1)) & ~(alignment - 1)); SizeResult val = { .is_valid = true, - .val = ((arena->offset + (alignment - 1)) & ~(alignment - 1)), - // Formula for alignment, found at stackoverflow, i came up with one - // but it involved 3 divisions, this one is way faster :) + .val = aligned - current_address, }; return val; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index aef968f..4db3995 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -7,4 +7,9 @@ target_link_libraries(test_arena cmocka::cmocka ) +enable_sanitizers(test_arena) + +target_compile_options(test_arena PRIVATE -fsanitize=address -fno-omit-frame-pointer) +target_link_options(test_arena PRIVATE -fsanitize=address) + add_test(NAME arena_tests COMMAND test_arena) diff --git a/test/test_arena.c b/test/test_arena.c index c170407..c7b6941 100644 --- a/test/test_arena.c +++ b/test/test_arena.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "arena.h" @@ -19,22 +20,61 @@ static void test_push_3_ints(void **state) { int int_to_push = 20; ArenaPointer result = arena_push(&arena, &int_to_push, sizeof(int), alignof(int)); assert_true(result.is_valid); - assert_int_equal(20, (int) *result.address); + assert_int_equal(20, *(int*)result.address); int_to_push = 30; result = arena_push(&arena, &int_to_push, sizeof(int), alignof(int)); assert_true(result.is_valid); - assert_int_equal(30, (int) *result.address); + assert_int_equal(30, *(int*) result.address); int_to_push = 40; result = arena_push(&arena, &int_to_push, sizeof(int), alignof(int)); assert_true(result.is_valid); - assert_int_equal(40, (int) *result.address); + assert_int_equal(40, *(int*) result.address); + + arena_destroy(&arena); } +static void test_push_3_ints_2_doubles(void **state) { + (void) state; + + ArenaResult value = arena_init((sizeof(int) * 3) + (sizeof(double) * 2)); + assert_true(value.is_valid); + Arena arena = value.arena; + + int int_to_push = 20; + ArenaPointer result = arena_push(&arena, &int_to_push, sizeof(int), alignof(int)); + assert_true(result.is_valid); + assert_int_equal(20, *(int*)result.address); + + double double_to_push = 4.57; + result = arena_push(&arena, &double_to_push, sizeof(double), alignof(double)); + assert_true(result.is_valid); + assert_double_equal(4.57, *(double*)result.address, 1e-6); + + int_to_push = 30; + result = arena_push(&arena, &int_to_push, sizeof(int), alignof(int)); + assert_true(result.is_valid); + assert_int_equal(30, *(int*)result.address); + + int_to_push = 40; + result = arena_push(&arena, &int_to_push, sizeof(int), alignof(int)); + assert_true(result.is_valid); + assert_int_equal(40, *(int*) result.address); + + double_to_push = 267.33; + result = arena_push(&arena, &double_to_push, sizeof(double), alignof(double)); + assert_true(result.is_valid); + assert_double_equal(267.33, *(double*) result.address, 1e-6); + + arena_destroy(&arena); +} + + int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_push_3_ints), + cmocka_unit_test(test_push_3_ints_2_doubles), };