From b3f5ce377265a53047531ead0fc99558c9800dfd Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Sun, 31 Dec 2023 16:07:58 +0100 Subject: [PATCH] Array tests, allocator design changes --- arena.c | 87 ++++++--- arena.h | 47 ++--- array.hpp | 102 +++++----- build.bat | 5 +- build.sh | 4 +- core.c | 8 - core.h | 2 + defer.hpp | 3 +- table.hpp | 456 ++++++++++++++++++++++---------------------- test/test_arena.cpp | 14 -- test/test_array.cpp | 122 ++++++++++++ 11 files changed, 485 insertions(+), 365 deletions(-) delete mode 100644 test/test_arena.cpp create mode 100644 test/test_array.cpp diff --git a/arena.c b/arena.c index 17dc1dc..6d6d025 100644 --- a/arena.c +++ b/arena.c @@ -22,6 +22,7 @@ MA_API void MA_MemoryCopy(void *dst, void *src, size_t size) { #include #define MA_CMalloc(x) malloc(x) #define MA_CFree(x) free(x) + #define MA_CRealloc(p, size) realloc(p, size) #endif #ifndef MA_StaticFunc @@ -94,24 +95,6 @@ MA_StaticFunc size_t MA__AlignLen(MA_Arena *a) { return aligned; } -MA_API void *MA__BeginPackedArray(MA_Arena *arena, size_t element_size) { - MA_ASSERT(arena->memory.data); - arena->len = MA__AlignLen(arena); - arena->saved_alignment = arena->alignment; - arena->alignment = 0; - arena->packed_array_begin = arena->len; - arena->packed_array_element_size = element_size; - void *result = arena->memory.data + arena->len; - return result; -} - -MA_API int MA_EndPackedArray(MA_Arena *arena) { - arena->alignment = arena->saved_alignment; - size_t different = (arena->len - arena->packed_array_begin); - int result = (int)((arena->len - arena->packed_array_begin) / arena->packed_array_element_size); - return result; -} - MA_API void MA_SetAlignment(MA_Arena *arena, int alignment) { arena->alignment = alignment; } @@ -154,7 +137,6 @@ MA_API void *MA_PushSize(MA_Arena *arena, size_t size) { MA_API void MA_InitEx(MA_Arena *a, size_t reserve) { a->memory = MV_Reserve(reserve); a->alignment = MA_DEFAULT_ALIGNMENT; - MA_INIT_HOOK(a); } MA_API void MA_Init(MA_Arena *a) { @@ -171,7 +153,6 @@ MA_API MA_Arena *MA_Bootstrap(void) { MA_Arena bootstrap_arena = {0}; MA_Arena *arena = MA_PushStruct(&bootstrap_arena, MA_Arena); *arena = bootstrap_arena; - arena->allocator.obj = arena; return arena; } @@ -180,7 +161,6 @@ MA_API void MA_InitFromBuffer(MA_Arena *arena, void *buffer, size_t size) { arena->memory.commit = size; arena->memory.reserve = size; arena->alignment = MA_DEFAULT_ALIGNMENT; - MA_INIT_HOOK(arena); } MA_API MA_Arena MA_MakeFromBuffer(void *buffer, size_t size) { @@ -190,6 +170,12 @@ MA_API MA_Arena MA_MakeFromBuffer(void *buffer, size_t size) { return arena; } +MA_API MA_Arena MA_Create() { + MA_Arena arena = {0}; + MA_Init(&arena); + return arena; +} + MA_API char *MA_PushStringCopy(MA_Arena *arena, char *p, size_t size) { char *copy_buffer = (char *)MA_PushSizeNonZeroed(arena, size + 1); MA_MemoryCopy(copy_buffer, p, size); @@ -233,12 +219,12 @@ MA_API void MA_Load(MA_Checkpoint checkpoint) { } MA_API void *M_AllocNonZeroed(M_Allocator allocator, size_t size) { - void *p = allocator.p(allocator.obj, M_AllocatorOp_Allocate, NULL, size); + void *p = allocator.p(allocator.obj, M_AllocatorOp_Allocate, NULL, size, 0); return p; } MA_API void *M_Alloc(M_Allocator allocator, size_t size) { - void *p = allocator.p(allocator.obj, M_AllocatorOp_Allocate, NULL, size); + void *p = allocator.p(allocator.obj, M_AllocatorOp_Allocate, NULL, size, 0); MA_MemoryZero(p, size); return p; } @@ -250,10 +236,16 @@ MA_API void *M_AllocCopy(M_Allocator allocator, void *p, size_t size) { } MA_API void M_Dealloc(M_Allocator allocator, void *p) { - allocator.p(allocator.obj, M_AllocatorOp_Deallocate, p, 0); + allocator.p(allocator.obj, M_AllocatorOp_Deallocate, p, 0, 0); } -MA_StaticFunc void *M_ClibAllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size) { +MA_API void *M_ReallocNonZeroed(M_Allocator allocator, void *p, size_t size, size_t old_size) { + void *result = allocator.p(allocator.obj, M_AllocatorOp_Reallocate, p, size, old_size); + // @todo: add old_size? because we can't zero + return result; +} + +MA_StaticFunc void *M_ClibAllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size, size_t old_size) { if (kind == M_AllocatorOp_Allocate) { return MA_CMalloc(size); } @@ -263,16 +255,26 @@ MA_StaticFunc void *M_ClibAllocatorProc(void *allocator, M_AllocatorOp kind, voi return NULL; } + if (kind == M_AllocatorOp_Reallocate) { + return MA_CRealloc(p, size); + } + MA_ASSERT("MA_Arena invalid codepath"); return NULL; } -MA_API void *MA_AllocatorProc(M_Allocator allocator, M_AllocatorOp kind, void *p, size_t size) { +MA_API void *MA_AllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size, size_t old_size) { if (kind == M_AllocatorOp_Allocate) { - return MA_PushSizeNonZeroed((MA_Arena *)allocator.obj, size); + return MA_PushSizeNonZeroed((MA_Arena *)allocator, size); } - if (kind == M_AllocatorOp_Deallocate) { + else if (kind == M_AllocatorOp_Reallocate) { + void *new_p = MA_PushSizeNonZeroed((MA_Arena *)allocator, size); + MA_MemoryCopy(new_p, p, old_size); + return new_p; + } + + else if (kind == M_AllocatorOp_Deallocate) { return NULL; } @@ -280,6 +282,35 @@ MA_API void *MA_AllocatorProc(M_Allocator allocator, M_AllocatorOp kind, void *p return NULL; } +MA_API void *MA_ExclusiveAllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size, size_t old_size) { + MA_Arena *arena = (MA_Arena *)allocator; + if (kind == M_AllocatorOp_Reallocate) { + if (size > arena->len) { + size_t size_to_push = size - arena->len; + MA_PushSizeNonZeroed(arena, size_to_push); + return arena->memory.data; + } + } + + if (kind == M_AllocatorOp_Deallocate) { + MA_DeallocateArena(arena); + return NULL; + } + + MA_ASSERT("MA_Arena invalid codepath"); + return NULL; +} + +MA_API M_Allocator MA_GetExclusiveAllocator(MA_Arena *arena) { + M_Allocator allocator = {arena, MA_ExclusiveAllocatorProc}; + return allocator; +} + +MA_API M_Allocator MA_GetAllocator(MA_Arena *arena) { + M_Allocator allocator = {arena, MA_AllocatorProc}; + return allocator; +} + MA_API M_Allocator M_GetSystemAllocator(void) { M_Allocator allocator; allocator.obj = 0; diff --git a/arena.h b/arena.h index 737a7a9..7b505db 100644 --- a/arena.h +++ b/arena.h @@ -26,24 +26,10 @@ typedef struct M_Allocator M_Allocator; #define MA_COMMIT_ADD_SIZE MA_MIB(4) #endif -#ifndef MA_INHERIT_HOOK - #define MA_INHERIT_HOOK -#endif - -#ifndef MA_C_INHERIT_HOOK - #define MA_C_INHERIT_HOOK M_Allocator allocator; -#endif - #ifndef MA_ZERO_IS_INITIALIZATION #define MA_ZERO_IS_INITIALIZATION 1 #endif -#ifndef MA_INIT_HOOK - #define MA_INIT_HOOK(arena) \ - arena->allocator.obj = (void *)arena; \ - arena->allocator.p = (M_AllocatorProc *)MA_AllocatorProc; -#endif - #ifndef MA_API #ifdef __cplusplus #define MA_API extern "C" @@ -72,12 +58,16 @@ typedef enum M_AllocatorOp { M_AllocatorOp_Invalid, M_AllocatorOp_Allocate, M_AllocatorOp_Deallocate, + M_AllocatorOp_Reallocate, } M_AllocatorOp; -typedef void *M_AllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size); +typedef void *M_AllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size, size_t old_size); +MA_API void *MA_AllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size, size_t old_size); +MA_API void *MA_ExclusiveAllocatorProc(void *allocator, M_AllocatorOp kind, void *p, size_t size, size_t old_size); + struct M_Allocator { void *obj; - void *(*p)(void *allocator, M_AllocatorOp kind, void *p, size_t size); + M_AllocatorProc *p; }; struct MV_Memory { @@ -86,15 +76,14 @@ struct MV_Memory { uint8_t *data; }; -struct MA_Arena MA_INHERIT_HOOK { - MA_C_INHERIT_HOOK +struct MA_Arena { MV_Memory memory; int alignment; - int saved_alignment; size_t len; - size_t packed_array_element_size; - size_t packed_array_begin; +#ifdef __cplusplus + operator M_Allocator() { return {this, MA_AllocatorProc}; } +#endif }; struct MA_Checkpoint { @@ -107,7 +96,6 @@ struct MA_Checkpoint { #define MA_PushStruct(a, T) (T *)MA_PushSize(a, sizeof(T)) #define MA_PushArray(a, T, c) (T *)MA_PushSize(a, sizeof(T) * (c)) #define MA_PushStructCopy(a, T, p) (T *)MA_PushCopy(a, (p), sizeof(T)) -#define MA_BeginPackedArray(arena, T) (T *)MA__BeginPackedArray(arena, sizeof(T)) #define MA_CheckpointScope(name, InArena) for (MA_Checkpoint name = MA_Save(InArena); name.arena; (MA_Load(name), name.arena = 0)) #define M_AllocStruct(a, T) (T *)M_Alloc((a), sizeof(T)) @@ -130,18 +118,20 @@ MA_API size_t MA_GetAlignOffset(size_t size, size_t align); MA_API size_t MA_AlignUp(size_t size, size_t align); MA_API size_t MA_AlignDown(size_t size, size_t align); MA_API void MA_DeallocateStub(MA_Arena *arena, void *p); + +MA_API void MA_InitEx(MA_Arena *a, size_t reserve); +MA_API void MA_Init(MA_Arena *a); +MA_API MA_Arena MA_Create(); + MA_API void MA_PopToPos(MA_Arena *arena, size_t pos); MA_API void * MA_PopSize(MA_Arena *arena, size_t size); MA_API void MA_DeallocateArena(MA_Arena *arena); MA_API void MA_Reset(MA_Arena *arena); -MA_API void * MA__BeginPackedArray(MA_Arena *arena, size_t element_size); -MA_API int MA_EndPackedArray(MA_Arena *arena); + MA_API void MA_SetAlignment(MA_Arena *arena, int alignment); MA_API uint8_t * MA_GetTop(MA_Arena *a); MA_API void * MA_PushSizeNonZeroed(MA_Arena *a, size_t size); MA_API void * MA_PushSize(MA_Arena *arena, size_t size); -MA_API void MA_InitEx(MA_Arena *a, size_t reserve); -MA_API void MA_Init(MA_Arena *a); MA_API void MA_MakeSureInitialized(MA_Arena *a); MA_API MA_Arena * MA_Bootstrap(void); MA_API void MA_InitFromBuffer(MA_Arena *arena, void *buffer, size_t size); @@ -155,7 +145,6 @@ MA_API void MA_Load(MA_Checkpoint checkpoint); MA_API MA_Checkpoint MA_GetScratchEx(MA_Arena **conflicts, int conflict_count); MA_API MA_Checkpoint MA_GetScratch(void); MA_API MA_Checkpoint MA_GetScratch1(MA_Arena *conflict); -MA_API void * MA_AllocatorProc(M_Allocator allocator, M_AllocatorOp kind, void *p, size_t size); MA_API MV_Memory MV_Reserve(size_t size); MA_API bool MV_Commit(MV_Memory *m, size_t commit); @@ -165,8 +154,11 @@ MA_API bool MV_DecommitPos(MV_Memory *m, size_t pos); MA_API void * M_AllocNonZeroed(M_Allocator allocator, size_t size); MA_API void * M_Alloc(M_Allocator allocator, size_t size); MA_API void * M_AllocCopy(M_Allocator allocator, void *p, size_t size); +MA_API void * M_ReallocNonZeroed(M_Allocator allocator, void *p, size_t size, size_t old_size); MA_API void M_Dealloc(M_Allocator allocator, void *p); MA_API M_Allocator M_GetSystemAllocator(void); +MA_API M_Allocator MA_GetExclusiveAllocator(MA_Arena *arena); +MA_API M_Allocator MA_GetAllocator(MA_Arena *arena); // clang-format on #ifndef MA_DISABLE_SCRATCH @@ -180,6 +172,7 @@ struct MA_Scratch { MA_Scratch() { this->checkpoint = MA_GetScratch(); } ~MA_Scratch() { MA_Load(checkpoint); } operator MA_Arena *() { return checkpoint.arena; } + operator M_Allocator() { return MA_GetAllocator(checkpoint.arena); } private: // @Note: Disable copy constructors, cause its error prone MA_Scratch(MA_Scratch &arena); diff --git a/array.hpp b/array.hpp index 3c86613..b32ede6 100644 --- a/array.hpp +++ b/array.hpp @@ -1,20 +1,8 @@ #pragma once -#ifndef ARRAY_PRIVATE_FUNCTION - #if defined(__GNUC__) || defined(__clang__) - #define ARRAY_PRIVATE_FUNCTION __attribute__((unused)) static - #else - #define ARRAY_PRIVATE_FUNCTION static - #endif -#endif - -#ifndef ARRAY_ALLOCATE - #include - #define ARRAY_ALLOCATE(allocator, size) malloc(size) -#endif - -#ifndef ARRAY_DEALLOCATE +#ifndef ARRAY_REALLOCATE #include + #define ARRAY_REALLOCATE(allocator, p, size, old_size) realloc(p, size) #define ARRAY_DEALLOCATE(allocator, p) free(p) #endif @@ -28,44 +16,45 @@ #define ARRAY_MemoryMove(dst, src, size) memmove(dst, src, size) #endif -#ifndef ARRAY_SET_DEFAULT_ALLOCATOR - #define ARRAY_SET_DEFAULT_ALLOCATOR +#ifndef ARRAY_Allocator + #define ARRAY_Allocator void * +#endif + // Example: // #define ARRAY_SET_DEFAULT_ALLOCATOR if (!allocator) allocator = global_heap; +#ifndef ARRAY_SET_DEFAULT_ALLOCATOR + #define ARRAY_SET_DEFAULT_ALLOCATOR #endif +// Iterating and removing elements +// +// ForArrayRemovable(array) { +// ForArrayRemovablePrepare(array); +// if (it == 4) ForArrayRemovableDeclare(); +// } +// #ifdef DEFER_HEADER -#define ForArrayRemovable(a) for (int __i = 0; __i < (a).len; __i += 1) -#define ForArrayRemovablePrepare(a) \ - auto &it = (a)[__i]; \ - bool remove_it = false; \ - defer { \ - if (remove_it) { \ - (a).ordered_remove(it); \ - __i -= 1; \ - } \ - } -#define ForArrayRemovableDeclare() (remove_it = true) + #define ForArrayRemovable(a) for (int __i = 0; __i < (a).len; __i += 1) + #define ForArrayRemovablePrepare(a) \ + auto &it = (a)[__i]; \ + bool remove_it = false; \ + defer { \ + if (remove_it) { \ + (a).ordered_remove(it); \ + __i -= 1; \ + } \ + } + #define ForArrayRemovableDeclare() (remove_it = true) #endif -#if !defined(ARRAY_ALLOCATOR_CODE) - #if defined(ARRAY_ALLOCATOR_TYPE) - #define ARRAY_ALLOCATOR_CODE(x) x - #define ARRAY_ALLOCATOR_PARAM ARRAY_ALLOCATOR_TYPE allocator, - #else - #define ARRAY_ALLOCATOR_CODE(x) - #define ARRAY_ALLOCATOR_PARAM - #endif -#endif - -#if !defined(For) -#define For2(array,it) for(auto &it : (array)) -#define For(array) For2(array,it) +#ifndef For + #define For2(it, array) for(auto &it : (array)) + #define For(array) For2(it, array) #endif template struct Array { - ARRAY_ALLOCATOR_CODE(ARRAY_ALLOCATOR_TYPE allocator;) + ARRAY_Allocator allocator; T *data; int cap, len; @@ -81,8 +70,8 @@ struct Array { bool is_first(T &item) { return &item == first(); } bool is_last(T &item) { return &item == last(); } - bool contains(T *item) { - bool result = item >= data && item < data + len; + bool contains(T &item) { + bool result = &item >= data && &item < data + len; return result; } @@ -154,24 +143,19 @@ struct Array { if (size > cap) { ARRAY_SET_DEFAULT_ALLOCATOR; - void *p = ARRAY_ALLOCATE(allocator, size * sizeof(T)); + void *p = ARRAY_REALLOCATE(allocator, data, size * sizeof(T), cap * sizeof(T)); ARRAY_ASSERT(p); - if (data) { - ARRAY_MemoryMove(p, data, len * sizeof(T)); - ARRAY_DEALLOCATE(allocator, data); - } - data = (T *)p; cap = size; } } - void init(ARRAY_ALLOCATOR_PARAM int size) { + void init(ARRAY_Allocator allocator, int size) { len = 0; cap = 0; data = 0; - ARRAY_ALLOCATOR_CODE(this->allocator = allocator;) + this->allocator = allocator; reserve(size); } @@ -190,6 +174,11 @@ struct Array { item = data[--len]; } + void unordered_remove_index(int index) { + ARRAY_ASSERT(index >= 0 && index < len); + data[index] = data[--len]; + } + int get_index(const T &item) { ptrdiff_t index = (ptrdiff_t)(&item - data); ARRAY_ASSERT(index >= 0 && index < len); @@ -201,8 +190,11 @@ struct Array { ARRAY_ASSERT(len > 0); ARRAY_ASSERT(&item >= begin() && &item < end()); int index = get_index(item); - ARRAY_ASSERT(index >= 0 && index < len); + ordered_remove_index(index); + } + void ordered_remove_index(int index) { + ARRAY_ASSERT(index >= 0 && index < len); int right_len = len - index - 1; ARRAY_MemoryMove(data + index, data + index + 1, right_len * sizeof(T)); len -= 1; @@ -230,9 +222,9 @@ struct Array { len = cap = 0; } - Array exact_copy(ARRAY_ALLOCATOR_CODE(ARRAY_ALLOCATOR_TYPE *allocator)) { + Array exact_copy(ARRAY_Allocator allocator) { Array result = {}; - ARRAY_ALLOCATOR_CODE(result.allocator = allocator;) + result.allocator = allocator; result.reserve(cap); ARRAY_MemoryMove(result.data, data, sizeof(T) * len); @@ -240,7 +232,7 @@ struct Array { return result; } - Array tight_copy(ARRAY_ALLOCATOR_CODE(ARRAY_ALLOCATOR_TYPE *allocator)) { + Array tight_copy(ARRAY_Allocator allocator) { Array result = {}; ARRAY_ALLOCATOR_CODE(result.allocator = allocator;) result.reserve(len); diff --git a/build.bat b/build.bat index c035605..9847a6e 100644 --- a/build.bat +++ b/build.bat @@ -14,13 +14,14 @@ set RELEASE_LINE=%RELEASE% %WRN% %COMMON% -link -incremental:no %LINK_RELEASE% mkdir build cd build -cl.exe -Fe:test_arena.exe ../test/test_arena.cpp %DEBUG_LINE% +cl.exe -Fe:test_array.exe ../test/test_array.cpp %DEBUG_LINE% if %errorlevel% neq 0 exit /b %errorlevel% -test_arena.exe +test_array.exe if %errorlevel% neq 0 exit /b %errorlevel% cl.exe -Fe:cpp_debug.exe ../test/main.cpp %DEBUG_LINE% if %errorlevel% neq 0 exit /b %errorlevel% + cl.exe -Fe:cpp_release.exe ../test/main.cpp %RELEASE_LINE% if %errorlevel% neq 0 exit /b %errorlevel% cl.exe -Fe:c_debug.exe ../test/main.c %DEBUG_LINE% diff --git a/build.sh b/build.sh index 3548691..1c376ff 100644 --- a/build.sh +++ b/build.sh @@ -3,6 +3,6 @@ set -e mkdir build cd build -clang -o test_arena ../test/test_arena.cpp -fno-exceptions -fno-rtti -Wno-writable-strings -./test_arena +clang -o test_array ../test/test_array.cpp -fno-exceptions -fno-rtti -Wno-writable-strings +./test_array cd .. \ No newline at end of file diff --git a/core.c b/core.c index df58df3..db9d9de 100644 --- a/core.c +++ b/core.c @@ -2,29 +2,21 @@ #define STB_SPRINTF_IMPLEMENTATION #include "stb_sprintf.h" - #define IO_VSNPRINTF stbsp_vsnprintf #define IO_SNPRINTF stbsp_snprintf #include "io.c" - #define MA_ASSERT(x) IO_Assert(x) #include "arena.c" - #define RE_ASSERT(x) IO_Assert(x) #include "regex.c" - #include "unicode.c" - #define S8_VSNPRINTF stbsp_vsnprintf #define S8_ALLOCATE(allocator, size) MA_PushSize(allocator, size) #define S8_ASSERT(x) IO_Assert(x) #define S8_MemoryCopy MA_MemoryCopy #include "string.c" - #define MU_ASSERT IO_Assert #include "multimedia.h" - #include "hash.c" #include "load_library.c" - #include "filesystem.c" diff --git a/core.h b/core.h index e6ce4a9..61d0a5b 100644 --- a/core.h +++ b/core.h @@ -21,6 +21,8 @@ #include "table.hpp" #define ARRAY_ASSERT IO_Assert #define ARRAY_ALLOCATOR_TYPE M_Allocator + #define ARRAY_REALLOCATE(allocator, p, size, old_size) M_ReallocNonZeroed(allocator, p, size, old_size) + #define ARRAY_DEALLOCATE(allocator, p) M_Dealloc(allocator, p) #define ARRAY_SET_DEFAULT_ALLOCATOR \ if (!allocator.p) allocator = M_GetSystemAllocator(); #include "array.hpp" diff --git a/defer.hpp b/defer.hpp index 4c6e1d3..29ccf1a 100644 --- a/defer.hpp +++ b/defer.hpp @@ -1,4 +1,5 @@ #pragma once +#define DEFER_HEADER template struct DEFER_ExitScope { @@ -19,4 +20,4 @@ class DEFER_ExitScopeHelp { #define DEFER_CONCAT_INTERNAL(x, y) x##y #define DEFER_CONCAT(x, y) DEFER_CONCAT_INTERNAL(x, y) -#define defer const auto DEFER_CONCAT(defer__, __LINE__) = DEFER_ExitScopeHelp() + [&]() +#define defer const auto DEFER_CONCAT(defer__, __LINE__) = DEFER_ExitScopeHelp() + [&]() \ No newline at end of file diff --git a/table.hpp b/table.hpp index f3f9851..19b0538 100644 --- a/table.hpp +++ b/table.hpp @@ -1,229 +1,229 @@ -#pragma once -#include -/* - Hash table implementation: - Pointers to values - Open adressing - Linear Probing - Power of 2 - Robin Hood hashing - Resizes on high probe count (min max load factor) - - Hash 0 is reserved for empty hash table entry -*/ - -// #define TABLE_ALLOCATOR_TYPE Allocator - -#ifndef TABLE_PRIVATE_FUNCTION - #if defined(__GNUC__) || defined(__clang__) - #define TABLE_PRIVATE_FUNCTION __attribute__((unused)) static - #else - #define TABLE_PRIVATE_FUNCTION static - #endif -#endif - -#ifndef TABLE_ALLOCATE - #include - #define TABLE_ALLOCATE(allocator, size) malloc(size) -#endif - -#ifndef TABLE_DEALLOCATE - #include - #define TABLE_DEALLOCATE(allocator, p) free(p) -#endif - -#ifndef TABLE_ASSERT - #include - #define TABLE_ASSERT(x) assert(x) -#endif - -#ifndef TABLE_SET_DEFAULT_ALLOCATOR - #define TABLE_SET_DEFAULT_ALLOCATOR -// Example: -// #define TABLE_SET_DEFAULT_ALLOCATOR if (!allocator) allocator = global_heap; -#endif - -#ifndef TABLE_HASH_BYTES - #define TABLE_HASH_BYTES TABLE__HashBytes -TABLE_PRIVATE_FUNCTION uint64_t TABLE__HashBytes(void *data, unsigned size) { - uint8_t *data8 = (uint8_t *)data; - uint64_t hash = (uint64_t)14695981039346656037ULL; - for (unsigned i = 0; i < size; i++) { - hash = hash ^ (uint64_t)(data8[i]); - hash = hash * (uint64_t)1099511628211ULL; - } - return hash; -} -#endif - -#define TABLE__WRAP_AROUND_POWER_OF_2(x, pow2) (((x) & ((pow2)-1llu))) - -TABLE_PRIVATE_FUNCTION int TABLE_CStringLen(char *str) { - int i = 0; - while(str[i]) i += 1; - return i; -} - -template -struct Table { - struct Entry { - uint64_t hash; - uint64_t key; - size_t distance; - Value value; - }; - - size_t len, cap; - Entry *values; -#ifdef TABLE_ALLOCATOR_TYPE - TABLE_ALLOCATOR_TYPE allocator; -#endif - - inline bool is_empty(Entry *entry) { return entry->hash == 0; } - inline bool is_occupied(Entry *entry) { return entry->hash != 0; } - - void reserve(size_t size) { - TABLE_ASSERT(size > cap && "New size is smaller then original size"); - TABLE_SET_DEFAULT_ALLOCATOR; - - Entry *old_values = values; - size_t old_cap = cap; - - values = (Entry *)TABLE_ALLOCATE(allocator, sizeof(Entry) * size); - for (int i = 0; i < size; i += 1) values[i] = {}; - cap = size; - - TABLE_ASSERT(!(old_values == 0 && len != 0)); - if (len == 0) { - if (old_values) TABLE_DEALLOCATE(allocator, old_values); - return; - } - - len = 0; - for (size_t i = 0; i < old_cap; i += 1) { - Entry *it = old_values + i; - if (is_occupied(it)) { - insert(it->key, it->value); - } - } - TABLE_DEALLOCATE(allocator, old_values); - } - - Entry *get_table_entry(uint64_t key) { - uint64_t hash = TABLE_HASH_BYTES(&key, sizeof(key)); - if (hash == 0) hash += 1; - uint64_t index = TABLE__WRAP_AROUND_POWER_OF_2(hash, cap); - uint64_t i = index; - uint64_t distance = 0; - for (;;) { - Entry *it = values + i; - if (distance > it->distance) { - return 0; - } - - if (it->hash == hash && it->key == key) { - return it; - } - - distance += 1; - i = TABLE__WRAP_AROUND_POWER_OF_2(i + 1, cap); - if (i == index) return 0; - } - TABLE_ASSERT(!"Invalid codepath"); - } - - // load factor calculation was rearranged - // to get rid of division: - //> 100 * len / cap = load_factor - //> len * 100 = load_factor * cap - inline bool reached_load_factor(size_t lfactor) { - return (len + 1) * 100 >= lfactor * cap; - } - - void insert(uint64_t key, const Value &value) { - if (reached_load_factor(max_load_factor)) { - if (cap == 0) cap = 16; // 32 cause cap*2 - reserve(cap * 2); - } - - uint64_t hash = TABLE_HASH_BYTES(&key, sizeof(key)); - if (hash == 0) hash += 1; - uint64_t index = TABLE__WRAP_AROUND_POWER_OF_2(hash, cap); - uint64_t i = index; - Entry to_insert = {hash, key, 0, value}; - for (;;) { - Entry *it = values + i; - if (is_empty(it)) { - *it = to_insert; - len += 1; - // If we have more then 8 consecutive items we try to resize - if (to_insert.distance > 8 && reached_load_factor(min_load_factor)) { - reserve(cap * 2); - } - return; - } - if (it->hash == hash && it->key == key) { - *it = to_insert; - // If we have more then 8 consecutive items we try to resize - if (to_insert.distance > 8 && reached_load_factor(min_load_factor)) { - reserve(cap * 2); - } - return; - } - - // Robin hood hashing - if (to_insert.distance > it->distance) { - Entry temp = to_insert; - to_insert = *it; - *it = temp; - } - - to_insert.distance += 1; - i = TABLE__WRAP_AROUND_POWER_OF_2(i + 1, cap); - TABLE_ASSERT(i != index && "Did a full 360 through a hash table, no good :( that shouldnt be possible"); - } - TABLE_ASSERT(!"Invalid codepath"); - } - - void remove(uint64_t key) { - Entry *entry = get_table_entry(key); - entry->hash = 0; - entry->distance = 0; - } - - Value *get(uint64_t key) { - Entry *v = get_table_entry(key); - if (!v) return 0; - return &v->value; - } - - Value *gets(char *str) { - int len = TABLE_CStringLen(str); - uint64_t hash = TABLE_HASH_BYTES(str, len); - return get(hash); - } - - void puts(char *str, const Value &value) { - int len = TABLE_CStringLen(str); - uint64_t hash = TABLE_HASH_BYTES(str, len); - insert(hash, value); - } - - void reset() { - len = 0; - for (size_t i = 0; i < cap; i += 1) { - Entry *it = values + i; - it->hash = 0; - } - } - - void dealloc() { - COR_DEALLOCATE(allocator, values); - len = 0; - cap = 0; - values = 0; - } - static const size_t max_load_factor = 80; - static const size_t min_load_factor = 50; - static const size_t significant_distance = 8; +#pragma once +#include +/* + Hash table implementation: + Pointers to values + Open adressing + Linear Probing + Power of 2 + Robin Hood hashing + Resizes on high probe count (min max load factor) + + Hash 0 is reserved for empty hash table entry +*/ + +// #define TABLE_ALLOCATOR_TYPE Allocator + +#ifndef TABLE_PRIVATE_FUNCTION + #if defined(__GNUC__) || defined(__clang__) + #define TABLE_PRIVATE_FUNCTION __attribute__((unused)) static + #else + #define TABLE_PRIVATE_FUNCTION static + #endif +#endif + +#ifndef TABLE_ALLOCATE + #include + #define TABLE_ALLOCATE(allocator, size) malloc(size) +#endif + +#ifndef TABLE_DEALLOCATE + #include + #define TABLE_DEALLOCATE(allocator, p) free(p) +#endif + +#ifndef TABLE_ASSERT + #include + #define TABLE_ASSERT(x) assert(x) +#endif + +#ifndef TABLE_SET_DEFAULT_ALLOCATOR + #define TABLE_SET_DEFAULT_ALLOCATOR +// Example: +// #define TABLE_SET_DEFAULT_ALLOCATOR if (!allocator) allocator = global_heap; +#endif + +#ifndef TABLE_HASH_BYTES + #define TABLE_HASH_BYTES TABLE__HashBytes +TABLE_PRIVATE_FUNCTION uint64_t TABLE__HashBytes(void *data, unsigned size) { + uint8_t *data8 = (uint8_t *)data; + uint64_t hash = (uint64_t)14695981039346656037ULL; + for (unsigned i = 0; i < size; i++) { + hash = hash ^ (uint64_t)(data8[i]); + hash = hash * (uint64_t)1099511628211ULL; + } + return hash; +} +#endif + +#define TABLE__WRAP_AROUND_POWER_OF_2(x, pow2) (((x) & ((pow2)-1llu))) + +TABLE_PRIVATE_FUNCTION int TABLE_CStringLen(char *str) { + int i = 0; + while(str[i]) i += 1; + return i; +} + +template +struct Table { + struct Entry { + uint64_t hash; + uint64_t key; + size_t distance; + Value value; + }; + +#ifdef TABLE_ALLOCATOR_TYPE + TABLE_ALLOCATOR_TYPE allocator; +#endif + size_t len, cap; + Entry *values; + + inline bool is_empty(Entry *entry) { return entry->hash == 0; } + inline bool is_occupied(Entry *entry) { return entry->hash != 0; } + + void reserve(size_t size) { + TABLE_ASSERT(size > cap && "New size is smaller then original size"); + TABLE_SET_DEFAULT_ALLOCATOR; + + Entry *old_values = values; + size_t old_cap = cap; + + values = (Entry *)TABLE_ALLOCATE(allocator, sizeof(Entry) * size); + for (int i = 0; i < size; i += 1) values[i] = {}; + cap = size; + + TABLE_ASSERT(!(old_values == 0 && len != 0)); + if (len == 0) { + if (old_values) TABLE_DEALLOCATE(allocator, old_values); + return; + } + + len = 0; + for (size_t i = 0; i < old_cap; i += 1) { + Entry *it = old_values + i; + if (is_occupied(it)) { + insert(it->key, it->value); + } + } + TABLE_DEALLOCATE(allocator, old_values); + } + + Entry *get_table_entry(uint64_t key) { + uint64_t hash = TABLE_HASH_BYTES(&key, sizeof(key)); + if (hash == 0) hash += 1; + uint64_t index = TABLE__WRAP_AROUND_POWER_OF_2(hash, cap); + uint64_t i = index; + uint64_t distance = 0; + for (;;) { + Entry *it = values + i; + if (distance > it->distance) { + return 0; + } + + if (it->hash == hash && it->key == key) { + return it; + } + + distance += 1; + i = TABLE__WRAP_AROUND_POWER_OF_2(i + 1, cap); + if (i == index) return 0; + } + TABLE_ASSERT(!"Invalid codepath"); + } + + // load factor calculation was rearranged + // to get rid of division: + //> 100 * len / cap = load_factor + //> len * 100 = load_factor * cap + inline bool reached_load_factor(size_t lfactor) { + return (len + 1) * 100 >= lfactor * cap; + } + + void insert(uint64_t key, const Value &value) { + if (reached_load_factor(max_load_factor)) { + if (cap == 0) cap = 16; // 32 cause cap*2 + reserve(cap * 2); + } + + uint64_t hash = TABLE_HASH_BYTES(&key, sizeof(key)); + if (hash == 0) hash += 1; + uint64_t index = TABLE__WRAP_AROUND_POWER_OF_2(hash, cap); + uint64_t i = index; + Entry to_insert = {hash, key, 0, value}; + for (;;) { + Entry *it = values + i; + if (is_empty(it)) { + *it = to_insert; + len += 1; + // If we have more then 8 consecutive items we try to resize + if (to_insert.distance > 8 && reached_load_factor(min_load_factor)) { + reserve(cap * 2); + } + return; + } + if (it->hash == hash && it->key == key) { + *it = to_insert; + // If we have more then 8 consecutive items we try to resize + if (to_insert.distance > 8 && reached_load_factor(min_load_factor)) { + reserve(cap * 2); + } + return; + } + + // Robin hood hashing + if (to_insert.distance > it->distance) { + Entry temp = to_insert; + to_insert = *it; + *it = temp; + } + + to_insert.distance += 1; + i = TABLE__WRAP_AROUND_POWER_OF_2(i + 1, cap); + TABLE_ASSERT(i != index && "Did a full 360 through a hash table, no good :( that shouldnt be possible"); + } + TABLE_ASSERT(!"Invalid codepath"); + } + + void remove(uint64_t key) { + Entry *entry = get_table_entry(key); + entry->hash = 0; + entry->distance = 0; + } + + Value *get(uint64_t key) { + Entry *v = get_table_entry(key); + if (!v) return 0; + return &v->value; + } + + Value *gets(char *str) { + int len = TABLE_CStringLen(str); + uint64_t hash = TABLE_HASH_BYTES(str, len); + return get(hash); + } + + void puts(char *str, const Value &value) { + int len = TABLE_CStringLen(str); + uint64_t hash = TABLE_HASH_BYTES(str, len); + insert(hash, value); + } + + void reset() { + len = 0; + for (size_t i = 0; i < cap; i += 1) { + Entry *it = values + i; + it->hash = 0; + } + } + + void dealloc() { + COR_DEALLOCATE(allocator, values); + len = 0; + cap = 0; + values = 0; + } + static const size_t max_load_factor = 80; + static const size_t min_load_factor = 50; + static const size_t significant_distance = 8; }; \ No newline at end of file diff --git a/test/test_arena.cpp b/test/test_arena.cpp deleted file mode 100644 index 8d1c9d7..0000000 --- a/test/test_arena.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "../io.c" - -#define MA_ASSERT(x) IO_Assert(x) -#include "../arena.c" - -int main() { - MA_Scratch scratch; - int *thing = MA_PushStruct(scratch, int); - *thing = 10; - - MA_Arena *arena = MA_Bootstrap(); - float *thingf = MA_PushStruct(arena, float); - *thingf = 10.0f; -} \ No newline at end of file diff --git a/test/test_array.cpp b/test/test_array.cpp new file mode 100644 index 0000000..9cc9297 --- /dev/null +++ b/test/test_array.cpp @@ -0,0 +1,122 @@ +#include "../io.c" +#define MA_ASSERT(x) IO_Assert(x) +#include "../arena.c" + +#include "../defer.hpp" +#define ARRAY_REALLOCATE(allocator, p, size, old_size) M_ReallocNonZeroed(allocator, p, size, old_size) +#define ARRAY_DEALLOCATE(allocator, p) M_Dealloc(allocator, p) +#define ARRAY_Allocator M_Allocator +#define ARRAY_SET_DEFAULT_ALLOCATOR \ + if (!allocator.p) allocator = M_GetSystemAllocator(); +#include "../array.hpp" + +void TestExclusiveArenaBackedArray() { + MA_Scratch scratch; + MA_Arena ex = MA_Create(); + Array array = {MA_GetExclusiveAllocator(&ex)}; + Array ptrs = {scratch}; + array.reserve(16); + ptrs.reserve(16); + void *initial_p0 = array.data; + void *initial_p1 = ptrs.data; + + for (int i = 0; i < 1000; i += 1) { + array.add(i); + ptrs.add(&array[i]); + } + for (int i = 0; i < 1000; i += 1) { + IO_Assert(array[i] == i); + IO_Assert(&array[i] == ptrs[i]); + } + + int i = 0; + For(array) { + IO_Assert(it == i++); + } + IO_Assert(initial_p0 == array.data); + IO_Assert(initial_p1 != ptrs.data); + + array.dealloc(); +} + +Array GenArray() { + Array result = {M_GetSystemAllocator()}; + for (int i = 0; i < 100; i += 1) result.add(i); + return result; +} + +void TestRemoveForLoop() { + Array array = GenArray(); + IO_Assert(array.len == 100); + IO_Assert(array[4] == 4); + ForArrayRemovable(array) { + ForArrayRemovablePrepare(array); + if (it == 4) ForArrayRemovableDeclare(); + } + IO_Assert(array[4] != 4); + IO_Assert(array[4] == 5); + IO_Assert(array[5] == 6); + IO_Assert(array[3] == 3); + IO_Assert(array.len == 99); +} + +void TestBasic() { + Array array = GenArray(); + array.unordered_remove_index(40); + IO_Assert(array.len == 99); + IO_Assert(array[40] != 40); + IO_Assert(array[40] == 99); + + array.ordered_remove_index(35); + IO_Assert(array.len == 98); + IO_Assert(array[35] == 36); + + array.ordered_remove_index(array[34]); + IO_Assert(array.len == 97); + IO_Assert(array[34] == 36); + + array.unordered_remove(array[30]); + IO_Assert(array.len == 96); + IO_Assert(array[30] != 30); + IO_Assert(array[30] == 98); + + array.insert(101, 20); + IO_Assert(array[20] == 101); + IO_Assert(array[21] == 20); + IO_Assert(array.len == 97); + + IO_Assert(array.contains(array[20])); + IO_Assert(array.contains(array[96])); + IO_Assert(array.contains(array[0])); + IO_Assert(!array.contains(array.data[97])); + + IO_Assert(array.is_first(array[0])); + IO_Assert(!array.is_first(array[1])); + + IO_Assert(!array.is_last(array[0])); + IO_Assert(array.is_last(array[array.len - 1])); + + array.reset(); + IO_Assert(array.len == 0); + array.dealloc(); +} + +void TestReverseLoop() { + Array array = GenArray(); + + int i = 99; + For(array.reverse()) { + assert(it == i--); + } + + array.dealloc(); +} + +int main() { + TestExclusiveArenaBackedArray(); + TestRemoveForLoop(); + TestBasic(); + TestReverseLoop(); + + return 0; +} \ No newline at end of file