├── .gitignore ├── LICENSE ├── README.md ├── benchmark ├── cpp_vector_main.cpp ├── cpp_vector_trio_main.cpp ├── das_main.c ├── run.bat ├── run.sh └── shared.h ├── das.c ├── das.h ├── das_examples.c ├── das_test.c ├── run_tests.bat └── run_tests.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.ilk 3 | *.pdb 4 | *.obj 5 | benchmark/*.exe 6 | benchmark/*.ilk 7 | benchmark/*.pdb 8 | benchmark/*.obj 9 | benchmark/das_main 10 | benchmark/cpp_vector_main 11 | benchmark/cpp_vector_trio_main 12 | das_test 13 | das_examples 14 | main 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD Zero Clause License 2 | 3 | Copyright (c) 2021 Henry Rose 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 10 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 12 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 13 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 14 | PERFORMANCE OF THIS SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DAS - dynamically allocated storage 2 | 3 | A feature rich dynamic allocation library. 4 | 5 | # Features 6 | - array-like stack (DasStk) 7 | - double ended queue (DasDeque) 8 | - custom allocator interface (DasAlctor) 9 | - allocation API that use custom allocators (das_alloc, das_realloc, das_dealloc) 10 | - unbuffered file abstraction 11 | - virtual memory abstraction 12 | - growable & virtual memory backed linear allocator and element pool 13 | - compiles as ISO C99 14 | - simple API to keep user code as simple as possible. 15 | - zeroed memory is initialization 16 | - no type decorations required on the Stack and Deque API 17 | - no executable bloat 18 | - we define a single function for each operation, that can be used for all types of DasStk/DasDeque 19 | - fast compile times 20 | - we do not generate any code, other than single line macros 21 | 22 | # Supported Platforms 23 | The code should ideally compile on any C99 compiler. C++ can be supported again but 24 | it will only work through extensions as lots of C99 features do not work in ISO C++. 25 | Patches are welcome to improve support for other compilers and language targets :) 26 | 27 | Tested Platforms: 28 | - Windows (visual studio (cl), clang) 29 | - Linux (clang & gcc) 30 | - MacOS (clang) - untested but should work since it's clang. let me know if you can test it! 31 | - IOS (clang) - untested but should work since it's clang. let me know if you can test it! 32 | - Android (gcc) - untested but should work since it's gcc. let me know if you can test it! 33 | 34 | # How to Build 35 | 36 | You only need to copy **das.h** and **das.c** into your project. 37 | 38 | In one of your C compilation units, include the **das.h** header and **das.c** source files like so: 39 | 40 | ``` 41 | #include "das.h" 42 | #include "das.c" 43 | ``` 44 | 45 | To use the library in other files, you need to include the **das.h** header. 46 | 47 | ``` 48 | #include "das.h" 49 | ``` 50 | 51 | # Documentation 52 | 53 | Most structs, functions and macros have their documentation above them in the **das.h** header file. 54 | 55 | If you are looking for examples, look in the **das_examples.c** file. There are comments to guide you through and it is suggested that you follow linearally to learn the API.
56 | 57 | In that file we have: 58 | - stk_example() 59 | - initializing a stack 60 | - pushing and popping elements 61 | - inserting and removing elements 62 | - deque_example() 63 | - initializing a double ended queue 64 | - pushing and popping elements from both ends 65 | - alloc_example() 66 | - allocation API 67 | - custom_allocator_example() 68 | - using a custom allocator (linear allocator) 69 | 70 | # Project Structure 71 | 72 | - das.c - main source file 73 | - das.h - main header file 74 | - das_examples.c - examples with comments, it is suggested that you follow linearally to learn the API. 75 | - das_test.c - test file for C 76 | - run_test.sh - posix shell script for compiling and running the two test files 77 | - compiles using gcc and clang 78 | - run_test.bat - batch script for compiling and running the two test files 79 | - compiles using clang 80 | - benchmark - a directory that holds a benchmark for compiling DasStk vs std::vector. See below for more info. 81 | 82 | # Benchmarks 83 | This benchmark tests **compiling** different DasStk implementations vs std::vector. In all tests except cpp_vector_trio_main, we are handling 130 different structures. For each of these, in the main function, a DasStk/std::vector is created and then elements get pushed on to it. These results are on an Intel i5 8250u CPU using Clang 10 on Linux in July 2020. No optimization level has been specified. The results are ordered by time where the fastest test comes first. 84 | 85 | - das_main.c 86 | - using DasStk 87 | - compile-time: 0.174s 88 | - size: 51K 89 | - main_function_size: 28K 90 | - cpp_vector_trio_main.cpp 91 | - using std::vector with 3 different structures instead of 130. 92 | - so the size will be less because of the small main function. 93 | - compile-time: 0.289s 94 | - size: 40K 95 | - main_function_size: 730B 96 | - cpp_vector_main.cpp 97 | - using std::vector 98 | - compile-time: 3.430s 99 | - size: 1.1M 100 | - main_function_size: 30K 101 | 102 | We do not have any run-time benchmarks, since these are unlikely to reflect what happens in the real world. At the end of the day, different implementations of stacks and deques are very similar. 103 | 104 | However std::vector will generate a optimized function for every implementation. But realistically, where DAS passes in size and align arguments, std::vector have constants precompiled in. As far as I know, thats a very negligible win if any at all. This is the only benefit that I could think of. Personally I don't think it's worth the time to compile these different versions of std::vector just for these things. 105 | 106 | In DAS, for each operation we have a single function that is used for every type. So there is a single push function for DasStk that is used for all types. These functions are not complicated and will fit in the instruction cache better. So if you are handling many different types of stacks or deques, then DAS is probably better. But again as far as I know, this is probably very negligible if you are not doing huge amounts of data. 107 | 108 | I hope this helps, do your own tests if you need :) 109 | 110 | 111 | -------------------------------------------------------------------------------- /benchmark/cpp_vector_main.cpp: -------------------------------------------------------------------------------- 1 | #include "shared.h" 2 | #include 3 | 4 | int main(int argc, char** argv) { 5 | 6 | #define STRUCT(type) \ 7 | std::vector s_##type = std::vector(); \ 8 | \ 9 | type v_##type = {0}; \ 10 | for (int i = 0; i < test_iteration_count; i += 1) { \ 11 | v_##type.a = i; \ 12 | v_##type.b = i; \ 13 | v_##type.c = i; \ 14 | v_##type.d = i; \ 15 | s_##type.push_back(v_##type); \ 16 | } 17 | /* end */ 18 | 19 | STRUCT_LIST 20 | 21 | #undef STRUCT 22 | 23 | return 0; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /benchmark/cpp_vector_trio_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | struct Test0 { 5 | int a, b, c, d; 6 | }; 7 | struct Test1 { 8 | int a, b, c, d; 9 | }; 10 | struct Test3 { 11 | int a, b, c, d; 12 | }; 13 | 14 | int main(int argc, char** argv) { 15 | std::vector s = std::vector(); \ 16 | 17 | Test0 v = {0}; \ 18 | for (int i = 0; i < 2048; i += 1) { \ 19 | v.a = i; \ 20 | v.b = i; \ 21 | v.c = i; \ 22 | v.d = i; \ 23 | s.push_back(v); \ 24 | } 25 | 26 | std::vector e = std::vector(); \ 27 | 28 | Test1 g = {0}; \ 29 | for (int i = 0; i < 2048; i += 1) { \ 30 | g.a = i; \ 31 | g.b = i; \ 32 | g.c = i; \ 33 | g.d = i; \ 34 | e.push_back(g); \ 35 | } 36 | 37 | std::vector t = std::vector(); \ 38 | 39 | Test3 j = {0}; \ 40 | for (int i = 0; i < 2048; i += 3) { \ 41 | j.a = i; \ 42 | j.b = i; \ 43 | j.c = i; \ 44 | j.d = i; \ 45 | t.push_back(j); \ 46 | } 47 | 48 | return 0; 49 | } 50 | 51 | -------------------------------------------------------------------------------- /benchmark/das_main.c: -------------------------------------------------------------------------------- 1 | #include "shared.h" 2 | #include "../das.h" 3 | #include "../das.c" 4 | 5 | #define STRUCT(type) \ 6 | typedef_DasStk(type); \ 7 | /* end */ 8 | 9 | STRUCT_LIST 10 | 11 | #undef STRUCT 12 | 13 | int main(int argc, char** argv) { 14 | 15 | #define STRUCT(type) \ 16 | DasStk(type) s_##type = NULL; \ 17 | DasStk_resize_cap(&s_##type, test_iteration_count); \ 18 | \ 19 | type v_##type = {0}; \ 20 | for (int i = 0; i < test_iteration_count; i += 1) { \ 21 | v_##type.a = i; \ 22 | v_##type.b = i; \ 23 | v_##type.c = i; \ 24 | v_##type.d = i; \ 25 | type* dst = DasStk_push(&s_##type, NULL); \ 26 | *dst = v_##type; \ 27 | } \ 28 | /* end */ 29 | 30 | STRUCT_LIST 31 | 32 | #undef STRUCT 33 | 34 | return 0; 35 | } 36 | 37 | -------------------------------------------------------------------------------- /benchmark/run.bat: -------------------------------------------------------------------------------- 1 | @ECHO off 2 | 3 | ECHO start build of das_main at %time% 4 | clang.exe -o das_main.exe das_main.c 5 | ECHO build of das_main finished at %time% 6 | 7 | ECHO. 8 | 9 | ECHO start build of cpp_vector_trio_main at %time% 10 | clang++.exe -o cpp_vector_trio_main.exe cpp_vector_trio_main.cpp 11 | ECHO build of cpp_vector_trio_main finished at %time% 12 | 13 | ECHO. 14 | 15 | ECHO start build of cpp_vector_main at %time% 16 | clang++.exe -o cpp_vector_main.exe cpp_vector_main.cpp 17 | ECHO build of cpp_vector_main finished at %time% 18 | 19 | -------------------------------------------------------------------------------- /benchmark/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "time it takes to build das_main:" 4 | time clang -o das_main das_main.c 5 | echo "-------------------" 6 | 7 | echo "time it takes to build cpp_vector_trio_main:" 8 | time clang++ -o cpp_vector_trio_main cpp_vector_trio_main.cpp 9 | echo "-------------------" 10 | 11 | echo "time it takes to build cpp_vector_main:" 12 | time clang++ -o cpp_vector_main cpp_vector_main.cpp 13 | echo "-------------------" 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /benchmark/shared.h: -------------------------------------------------------------------------------- 1 | #ifdef __cplusplus 2 | extern "C" { 3 | #endif 4 | 5 | #define test_iteration_count 2048 6 | 7 | 8 | #define STRUCT_LIST \ 9 | STRUCT(Test0) \ 10 | STRUCT(Test1) \ 11 | STRUCT(Test2) \ 12 | STRUCT(Test3) \ 13 | STRUCT(Test4) \ 14 | STRUCT(Test5) \ 15 | STRUCT(Test6) \ 16 | STRUCT(Test7) \ 17 | STRUCT(Test8) \ 18 | STRUCT(Test9) \ 19 | STRUCT(Test10) \ 20 | STRUCT(Test11) \ 21 | STRUCT(Test12) \ 22 | STRUCT(Test13) \ 23 | STRUCT(Test14) \ 24 | STRUCT(Test15) \ 25 | STRUCT(Test16) \ 26 | STRUCT(Test17) \ 27 | STRUCT(Test18) \ 28 | STRUCT(Test19) \ 29 | STRUCT(Test20) \ 30 | STRUCT(Test21) \ 31 | STRUCT(Test22) \ 32 | STRUCT(Test23) \ 33 | STRUCT(Test24) \ 34 | STRUCT(Test25) \ 35 | STRUCT(Test26) \ 36 | STRUCT(Test27) \ 37 | STRUCT(Test28) \ 38 | STRUCT(Test29) \ 39 | STRUCT(Test30) \ 40 | STRUCT(Test31) \ 41 | STRUCT(Test32) \ 42 | STRUCT(Test33) \ 43 | STRUCT(Test34) \ 44 | STRUCT(Test35) \ 45 | STRUCT(Test36) \ 46 | STRUCT(Test37) \ 47 | STRUCT(Test38) \ 48 | STRUCT(Test39) \ 49 | STRUCT(Test40) \ 50 | STRUCT(Test41) \ 51 | STRUCT(Test42) \ 52 | STRUCT(Test43) \ 53 | STRUCT(Test44) \ 54 | STRUCT(Test45) \ 55 | STRUCT(Test46) \ 56 | STRUCT(Test47) \ 57 | STRUCT(Test48) \ 58 | STRUCT(Test49) \ 59 | STRUCT(Test50) \ 60 | STRUCT(Test51) \ 61 | STRUCT(Test52) \ 62 | STRUCT(Test53) \ 63 | STRUCT(Test54) \ 64 | STRUCT(Test55) \ 65 | STRUCT(Test56) \ 66 | STRUCT(Test57) \ 67 | STRUCT(Test58) \ 68 | STRUCT(Test59) \ 69 | STRUCT(Test60) \ 70 | STRUCT(Test61) \ 71 | STRUCT(Test62) \ 72 | STRUCT(Test63) \ 73 | STRUCT(Test64) \ 74 | STRUCT(Test65) \ 75 | STRUCT(Test66) \ 76 | STRUCT(Test67) \ 77 | STRUCT(Test68) \ 78 | STRUCT(Test69) \ 79 | STRUCT(Test70) \ 80 | STRUCT(Test71) \ 81 | STRUCT(Test72) \ 82 | STRUCT(Test73) \ 83 | STRUCT(Test74) \ 84 | STRUCT(Test75) \ 85 | STRUCT(Test76) \ 86 | STRUCT(Test77) \ 87 | STRUCT(Test78) \ 88 | STRUCT(Test79) \ 89 | STRUCT(Test80) \ 90 | STRUCT(Test81) \ 91 | STRUCT(Test82) \ 92 | STRUCT(Test83) \ 93 | STRUCT(Test84) \ 94 | STRUCT(Test85) \ 95 | STRUCT(Test86) \ 96 | STRUCT(Test87) \ 97 | STRUCT(Test88) \ 98 | STRUCT(Test89) \ 99 | STRUCT(Test90) \ 100 | STRUCT(Test91) \ 101 | STRUCT(Test92) \ 102 | STRUCT(Test93) \ 103 | STRUCT(Test94) \ 104 | STRUCT(Test95) \ 105 | STRUCT(Test96) \ 106 | STRUCT(Test97) \ 107 | STRUCT(Test98) \ 108 | STRUCT(Test99) \ 109 | STRUCT(Test100) \ 110 | STRUCT(Test101) \ 111 | STRUCT(Test102) \ 112 | STRUCT(Test103) \ 113 | STRUCT(Test104) \ 114 | STRUCT(Test105) \ 115 | STRUCT(Test106) \ 116 | STRUCT(Test107) \ 117 | STRUCT(Test108) \ 118 | STRUCT(Test109) \ 119 | STRUCT(Test110) \ 120 | STRUCT(Test111) \ 121 | STRUCT(Test112) \ 122 | STRUCT(Test113) \ 123 | STRUCT(Test114) \ 124 | STRUCT(Test115) \ 125 | STRUCT(Test116) \ 126 | STRUCT(Test117) \ 127 | STRUCT(Test118) \ 128 | STRUCT(Test119) \ 129 | STRUCT(Test120) \ 130 | STRUCT(Test121) \ 131 | STRUCT(Test122) \ 132 | STRUCT(Test123) \ 133 | STRUCT(Test124) \ 134 | STRUCT(Test125) \ 135 | STRUCT(Test126) \ 136 | STRUCT(Test127) \ 137 | STRUCT(Test128) \ 138 | STRUCT(Test129) \ 139 | /* end */ 140 | 141 | #define STRUCT(name) typedef struct { int a, b, c, d; } name; 142 | STRUCT_LIST 143 | #undef STRUCT 144 | 145 | #ifdef __cplusplus 146 | } 147 | #endif 148 | 149 | -------------------------------------------------------------------------------- /das.c: -------------------------------------------------------------------------------- 1 | #ifndef DAS_H 2 | #include "das.h" 3 | #endif 4 | 5 | #ifdef __linux__ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #elif _WIN32 14 | #include 15 | #endif 16 | 17 | // ====================================================================== 18 | // 19 | // 20 | // General 21 | // 22 | // 23 | // ====================================================================== 24 | 25 | #include // va_start, va_end 26 | 27 | das_noreturn void _das_abort(const char* file, int line, const char* func, char* assert_test, char* message_fmt, ...) { 28 | if (assert_test) { 29 | fprintf(stderr, "assertion failed: %s\nmessage: ", assert_test); 30 | } else { 31 | fprintf(stderr, "abort reason: "); 32 | } 33 | 34 | va_list args; 35 | va_start(args, message_fmt); 36 | vfprintf(stderr, message_fmt, args); 37 | va_end(args); 38 | 39 | fprintf(stderr, "\nfile: %s:%d\n%s\n", file, line, func); 40 | abort(); 41 | } 42 | 43 | // ====================================================================== 44 | // 45 | // 46 | // Memory Utilities 47 | // 48 | // 49 | // ====================================================================== 50 | 51 | void* das_ptr_round_up_align(void* ptr, uintptr_t align) { 52 | das_debug_assert(das_is_power_of_two(align), "align must be a power of two but got: %zu", align); 53 | return (void*)(((uintptr_t)ptr + (align - 1)) & ~(align - 1)); 54 | } 55 | 56 | void* das_ptr_round_down_align(void* ptr, uintptr_t align) { 57 | das_debug_assert(das_is_power_of_two(align), "align must be a power of two but got: %zu", align); 58 | return (void*)((uintptr_t)ptr & ~(align - 1)); 59 | } 60 | 61 | // ====================================================================== 62 | // 63 | // 64 | // Custom Allocator API 65 | // 66 | // 67 | // ====================================================================== 68 | 69 | #include 70 | 71 | #ifdef _WIN32 72 | 73 | // fortunately, windows provides aligned memory allocation function 74 | void* das_system_alloc_fn(void* alloc_data, void* ptr, uintptr_t old_size, uintptr_t size, uintptr_t align) { 75 | if (!ptr && size == 0) { 76 | // reset not supported so do nothing 77 | return NULL; 78 | } else if (!ptr) { 79 | // allocate 80 | return _aligned_malloc(size, align); 81 | } else if (ptr && size > 0) { 82 | // reallocate 83 | return _aligned_realloc(ptr, size, align); 84 | } else { 85 | // deallocate 86 | _aligned_free(ptr); 87 | return NULL; 88 | } 89 | } 90 | 91 | #else // posix 92 | 93 | // 94 | // the C11 standard says malloc is guaranteed aligned to alignof(max_align_t). 95 | // but since we are targeting C99 we have defined our own das_max_align_t that should resemble the same thing. 96 | // so allocations that have alignment less than or equal to this, can directly call malloc, realloc and free. 97 | // luckly this is for most allocations. 98 | // 99 | // but there are alignments that are larger (for example is intel AVX256 primitives). 100 | // these require calls aligned_alloc. 101 | void* das_system_alloc_fn(void* alloc_data, void* ptr, uintptr_t old_size, uintptr_t size, uintptr_t align) { 102 | if (!ptr && size == 0) { 103 | // reset not supported so do nothing 104 | return NULL; 105 | } else if (!ptr) { 106 | // allocate 107 | 108 | // the posix documents say that align must be atleast sizeof(void*) 109 | align = das_max_u(align, sizeof(void*)); 110 | das_assert(posix_memalign(&ptr, align, size) == 0, "posix_memalign failed: errno %u", errno); 111 | return ptr; 112 | } else if (ptr && size > 0) { 113 | // reallocate 114 | if (align <= alignof(das_max_align_t)) { 115 | return realloc(ptr, size); 116 | } 117 | 118 | // 119 | // there is no aligned realloction on any posix based systems :( 120 | void* new_ptr = NULL; 121 | // the posix documents say that align must be atleast sizeof(void*) 122 | align = das_max_u(align, sizeof(void*)); 123 | das_assert(posix_memalign(&new_ptr, align, size) == 0, "posix_memalign failed: errno %u", errno); 124 | 125 | memcpy(new_ptr, ptr, das_min_u(old_size, size)); 126 | free(ptr); 127 | return new_ptr; 128 | } else { 129 | // deallocate 130 | free(ptr); 131 | return NULL; 132 | } 133 | } 134 | 135 | #endif // _WIN32 136 | 137 | // ====================================================================== 138 | // 139 | // 140 | // DasStk - linear stack of elements (LIFO) 141 | // 142 | // 143 | // ====================================================================== 144 | 145 | DasBool _DasStk_init_with_alctor(_DasStkHeader** header_out, uintptr_t header_size, uintptr_t init_cap, DasAlctor alctor, uintptr_t elmt_size) { 146 | init_cap = das_max_u(DasStk_min_cap, init_cap); 147 | uintptr_t new_size = header_size + init_cap * elmt_size; 148 | _DasStkHeader* new_header = das_alloc(alctor, new_size, alignof(das_max_align_t)); 149 | if (!new_header) return das_false; 150 | 151 | // 152 | // initialize the header and pass out the new header pointer 153 | new_header->count = 0; 154 | new_header->cap = init_cap; 155 | new_header->alctor = alctor; 156 | *header_out = new_header; 157 | return das_true; 158 | } 159 | 160 | DasBool _DasStk_init_clone_with_alctor(_DasStkHeader** dst_header_in_out, uintptr_t header_size, _DasStkHeader* src_header, DasAlctor alctor, uintptr_t elmt_size) { 161 | uintptr_t src_count = DasStk_count(&src_header); 162 | if (!_DasStk_init_with_alctor(dst_header_in_out, header_size, src_count, alctor, elmt_size)) 163 | return das_false; 164 | 165 | _DasStkHeader* dst_header = *dst_header_in_out; 166 | 167 | void* dst = das_ptr_add(dst_header, header_size); 168 | void* src = das_ptr_add(src_header, header_size); 169 | memcpy(dst, src, src_count * elmt_size); 170 | dst_header->count = src_count; 171 | return das_true; 172 | } 173 | 174 | void _DasStk_deinit(_DasStkHeader** header_in_out, uintptr_t header_size, uintptr_t elmt_size) { 175 | _DasStkHeader* header = *header_in_out; 176 | uintptr_t size = header_size + header->cap * elmt_size; 177 | das_dealloc(header->alctor, header, size, alignof(das_max_align_t)); 178 | *header_in_out = NULL; 179 | } 180 | 181 | uintptr_t _DasStk_assert_idx(_DasStkHeader* header, uintptr_t idx, uintptr_t elmt_size) { 182 | das_assert(header != NULL && idx < header->count, "idx '%zu' is out of bounds for a stack of count '%zu'", idx, header ? header->count : 0); 183 | return idx; 184 | } 185 | 186 | DasBool _DasStk_resize(_DasStkHeader** header_in_out, uintptr_t header_size, uintptr_t new_count, DasBool zero, uintptr_t elmt_size) { 187 | _DasStkHeader* header = *header_in_out; 188 | if (new_count == 0) { 189 | if (header) { 190 | _DasStk_deinit(header_in_out, header_size, elmt_size); 191 | } 192 | return das_true; 193 | } 194 | 195 | // 196 | // extend the capacity of the stack if the new count extends past the capacity. 197 | uintptr_t old_cap = DasStk_cap(header_in_out); 198 | if (old_cap < new_count) { 199 | DasBool res = _DasStk_resize_cap(header_in_out, header_size, das_max_u(new_count, old_cap * 2), elmt_size); 200 | if (!res) return das_false; 201 | header = *header_in_out; 202 | } 203 | 204 | // 205 | // zero the new memory if requested 206 | uintptr_t count = header->count; 207 | if (zero && new_count > count) { 208 | void* data = das_ptr_add(header, header_size); 209 | memset(das_ptr_add(data, count * elmt_size), 0, (new_count - count) * elmt_size); 210 | } 211 | 212 | header->count = new_count; 213 | return das_true; 214 | } 215 | 216 | DasBool _DasStk_resize_cap(_DasStkHeader** header_in_out, uintptr_t header_size, uintptr_t new_cap, uintptr_t elmt_size) { 217 | _DasStkHeader* header = *header_in_out; 218 | uintptr_t cap = header ? header->cap : 0; 219 | uintptr_t count = header ? header->count : 0; 220 | DasAlctor alctor = header && header->alctor.fn != NULL ? header->alctor : DasAlctor_default; 221 | 222 | // 223 | // return if the capacity has not changed 224 | new_cap = das_max_u(das_max_u(DasStk_min_cap, new_cap), count); 225 | if (cap == new_cap) return das_true; 226 | 227 | // 228 | // reallocate the stack while taking into account of size of the header. 229 | // the block of memory is aligned to alignof(das_max_align_t). 230 | uintptr_t size = header ? header_size + cap * elmt_size : 0; 231 | uintptr_t new_size = header_size + new_cap * elmt_size; 232 | _DasStkHeader* new_header = das_realloc(alctor, header, size, new_size, alignof(das_max_align_t)); 233 | if (!new_header) return das_false; 234 | if (!header) { 235 | new_header->alctor = alctor; 236 | new_header->count = 0; 237 | } 238 | 239 | // 240 | // update the capacity in the header and pass out the new header pointer 241 | new_header->cap = new_cap; 242 | *header_in_out = new_header; 243 | return das_true; 244 | } 245 | 246 | void* _DasStk_insert_many(_DasStkHeader** header_in_out, uintptr_t header_size, uintptr_t idx, void* elmts, uintptr_t elmts_count, uintptr_t elmt_size) { 247 | _DasStkHeader* header = *header_in_out; 248 | das_assert(idx <= header->count, "insert idx '%zu' must be less than or equal to count of '%zu'", idx, header->count); 249 | uintptr_t count = header ? header->count : 0; 250 | uintptr_t cap = header ? header->cap : 0; 251 | 252 | // 253 | // increase the capacity if the number of elements 254 | // inserted makes the stack exceed it's capacity. 255 | uintptr_t new_count = count + elmts_count; 256 | if (cap < new_count) { 257 | DasBool res = _DasStk_resize_cap(header_in_out, header_size, das_max_u(new_count, cap * 2), elmt_size); 258 | if (!res) return NULL; 259 | header = *header_in_out; 260 | } 261 | 262 | void* data = das_ptr_add(header, header_size); 263 | void* dst = das_ptr_add(data, idx * elmt_size); 264 | 265 | // shift the elements from idx to (idx + elmts_count), to the right 266 | // to make room for the elements 267 | memmove(das_ptr_add(dst, elmts_count * elmt_size), dst, (header->count - idx) * elmt_size); 268 | 269 | if (elmts) { 270 | memcpy(dst, elmts, elmts_count * elmt_size); 271 | } 272 | 273 | header->count = new_count; 274 | return dst; 275 | } 276 | 277 | void* _DasStk_push_many(_DasStkHeader** header_in_out, uintptr_t header_size, void* elmts, uintptr_t elmts_count, uintptr_t elmt_size) { 278 | das_debug_assert(elmts_count != 0, "cannot push zero elements on a stack"); 279 | _DasStkHeader* header = *header_in_out; 280 | uintptr_t count = header ? header->count : 0; 281 | uintptr_t cap = header ? header->cap : 0; 282 | uintptr_t idx = count; 283 | 284 | // 285 | // increase the capacity if the number of elements 286 | // pushed on makes the stack exceed it's capacity. 287 | uintptr_t new_count = count + elmts_count; 288 | if (new_count > cap) { 289 | DasBool res = _DasStk_resize_cap(header_in_out, header_size, das_max_u(new_count, cap * 2), elmt_size); 290 | if (!res) return NULL; 291 | header = *header_in_out; 292 | } 293 | 294 | void* dst = das_ptr_add(header, header_size + idx * elmt_size); 295 | if (elmts) { 296 | memcpy(dst, elmts, elmts_count * elmt_size); 297 | } 298 | 299 | header->count = new_count; 300 | return dst; 301 | } 302 | 303 | uintptr_t _DasStk_pop_many(_DasStkHeader* header, uintptr_t elmts_count, uintptr_t elmt_size) { 304 | elmts_count = das_min_u(elmts_count, header->count); 305 | header->count -= elmts_count; 306 | return elmts_count; 307 | } 308 | 309 | void _DasStk_remove_swap_range(_DasStkHeader* header, uintptr_t header_size, uintptr_t start_idx, uintptr_t end_idx, uintptr_t elmt_size) { 310 | das_assert(start_idx <= header->count, "start_idx '%zu' must be less than the count of '%zu'", start_idx, header->count); 311 | das_assert(end_idx <= header->count, "end_idx '%zu' must be less than or equal to count of '%zu'", end_idx, header->count); 312 | 313 | // 314 | // get the pointer where the elements are being removed 315 | uintptr_t remove_count = end_idx - start_idx; 316 | void* data = das_ptr_add(header, header_size); 317 | void* dst = das_ptr_add(data, start_idx * elmt_size); 318 | 319 | // 320 | // work out where the elements at the back of the stack start from. 321 | uintptr_t src_idx = header->count; 322 | header->count -= remove_count; 323 | if (remove_count > header->count) remove_count = header->count; 324 | src_idx -= remove_count; 325 | 326 | // 327 | // replace the removed elements with the elements from the back of the stack. 328 | void* src = das_ptr_add(data, src_idx * elmt_size); 329 | memmove(dst, src, remove_count * elmt_size); 330 | } 331 | 332 | void _DasStk_remove_shift_range(_DasStkHeader* header, uintptr_t header_size, uintptr_t start_idx, uintptr_t end_idx, uintptr_t elmt_size) { 333 | das_assert(start_idx <= header->count, "start_idx '%zu' must be less than the count of '%zu'", start_idx, header->count); 334 | das_assert(end_idx <= header->count, "end_idx '%zu' must be less than or equal to count of '%zu'", end_idx, header->count); 335 | 336 | // 337 | // get the pointer where the elements are being removed 338 | uintptr_t remove_count = end_idx - start_idx; 339 | void* data = das_ptr_add(header, header_size); 340 | void* dst = das_ptr_add(data, start_idx * elmt_size); 341 | 342 | // 343 | // now replace the elements by shifting the elements to the right over them. 344 | if (end_idx < header->count) { 345 | void* src = das_ptr_add(dst, remove_count * elmt_size); 346 | memmove(dst, src, (header->count - (start_idx + remove_count)) * elmt_size); 347 | } 348 | header->count -= remove_count; 349 | } 350 | 351 | char* DasStk_push_str(DasStk(char)* stk, char* str) { 352 | uintptr_t len = strlen(str); 353 | return DasStk_push_many(stk, str, len); 354 | } 355 | 356 | void* DasStk_push_str_fmtv(DasStk(char)* stk, char* fmt, va_list args) { 357 | va_list args_copy; 358 | va_copy(args_copy, args); 359 | 360 | // add 1 so we have enough room for the null terminator that vsnprintf always outputs 361 | // vsnprintf will return -1 on an encoding error. 362 | uintptr_t count = vsnprintf(NULL, 0, fmt, args_copy) + 1; 363 | va_end(args_copy); 364 | das_assert(count >= 1, "a vsnprintf encoding error has occurred"); 365 | 366 | // 367 | // resize the stack to have enough room to store the pushed formatted string 368 | uintptr_t insert_idx = DasStk_count(stk); 369 | uintptr_t new_count = DasStk_count(stk) + count; 370 | DasBool res = DasStk_resize_cap(stk, new_count); 371 | if (!res) return NULL; 372 | 373 | // 374 | // now call vsnprintf for real this time, with a buffer 375 | // to actually copy the formatted string. 376 | char* ptr = das_ptr_add(DasStk_data(stk), insert_idx); 377 | count = vsnprintf(ptr, count, fmt, args); 378 | DasStk_set_count(stk, new_count - 1); // now set the new count minus the null terminator 379 | return ptr; 380 | } 381 | 382 | void* DasStk_push_str_fmt(DasStk(char)* stk, char* fmt, ...) { 383 | va_list args; 384 | va_start(args, fmt); 385 | void* ptr = DasStk_push_str_fmtv(stk, fmt, args); 386 | va_end(args); 387 | return ptr; 388 | } 389 | 390 | // ====================================================================== 391 | // 392 | // 393 | // DasDeque - double ended queue (ring buffer) 394 | // 395 | // 396 | // ====================================================================== 397 | 398 | static inline uintptr_t _DasDeque_wrapping_add(uintptr_t cap, uintptr_t idx, uintptr_t value) { 399 | uintptr_t res = idx + value; 400 | if (res >= cap) res = value - (cap - idx); 401 | return res; 402 | } 403 | 404 | static inline uintptr_t _DasDeque_wrapping_sub(uintptr_t cap, uintptr_t idx, uintptr_t value) { 405 | uintptr_t res = idx - value; 406 | if (res > idx) res = cap - (value - idx); 407 | return res; 408 | } 409 | 410 | DasBool _DasDeque_init_with_alctor(_DasDequeHeader** header_out, uintptr_t header_size, uintptr_t init_cap, DasAlctor alctor, uintptr_t elmt_size) { 411 | // add one because the back_idx needs to point to the next empty element slot. 412 | init_cap = das_max_u(DasDeque_min_cap, init_cap); 413 | init_cap += 1; 414 | 415 | uintptr_t new_size = header_size + init_cap * elmt_size; 416 | _DasDequeHeader* new_header = das_alloc(alctor, new_size, alignof(das_max_align_t)); 417 | if (!new_header) return das_false; 418 | 419 | // 420 | // initialize the header and pass out the new header pointer 421 | new_header->cap = init_cap; 422 | new_header->front_idx = 0; 423 | new_header->back_idx = 0; 424 | new_header->alctor = alctor; 425 | *header_out = new_header; 426 | return das_true; 427 | 428 | } 429 | 430 | 431 | void _DasDeque_deinit(_DasDequeHeader** header_in_out, uintptr_t header_size, uintptr_t elmt_size) { 432 | _DasDequeHeader* header = *header_in_out; 433 | uintptr_t size = header_size + header->cap * elmt_size; 434 | das_dealloc(header->alctor, header, size, alignof(das_max_align_t)); 435 | *header_in_out = NULL; 436 | } 437 | 438 | uintptr_t _DasDeque_assert_idx(_DasDequeHeader* header, uintptr_t idx, uintptr_t elmt_size) { 439 | uintptr_t count = DasDeque_count(&header); 440 | das_assert(idx < count, "idx '%zu' is out of bounds for a deque of count '%zu'", idx, count); 441 | idx = _DasDeque_wrapping_add(header->cap, header->front_idx, idx); 442 | return idx; 443 | } 444 | 445 | DasBool _DasDeque_resize_cap(_DasDequeHeader** header_in_out, uintptr_t header_size, uintptr_t new_cap, uintptr_t elmt_size) { 446 | _DasDequeHeader* header = *header_in_out; 447 | uintptr_t cap = header ? header->cap : 0; 448 | new_cap = das_max_u(das_max_u(DasDeque_min_cap, new_cap), DasDeque_count(header_in_out)); 449 | // add one because the back_idx needs to point to the next empty element slot. 450 | new_cap += 1; 451 | if (new_cap == cap) return das_true; 452 | 453 | DasAlctor alctor = header && header->alctor.fn != NULL ? header->alctor : DasAlctor_default; 454 | 455 | uintptr_t size = header ? header_size + cap * elmt_size : 0; 456 | uintptr_t new_size = header_size + new_cap * elmt_size; 457 | _DasDequeHeader* new_header = das_realloc(alctor, header, size, new_size, alignof(das_max_align_t)); 458 | if (!new_header) return das_false; 459 | if (!header) { 460 | new_header->alctor = alctor; 461 | new_header->front_idx = 0; 462 | new_header->back_idx = 0; 463 | new_header->cap = 0; 464 | } 465 | 466 | uintptr_t old_cap = new_header->cap; 467 | new_header->cap = new_cap; 468 | *header_in_out = new_header; 469 | 470 | void* data = das_ptr_add(new_header, header_size); 471 | 472 | // move the front_idx and back_idx around to resolve the gaps that could have been created after resizing the buffer 473 | // 474 | // A - no gaps created so no need to change 475 | // -------- 476 | // F B F B 477 | // [ V V V . ] -> [ V V V . . . . ] 478 | // 479 | // B - less elements before back_idx than elements from front_idx, so copy back_idx after the front_idx 480 | // B F F B 481 | // [ V V . V V V ] -> [ . . . V V V V V . . . . . ] 482 | // 483 | // C - more elements before back_idx than elements from front_idx, so copy front_idx to the end 484 | // B F B F 485 | // [ V V . V] -> [ V V . . . . . . V ] 486 | // 487 | if (new_header->front_idx <= new_header->back_idx) { // A 488 | // do nothing 489 | } else if (new_header->back_idx < old_cap - new_header->front_idx) { // B 490 | memcpy(das_ptr_add(data, old_cap * elmt_size), data, new_header->back_idx * elmt_size); 491 | new_header->back_idx += old_cap; 492 | das_debug_assert(new_header->back_idx > new_header->front_idx, "back_idx must come after front_idx"); 493 | } else { // C 494 | uintptr_t new_front_idx = new_cap - (old_cap - new_header->front_idx); 495 | memcpy(das_ptr_add(data, new_front_idx * elmt_size), das_ptr_add(data, new_header->front_idx * elmt_size), (old_cap - new_header->front_idx) * elmt_size); 496 | new_header->front_idx = new_front_idx; 497 | das_debug_assert(new_header->back_idx < new_header->front_idx, "front_idx must come after back_idx"); 498 | } 499 | 500 | das_debug_assert(new_header->back_idx < new_cap, "back_idx must remain in bounds"); 501 | das_debug_assert(new_header->front_idx < new_cap, "front_idx must remain in bounds"); 502 | return das_true; 503 | } 504 | 505 | void _DasDeque_read(_DasDequeHeader* header, uintptr_t header_size, uintptr_t idx, void* elmts_out, uintptr_t elmts_count, uintptr_t elmt_size) { 506 | uintptr_t count = DasDeque_count(&header); 507 | das_assert(idx + elmts_count <= count, "idx '%zu' and elmts_count '%zu' will go out of bounds for a deque of count '%zu'", idx, count); 508 | 509 | idx = _DasDeque_wrapping_add(header->cap, header->front_idx, idx); 510 | 511 | void* data = das_ptr_add(header, header_size); 512 | if (header->cap < idx + elmts_count) { 513 | // 514 | // there is enough elements that the read will 515 | // exceed the back and cause the idx to loop around. 516 | // so copy in two parts 517 | uintptr_t rem_count = header->cap - idx; 518 | // copy to the end of the buffer 519 | memcpy(elmts_out, das_ptr_add(data, idx * elmt_size), rem_count * elmt_size); 520 | // copy to the beginning of the buffer 521 | memcpy(das_ptr_add(elmts_out, rem_count * elmt_size), data, ((elmts_count - rem_count) * elmt_size)); 522 | } else { 523 | // 524 | // coping the elements can be done in a single copy 525 | memcpy(elmts_out, das_ptr_add(data, idx * elmt_size), elmts_count * elmt_size); 526 | } 527 | } 528 | 529 | void _DasDeque_write(_DasDequeHeader* header, uintptr_t header_size, uintptr_t idx, void* elmts, uintptr_t elmts_count, uintptr_t elmt_size) { 530 | uintptr_t count = DasDeque_count(&header); 531 | das_assert(idx + elmts_count <= count, "idx '%zu' and elmts_count '%zu' will go out of bounds for a deque of count '%zu'", idx, count); 532 | 533 | idx = _DasDeque_wrapping_add(header->cap, header->front_idx, idx); 534 | 535 | void* data = das_ptr_add(header, header_size); 536 | if (header->cap < idx + elmts_count) { 537 | // 538 | // there is enough elements that the write will 539 | // exceed the back and cause the idx to loop around. 540 | // so copy in two parts 541 | uintptr_t rem_count = header->cap - idx; 542 | // copy to the end of the buffer 543 | memcpy(das_ptr_add(data, idx * elmt_size), elmts, rem_count * elmt_size); 544 | // copy to the beginning of the buffer 545 | memcpy(data, das_ptr_add(elmts, rem_count * elmt_size), ((elmts_count - rem_count) * elmt_size)); 546 | } else { 547 | // 548 | // coping the elements can be done in a single copy 549 | memcpy(das_ptr_add(data, idx * elmt_size), elmts, elmts_count * elmt_size); 550 | } 551 | } 552 | 553 | uintptr_t _DasDeque_push_front_many(_DasDequeHeader** header_in_out, uintptr_t header_size, void* elmts, uintptr_t elmts_count, uintptr_t elmt_size) { 554 | _DasDequeHeader* header = *header_in_out; 555 | uintptr_t new_count = DasDeque_count(header_in_out) + elmts_count; 556 | uintptr_t cap = header ? header->cap : 0; 557 | if (cap < new_count + 1) { 558 | DasBool res = _DasDeque_resize_cap(header_in_out, header_size, das_max_u(cap * 2, new_count), elmt_size); 559 | if (!res) return UINTPTR_MAX; 560 | header = *header_in_out; 561 | } 562 | 563 | header->front_idx = _DasDeque_wrapping_sub(header->cap, header->front_idx, elmts_count); 564 | 565 | if (elmts) { 566 | _DasDeque_write(header, header_size, 0, elmts, elmts_count, elmt_size); 567 | } 568 | 569 | return 0; 570 | } 571 | 572 | uintptr_t _DasDeque_push_back_many(_DasDequeHeader** header_in_out, uintptr_t header_size, void* elmts, uintptr_t elmts_count, uintptr_t elmt_size) { 573 | _DasDequeHeader* header = *header_in_out; 574 | uintptr_t insert_idx = DasDeque_count(header_in_out); 575 | uintptr_t new_count = insert_idx + elmts_count; 576 | uintptr_t cap = header ? header->cap : 0; 577 | if (cap < new_count + 1) { 578 | DasBool res = _DasDeque_resize_cap(header_in_out, header_size, das_max_u(cap * 2, new_count), elmt_size); 579 | if (!res) return UINTPTR_MAX; 580 | header = *header_in_out; 581 | } 582 | 583 | header->back_idx = _DasDeque_wrapping_add(header->cap, header->back_idx, elmts_count); 584 | 585 | if (elmts) { 586 | _DasDeque_write(header, header_size, insert_idx, elmts, elmts_count, elmt_size); 587 | } 588 | 589 | return insert_idx; 590 | } 591 | 592 | uintptr_t _DasDeque_pop_front_many(_DasDequeHeader* header, uintptr_t elmts_count, uintptr_t elmt_size) { 593 | if (header->front_idx == header->back_idx) return 0; 594 | elmts_count = das_min_u(elmts_count, DasDeque_count(&header)); 595 | 596 | header->front_idx = _DasDeque_wrapping_add(header->cap, header->front_idx, elmts_count); 597 | return elmts_count; 598 | } 599 | 600 | uintptr_t _DasDeque_pop_back_many(_DasDequeHeader* header, uintptr_t elmts_count, uintptr_t elmt_size) { 601 | if (header->front_idx == header->back_idx) return 0; 602 | elmts_count = das_min_u(elmts_count, DasDeque_count(&header)); 603 | 604 | header->back_idx = _DasDeque_wrapping_sub(header->cap, header->back_idx, elmts_count); 605 | return elmts_count; 606 | } 607 | 608 | // =========================================================================== 609 | // 610 | // 611 | // Stacktrace 612 | // 613 | // 614 | // =========================================================================== 615 | 616 | #ifdef __GNUC__ 617 | #include 618 | #endif 619 | 620 | DasBool das_stacktrace(uint32_t ignore_levels_count, DasStk(char)* string_out) { 621 | #define STACKTRACE_LEVELS_MAX 128 622 | #if defined(__GNUC__) 623 | void* stacktrace_levels[STACKTRACE_LEVELS_MAX]; 624 | int stacktrace_levels_count = backtrace(stacktrace_levels, STACKTRACE_LEVELS_MAX); 625 | 626 | char** strings = backtrace_symbols(stacktrace_levels, stacktrace_levels_count); 627 | if (strings == NULL) return das_false; 628 | 629 | for (int i = ignore_levels_count; i < stacktrace_levels_count; i += 1) { 630 | if (!DasStk_push_str_fmt(string_out, "%u: %s\n", stacktrace_levels_count - i - 1, strings[i])) { 631 | return das_false; 632 | } 633 | } 634 | 635 | free(strings); 636 | #elif defined(_WIN32) 637 | void* stacktrace_levels[STACKTRACE_LEVELS_MAX]; 638 | HANDLE process = GetCurrentProcess(); 639 | 640 | SymInitialize(process, NULL, TRUE); 641 | 642 | uint32_t stacktrace_levels_count = CaptureStackBackTrace(0, STACKTRACE_LEVELS_MAX, stacktrace_levels, NULL); 643 | SYMBOL_INFO* symbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + 256 * sizeof(char), 1); 644 | symbol->MaxNameLen = 255; 645 | symbol->SizeOfStruct = sizeof(SYMBOL_INFO); 646 | 647 | for (uint32_t i = ignore_levels_count; i < stacktrace_levels_count; i += 1) { 648 | SymFromAddr(process, (DWORD64)stacktrace_levels[i], 0, symbol); 649 | if (!DasStk_push_str_fmt(string_out, "%u: %s - [0x%0X]\n", stacktrace_levels_count - i - 1, symbol->Name, symbol->Address)) { 650 | return das_false; 651 | } 652 | } 653 | 654 | free(symbol); 655 | #else 656 | #error "das_stacktrace has been unimplemented for this platform" 657 | #endif 658 | 659 | return das_true; 660 | } 661 | 662 | // =========================================================================== 663 | // 664 | // 665 | // Platform Error Handling 666 | // 667 | // 668 | // =========================================================================== 669 | 670 | DasError _das_get_last_error() { 671 | #ifdef __linux__ 672 | return errno; 673 | #elif _WIN32 674 | return GetLastError(); 675 | #else 676 | #error "unimplemented virtual memory API for this platform" 677 | #endif 678 | } 679 | 680 | DasErrorStrRes das_get_error_string(DasError error, char* buf_out, uint32_t buf_out_len) { 681 | #if _WIN32 682 | DWORD res = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, error, 0, (LPTSTR)buf_out, buf_out_len, NULL); 683 | if (res == 0) { 684 | error = GetLastError(); 685 | das_abort("TODO handle error code: %u", error); 686 | } 687 | #elif _GNU_SOURCE 688 | // GNU version (screw these guys for changing the way this works) 689 | char* buf = strerror_r(error, buf_out, buf_out_len); 690 | if (strcmp(buf, "Unknown error") == 0) { 691 | return DasErrorStrRes_invalid_error_arg; 692 | } 693 | 694 | // if its static string then copy it to the buffer 695 | if (buf != buf_out) { 696 | strncpy(buf_out, buf, buf_out_len); 697 | 698 | uint32_t len = strlen(buf); 699 | if (len < buf_out_len) { 700 | return DasErrorStrRes_not_enough_space_in_buffer; 701 | } 702 | } 703 | #elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 704 | // use the XSI standard behavior. 705 | int res = strerror_r(error, buf_out, buf_out_len); 706 | if (res != 0) { 707 | int errnum = res; 708 | if (res == -1) 709 | errnum = errno; 710 | 711 | if (errnum == EINVAL) { 712 | return DasErrorStrRes_invalid_error_arg; 713 | } else if (errnum == ERANGE) { 714 | return DasErrorStrRes_not_enough_space_in_buffer; 715 | } 716 | das_abort("unexpected errno: %u", errnum); 717 | } 718 | #else 719 | #error "unimplemented virtual memory error string" 720 | #endif 721 | return DasErrorStrRes_success; 722 | } 723 | 724 | // =========================================================================== 725 | // 726 | // 727 | // File Abstraction 728 | // 729 | // 730 | // =========================================================================== 731 | 732 | DasError das_file_open(char* path, DasFileFlags flags, DasFileHandle* file_handle_out) { 733 | das_assert( 734 | (flags & (DasFileFlags_read | DasFileFlags_write | DasFileFlags_append)), 735 | "DasFileFlags_{read, write or append} must be set when opening a file" 736 | ); 737 | 738 | das_assert( 739 | (flags & (DasFileFlags_write | DasFileFlags_append)) 740 | || !(flags & (DasFileFlags_create_if_not_exist | DasFileFlags_create_new | DasFileFlags_truncate)), 741 | "file must be opened with DasFileFlags_{write or append} if DasFileFlags_{create_if_not_exist, create_new or truncate} exists" 742 | ); 743 | 744 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 745 | int fd_flags = O_CLOEXEC; 746 | 747 | if (flags & DasFileFlags_create_new) { 748 | fd_flags |= O_CREAT | O_EXCL; 749 | } else { 750 | if (flags & DasFileFlags_create_if_not_exist) { 751 | fd_flags |= O_CREAT; 752 | } 753 | 754 | if (flags & DasFileFlags_truncate) { 755 | fd_flags |= O_TRUNC; 756 | } 757 | } 758 | 759 | if ((flags & DasFileFlags_read) && (flags & DasFileFlags_write)) { 760 | fd_flags |= O_RDWR; 761 | if (flags & DasFileFlags_append) { 762 | fd_flags |= O_APPEND; 763 | } 764 | } else if (flags & DasFileFlags_read) { 765 | fd_flags |= O_RDONLY; 766 | } else if (flags & DasFileFlags_write) { 767 | fd_flags |= O_WRONLY; 768 | if (flags & DasFileFlags_append) { 769 | fd_flags |= O_APPEND; 770 | } 771 | } 772 | 773 | mode_t mode = 0666; // TODO allow the user to supply a mode. 774 | int fd = open(path, fd_flags, mode); 775 | if (fd == -1) return _das_get_last_error(); 776 | 777 | *file_handle_out = (DasFileHandle) { .raw = fd }; 778 | #elif _WIN32 779 | DWORD win_access = 0; 780 | DWORD win_creation = 0; 781 | 782 | if (flags & DasFileFlags_create_new) { 783 | win_creation |= CREATE_NEW; 784 | } else { 785 | if ((flags & DasFileFlags_create_if_not_exist) && (flags & DasFileFlags_truncate)) { 786 | win_creation |= CREATE_ALWAYS; 787 | } else if (flags & DasFileFlags_create_if_not_exist) { 788 | win_creation |= OPEN_ALWAYS; 789 | } else if (flags & DasFileFlags_truncate) { 790 | win_creation |= TRUNCATE_EXISTING; 791 | } 792 | } 793 | 794 | if (win_creation == 0) 795 | win_creation = OPEN_EXISTING; 796 | 797 | if (flags & DasFileFlags_read) { 798 | win_access |= GENERIC_READ; 799 | } 800 | 801 | if (flags & DasFileFlags_write) { 802 | if (flags & DasFileFlags_append) { 803 | win_access |= FILE_GENERIC_WRITE & ~FILE_WRITE_DATA; 804 | } else { 805 | win_access |= GENERIC_WRITE; 806 | } 807 | } 808 | 809 | HANDLE handle = CreateFileA(path, win_access, 0, NULL, win_creation, FILE_ATTRIBUTE_NORMAL, 0); 810 | if (handle == INVALID_HANDLE_VALUE) 811 | return _das_get_last_error(); 812 | 813 | *file_handle_out = (DasFileHandle) { .raw = handle }; 814 | #endif 815 | 816 | return DasError_success; 817 | } 818 | 819 | DasError das_file_close(DasFileHandle handle) { 820 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 821 | if (close(handle.raw) != 0) 822 | return _das_get_last_error(); 823 | #elif _WIN32 824 | if (CloseHandle(handle.raw) == 0) 825 | return _das_get_last_error(); 826 | #else 827 | #error "unimplemented file API for this platform" 828 | #endif 829 | 830 | return DasError_success; 831 | } 832 | 833 | DasError das_file_size(DasFileHandle handle, uint64_t* size_out) { 834 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 835 | // get the size of the file 836 | struct stat s = {0}; 837 | if (fstat(handle.raw, &s) != 0) return _das_get_last_error(); 838 | *size_out = s.st_size; 839 | #elif _WIN32 840 | LARGE_INTEGER size; 841 | if (!GetFileSizeEx(handle.raw, &size)) return _das_get_last_error(); 842 | *size_out = size.QuadPart; 843 | #else 844 | #error "unimplemented file API for this platform" 845 | #endif 846 | return DasError_success; 847 | } 848 | 849 | DasError das_file_read(DasFileHandle handle, void* data_out, uintptr_t length, uintptr_t* bytes_read_out) { 850 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 851 | ssize_t bytes_read = read(handle.raw, data_out, length); 852 | if (bytes_read == (ssize_t)-1) 853 | return _das_get_last_error(); 854 | #elif _WIN32 855 | length = das_min_u(length, UINT32_MAX); 856 | DWORD bytes_read; 857 | if (ReadFile(handle.raw, data_out, length, &bytes_read, NULL) == 0) 858 | return _das_get_last_error(); 859 | #else 860 | #error "unimplemented file API for this platform" 861 | #endif 862 | 863 | *bytes_read_out = bytes_read; 864 | return DasError_success; 865 | } 866 | 867 | DasError das_file_read_exact(DasFileHandle handle, void* data_out, uintptr_t length, uintptr_t* bytes_read_out) { 868 | uintptr_t og_length = length; 869 | while (length) { 870 | uintptr_t bytes_read; 871 | DasError error = das_file_read(handle, data_out, length, &bytes_read); 872 | if (error) return error; 873 | 874 | if (bytes_read == 0) 875 | break; 876 | 877 | data_out = das_ptr_add(data_out, bytes_read); 878 | length -= bytes_read; 879 | } 880 | 881 | *bytes_read_out = og_length - length; 882 | return DasError_success; 883 | } 884 | 885 | DasError das_file_write(DasFileHandle handle, void* data, uintptr_t length, uintptr_t* bytes_written_out) { 886 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 887 | ssize_t bytes_written = write(handle.raw, data, length); 888 | if (bytes_written == (ssize_t)-1) 889 | return _das_get_last_error(); 890 | #elif _WIN32 891 | length = das_min_u(length, UINT32_MAX); 892 | DWORD bytes_written; 893 | if (WriteFile(handle.raw, data, length, &bytes_written, NULL) == 0) 894 | return _das_get_last_error(); 895 | #else 896 | #error "unimplemented file API for this platform" 897 | #endif 898 | 899 | *bytes_written_out = bytes_written; 900 | return DasError_success; 901 | } 902 | 903 | DasError das_file_write_exact(DasFileHandle handle, void* data, uintptr_t length, uintptr_t* bytes_written_out) { 904 | uintptr_t og_length = length; 905 | while (length) { 906 | uintptr_t bytes_written; 907 | DasError error = das_file_write(handle, data, length, &bytes_written); 908 | if (error) return error; 909 | 910 | if (bytes_written == 0) 911 | break; 912 | 913 | data = das_ptr_add(data, bytes_written); 914 | length -= bytes_written; 915 | } 916 | 917 | *bytes_written_out = og_length - length; 918 | return DasError_success; 919 | } 920 | 921 | DasError das_file_seek(DasFileHandle handle, int64_t offset, DasFileSeekFrom from, uint64_t* cursor_offset_out) { 922 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 923 | int whence = 0; 924 | switch (from) { 925 | case DasFileSeekFrom_start: whence = SEEK_SET; break; 926 | case DasFileSeekFrom_current: whence = SEEK_CUR; break; 927 | case DasFileSeekFrom_end: whence = SEEK_END; break; 928 | } 929 | off_t cursor_offset = lseek(handle.raw, offset, whence); 930 | if (cursor_offset == (off_t)-1) 931 | return _das_get_last_error(); 932 | *cursor_offset_out = cursor_offset; 933 | #elif _WIN32 934 | int move_method = 0; 935 | switch (from) { 936 | case DasFileSeekFrom_start: move_method = FILE_BEGIN; break; 937 | case DasFileSeekFrom_current: move_method = FILE_CURRENT; break; 938 | case DasFileSeekFrom_end: move_method = FILE_END; break; 939 | } 940 | LARGE_INTEGER distance_to_move; 941 | distance_to_move.QuadPart = offset; 942 | LARGE_INTEGER new_offset = {0}; 943 | 944 | if (!SetFilePointerEx(handle.raw, distance_to_move, &new_offset, move_method)) 945 | return _das_get_last_error(); 946 | 947 | *cursor_offset_out = new_offset.QuadPart; 948 | #else 949 | #error "unimplemented file API for this platform" 950 | #endif 951 | return DasError_success; 952 | } 953 | 954 | DasError das_file_flush(DasFileHandle handle) { 955 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 956 | if (fsync(handle.raw) != 0) 957 | return _das_get_last_error(); 958 | #elif _WIN32 959 | if (FlushFileBuffers(handle.raw) == 0) 960 | return _das_get_last_error(); 961 | #else 962 | #error "unimplemented file API for this platform" 963 | #endif 964 | return DasError_success; 965 | } 966 | 967 | // =========================================================================== 968 | // 969 | // 970 | // Virtual Memory Abstraction 971 | // 972 | // 973 | // =========================================================================== 974 | 975 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 976 | static int _das_virt_mem_prot_unix(DasVirtMemProtection prot) { 977 | switch (prot) { 978 | case DasVirtMemProtection_no_access: return 0; 979 | case DasVirtMemProtection_read: return PROT_READ; 980 | case DasVirtMemProtection_read_write: return PROT_READ | PROT_WRITE; 981 | case DasVirtMemProtection_exec_read: return PROT_EXEC | PROT_READ; 982 | case DasVirtMemProtection_exec_read_write: return PROT_READ | PROT_WRITE | PROT_EXEC; 983 | } 984 | return 0; 985 | } 986 | #elif _WIN32 987 | static DWORD _das_virt_mem_prot_windows(DasVirtMemProtection prot) { 988 | switch (prot) { 989 | case DasVirtMemProtection_no_access: return PAGE_NOACCESS; 990 | case DasVirtMemProtection_read: return PAGE_READONLY; 991 | case DasVirtMemProtection_read_write: return PAGE_READWRITE; 992 | case DasVirtMemProtection_exec_read: return PAGE_EXECUTE_READ; 993 | case DasVirtMemProtection_exec_read_write: return PAGE_EXECUTE_READWRITE; 994 | } 995 | return 0; 996 | } 997 | #else 998 | #error "unimplemented virtual memory API for this platform" 999 | #endif 1000 | 1001 | DasError das_virt_mem_page_size(uintptr_t* page_size_out, uintptr_t* reserve_align_out) { 1002 | #ifdef __linux__ 1003 | long page_size = sysconf(_SC_PAGESIZE); 1004 | if (page_size == (long)-1) 1005 | return _das_get_last_error(); 1006 | 1007 | *page_size_out = page_size; 1008 | *reserve_align_out = page_size; 1009 | #elif _WIN32 1010 | SYSTEM_INFO si; 1011 | GetNativeSystemInfo(&si); 1012 | *page_size_out = si.dwPageSize; 1013 | *reserve_align_out = si.dwAllocationGranularity; 1014 | #else 1015 | #error "unimplemented virtual memory API for this platform" 1016 | #endif 1017 | 1018 | return DasError_success; 1019 | } 1020 | 1021 | DasError das_virt_mem_reserve(void* requested_addr, uintptr_t size, void** addr_out) { 1022 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 1023 | // memory is automatically commited on Unix based OSs, 1024 | // so we will restrict the memory from being accessed on reserved. 1025 | int prot = 0; 1026 | 1027 | // MAP_ANON = means map physical memory and not a file. it also means the memory will be initialized to zero 1028 | // MAP_PRIVATE = keep memory private so child process cannot access them 1029 | // MAP_NORESERVE = do not reserve any swap space for this mapping 1030 | void* addr = mmap(requested_addr, size, prot, MAP_ANON | MAP_PRIVATE | MAP_NORESERVE, -1, 0); 1031 | if (addr == MAP_FAILED) 1032 | return _das_get_last_error(); 1033 | #elif _WIN32 1034 | void* addr = VirtualAlloc(requested_addr, size, MEM_RESERVE, PAGE_NOACCESS); 1035 | if (addr == NULL) 1036 | return _das_get_last_error(); 1037 | #else 1038 | #error "TODO implement virtual memory for this platform" 1039 | #endif 1040 | 1041 | *addr_out = addr; 1042 | return DasError_success; 1043 | } 1044 | 1045 | DasError das_virt_mem_commit(void* addr, uintptr_t size, DasVirtMemProtection protection) { 1046 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 1047 | // memory is automatically commited on Unix based OSs, 1048 | // memory is restricted from being accessed in our das_virt_mem_reserve. 1049 | // so lets just apply the protection for the address space. 1050 | // and then advise the OS that these pages will be used soon. 1051 | int prot = _das_virt_mem_prot_unix(protection); 1052 | if (mprotect(addr, size, prot) != 0) return _das_get_last_error(); 1053 | if (madvise(addr, size, MADV_WILLNEED) != 0) return _das_get_last_error(); 1054 | #elif _WIN32 1055 | DWORD prot = _das_virt_mem_prot_windows(protection); 1056 | if (VirtualAlloc(addr, size, MEM_COMMIT, prot) == NULL) 1057 | return _das_get_last_error(); 1058 | #else 1059 | #error "TODO implement virtual memory for this platform" 1060 | #endif 1061 | return DasError_success; 1062 | } 1063 | 1064 | DasError das_virt_mem_protection_set(void* addr, uintptr_t size, DasVirtMemProtection protection) { 1065 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 1066 | int prot = _das_virt_mem_prot_unix(protection); 1067 | if (mprotect(addr, size, prot) != 0) 1068 | return _das_get_last_error(); 1069 | #elif _WIN32 1070 | DWORD prot = _das_virt_mem_prot_windows(protection); 1071 | DWORD old_prot; // unused 1072 | if (!VirtualProtect(addr, size, prot, &old_prot)) 1073 | return _das_get_last_error(); 1074 | #else 1075 | #error "TODO implement virtual memory for this platform" 1076 | #endif 1077 | return DasError_success; 1078 | } 1079 | 1080 | DasError das_virt_mem_decommit(void* addr, uintptr_t size) { 1081 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 1082 | 1083 | // 1084 | // advise the OS that these pages will not be needed. 1085 | // the OS will zero fill these pages before you next access them. 1086 | if (madvise(addr, size, MADV_DONTNEED) != 0) 1087 | return _das_get_last_error(); 1088 | 1089 | // memory is automatically commited on Unix based OSs, 1090 | // so we will restrict the memory from being accessed when we "decommit. 1091 | int prot = 0; 1092 | if (mprotect(addr, size, prot) != 0) 1093 | return _das_get_last_error(); 1094 | #elif _WIN32 1095 | if (VirtualFree(addr, size, MEM_DECOMMIT) == 0) 1096 | return _das_get_last_error(); 1097 | #else 1098 | #error "TODO implement virtual memory for this platform" 1099 | #endif 1100 | return DasError_success; 1101 | } 1102 | 1103 | DasError das_virt_mem_release(void* addr, uintptr_t size) { 1104 | #ifdef __linux__ 1105 | if (munmap(addr, size) != 0) 1106 | return _das_get_last_error(); 1107 | #elif _WIN32 1108 | // 1109 | // unfortunately on Windows all memory must be release at once 1110 | // that was reserved with VirtualAlloc. 1111 | if (!VirtualFree(addr, 0, MEM_RELEASE)) 1112 | return _das_get_last_error(); 1113 | #else 1114 | #error "TODO implement virtual memory for this platform" 1115 | #endif 1116 | return DasError_success; 1117 | } 1118 | 1119 | DasError das_virt_mem_map_file(void* requested_addr, DasFileHandle file_handle, DasVirtMemProtection protection, uint64_t offset, uintptr_t size, void** addr_out, DasMapFileHandle* map_file_handle_out) { 1120 | das_assert(protection != DasVirtMemProtection_no_access, "cannot map a file with no access"); 1121 | 1122 | uintptr_t reserve_align; 1123 | uintptr_t page_size; 1124 | DasError error = das_virt_mem_page_size(&page_size, &reserve_align); 1125 | if (error) return error; 1126 | 1127 | // 1128 | // round down the offset to the nearest multiple of reserve_align 1129 | // 1130 | uint64_t offset_diff = offset % reserve_align; 1131 | offset -= offset_diff; 1132 | 1133 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 1134 | *map_file_handle_out = NULL; 1135 | int prot = _das_virt_mem_prot_unix(protection); 1136 | 1137 | size = das_round_up_nearest_multiple_u(size, page_size); 1138 | void* addr = mmap(requested_addr, size, prot, MAP_SHARED, file_handle.raw, offset); 1139 | if (addr == MAP_FAILED) 1140 | return _das_get_last_error(); 1141 | #elif _WIN32 1142 | 1143 | DWORD prot = _das_virt_mem_prot_windows(protection); 1144 | 1145 | DWORD size_high = size >> 32; 1146 | DWORD size_low = size; 1147 | 1148 | // create a file mapping object for the file 1149 | HANDLE map_file_handle = CreateFileMappingA(file_handle.raw, NULL, prot, 0, 0, NULL); 1150 | if (map_file_handle == NULL) 1151 | return _das_get_last_error(); 1152 | *map_file_handle_out = map_file_handle; 1153 | 1154 | DWORD access = 0; 1155 | switch (protection) { 1156 | case DasVirtMemProtection_exec_read: 1157 | case DasVirtMemProtection_read: 1158 | access = FILE_MAP_READ; 1159 | break; 1160 | case DasVirtMemProtection_exec_read_write: 1161 | case DasVirtMemProtection_read_write: 1162 | access = FILE_MAP_ALL_ACCESS; 1163 | break; 1164 | } 1165 | 1166 | DWORD offset_high = offset >> 32; 1167 | DWORD offset_low = offset; 1168 | 1169 | void* addr = MapViewOfFile(map_file_handle, access, offset_high, offset_low, size); 1170 | if (addr == NULL) 1171 | return _das_get_last_error(); 1172 | #else 1173 | #error "TODO implement virtual memory for this platform" 1174 | #endif 1175 | 1176 | // 1177 | // move the pointer to where the user's request offset into the file will be. 1178 | addr = das_ptr_add(addr, offset_diff); 1179 | *addr_out = addr; 1180 | 1181 | return DasError_success; 1182 | } 1183 | 1184 | DasError das_virt_mem_unmap_file(void* addr, uintptr_t size, DasMapFileHandle map_file_handle) { 1185 | uintptr_t reserve_align; 1186 | uintptr_t page_size; 1187 | DasError error = das_virt_mem_page_size(&page_size, &reserve_align); 1188 | if (error) return error; 1189 | 1190 | // 1191 | // when mapping a file the user is given an offset from the start of the range pages that are mapped 1192 | // to match the file offset they requested. 1193 | // so round down to the address to the nearest reserve align. 1194 | addr = das_ptr_round_down_align(addr, reserve_align); 1195 | 1196 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 1197 | size = das_round_up_nearest_multiple_u(size, page_size); 1198 | return das_virt_mem_release(addr, size); 1199 | #elif _WIN32 1200 | 1201 | if (!UnmapViewOfFile(addr)) 1202 | return _das_get_last_error(); 1203 | 1204 | if (!CloseHandle(map_file_handle)) 1205 | return _das_get_last_error(); 1206 | 1207 | return DasError_success; 1208 | #else 1209 | #error "TODO implement virtual memory for this platform" 1210 | #endif 1211 | } 1212 | 1213 | // =========================================================================== 1214 | // 1215 | // 1216 | // Linear Allocator 1217 | // 1218 | // 1219 | // =========================================================================== 1220 | 1221 | DasError DasLinearAlctor_init(DasLinearAlctor* alctor, uintptr_t reserved_size, uintptr_t commit_grow_size) { 1222 | uintptr_t reserve_align; 1223 | uintptr_t page_size; 1224 | DasError error = das_virt_mem_page_size(&page_size, &reserve_align); 1225 | if (error) return error; 1226 | 1227 | // reserved_size must be a multiple of the reserve_align. 1228 | // commit_grow_size must be multiple of the page size. 1229 | commit_grow_size = das_round_up_nearest_multiple_u(commit_grow_size, page_size); 1230 | reserved_size = das_round_up_nearest_multiple_u(reserved_size, reserve_align); 1231 | reserved_size = das_round_up_nearest_multiple_u(reserved_size, commit_grow_size); 1232 | void* address_space; 1233 | error = das_virt_mem_reserve(NULL, reserved_size, &address_space); 1234 | if (error) return error; 1235 | 1236 | alctor->address_space = address_space; 1237 | alctor->pos = 0; 1238 | alctor->commited_size = 0; 1239 | alctor->commit_grow_size = commit_grow_size; 1240 | alctor->reserved_size = reserved_size; 1241 | return DasError_success; 1242 | } 1243 | 1244 | DasError DasLinearAlctor_deinit(DasLinearAlctor* alctor) { 1245 | return das_virt_mem_release(alctor->address_space, alctor->reserved_size); 1246 | } 1247 | 1248 | static DasBool _DasLinearAlctor_commit_next_chunk(DasLinearAlctor* alctor) { 1249 | if (alctor->commited_size == alctor->reserved_size) { 1250 | // linear alloctor reserved_size has been exhausted. 1251 | // there is no more memory to commit. 1252 | return das_false; 1253 | } else { 1254 | // 1255 | // commit the next pages of memory using the grow size the linear allocator was initialized with. 1256 | void* next_pages_start = das_ptr_add(alctor->address_space, alctor->commited_size); 1257 | 1258 | // 1259 | // make sure the commit grow size does not go past the reserved address_space. 1260 | uintptr_t grow_size = das_min_u(alctor->commit_grow_size, alctor->reserved_size - alctor->commited_size); 1261 | 1262 | DasError error = das_virt_mem_commit(next_pages_start, grow_size, DasVirtMemProtection_read_write); 1263 | das_assert(error == 0, "failed to commit memory next_pages_start(%p), grow_size(%zu), error_code(0x%x)", 1264 | next_pages_start, grow_size, error); 1265 | alctor->commited_size += grow_size; 1266 | return das_true; 1267 | } 1268 | } 1269 | 1270 | void* DasLinearAlctor_alloc_fn(void* alctor_data, void* ptr, uintptr_t old_size, uintptr_t size, uintptr_t align) { 1271 | DasLinearAlctor* alctor = (DasLinearAlctor*)alctor_data; 1272 | if (!ptr && size == 0) { 1273 | // reset by decommiting the memory back to the OS but retaining the reserved address space. 1274 | DasError error = das_virt_mem_decommit(alctor->address_space, alctor->commited_size); 1275 | das_assert(error == 0, "failed to decommit memory address_space(%p), commited_size(%zu)", 1276 | alctor->address_space, alctor->commited_size); 1277 | 1278 | alctor->pos = 0; 1279 | alctor->commited_size = 0; 1280 | } else if (!ptr) { 1281 | // allocate 1282 | while (1) { 1283 | // 1284 | // get the next pointer and align it 1285 | ptr = das_ptr_add(alctor->address_space, alctor->pos); 1286 | ptr = das_ptr_round_up_align(ptr, align); 1287 | uint32_t next_pos = das_ptr_diff(ptr, alctor->address_space) + size; 1288 | if (next_pos <= alctor->commited_size) { 1289 | // 1290 | // success, the requested size can fit in the linear block of memory. 1291 | alctor->pos = next_pos; 1292 | return ptr; 1293 | } else { 1294 | // 1295 | // failure, not enough room in the linear block of memory that is commited. 1296 | // so lets try to commit more memory. 1297 | if (!_DasLinearAlctor_commit_next_chunk(alctor)) 1298 | return NULL; 1299 | } 1300 | } 1301 | } else if (ptr && size > 0) { 1302 | // reallocate 1303 | 1304 | // check if the ptr is the last allocation to resize in place 1305 | if (das_ptr_add(alctor->address_space, alctor->pos - old_size) == ptr) { 1306 | while (1) { 1307 | uint32_t next_pos = das_ptr_diff(ptr, alctor->address_space) + size; 1308 | if (next_pos <= alctor->commited_size) { 1309 | alctor->pos = next_pos; 1310 | return ptr; 1311 | } else { 1312 | // 1313 | // failure, not enough room in the linear block of memory that is commited. 1314 | // so lets try to commit more memory. 1315 | if (!_DasLinearAlctor_commit_next_chunk(alctor)) 1316 | return NULL; 1317 | } 1318 | } 1319 | } 1320 | 1321 | // if we cannot extend in place, then just allocate a new block. 1322 | void* new_ptr = DasLinearAlctor_alloc_fn(alctor, NULL, 0, size, align); 1323 | if (new_ptr == NULL) { return NULL; } 1324 | 1325 | memcpy(new_ptr, ptr, size < old_size ? size : old_size); 1326 | return new_ptr; 1327 | } else { 1328 | // deallocate 1329 | // do nothing 1330 | return NULL; 1331 | } 1332 | 1333 | return NULL; 1334 | } 1335 | 1336 | // =========================================================================== 1337 | // 1338 | // 1339 | // Pool 1340 | // 1341 | // 1342 | // =========================================================================== 1343 | 1344 | static inline _DasPoolRecord* _DasPool_records(_DasPool* pool, uintptr_t elmt_size) { 1345 | return das_ptr_add(pool->address_space, (uintptr_t)pool->reserved_cap * elmt_size); 1346 | } 1347 | 1348 | static inline DasPoolElmtId _DasPool_record_to_id(_DasPool* pool, _DasPoolRecord* record, uint32_t idx_id, uint32_t index_bits) { 1349 | DasPoolElmtId index_mask = (1 << index_bits) - 1; 1350 | DasPoolElmtId id = record->next_id; 1351 | id &= ~index_mask; // clear out the index id that points to the next record 1352 | id |= idx_id; // now store the index id that points to this record 1353 | return id; 1354 | } 1355 | 1356 | static void _DasPool_assert_id(_DasPool* pool, DasPoolElmtId elmt_id, uintptr_t elmt_size, uint32_t index_bits) { 1357 | das_assert(elmt_id, "the element id cannot be null"); 1358 | das_assert(elmt_id & DasPoolElmtId_is_allocated_bit_MASK, "the provided element identifier does not have the allocated bit set."); 1359 | DasPoolElmtId index_mask = (1 << index_bits) - 1; 1360 | uint32_t idx_id = elmt_id & index_mask; 1361 | das_assert(idx_id, "the index identifier cannot be null"); 1362 | _DasPoolRecord* record = &_DasPool_records(pool, elmt_size)[idx_id - 1]; 1363 | das_assert(record->next_id & DasPoolElmtId_is_allocated_bit_MASK, "the record is not allocated"); 1364 | 1365 | DasPoolElmtId counter_mask = DasPoolElmtId_counter_mask(index_bits); 1366 | uint32_t counter = (elmt_id & counter_mask) >> index_bits; 1367 | uint32_t record_counter = (record->next_id & counter_mask) >> index_bits; 1368 | das_assert(counter == record_counter, "use after free detected... the provided element identifier has a counter of '%u' but the internal one is '%u'", counter, record_counter); 1369 | } 1370 | 1371 | DasError _DasPool_init(_DasPool* pool, uint32_t reserved_cap, uint32_t commit_grow_count, uintptr_t elmt_size) { 1372 | das_zero_elmt(pool); 1373 | 1374 | uintptr_t reserve_align; 1375 | uintptr_t page_size; 1376 | DasError error = das_virt_mem_page_size(&page_size, &reserve_align); 1377 | if (error) return error; 1378 | 1379 | // 1380 | // reserve the whole address space for the elements array and the records array. 1381 | uintptr_t elmts_size = das_round_up_nearest_multiple_u((uintptr_t)reserved_cap * elmt_size, reserve_align); 1382 | uintptr_t records_size = das_round_up_nearest_multiple_u((uintptr_t)reserved_cap * sizeof(_DasPoolRecord), reserve_align); 1383 | uintptr_t reserved_size = elmts_size + records_size; 1384 | error = das_virt_mem_reserve(NULL, reserved_size, &pool->address_space); 1385 | if (error) return error; 1386 | 1387 | // 1388 | // see how many elements we can actually fit in the rounded up reserved size of the elements. 1389 | reserved_cap = elmts_size / elmt_size; 1390 | 1391 | // 1392 | // see how many elements we can actually grow by rounding up grow size to the page size. 1393 | uintptr_t commit_grow_size = das_round_up_nearest_multiple_u((uintptr_t)commit_grow_count * elmt_size, page_size); 1394 | commit_grow_count = commit_grow_size / elmt_size; 1395 | 1396 | pool->page_size = page_size; 1397 | pool->reserved_cap = reserved_cap; 1398 | pool->commit_grow_count = commit_grow_count; 1399 | 1400 | return DasError_success; 1401 | } 1402 | 1403 | DasError _DasPool_deinit(_DasPool* pool, uintptr_t elmt_size) { 1404 | uintptr_t reserve_align; 1405 | uintptr_t page_size; 1406 | DasError error = das_virt_mem_page_size(&page_size, &reserve_align); 1407 | if (error) return error; 1408 | 1409 | // 1410 | // decommit and release the reserved address space 1411 | uintptr_t elmts_size = das_round_up_nearest_multiple_u((uintptr_t)pool->reserved_cap * elmt_size, reserve_align); 1412 | uintptr_t records_size = das_round_up_nearest_multiple_u((uintptr_t)pool->reserved_cap * sizeof(_DasPoolRecord), reserve_align); 1413 | uintptr_t reserved_size = elmts_size + records_size; 1414 | error = das_virt_mem_release(pool->address_space, reserved_size); 1415 | if (error) return error; 1416 | 1417 | *pool = (_DasPool){0}; 1418 | return DasError_success; 1419 | } 1420 | 1421 | DasError _DasPool_reset(_DasPool* pool, uintptr_t elmt_size) { 1422 | if (pool->commited_cap == 0) 1423 | return DasError_success; 1424 | 1425 | uintptr_t elmts_size = das_round_up_nearest_multiple_u((uintptr_t)pool->commited_cap * elmt_size, pool->page_size); 1426 | uintptr_t records_size = das_round_up_nearest_multiple_u((uintptr_t)pool->commited_cap * sizeof(_DasPoolRecord), pool->page_size); 1427 | void* elmts = pool->address_space; 1428 | _DasPoolRecord* records = _DasPool_records(pool, elmt_size); 1429 | 1430 | // 1431 | // decommit all of the commited pages of memory for the elements 1432 | DasError error = das_virt_mem_decommit(elmts, elmts_size); 1433 | if (error) return error; 1434 | 1435 | // 1436 | // decommit all of the commited pages of memory for the records 1437 | error = das_virt_mem_decommit(records, records_size); 1438 | if (error) return error; 1439 | 1440 | pool->count = 0; 1441 | pool->cap = 0; 1442 | pool->commited_cap = 0; 1443 | pool->free_list_head_id = 0; 1444 | pool->alloced_list_head_id = 0; 1445 | pool->alloced_list_tail_id = 0; 1446 | return DasError_success; 1447 | } 1448 | 1449 | DasBool _DasPool_commit_next_chunk(_DasPool* pool, uintptr_t elmt_size) { 1450 | das_assert(pool->address_space, "pool has not been initialized. use DasPool_init before allocating"); 1451 | if (pool->commited_cap == pool->reserved_cap) 1452 | return das_false; 1453 | 1454 | uintptr_t grow_count = das_min_u(pool->commit_grow_count, pool->reserved_cap - pool->commited_cap); 1455 | 1456 | uintptr_t elmts_size = das_round_up_nearest_multiple_u((uintptr_t)pool->commited_cap * elmt_size, pool->page_size); 1457 | uintptr_t records_size = das_round_up_nearest_multiple_u((uintptr_t)pool->commited_cap * sizeof(_DasPoolRecord), pool->page_size); 1458 | 1459 | // 1460 | // commit the next chunk of memory at the end of the currently commited elments 1461 | void* elmts_to_commit = das_ptr_add(pool->address_space, elmts_size); 1462 | uintptr_t elmts_grow_size = das_round_up_nearest_multiple_u((uintptr_t)grow_count * elmt_size, pool->page_size); 1463 | DasError error = das_virt_mem_commit(elmts_to_commit, elmts_grow_size, DasVirtMemProtection_read_write); 1464 | das_assert(error == 0, "unexpected error our parameters should be correct: 0x%x", error); 1465 | 1466 | // 1467 | // commit the next chunk of memory at the end of the currently commited records 1468 | _DasPoolRecord* records_to_commit = das_ptr_add(_DasPool_records(pool, elmt_size), records_size); 1469 | uintptr_t records_grow_size = das_round_up_nearest_multiple_u((uintptr_t)grow_count * sizeof(_DasPoolRecord), pool->page_size); 1470 | error = das_virt_mem_commit(records_to_commit, records_grow_size, DasVirtMemProtection_read_write); 1471 | das_assert(error == 0, "unexpected error our parameters should be correct: 0x%x", error); 1472 | 1473 | // 1474 | // calculate commited_cap by using the new elements size in bytes and dividing to get an accurate number. 1475 | // adding the grow_count will lose precision if the elmt_size is not directly divisble by the page_size. 1476 | uintptr_t new_elmts_size = elmts_size + elmts_grow_size; 1477 | pool->commited_cap = new_elmts_size / elmt_size; 1478 | 1479 | return das_true; 1480 | } 1481 | 1482 | DasError _DasPool_reset_and_populate(_DasPool* pool, void* elmts, uint32_t count, uintptr_t elmt_size) { 1483 | DasError error = _DasPool_reset(pool, elmt_size); 1484 | if (error) return error; 1485 | 1486 | while (pool->commited_cap < count) { 1487 | if (!_DasPool_commit_next_chunk(pool, elmt_size)) { 1488 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 1489 | return EOVERFLOW; 1490 | #elif _WIN32 1491 | return ERROR_BUFFER_OVERFLOW; 1492 | #endif 1493 | } 1494 | } 1495 | 1496 | // 1497 | // set the records for the elements passed in to the function to allocated and point to the next element. 1498 | _DasPoolRecord* records = _DasPool_records(pool, elmt_size); 1499 | for (uint32_t i = 0; i < count; i += 1) { 1500 | records[i].prev_id = i; // the current index is the identifier of the previous record 1501 | records[i].next_id = DasPoolElmtId_is_allocated_bit_MASK | (i + 2); // + 2 as identifiers are +1 an index 1502 | } 1503 | 1504 | // 1505 | // copy the elements and set the values in the pool structure 1506 | memcpy(pool->address_space, elmts, (uintptr_t)count * elmt_size); 1507 | pool->count = count; 1508 | pool->cap = count; 1509 | 1510 | return DasError_success; 1511 | } 1512 | 1513 | void* _DasPool_alloc(_DasPool* pool, DasPoolElmtId* id_out, uintptr_t elmt_size, uint32_t index_bits) { 1514 | // 1515 | // if the pool is full, try to increment the capacity by one if we have enough commit memory. 1516 | // if not commit a new chunk. 1517 | // if the pool is not full allocate from the head of the free list. 1518 | uint32_t idx_id; 1519 | if (pool->count == pool->cap) { 1520 | if (pool->cap == pool->commited_cap) { 1521 | if (!_DasPool_commit_next_chunk(pool, elmt_size)) 1522 | return NULL; 1523 | } 1524 | 1525 | pool->cap += 1; 1526 | idx_id = pool->cap; 1527 | } else { 1528 | idx_id = pool->free_list_head_id; 1529 | } 1530 | 1531 | // 1532 | // allocate an element by removing it from the free list 1533 | // and adding it to the allocated list. 1534 | // 1535 | 1536 | DasPoolElmtId index_mask = (1 << index_bits) - 1; 1537 | uint32_t idx = idx_id - 1; 1538 | _DasPoolRecord* records = _DasPool_records(pool, elmt_size); 1539 | _DasPoolRecord* record_ptr = &records[idx]; 1540 | DasPoolElmtId record = record_ptr->next_id; 1541 | 1542 | das_debug_assert(record_ptr->prev_id == 0, "the surposedly free list head links to a previous element... so it is not the list head"); 1543 | das_debug_assert(!(record & DasPoolElmtId_is_allocated_bit_MASK), "allocated element is in the free list of the pool"); 1544 | 1545 | // set the is allocated bit 1546 | record |= DasPoolElmtId_is_allocated_bit_MASK; 1547 | 1548 | // get the next free id from the record 1549 | uint32_t next_free_idx_id = record & index_mask; 1550 | 1551 | // clear the index id 1552 | record &= ~index_mask; 1553 | 1554 | // 1555 | // create pool identifier by strapping the index id to it. 1556 | *id_out = record | idx_id; 1557 | 1558 | record_ptr->next_id = record; 1559 | 1560 | // 1561 | // make the next free record the new list head 1562 | if (next_free_idx_id && next_free_idx_id - 1 < pool->cap) { 1563 | records[next_free_idx_id - 1].prev_id = 0; 1564 | } 1565 | 1566 | // 1567 | // make the old allocated list tail point to the newly allocated element. 1568 | // and we will then point back to it. 1569 | if (pool->alloced_list_tail_id) { 1570 | records[pool->alloced_list_tail_id - 1].next_id |= idx_id; 1571 | record_ptr->prev_id = pool->alloced_list_tail_id; 1572 | } 1573 | 1574 | if (pool->alloced_list_head_id == 0) { 1575 | pool->alloced_list_head_id = idx_id; 1576 | } 1577 | 1578 | void* allocated_elmt = das_ptr_add(pool->address_space, (uintptr_t)idx * elmt_size); 1579 | if (idx_id == pool->free_list_head_id) { 1580 | // data comes from the free list so lets zero it. 1581 | memset(allocated_elmt, 0, elmt_size); 1582 | } 1583 | 1584 | // 1585 | // update the list head/tail ids 1586 | // 1587 | pool->free_list_head_id = next_free_idx_id; 1588 | pool->alloced_list_tail_id = idx_id; 1589 | pool->count += 1; 1590 | 1591 | return allocated_elmt; 1592 | } 1593 | 1594 | void _DasPool_dealloc(_DasPool* pool, DasPoolElmtId elmt_id, uintptr_t elmt_size, uint32_t index_bits) { 1595 | _DasPool_assert_id(pool, elmt_id, elmt_size, index_bits); 1596 | 1597 | DasPoolElmtId index_mask = (1 << index_bits) - 1; 1598 | 1599 | _DasPoolRecord* records = _DasPool_records(pool, elmt_size); 1600 | uint32_t dealloced_idx_id = elmt_id & index_mask; 1601 | _DasPoolRecord* dealloced_record_ptr = &records[dealloced_idx_id - 1]; 1602 | DasPoolElmtId dealloced_record = dealloced_record_ptr->next_id; 1603 | 1604 | uint32_t prev_allocated_elmt_id = dealloced_record_ptr->prev_id; 1605 | uint32_t next_allocated_elmt_id = dealloced_record & index_mask; 1606 | 1607 | 1608 | // 1609 | // place the deallocated element in the free list somewhere. 1610 | // 1611 | uint32_t next_free_idx_id = pool->free_list_head_id; 1612 | if (pool->order_free_list_on_dealloc && next_free_idx_id < dealloced_idx_id) { 1613 | // 1614 | // we have order the free list enabled, so store in low to high order. 1615 | // this will keep allocations new allocations closer to eachother. 1616 | // move up the free list until the deallocating elmt_id is less than that index id the record points to. 1617 | DasPoolElmtId* elmt_next_free_idx_id; 1618 | uint32_t nfi = next_free_idx_id; 1619 | uint32_t count = pool->count; 1620 | for (uint32_t i = 0; i < count; i += 1) { 1621 | elmt_next_free_idx_id = &records[nfi - 1].next_id; 1622 | nfi = *elmt_next_free_idx_id & index_bits; 1623 | if (nfi > dealloced_idx_id) break; 1624 | } 1625 | 1626 | // 1627 | // now make the record point this deallocated record instead 1628 | // 1629 | DasPoolElmtId record = *elmt_next_free_idx_id; 1630 | 1631 | // 1632 | // before we do, make the record we used to point to, point back to the deallocated element instead. 1633 | if (nfi) { 1634 | records[nfi - 1].prev_id = dealloced_idx_id; 1635 | } 1636 | dealloced_record_ptr->prev_id = nfi; 1637 | 1638 | record &= ~index_mask; // clear the index id that points to the original next free element 1639 | record |= dealloced_idx_id; // set the index id of the deallocated element 1640 | *elmt_next_free_idx_id = record; 1641 | 1642 | next_free_idx_id = nfi; 1643 | } else { 1644 | // 1645 | // place at the head of the free list 1646 | pool->free_list_head_id = dealloced_idx_id; 1647 | dealloced_record_ptr->prev_id = 0; 1648 | } 1649 | 1650 | // 1651 | // "free" the element by updating the records. 1652 | // 1653 | { 1654 | // 1655 | // go to the next element our deallocated element used to point to and make it point back to 1656 | // the previous element the deallocated element used to point to. 1657 | if (next_allocated_elmt_id) { 1658 | records[next_allocated_elmt_id - 1].prev_id = prev_allocated_elmt_id; 1659 | } else { 1660 | pool->alloced_list_tail_id = prev_allocated_elmt_id; 1661 | } 1662 | 1663 | if (prev_allocated_elmt_id) { 1664 | _DasPoolRecord* pr = &records[prev_allocated_elmt_id - 1]; 1665 | DasPoolElmtId r = pr->next_id; 1666 | r &= ~index_mask; // clear the index that points to the element we have just deallocated 1667 | r |= next_allocated_elmt_id; // now make it point to the element our deallocated element used to point to. 1668 | pr->next_id = r; 1669 | } else { 1670 | pool->alloced_list_head_id = next_allocated_elmt_id; 1671 | } 1672 | 1673 | // 1674 | // extract the counter and then increment it's value. 1675 | // but make sure it wrap if we reach the maximum the counter bits can hold. 1676 | // 1677 | DasPoolElmtId counter_mask = DasPoolElmtId_counter_mask(index_bits); 1678 | uint32_t counter = (elmt_id & counter_mask) >> index_bits; 1679 | uint32_t counter_max = counter_mask >> index_bits; 1680 | if (counter == counter_max) { 1681 | counter = 0; 1682 | } else { 1683 | counter += 1; 1684 | } 1685 | 1686 | // 1687 | // now update the counter by clearing and setting. 1688 | dealloced_record &= ~counter_mask; 1689 | dealloced_record |= counter << index_bits; 1690 | 1691 | // 1692 | // now set the index to the next element in the free list. 1693 | dealloced_record &= ~index_mask; 1694 | dealloced_record |= next_free_idx_id; 1695 | 1696 | // 1697 | // clear the is allocated bit then store the record back in the array. 1698 | dealloced_record &= ~DasPoolElmtId_is_allocated_bit_MASK; 1699 | dealloced_record_ptr->next_id = dealloced_record; 1700 | } 1701 | 1702 | pool->count -= 1; 1703 | } 1704 | 1705 | void* _DasPool_id_to_ptr(_DasPool* pool, DasPoolElmtId elmt_id, uintptr_t elmt_size, uint32_t index_bits) { 1706 | _DasPool_assert_id(pool, elmt_id, elmt_size, index_bits); 1707 | DasPoolElmtId index_mask = (1 << index_bits) - 1; 1708 | uint32_t idx = (elmt_id & index_mask) - 1; 1709 | return das_ptr_add(pool->address_space, (uintptr_t)idx * elmt_size); 1710 | } 1711 | 1712 | uint32_t _DasPool_id_to_idx(_DasPool* pool, DasPoolElmtId elmt_id, uintptr_t elmt_size, uint32_t index_bits) { 1713 | _DasPool_assert_id(pool, elmt_id, elmt_size, index_bits); 1714 | DasPoolElmtId index_mask = (1 << index_bits) - 1; 1715 | return (elmt_id & index_mask) - 1; 1716 | } 1717 | 1718 | DasPoolElmtId _DasPool_ptr_to_id(_DasPool* pool, void* ptr, uintptr_t elmt_size, uint32_t index_bits) { 1719 | das_debug_assert(pool->address_space <= ptr && ptr < das_ptr_add(pool->address_space, (uintptr_t)pool->cap * elmt_size), "pointer was not allocated with this pool"); 1720 | uint32_t idx = das_ptr_diff(ptr, pool->address_space) / elmt_size; 1721 | _DasPoolRecord* record = &_DasPool_records(pool, elmt_size)[idx]; 1722 | das_debug_assert(record->next_id & DasPoolElmtId_is_allocated_bit_MASK, "the pointer is a freed element"); 1723 | 1724 | // 1725 | // lets make an identifier from the record by just setting the index in it. 1726 | return _DasPool_record_to_id(pool, record, idx + 1, index_bits); 1727 | } 1728 | 1729 | uint32_t _DasPool_ptr_to_idx(_DasPool* pool, void* ptr, uintptr_t elmt_size, uint32_t index_bits) { 1730 | das_debug_assert(pool->address_space <= ptr && ptr < das_ptr_add(pool->address_space, (uintptr_t)pool->cap * elmt_size), "pointer was not allocated with this pool"); 1731 | uint32_t idx = das_ptr_diff(ptr, pool->address_space) / elmt_size; 1732 | _DasPoolRecord* record = &_DasPool_records(pool, elmt_size)[idx]; 1733 | das_debug_assert(record->next_id & DasPoolElmtId_is_allocated_bit_MASK, "the pointer is a freed element"); 1734 | return idx; 1735 | } 1736 | 1737 | void* _DasPool_idx_to_ptr(_DasPool* pool, uint32_t idx, uintptr_t elmt_size) { 1738 | das_assert(idx < pool->cap, "index of '%u' is out of the pool boundary of '%u' elements", idx, pool->cap); 1739 | 1740 | _DasPoolRecord* record = &_DasPool_records(pool, elmt_size)[idx]; 1741 | das_debug_assert(record->next_id & DasPoolElmtId_is_allocated_bit_MASK, "the index is a freed element"); 1742 | 1743 | return das_ptr_add(pool->address_space, (uintptr_t)idx * elmt_size); 1744 | } 1745 | 1746 | DasPoolElmtId _DasPool_idx_to_id(_DasPool* pool, uint32_t idx, uintptr_t elmt_size, uint32_t index_bits) { 1747 | das_assert(idx < pool->cap, "index of '%u' is out of the pool boundary of '%u' elements", idx, pool->cap); 1748 | 1749 | _DasPoolRecord* record = &_DasPool_records(pool, elmt_size)[idx]; 1750 | das_debug_assert(record->next_id & DasPoolElmtId_is_allocated_bit_MASK, "the index is a freed element"); 1751 | 1752 | // 1753 | // lets make an identifier from the record by just setting the index in it. 1754 | return _DasPool_record_to_id(pool, record, idx + 1, index_bits); 1755 | } 1756 | 1757 | DasPoolElmtId _DasPool_iter_next(_DasPool* pool, DasPoolElmtId elmt_id, uintptr_t elmt_size, uint32_t index_bits) { 1758 | // 1759 | // if NULL is passed in, then get the first record of the allocated list. 1760 | if (elmt_id == 0) { 1761 | uint32_t first_allocated_id = pool->alloced_list_head_id; 1762 | if (first_allocated_id == 0) 1763 | return 0; 1764 | 1765 | return _DasPool_idx_to_id(pool, first_allocated_id - 1, elmt_size, index_bits); 1766 | } 1767 | 1768 | _DasPool_assert_id(pool, elmt_id, elmt_size, index_bits); 1769 | // 1770 | // extract the index from the element identifier 1771 | DasPoolElmtId index_mask = (1 << index_bits) - 1; 1772 | uint32_t idx_id = elmt_id & index_mask; 1773 | 1774 | // 1775 | // get the record for the element 1776 | _DasPoolRecord* records = _DasPool_records(pool, elmt_size); 1777 | DasPoolElmtId record = records[idx_id - 1].next_id; 1778 | 1779 | // 1780 | // extract the next element index from the record 1781 | uint32_t next_idx_id = record & index_mask; 1782 | if (next_idx_id == 0) return 0; 1783 | 1784 | // 1785 | // now convert the next record to an identifier for that next record/element 1786 | _DasPoolRecord* next_record = &records[next_idx_id - 1]; 1787 | return _DasPool_record_to_id(pool, next_record, next_idx_id, index_bits); 1788 | } 1789 | 1790 | DasPoolElmtId _DasPool_iter_prev(_DasPool* pool, DasPoolElmtId elmt_id, uintptr_t elmt_size, uint32_t index_bits) { 1791 | // 1792 | // if NULL is passed in, then get the last record of the allocated list. 1793 | if (elmt_id == 0) { 1794 | uint32_t last_allocated_id = pool->alloced_list_tail_id; 1795 | if (last_allocated_id == 0) 1796 | return 0; 1797 | 1798 | return _DasPool_idx_to_id(pool, last_allocated_id - 1, elmt_size, index_bits); 1799 | } 1800 | 1801 | _DasPool_assert_id(pool, elmt_id, elmt_size, index_bits); 1802 | // 1803 | // extract the index from the element identifier 1804 | DasPoolElmtId index_mask = (1 << index_bits) - 1; 1805 | uint32_t idx_id = elmt_id & index_mask; 1806 | 1807 | // 1808 | // get the record for the element 1809 | _DasPoolRecord* records = _DasPool_records(pool, elmt_size); 1810 | uint32_t prev_idx_id = records[idx_id - 1].prev_id; 1811 | if (prev_idx_id == 0) return 0; 1812 | 1813 | // 1814 | // now convert the previous record to an identifier for that previous record/element 1815 | _DasPoolRecord* prev_record = &records[prev_idx_id - 1]; 1816 | return _DasPool_record_to_id(pool, prev_record, prev_idx_id, index_bits); 1817 | } 1818 | 1819 | DasPoolElmtId _DasPool_decrement_record_counter(_DasPool* pool, DasPoolElmtId elmt_id, uintptr_t elmt_size, uint32_t index_bits) { 1820 | _DasPool_assert_id(pool, elmt_id, elmt_size, index_bits); 1821 | DasPoolElmtId index_mask = (1 << index_bits) - 1; 1822 | uint32_t idx_id = elmt_id & index_mask; 1823 | _DasPoolRecord* record = &_DasPool_records(pool, elmt_size)[idx_id - 1]; 1824 | 1825 | DasPoolElmtId counter_mask = DasPoolElmtId_counter_mask(index_bits); 1826 | uint32_t counter = (elmt_id & counter_mask) >> index_bits; 1827 | uint32_t counter_max = counter_mask >> index_bits; 1828 | if (counter == 0) { 1829 | counter = counter_max; 1830 | } else { 1831 | counter -= 1; 1832 | } 1833 | 1834 | // 1835 | // now update the counter by clearing and setting. 1836 | record->next_id &= ~counter_mask; 1837 | record->next_id |= counter << index_bits; 1838 | 1839 | return _DasPool_record_to_id(pool, record, idx_id, index_bits); 1840 | } 1841 | 1842 | DasBool _DasPool_is_idx_allocated(_DasPool* pool, uint32_t idx, uintptr_t elmt_size) { 1843 | das_assert(idx < pool->cap, "index of '%u' is out of the pool boundary of '%u' elements", idx, pool->cap); 1844 | _DasPoolRecord* record = &_DasPool_records(pool, elmt_size)[idx]; 1845 | return (record->next_id & DasPoolElmtId_is_allocated_bit_MASK) == DasPoolElmtId_is_allocated_bit_MASK; 1846 | } 1847 | 1848 | DasBool _DasPool_is_id_valid(_DasPool* pool, DasPoolElmtId elmt_id, uintptr_t elmt_size, uint32_t index_bits) { 1849 | if (elmt_id == 0) return das_false; 1850 | if ((elmt_id & DasPoolElmtId_is_allocated_bit_MASK) == 0) return das_false; 1851 | DasPoolElmtId index_mask = (1 << index_bits) - 1; 1852 | uint32_t idx_id = elmt_id & index_mask; 1853 | if (idx_id == 0) return das_false; 1854 | _DasPoolRecord* record = &_DasPool_records(pool, elmt_size)[idx_id - 1]; 1855 | if ((record->next_id & DasPoolElmtId_is_allocated_bit_MASK) == 0) return das_false; 1856 | 1857 | DasPoolElmtId counter_mask = DasPoolElmtId_counter_mask(index_bits); 1858 | uint32_t counter = (elmt_id & counter_mask) >> index_bits; 1859 | uint32_t record_counter = (record->next_id & counter_mask) >> index_bits; 1860 | if (counter != record_counter) return das_false; 1861 | 1862 | return das_true; 1863 | } 1864 | 1865 | -------------------------------------------------------------------------------- /das.h: -------------------------------------------------------------------------------- 1 | #ifndef DAS_H 2 | #define DAS_H 3 | 4 | #if _WIN32 5 | // TODO: i have read that the windows headers can really slow down compile times. 6 | // since the win32 api is stable maybe we should forward declare the functions and constants manually ourselves. 7 | // maybe we can generate this using the new win32metadata thing if we can figure that out. 8 | #define NOMINMAX 9 | #define WIN32_LEAN_AND_MEAN 10 | #include 11 | #endif 12 | 13 | // ====================================================================== 14 | // 15 | // 16 | // Configuration 17 | // 18 | // 19 | // ====================================================================== 20 | 21 | // 22 | // if you use types that have a larger alignment that 16 bytes. eg. Intel SSE primitives __m128, __m256. 23 | // override this macro as so the DasStk and DasDeque will work properly with these types. 24 | // 25 | #ifndef das_max_align_t 26 | #define das_max_align_t long double 27 | #endif 28 | 29 | #ifndef DasStk_min_cap 30 | #define DasStk_min_cap 16 31 | #endif 32 | 33 | #ifndef DasDeque_min_cap 34 | #define DasDeque_min_cap 16 35 | #endif 36 | 37 | #ifndef DasAlctor_default 38 | #define DasAlctor_default DasAlctor_system 39 | #endif 40 | 41 | // ====================================================================== 42 | // 43 | // 44 | // General 45 | // 46 | // 47 | // ====================================================================== 48 | 49 | #include 50 | #include // fprintf, vsnprintf 51 | #include // abort 52 | #include // memcpy, memmove 53 | #include 54 | #include 55 | #include 56 | 57 | #if _WIN32 58 | // forward declare here to avoid including windows.h in the header file. 59 | typedef void* HANDLE; 60 | #endif 61 | 62 | typedef uint8_t DasBool; 63 | #define das_false 0 64 | #define das_true 1 65 | 66 | #define das_static_assert(x, msg) int das_static_assert(int dummy[(x)?1:-1]) 67 | 68 | #ifndef das_noreturn 69 | 70 | #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L 71 | #define das_noreturn _Noreturn 72 | #elif defined(__GNUC__) 73 | #define das_noreturn __attribute__((noreturn)) 74 | #elif _WIN32 75 | #define das_noreturn __declspec(noreturn) 76 | #else 77 | #define das_noreturn 78 | #endif 79 | 80 | #endif // das_noreturn 81 | 82 | #ifdef _WIN32 83 | #define das_function_name __FUNCTION__ 84 | #elif defined(__GNUC__) 85 | #define das_function_name __func__ 86 | #else 87 | #define das_function_name NULL 88 | #endif 89 | 90 | das_noreturn void _das_abort(const char* file, int line, const char* func, char* assert_test, char* message_fmt, ...); 91 | #define das_abort(...) \ 92 | _das_abort(__FILE__, __LINE__, das_function_name, NULL, __VA_ARGS__) 93 | 94 | #define das_assert(cond, ...) \ 95 | if (!(cond)) _das_abort(__FILE__, __LINE__, das_function_name, #cond, __VA_ARGS__) 96 | 97 | #define das_assert_loc(file, line, func, cond, ...) \ 98 | if (!(cond)) _das_abort(file, line, func, #cond, __VA_ARGS__) 99 | 100 | #if DAS_DEBUG_ASSERTIONS 101 | #define das_debug_assert das_assert 102 | #else 103 | #define das_debug_assert(cond, ...) (void)(cond) 104 | #endif 105 | 106 | #ifndef alignof 107 | #define alignof _Alignof 108 | #endif 109 | 110 | static inline uintptr_t das_min_u(uintptr_t a, uintptr_t b) { return a < b ? a : b; } 111 | static inline intptr_t das_min_s(intptr_t a, intptr_t b) { return a < b ? a : b; } 112 | static inline double das_min_f(double a, double b) { return a < b ? a : b; } 113 | 114 | static inline uintptr_t das_max_u(uintptr_t a, uintptr_t b) { return a > b ? a : b; } 115 | static inline intptr_t das_max_s(intptr_t a, intptr_t b) { return a > b ? a : b; } 116 | static inline double das_max_f(double a, double b) { return a > b ? a : b; } 117 | 118 | static inline uintptr_t das_clamp_u(uintptr_t v, uintptr_t min, uintptr_t max) { return v > max ? max : (v >= min ? v : min); } 119 | static inline intptr_t das_clamp_s(intptr_t v, intptr_t min, intptr_t max) { return v > max ? max : (v >= min ? v : min); } 120 | static inline double das_clamp_f(double v, double min, double max) { return v > max ? max : (v >= min ? v : min); } 121 | 122 | static inline uintptr_t das_round_up_nearest_multiple_u(uintptr_t v, uintptr_t multiple) { 123 | uintptr_t rem = v % multiple; 124 | if (rem == 0) return v; 125 | return v + multiple - rem; 126 | } 127 | 128 | static inline intptr_t das_round_up_nearest_multiple_s(intptr_t v, intptr_t multiple) { 129 | intptr_t rem = v % multiple; 130 | if (v > 0) { 131 | return v + multiple - rem; 132 | } else { 133 | return v + rem; 134 | } 135 | } 136 | 137 | static inline double das_round_up_nearest_multiple_f(double v, double multiple) { 138 | double rem = fmod(v, multiple); 139 | if (v > 0.0) { 140 | return v + multiple - rem; 141 | } else { 142 | return v + rem; 143 | } 144 | } 145 | 146 | static inline uintptr_t das_round_down_nearest_multiple_u(uintptr_t v, uintptr_t multiple) { 147 | uintptr_t rem = v % multiple; 148 | return v - rem; 149 | } 150 | 151 | static inline intptr_t das_round_down_nearest_multiple_s(intptr_t v, intptr_t multiple) { 152 | intptr_t rem = v % multiple; 153 | if (v >= 0) { 154 | return v - rem; 155 | } else { 156 | return v - rem - multiple; 157 | } 158 | } 159 | 160 | static inline double das_round_down_nearest_multiple_f(double v, double multiple) { 161 | double rem = fmod(v, multiple); 162 | if (v >= 0.0) { 163 | return v - rem; 164 | } else { 165 | return v - rem - multiple; 166 | } 167 | } 168 | 169 | #define das_most_set_bit_idx(v) _das_most_set_bit_idx(v, sizeof(v)) 170 | static inline uintmax_t _das_most_set_bit_idx(uintmax_t v, uint32_t sizeof_type) { 171 | #if defined(__GNUC__) 172 | uint32_t idx = sizeof(v) * 8; 173 | uint32_t type_diff = (sizeof(uintmax_t) - sizeof_type) * 8; 174 | idx -= type_diff; 175 | if (sizeof_type == sizeof(long)) { 176 | idx -= __builtin_clzl(v); 177 | } else if (sizeof_type == sizeof(int)) { 178 | idx -= __builtin_clz(v); 179 | } else { 180 | idx -= __builtin_clzll(v); 181 | } 182 | idx -= 1; 183 | return idx; 184 | #elif defined(_WIN32) 185 | DWORD leading_zero = 0; 186 | if (sizeof_type == sizeof(int)) { 187 | _BitScanReverse(&leading_zero, v); 188 | } else { 189 | _BitScanReverse64(&leading_zero, v); 190 | } 191 | return leading_zero; 192 | #else 193 | #error "unhandle least set bit for this platform" 194 | #endif 195 | } 196 | 197 | static inline uintmax_t das_least_set_bit_idx(uintmax_t v) { 198 | #if defined(__GNUC__) 199 | if (sizeof(v) == sizeof(long)) { 200 | return __builtin_ctzl(v); 201 | } else if (sizeof(v) == sizeof(int)) { 202 | return __builtin_ctz(v); 203 | } else { 204 | return __builtin_ctzll(v); 205 | } 206 | #elif defined(_WIN32) 207 | DWORD trailing_zero = 0; 208 | if (sizeof(v) == sizeof(int)) { 209 | _BitScanForward(&trailing_zero, v); 210 | } else { 211 | _BitScanForward64(&trailing_zero, v); 212 | } 213 | return trailing_zero; 214 | #else 215 | #error "unhandle least set bit for this platform" 216 | #endif 217 | } 218 | 219 | // ====================================================================== 220 | // 221 | // 222 | // Memory Utilities 223 | // 224 | // 225 | // ====================================================================== 226 | 227 | extern void* das_ptr_round_up_align(void* ptr, uintptr_t align); 228 | extern void* das_ptr_round_down_align(void* ptr, uintptr_t align); 229 | #define das_is_power_of_two(v) (((v) != 0) && (((v) & ((v) - 1)) == 0)) 230 | #define das_ptr_add(ptr, by) (void*)((uintptr_t)(ptr) + (uintptr_t)(by)) 231 | #define das_ptr_sub(ptr, by) (void*)((uintptr_t)(ptr) - (uintptr_t)(by)) 232 | #define das_ptr_diff(to, from) ((char*)(to) - (char*)(from)) 233 | #define das_zero_elmt(ptr) memset(ptr, 0, sizeof(*(ptr))) 234 | #define das_zero_elmts(ptr, elmts_count) memset(ptr, 0, sizeof(*(ptr)) * (elmts_count)) 235 | #define das_zero_array(array) memset(array, 0, sizeof(array)) 236 | #define das_copy_array(dst, src) memcpy(dst, src, sizeof(dst)) 237 | #define das_copy_elmts(dst, src, elmts_count) memcpy(dst, src, elmts_count * sizeof(*(dst))) 238 | #define das_copy_overlap_elmts(dst, src, elmts_count) memmove(dst, src, elmts_count * sizeof(*(dst))) 239 | #define das_cmp_array(a, b) (memcmp(a, b, sizeof(a)) == 0) 240 | #define das_cmp_elmt(a, b) (memcmp(a, b, sizeof(*(a))) == 0) 241 | #define das_cmp_elmts(a, b, elmts_count) (memcmp(a, b, elmts_count * sizeof(*(a))) == 0) 242 | 243 | // for X86/64 and ARM. maybe be different for other architectures. 244 | #define das_cache_line_size 64 245 | 246 | // ====================================================================== 247 | // 248 | // 249 | // Custom Allocator API 250 | // 251 | // 252 | // ====================================================================== 253 | 254 | // an allocation function for allocating, reallocating and deallocating memory. 255 | /* 256 | if (!ptr && size == 0) { 257 | // reset 258 | } else if (!ptr) { 259 | // allocate 260 | } else if (ptr && size > 0) { 261 | // reallocate 262 | } else { 263 | // deallocate 264 | } 265 | */ 266 | // returns NULL on allocation failure 267 | typedef void* (*DasAllocFn)(void* alctor_data, void* ptr, uintptr_t old_size, uintptr_t size, uintptr_t align); 268 | 269 | // 270 | // DasAlctor is the custom allocator data structure. 271 | // is used to point to the implementation of a custom allocator. 272 | // 273 | // EXAMPLE of a local alloctor: 274 | // 275 | // typedef struct { 276 | // void* data; 277 | // uint32_t pos; 278 | // uint32_t size; 279 | // } LinearAlctor; 280 | // 281 | // void* LinearAlctor_alloc_fn(void* alctor_data, void* ptr, uintptr_t old_size, uintptr_t size, uintptr_t align) 282 | // 283 | // // we zero the buffer so this allocator will allocate zeroed memory. 284 | // char buffer[1024] = {0}; 285 | // LinearAlctor linear_alctor = { .data = buffer, .pos = 0, .size = sizeof(buffer) }; 286 | // DasAlctor alctor = { .data = &linear_alctor, .fn = LinearAlctor_alloc_fn }; 287 | // 288 | typedef struct { 289 | // the allocation function, see the notes above DasAllocFn 290 | DasAllocFn fn; 291 | 292 | // the data is an optional pointer used to point to an allocator's data structure. 293 | // it is passed into the DasAllocFn as the first argument. 294 | // this field can be NULL if you are implementing a global allocator. 295 | void* data; 296 | } DasAlctor; 297 | 298 | void* das_system_alloc_fn(void* alloc_data, void* ptr, uintptr_t old_size, uintptr_t size, uintptr_t align); 299 | #define DasAlctor_system ((DasAlctor){ .fn = das_system_alloc_fn, .data = NULL }) 300 | 301 | // ====================================================================== 302 | // 303 | // 304 | // Dynamic Allocation API 305 | // 306 | // 307 | // ====================================================================== 308 | // these macros will de/allocate a memory from the supplied allocator. 309 | // memory will be uninitialized unless the allocator you have zeros the memory for you. 310 | // 311 | // if you allocate some memory, and you want deallocate or expand the memory. 312 | // it is up to you provide the correct 'old_size' that you allocated the memory with in the first place. 313 | // unless you know that the allocator you are using does not care about that. 314 | // 315 | // all @param(align), must be a power of two and greater than 0. 316 | // 317 | 318 | // allocates memory that can hold a single element of type @param(T). 319 | // @return: an appropriately aligned pointer to that allocated memory location. 320 | #define das_alloc_elmt(T, alctor) (T*)das_alloc(alctor, sizeof(T), alignof(T)) 321 | 322 | // deallocates memory that can hold a single element of typeof(*@param(ptr)). 323 | #define das_dealloc_elmt(T, alctor, ptr) das_dealloc(alctor, ptr, sizeof(T), alignof(T)) 324 | 325 | // allocates memory that can hold an array of @param(count) number of @param(T) elements. 326 | // @return: an appropriately aligned pointer to that allocated memory location. 327 | #define das_alloc_array(T, alctor, count) (T*)das_alloc(alctor, (count) * sizeof(T), alignof(T)) 328 | 329 | // reallocates memory that can hold an array of @param(old_count) to @param(count) number of typeof(*@param(ptr)) elements. 330 | // the memory of @param(ptr)[0] up to @param(ptr)[old_size] is preserved. 331 | // @return a pointer to allocated memory with enough bytes to store a single element of type typeof(*@param(ptr)). 332 | #define das_realloc_array(T, alctor, ptr, old_count, count) \ 333 | das_realloc(alctor, ptr, (old_count) * sizeof(*(ptr)), (count) * sizeof(T), alignof(T)) 334 | 335 | // deallocates memory that can hold an array of @param(old_count) number of typeof(*@param(ptr)) elements. 336 | #define das_dealloc_array(T, alctor, ptr, old_count) das_dealloc(alctor, ptr, (old_count) * sizeof(T), alignof(T)) 337 | 338 | // allocates @param(size) bytes of memory that is aligned to @param(align) 339 | #define das_alloc(alctor, size, align) alctor.fn(alctor.data, NULL, 0, size, align); 340 | 341 | // reallocates @param(old_size) to @param(size) bytes of memory that is aligned to @param(align) 342 | // the memory of @param(ptr) up to @param(ptr) + @param(old_size') is preserved. 343 | // if @param(ptr) == NULL, then this will call the alloc function for current allocator instead 344 | #define das_realloc(alctor, ptr, old_size, size, align) alctor.fn(alctor.data, ptr, old_size, size, align); 345 | 346 | // deallocates @param(old_size) bytes of memory that is aligned to @param(align' 347 | #define das_dealloc(alctor, ptr, old_size, align) alctor.fn(alctor.data, ptr, old_size, 0, align); 348 | 349 | #define das_alloc_reset(alctor) alctor.fn(alctor.data, NULL, 0, 0, 0); 350 | 351 | // ====================================================================== 352 | // 353 | // 354 | // DasStk - linear stack of elements (LIFO) 355 | // 356 | // 357 | // ====================================================================== 358 | // 359 | // LIFO is the optimal usage, so push elements and pop them from the end. 360 | // can be used as a growable array. 361 | // elements are allocated using the Dynamic Allocation API that is defined lower down in this file. 362 | // 363 | // DasStk API example usage: 364 | // 365 | // DasStk(int) stack_of_ints; 366 | // DasStk_resize_cap(&stack_of_ints, 64); 367 | // 368 | // int* elmt = DasStk_push(&stack_of_ints); 369 | // *elmt = 55; 370 | // 371 | // DasStk_pop(&stack_of_ints); 372 | // 373 | 374 | #define DasStk(T) DasStkHeader_##T* 375 | 376 | // 377 | // the internal header used in the dynamic internal functions. 378 | // WARNING: these field must match the DasStkHeader generated with the typedef_DasStk macro below 379 | typedef struct _DasStkHeader _DasStkHeader; 380 | struct _DasStkHeader { 381 | uintptr_t count; 382 | uintptr_t cap; 383 | DasAlctor alctor; 384 | }; 385 | 386 | 387 | // 388 | // you need to make sure that you use this macro in global scope 389 | // to define a structure for the type you want to use. 390 | // 391 | // we have a funky name for the data field, 392 | // so an error will get thrown if you pass the 393 | // wrong structure into one of the macros below. 394 | #define typedef_DasStk(T) \ 395 | typedef struct { \ 396 | uintptr_t count; \ 397 | uintptr_t cap; \ 398 | DasAlctor alctor; \ 399 | T DasStk_data[]; \ 400 | } DasStkHeader_##T 401 | 402 | typedef_DasStk(char); 403 | typedef_DasStk(short); 404 | typedef_DasStk(int); 405 | typedef_DasStk(long); 406 | typedef_DasStk(float); 407 | typedef_DasStk(double); 408 | typedef_DasStk(size_t); 409 | typedef_DasStk(ptrdiff_t); 410 | typedef_DasStk(uint8_t); 411 | typedef_DasStk(uint16_t); 412 | typedef_DasStk(uint32_t); 413 | typedef_DasStk(uint64_t); 414 | typedef_DasStk(int8_t); 415 | typedef_DasStk(int16_t); 416 | typedef_DasStk(int32_t); 417 | typedef_DasStk(int64_t); 418 | 419 | #define DasStk_data(stk_ptr) ((*stk_ptr) ? (*(stk_ptr))->DasStk_data : NULL) 420 | #define DasStk_count(stk_ptr) ((*stk_ptr) ? (*(stk_ptr))->count : 0) 421 | #define DasStk_set_count(stk_ptr, new_count) ((*stk_ptr) ? (*(stk_ptr))->count = (new_count) : 0) 422 | #define DasStk_cap(stk_ptr) ((*stk_ptr) ? (*(stk_ptr))->cap : 0) 423 | #define DasStk_elmt_size(stk_ptr) (sizeof(*(*(stk_ptr))->DasStk_data)) 424 | #define DasStk_alctor(stk_ptr) ((*stk_ptr) ? (*(stk_ptr))->alctor : DasAlctor_default) 425 | 426 | #define DasStk_foreach(stk_ptr, INDEX_NAME) \ 427 | for (uintptr_t INDEX_NAME = 0, _end_ = DasStk_count(stk_ptr); INDEX_NAME < _end_; INDEX_NAME += 1) 428 | 429 | // 430 | // there is no DasStk_init, zeroed data is initialization. 431 | // 432 | 433 | // 434 | // preallocates a stack with enough capacity to store init_cap number of elements. 435 | #define DasStk_init_with_cap(stk_ptr, init_cap) DasStk_init_with_alctor(stk_ptr, init_cap, DasAlctor_default) 436 | // the memory is allocator with the supplied allocator and is used for all future reallocations. 437 | #define DasStk_init_with_alctor(stk_ptr, init_cap, alctor) \ 438 | _DasStk_init_with_alctor((_DasStkHeader**)stk_ptr, sizeof(**(stk_ptr)), init_cap, alctor, DasStk_elmt_size(stk_ptr)) 439 | extern DasBool _DasStk_init_with_alctor(_DasStkHeader** header_out, uintptr_t header_size, uintptr_t init_cap, DasAlctor alctor, uintptr_t elmt_size); 440 | 441 | // 442 | // initialize a stack by copying all the elements from another stack. 443 | #define DasStk_init_clone(dst_stk_ptr, src_stk_ptr) DasStk_init_clone_with_alctor(dst_stk_ptr, src_stk_ptr, DasAlctor_default) 444 | // the memory is allocator with the supplied allocator and is used for all future reallocations. 445 | #define DasStk_init_clone_with_alctor(dst_stk_ptr, src_stk_ptr, alctor) \ 446 | _DasStk_init_clone_with_alctor((_DasStkHeader**)dst_stk_ptr, sizeof(**(dst_stk_ptr)), (_DasStkHeader*)*(src_stk_ptr), alctor, DasStk_elmt_size(dst_stk_ptr)) 447 | DasBool _DasStk_init_clone_with_alctor(_DasStkHeader** dst_header_in_out, uintptr_t header_size, _DasStkHeader* src_header, DasAlctor alctor, uintptr_t elmt_size); 448 | 449 | // deallocates the memory and sets the stack to being empty. 450 | #define DasStk_deinit(stk_ptr) _DasStk_deinit((_DasStkHeader**)stk_ptr, sizeof(**(stk_ptr)), DasStk_elmt_size(stk_ptr)) 451 | extern void _DasStk_deinit(_DasStkHeader** header_in_out, uintptr_t header_size, uintptr_t elmt_size); 452 | 453 | // removes all the elements from the deque 454 | #define DasStk_clear(stk_ptr) ((*stk_ptr) ? (*(stk_ptr))->count = 0 : 0) 455 | 456 | // returns a pointer to the element at idx. 457 | // this function will abort if idx is out of bounds 458 | #define DasStk_get(stk_ptr, idx) (&DasStk_data(stk_ptr)[_DasStk_assert_idx((_DasStkHeader*)*(stk_ptr), idx, DasStk_elmt_size(stk_ptr))]) 459 | extern uintptr_t _DasStk_assert_idx(_DasStkHeader* header, uintptr_t idx, uintptr_t elmt_size); 460 | 461 | // returns a pointer to the element at idx starting from the back of the stack. 462 | // this function will abort if idx is out of bounds 463 | #define DasStk_get_back(stk_ptr, idx) DasStk_get(stk_ptr, DasStk_count(stk_ptr) - 1 - (idx)) 464 | 465 | // returns a pointer to the first element 466 | // this function will abort if the stack is empty 467 | #define DasStk_get_first(stk_ptr) DasStk_get(stk_ptr, 0) 468 | 469 | // returns a pointer to the last element 470 | // this function will abort if the stack is empty 471 | #define DasStk_get_last(stk_ptr) DasStk_get(stk_ptr, DasStk_count(stk_ptr) - 1) 472 | 473 | // resizes the stack to have new_count number of elements. 474 | // if new_count is greater than DasStk_cap(stk_ptr), then this function will internally call DasStk_resize_cap to make more room. 475 | // if new_count is greater than DasStk_count(stk_ptr), new elements will be uninitialized, 476 | // unless you want the to be zeroed by passing in zero == das_true. 477 | // returns das_false on allocation failure, otherwise das_true 478 | #define DasStk_resize(stk_ptr, new_count, zero) \ 479 | _DasStk_resize((_DasStkHeader**)stk_ptr, sizeof(**(stk_ptr)), new_count, zero, DasStk_elmt_size(stk_ptr)) 480 | extern DasBool _DasStk_resize(_DasStkHeader** header_in_out, uintptr_t header_size, uintptr_t new_count, DasBool zero, uintptr_t elmt_size); 481 | 482 | // reallocates the capacity of the stack to have enough room for new_cap number of elements. 483 | // if new_cap is less than DasStk_count(stk_ptr) then new_cap is set to hold atleast DasStk_count(stk_ptr). 484 | // returns das_false on allocation failure, otherwise das_true 485 | #define DasStk_resize_cap(stk_ptr, new_cap) \ 486 | _DasStk_resize_cap((_DasStkHeader**)stk_ptr, sizeof(**(stk_ptr)), new_cap, DasStk_elmt_size(stk_ptr)) 487 | extern DasBool _DasStk_resize_cap(_DasStkHeader** header_in_out, uintptr_t header_size, uintptr_t new_cap, uintptr_t elmt_size); 488 | 489 | // inserts element/s onto the index position in the stack that will be initialized with 490 | // the data stored at @param(elmts). if @param(elmts) is NULL, then the elements are uninitialized. 491 | // the elements from the index position in the stack, will be shifted to the right. 492 | // to make room for the inserted element/s. 493 | // returns a pointer to the first new element, but NULL will be returned on allocation failure. 494 | #define DasStk_insert(stk_ptr, idx, elmt) DasStk_insert_many(stk_ptr, idx, elmt, 1) 495 | #define DasStk_insert_many(stk_ptr, idx, elmts, elmts_count) \ 496 | _DasStk_insert_many((_DasStkHeader**)stk_ptr, sizeof(**(stk_ptr)), idx, elmts, elmts_count, DasStk_elmt_size(stk_ptr)) 497 | extern void* _DasStk_insert_many(_DasStkHeader** header_in_out, uintptr_t header_size, uintptr_t idx, void* elmts, uintptr_t elmts_count, uintptr_t elmt_size); 498 | 499 | // pushes element/s onto the end of the stack that will be initialized with 500 | // the data stored at @param(elmts). if @param(elmts) is NULL, then the elements are uninitialized. 501 | // returns a pointer to the first new element, but NULL will be returned on allocation failure. 502 | #define DasStk_push(stk_ptr, elmt) DasStk_push_many(stk_ptr, elmt, 1) 503 | #define DasStk_push_many(stk_ptr, elmts, elmts_count) \ 504 | _DasStk_push_many((_DasStkHeader**)stk_ptr, sizeof(**(stk_ptr)), elmts, elmts_count, DasStk_elmt_size(stk_ptr)) 505 | extern void* _DasStk_push_many(_DasStkHeader** header_in_out, uintptr_t header_size, void* elmts, uintptr_t elmts_count, uintptr_t elmt_size); 506 | 507 | // removes element/s from the end of the stack 508 | // returns the number of elements popped from the stack. 509 | #define DasStk_pop(stk_ptr) DasStk_pop_many(stk_ptr, 1) 510 | #define DasStk_pop_many(stk_ptr, elmts_count) \ 511 | _DasStk_pop_many((_DasStkHeader*)*(stk_ptr), elmts_count, DasStk_elmt_size(stk_ptr)) 512 | extern uintptr_t _DasStk_pop_many(_DasStkHeader* header, uintptr_t elmts_count, uintptr_t elmt_size); 513 | 514 | // removes element/s from the start_idx up to end_idx (exclusively). 515 | // elements at the end of the stack are moved to replace the removed elements. 516 | #define DasStk_remove_swap(stk_ptr, idx) \ 517 | DasStk_remove_swap_range(stk_ptr, idx, idx + 1) 518 | #define DasStk_remove_swap_range(stk_ptr, start_idx, end_idx) \ 519 | _DasStk_remove_swap_range((_DasStkHeader*)*(stk_ptr), sizeof(**(stk_ptr)), start_idx, end_idx, DasStk_elmt_size(stk_ptr)) 520 | extern void _DasStk_remove_swap_range(_DasStkHeader* header, uintptr_t header_size, uintptr_t start_idx, uintptr_t end_idx, uintptr_t elmt_size); 521 | 522 | // removes element/s from the start_idx up to end_idx (exclusively). 523 | // elements that come after are shifted to the left to replace the removed elements. 524 | #define DasStk_remove_shift(stk_ptr, idx) \ 525 | DasStk_remove_shift_range(stk_ptr, idx, idx + 1) 526 | #define DasStk_remove_shift_range(stk_ptr, start_idx, end_idx) \ 527 | _DasStk_remove_shift_range((_DasStkHeader*)*(stk_ptr), sizeof(**(stk_ptr)), start_idx, end_idx, DasStk_elmt_size(stk_ptr)) 528 | extern void _DasStk_remove_shift_range(_DasStkHeader* header, uintptr_t header_size, uintptr_t start_idx, uintptr_t end_idx, uintptr_t elmt_size); 529 | 530 | // pushes the string on to the end of a byte stack. 531 | // returns the pointer to the start where the string is placed in the stack, but NULL will be returned on allocation failure. 532 | extern char* DasStk_push_str(DasStk(char)* stk, char* str); 533 | 534 | // pushes the formatted string on to the end of a byte stack. 535 | // returns the pointer to the start where the string is placed in the stack, but NULL will be returned on allocation failure. 536 | extern void* DasStk_push_str_fmtv(DasStk(char)* stk, char* fmt, va_list args); 537 | #ifdef __GNUC__ 538 | extern void* DasStk_push_str_fmt(DasStk(char)* stk, char* fmt, ...) __attribute__ ((format (printf, 2, 3))); 539 | #else 540 | extern void* DasStk_push_str_fmt(DasStk(char)* stk, char* fmt, ...); 541 | #endif 542 | 543 | // ====================================================================== 544 | // 545 | // 546 | // DasDeque - double ended queue (ring buffer) 547 | // 548 | // 549 | // ====================================================================== 550 | // 551 | // designed to be used for FIFO or LIFO operations. 552 | // elements are allocated using the Dynamic Allocation API that is defined lower down in this file. 553 | // 554 | // - empty when 'front_idx' == 'back_idx' 555 | // - internally 'cap' is the number of allocated elements, but the deque can only hold 'cap' - 1. 556 | // this is because the back_idx needs to point to the next empty element slot. 557 | // - 'front_idx' will point to the item at the front of the queue 558 | // - 'back_idx' will point to the item after the item at the back of the queue 559 | // 560 | // DasDeque API example usage: 561 | // 562 | // DasDeque(int) queue_of_ints; 563 | // DasDeque_init_with_cap(&queue_of_ints, 64); 564 | // 565 | // DasDeque_push_back(&queue_of_ints, NULL); 566 | // *DasDeque_get_last(&queue_of_ints) = 22; 567 | // 568 | // int popped_value = *DasDeque_get(&queue_of_ints, 0); 569 | // DasDeque_pop_front(&queue_of_ints); 570 | 571 | #define DasDeque(T) DasDequeHeader_##T* 572 | 573 | // 574 | // the internal header used in the dynamic internal functions. 575 | // WARNING: these field must match the DasDequeHeader generated with the typedef_DasDeque macro below 576 | typedef struct { 577 | uintptr_t cap; 578 | uintptr_t front_idx; 579 | uintptr_t back_idx; 580 | DasAlctor alctor; 581 | } _DasDequeHeader; 582 | 583 | // 584 | // you need to make sure that you use this macro in global scope 585 | // to define a structure for the type you want to use. 586 | // 587 | // we have a funky name for the data field, 588 | // so an error will get thrown if you pass the 589 | // wrong structure into one of the macros below. 590 | #define typedef_DasDeque(T) \ 591 | typedef struct { \ 592 | uintptr_t cap; \ 593 | uintptr_t front_idx; \ 594 | uintptr_t back_idx; \ 595 | DasAlctor alctor; \ 596 | T DasDeque_data[]; \ 597 | } DasDequeHeader_##T 598 | 599 | typedef_DasDeque(char); 600 | typedef_DasDeque(short); 601 | typedef_DasDeque(int); 602 | typedef_DasDeque(long); 603 | typedef_DasDeque(float); 604 | typedef_DasDeque(double); 605 | typedef_DasDeque(size_t); 606 | typedef_DasDeque(ptrdiff_t); 607 | typedef_DasDeque(uint8_t); 608 | typedef_DasDeque(uint16_t); 609 | typedef_DasDeque(uint32_t); 610 | typedef_DasDeque(uint64_t); 611 | typedef_DasDeque(int8_t); 612 | typedef_DasDeque(int16_t); 613 | typedef_DasDeque(int32_t); 614 | typedef_DasDeque(int64_t); 615 | 616 | #define DasDeque_data(deque_ptr) ((*deque_ptr) ? (*(deque_ptr))->DasDeque_data : NULL) 617 | 618 | // returns the number of elements that can be stored in the queue before a reallocation is required. 619 | #define DasDeque_cap(deque_ptr) ((*deque_ptr) ? ((*(deque_ptr))->cap == 0 ? 0 : (*(deque_ptr))->cap - 1) : 0) 620 | 621 | // returns the number of elements 622 | #define DasDeque_count(deque_ptr) \ 623 | ((*deque_ptr) \ 624 | ? ((*(deque_ptr))->back_idx >= (*(deque_ptr))->front_idx \ 625 | ? (*(deque_ptr))->back_idx - (*(deque_ptr))->front_idx \ 626 | : (*(deque_ptr))->back_idx + ((*(deque_ptr))->cap - (*(deque_ptr))->front_idx) \ 627 | ) \ 628 | : 0 \ 629 | ) 630 | 631 | #define DasDeque_front_idx(deque_ptr) ((*deque_ptr) ? (*(deque_ptr))->front_idx : 0) 632 | #define DasDeque_back_idx(deque_ptr) ((*deque_ptr) ? (*(deque_ptr))->back_idx : 0) 633 | #define DasDeque_elmt_size(deque_ptr) (sizeof(*(*(deque_ptr))->DasDeque_data)) 634 | #define DasDeque_alctor(deque_ptr) ((*deque_ptr) ? (*(deque_ptr))->alctor : DasAlctor_default) 635 | 636 | #define DasDeque_foreach(stk_ptr, INDEX_NAME) \ 637 | for (uintptr_t INDEX_NAME = 0, _end_ = DasDeque_count(stk_ptr); INDEX_NAME < _end_; INDEX_NAME += 1) 638 | 639 | // 640 | // there is no DasDeque_init, zeroed data is initialization. 641 | // 642 | 643 | // preallocates a deque with enough capacity to store init_cap number of elements. 644 | #define DasDeque_init_with_cap(deque_ptr, init_cap) DasDeque_init_with_alctor(deque_ptr, init_cap, DasAlctor_default) 645 | // the memory is allocator with the supplied allocator and is used for all future reallocations. 646 | #define DasDeque_init_with_alctor(deque_ptr, init_cap, alctor) \ 647 | _DasDeque_init_with_alctor((_DasDequeHeader**)deque_ptr, sizeof(**(deque_ptr)), init_cap, alctor, DasDeque_elmt_size(deque_ptr)) 648 | extern DasBool _DasDeque_init_with_alctor(_DasDequeHeader** header_out, uintptr_t header_size, uintptr_t init_cap, DasAlctor alctor, uintptr_t elmt_size); 649 | 650 | // deallocates the memory and sets the deque to being empty. 651 | #define DasDeque_deinit(deque_ptr) _DasDeque_deinit((_DasDequeHeader**)deque_ptr, sizeof(**(deque_ptr)), DasDeque_elmt_size(deque_ptr)) 652 | extern void _DasDeque_deinit(_DasDequeHeader** header_in_out, uintptr_t header_size, uintptr_t elmt_size); 653 | 654 | // removes all the elements from the deque 655 | #define DasDeque_clear(deque_ptr) ((*deque_ptr) ? (*(deque_ptr))->front_idx = (*(deque_ptr))->back_idx : 0) 656 | 657 | // returns a pointer to the element at idx. 658 | // this function will abort if idx is out of bounds 659 | #define DasDeque_get(deque_ptr, idx) (&DasDeque_data(deque_ptr)[_DasDeque_assert_idx((_DasDequeHeader*)*(deque_ptr), idx, DasDeque_elmt_size(deque_ptr))]) 660 | extern uintptr_t _DasDeque_assert_idx(_DasDequeHeader* header, uintptr_t idx, uintptr_t elmt_size); 661 | 662 | // returns a pointer to the element at idx starting from the back of the deque. 663 | // this function will abort if idx is out of bounds 664 | #define DasDeque_get_back(deque_ptr, idx) DasDeque_get(deque_ptr, DasDeque_count(deque_ptr) - 1 - (idx)) 665 | 666 | // returns a pointer to the first element 667 | // this function will abort if the deque is empty 668 | #define DasDeque_get_first(deque_ptr) DasDeque_get(deque_ptr, 0) 669 | // 670 | // returns a pointer to the last element 671 | // this function will abort if the deque is empty 672 | #define DasDeque_get_last(deque_ptr) DasDeque_get(deque_ptr, DasDeque_count(deque_ptr) - 1) 673 | 674 | // reallocates the capacity of the data to have enough room for new_cap number of elements. 675 | // if new_cap is less than DasDeque_count(deque_ptr) then new_cap is set to DasDeque_count(deque_ptr). 676 | // returns das_false on allocation failure, otherwise das_true 677 | #define DasDeque_resize_cap(deque_ptr, new_cap) \ 678 | _DasDeque_resize_cap((_DasDequeHeader**)deque_ptr, sizeof(**(deque_ptr)), new_cap, DasDeque_elmt_size(deque_ptr)) 679 | DasBool _DasDeque_resize_cap(_DasDequeHeader** header_in_out, uintptr_t header_size, uintptr_t new_cap, uintptr_t elmt_size); 680 | 681 | 682 | // read elements out of the deque starting from a specified index. 683 | #define DasDeque_read(deque_ptr, idx, elmts_out, elmts_count) _DasDeque_read((_DasDequeHeader*)*(deque_ptr), sizeof(**(deque_ptr)), idx, elmts_out, elmts_count, DasDeque_elmt_size(deque_ptr)) 684 | void _DasDeque_read(_DasDequeHeader* header, uintptr_t header_size, uintptr_t idx, void* elmts_out, uintptr_t elmts_count, uintptr_t elmt_size); 685 | 686 | // writes elements to the deque starting from a specified index. 687 | #define DasDeque_write(deque_ptr, idx, elmts, elmts_count) _DasDeque_write((_DasDequeHeader*)*(deque_ptr), sizeof(**(deque_ptr)), idx, elmts, elmts_count, DasDeque_elmt_size(deque_ptr)) 688 | void _DasDeque_write(_DasDequeHeader* header, uintptr_t header_size, uintptr_t idx, void* elmts, uintptr_t elmts_count, uintptr_t elmt_size); 689 | 690 | // pushes element/s at the FRONT of the deque with the data stored at @param(elmts). 691 | // if @param(elmts) is NULL, then the elements are uninitialized. 692 | // returns an index to the first new element in the deque (which is always 0), 693 | // but UINTPTR_MAX will be returned on allocation failure 694 | #define DasDeque_push_front(deque_ptr, elmt) DasDeque_push_front_many(deque_ptr, elmt, 1) 695 | #define DasDeque_push_front_many(deque_ptr, elmts, elmts_count) _DasDeque_push_front_many((_DasDequeHeader**)deque_ptr, sizeof(**(deque_ptr)), elmts, elmts_count, DasDeque_elmt_size(deque_ptr)) 696 | uintptr_t _DasDeque_push_front_many(_DasDequeHeader** header_in_out, uintptr_t header_size, void* elmts, uintptr_t elmts_count, uintptr_t elmt_size); 697 | 698 | // pushes element/s at the BACK of the deque with the data stored at @param(elmts). 699 | // if @param(elmts) is NULL, then the elements are uninitialized. 700 | // returns an index to the first new element in the deque, 701 | // but UINTPTR_MAX will be returned on allocation failure 702 | #define DasDeque_push_back(deque_ptr, elmt) DasDeque_push_back_many(deque_ptr, elmt, 1) 703 | #define DasDeque_push_back_many(deque_ptr, elmts, elmts_count) _DasDeque_push_back_many((_DasDequeHeader**)deque_ptr, sizeof(**(deque_ptr)), elmts, elmts_count, DasDeque_elmt_size(deque_ptr)) 704 | uintptr_t _DasDeque_push_back_many(_DasDequeHeader** header_in_out, uintptr_t header_size, void* elmts, uintptr_t elmts_count, uintptr_t elmt_size); 705 | 706 | // removes element/s from the FRONT of the deque 707 | // returns the number of elements popped from the deque. 708 | #define DasDeque_pop_front(deque_ptr) DasDeque_pop_front_many(deque_ptr, 1) 709 | #define DasDeque_pop_front_many(deque_ptr, elmts_count) _DasDeque_pop_front_many((_DasDequeHeader*)*(deque_ptr), elmts_count, DasDeque_elmt_size(deque_ptr)) 710 | uintptr_t _DasDeque_pop_front_many(_DasDequeHeader* header, uintptr_t elmts_count, uintptr_t elmt_size); 711 | 712 | // removes element/s from the BACK of the deque 713 | // returns the number of elements popped from the deque. 714 | #define DasDeque_pop_back(deque_ptr) DasDeque_pop_back_many(deque_ptr, 1) 715 | #define DasDeque_pop_back_many(deque_ptr, elmts_count) _DasDeque_pop_back_many((_DasDequeHeader*)*(deque_ptr), elmts_count, DasDeque_elmt_size(deque_ptr)) 716 | uintptr_t _DasDeque_pop_back_many(_DasDequeHeader* header, uintptr_t elmts_count, uintptr_t elmt_size); 717 | 718 | // =========================================================================== 719 | // 720 | // 721 | // Stacktrace 722 | // 723 | // 724 | // =========================================================================== 725 | 726 | DasBool das_stacktrace(uint32_t ignore_levels_count, DasStk(char)* string_out); 727 | 728 | // =========================================================================== 729 | // 730 | // 731 | // Platform Error Handling 732 | // 733 | // 734 | // =========================================================================== 735 | 736 | // 737 | // 0 means success 738 | // TODO: this just directly maps to errno on Unix and DWORD GetLastError on Windows. 739 | // the problem is we cant programatically handle errors in a cross platform way. 740 | // in future create our own enumeration that contains all the errors. 741 | // right now the user can just get the string by calling das_get_error_string. 742 | typedef uint32_t DasError; 743 | #define DasError_success 0 744 | 745 | typedef uint8_t DasErrorStrRes; 746 | enum { 747 | DasErrorStrRes_success, 748 | DasErrorStrRes_invalid_error_arg, 749 | DasErrorStrRes_not_enough_space_in_buffer, 750 | }; 751 | 752 | // 753 | // get the string of @param(error) and writes it into the @param(buf_out) pointer upto @param(buf_out_len) 754 | // @param(buf_out) is null terminated on success. 755 | // 756 | // @return: the result detailing the success or error. 757 | // 758 | DasErrorStrRes das_get_error_string(DasError error, char* buf_out, uint32_t buf_out_len); 759 | 760 | // =========================================================================== 761 | // 762 | // 763 | // File Abstraction 764 | // 765 | // 766 | // =========================================================================== 767 | // 768 | // heavily inspired by the Rust standard library File API. 769 | // we needed a file abstraction for the virtual memory 770 | // but it's nice being able to talk directly to the OS 771 | // instead and have an unbuffered API unlike fread, fwrite. 772 | // 773 | // 774 | 775 | typedef uint8_t DasFileFlags; 776 | enum { 777 | // specifies that the open file can be read from. 778 | DasFileFlags_read = 0x1, 779 | // specifies that the open file can be written to. 780 | // starts the cursor at the start of the file if append is not set. 781 | DasFileFlags_write = 0x2, 782 | // specifies that the open file can be written to. 783 | // the cursor at the end of the file. 784 | DasFileFlags_append = 0x4, 785 | // truncate an existing file by removing it's contents are starting from 0 bytes. 786 | // must be opened with write or append. 787 | DasFileFlags_truncate = 0x8, 788 | // creates a new file if it does not exist. 789 | // must be opened with write or append. 790 | DasFileFlags_create_if_not_exist = 0x10, 791 | // only creates a new file and errors if the file exists. 792 | // enabling this will ignore create_if_not_exist and truncate. 793 | DasFileFlags_create_new = 0x20, 794 | }; 795 | 796 | typedef struct DasFileHandle DasFileHandle; 797 | struct DasFileHandle { 798 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 799 | int raw; // native Unix File Descriptor 800 | #elif _WIN32 801 | HANDLE raw; // native Windows File Handle 802 | #else 803 | #error "unimplemented file API for this platform" 804 | #endif 805 | }; 806 | 807 | // 808 | // opens a file to be use by the other file functions or das_virt_mem_map_file. 809 | // 810 | // @param(path): a path to the file you wish to open 811 | // 812 | // @param(flags): flags to choose how you want the file to be opened. 813 | // see DasFileFlags for more information. 814 | // 815 | // @param(file_handle_out): a pointer to the file handle that is set 816 | // when this function returns successfully. 817 | // 818 | // @return: 0 on success, otherwise a error code to indicate the error. 819 | // 820 | DasError das_file_open(char* path, DasFileFlags flags, DasFileHandle* file_handle_out); 821 | 822 | // 823 | // closes a file that was opened with das_file_open. 824 | // 825 | // @param(handle): the file handle created with successful call to das_file_open 826 | // 827 | // @return: 0 on success, otherwise a error code to indicate the error. 828 | // 829 | DasError das_file_close(DasFileHandle handle); 830 | 831 | // 832 | // gets the size of the file in bytes 833 | // 834 | // @param(handle): the file handle created with successful call to das_file_open 835 | // 836 | // @param(size_out): a pointer to a value that is set to size of the file when this function returns successfully. 837 | // 838 | // @return: 0 on success, otherwise a error code to indicate the error. 839 | // 840 | DasError das_file_size(DasFileHandle handle, uint64_t* size_out); 841 | 842 | // 843 | // attempts to read bytes from a file at it's current cursor in one go. 844 | // the cursor is incremented by the number of bytes read. 845 | // 846 | // @param(handle): the file handle created with successful call to das_file_open 847 | // 848 | // @param(data_out): a pointer to where the data is read to. 849 | // 850 | // @param(length): the number of bytes you wish to try and read into @param(data_out) in one go. 851 | // 852 | // @param(bytes_read_out): a pointer to a value that is set to the number of bytes read when this function returns successfully. 853 | // on success: if *bytes_read_out == 0 then the end of the file has been reached and no bytes where read 854 | // 855 | // @return: 0 on success, otherwise a error code to indicate the error. 856 | // 857 | DasError das_file_read(DasFileHandle handle, void* data_out, uintptr_t length, uintptr_t* bytes_read_out); 858 | 859 | // 860 | // attempts to read bytes from a file at it's current cursor until the supplied output buffer is filled up. 861 | // the cursor is incremented by the number of bytes read. 862 | // 863 | // @param(handle): the file handle created with successful call to das_file_open 864 | // 865 | // @param(data_out): a pointer to where the data is read to. 866 | // 867 | // @param(length): the number of bytes you wish to try and read into @param(data_out) 868 | // 869 | // @param(bytes_read_out): a pointer to a value that is set to the number of bytes read when this function returns successfully. 870 | // on success: if *bytes_read_out == 0 then the end of the file has been reached and no bytes where read 871 | // 872 | // @return: 0 on success, otherwise a error code to indicate the error. 873 | // 874 | DasError das_file_read_exact(DasFileHandle handle, void* data_out, uintptr_t length, uintptr_t* bytes_read_out); 875 | 876 | // 877 | // attempts to write bytes to a file at it's current cursor in one go. 878 | // the cursor is incremented by the number of bytes written. 879 | // 880 | // @param(handle): the file handle created with successful call to das_file_open 881 | // 882 | // @param(data): a pointer to where the data is written to. 883 | // 884 | // @param(length): the number of bytes you wish to try and write into @param(data) in one go. 885 | // 886 | // @param(bytes_written_out): a pointer to a value that is set to the number of bytes written when this function returns successfully. 887 | // on success: if *bytes_written_out == 0 then the end of the file has been reached and no bytes where written 888 | // 889 | // @return: 0 on success, otherwise a error code to indicate the error. 890 | // 891 | DasError das_file_write(DasFileHandle handle, void* data, uintptr_t length, uintptr_t* bytes_written_out); 892 | 893 | // 894 | // attempts to write bytes from a file at it's current cursor until the supplied output buffer is filled up. 895 | // the cursor is incremented by the number of bytes written. 896 | // 897 | // @param(handle): the file handle created with successful call to das_file_open 898 | // 899 | // @param(data): a pointer to where the data is written to. 900 | // 901 | // @param(length): the number of bytes you wish to try and write into @param(data) 902 | // 903 | // @param(bytes_written_out): a pointer to a value that is set to the number of bytes written when this function returns successfully. 904 | // on success: if *bytes_written_out == 0 then the end of the file has been reached and no bytes where written 905 | // 906 | // @return: 0 on success, otherwise a error code to indicate the error. 907 | // 908 | DasError das_file_write_exact(DasFileHandle handle, void* data, uintptr_t length, uintptr_t* bytes_written_out); 909 | 910 | typedef uint8_t DasFileSeekFrom; 911 | enum { 912 | // the file cursor offset will be set to @param(offset) in bytes 913 | DasFileSeekFrom_start, 914 | // the file cursor offset will be set to the file's current cursor offset + @param(offset) in bytes 915 | DasFileSeekFrom_current, 916 | // the file cursor offset will be set to the file's size + @param(offset) in bytes 917 | DasFileSeekFrom_end 918 | }; 919 | 920 | // 921 | // attempts to move the file's cursor to a different location in the file to read and write from. 922 | // 923 | // @param(handle): the file handle created with successful call to das_file_open 924 | // 925 | // @param(offset): offset from where the @param(from) is targeting. see DasFileSeekFrom for more information 926 | // 927 | // @param(from): a target to seek from in the file. see DasFileSeekFrom for more information 928 | // 929 | // @param(cursor_offset_out): a pointer to a value that is set to the file's cursor offset when this function returns successfully. 930 | // 931 | // @return: 0 on success, otherwise a error code to indicate the error. 932 | // 933 | DasError das_file_seek(DasFileHandle handle, int64_t offset, DasFileSeekFrom from, uint64_t* cursor_offset_out); 934 | 935 | // 936 | // flush any queued data in the OS for the file to the storage device. 937 | // 938 | // @param(handle): the file handle created with successful call to das_file_open 939 | // 940 | // @return: 0 on success, otherwise a error code to indicate the error. 941 | // 942 | DasError das_file_flush(DasFileHandle handle); 943 | 944 | // =========================================================================== 945 | // 946 | // 947 | // Virtual Memory Abstraction 948 | // 949 | // 950 | // =========================================================================== 951 | 952 | typedef uint8_t DasVirtMemProtection; 953 | enum { 954 | DasVirtMemProtection_no_access, 955 | DasVirtMemProtection_read, 956 | DasVirtMemProtection_read_write, 957 | DasVirtMemProtection_exec_read, 958 | DasVirtMemProtection_exec_read_write, 959 | }; 960 | 961 | // 962 | // @param(page_size_out): 963 | // the page size of the OS. 964 | // used to align the parameters of the virtual memory functions to a page. 965 | // 966 | // @param(reserve_align_out): 967 | // a pointer to recieve the alignment of the virtual memory reserve address. 968 | // this is used as the alignment the requested_addr parameter of das_virt_mem_reserve. 969 | // this is guaranteed to the be the same as page size or a multiple of it. 970 | // On Unix: this is just the page_size 971 | // On Windows: this is what known as the page granularity. 972 | // 973 | // @return: 0 on success, otherwise a error code to indicate the error. 974 | // 975 | DasError das_virt_mem_page_size(uintptr_t* page_size_out, uintptr_t* reserve_align_out); 976 | 977 | // 978 | // reserve a range of the virtual address space but does not commit any physical pages of memory. 979 | // none of this memory cannot be used until das_virt_mem_commit is called. 980 | // 981 | // WARNING: on Windows, you cannot release sub sections of the address space. 982 | // you can only release the full reserved address space that is issued by this function call. 983 | // there are also restriction on protection, see das_virt_mem_protection_set. 984 | // 985 | // @param(requested_addr): the requested start address you wish to reserve. 986 | // be a aligned to the reserve_align that is retrieved from das_virt_mem_page_size function. 987 | // this is not guaranteed and is only used as hint. 988 | // NULL will not be used as a hint, instead the OS will choose an address for you. 989 | // 990 | // @param(size): the size in bytes you wish to reserve from the @param(requested_addr) 991 | // must be a multiple of the reserve_align that is retrieved from das_virt_mem_page_size function. 992 | // 993 | // @param(addr_out) a pointer to a value that is set to the start of the reserved block of memory 994 | // when this function returns successfully. 995 | // 996 | // @return: 0 on success, otherwise a error code to indicate the error. 997 | // 998 | DasError das_virt_mem_reserve(void* requested_addr, uintptr_t size, void** addr_out); 999 | 1000 | // 1001 | // requests the OS to commit physical pages of memory to the the address space. 1002 | // this address space must be a full or subsection of the reserved address space with das_virt_mem_reserve. 1003 | // the memory in the commited address space will be zeroed after calling this function. 1004 | // 1005 | // @param(addr): the start address of the memory you wish to commit. 1006 | // must be a aligned to the page size das_virt_mem_page_size returns. 1007 | // this is not guaranteed and is only used as hint. 1008 | // NULL will not be used as a hint. 1009 | // 1010 | // @param(size): the size in bytes you wish to reserve from the @param(addr) 1011 | // must be a aligned to the page size das_virt_mem_page_size returns. 1012 | // 1013 | // @param(protection): what the memory is allowed to be used for 1014 | // 1015 | // @return: 0 on success, otherwise a error code to indicate the error. 1016 | // 1017 | DasError das_virt_mem_commit(void* addr, uintptr_t size, DasVirtMemProtection protection); 1018 | 1019 | // 1020 | // change the protection of a range of memory. 1021 | // this memory must have been reserved with das_virt_mem_reserve. 1022 | // 1023 | // WARNING: on Windows, you can change protection of any number pages 1024 | // but that they all must come from the same call they where reserved with. 1025 | // 1026 | // @param(addr): the start of the pages you wish to change the protection for. 1027 | // must be a aligned to the page size das_virt_mem_page_size returns. 1028 | // 1029 | // @param(size): the size in bytes of the memory you wish to change protection for. 1030 | // must be a aligned to the page size das_virt_mem_page_size returns. 1031 | // 1032 | // @param(protection): what the memory is allowed to be used for 1033 | // 1034 | // @return: 0 on success, otherwise a error code to indicate the error. 1035 | // 1036 | DasError das_virt_mem_protection_set(void* addr, uintptr_t size, DasVirtMemProtection protection); 1037 | 1038 | // 1039 | // gives the memory back to the OS but will keep the address space reserved 1040 | // 1041 | // @param(addr): the start of the pages you wish to decommit. 1042 | // must be a aligned to the page size das_virt_mem_page_size returns. 1043 | // 1044 | // @param(size): the size in bytes of the memory you wish to release. 1045 | // must be a aligned to the page size das_virt_mem_page_size returns. 1046 | // 1047 | // @return: 0 on success, otherwise a error code to indicate the error. 1048 | // 1049 | DasError das_virt_mem_decommit(void* addr, uintptr_t size); 1050 | 1051 | // 1052 | // gives the reserved pages back to the OS. the address range must have be reserved with das_virt_mem_reserve. 1053 | // all commit pages in the released address space are automatically decommit when you release. 1054 | // 1055 | // on non Windows systems only: 1056 | // you can target sub pages of the original allocation but just make sure the parameters are aligned. 1057 | // 1058 | // WARNING: on Windows, you cannot release sub sections of the address space. 1059 | // you can only release the full reserved address space that is issued by das_virt_mem_reserve. 1060 | // so @param(size) is ignored on Windows. 1061 | // 1062 | // @param(addr): the start of the pages you wish to release. 1063 | // must be a aligned to the page size das_virt_mem_page_size returns. 1064 | // 1065 | // @param(size): the size in bytes of the memory you wish to release. 1066 | // must be a aligned to the page size das_virt_mem_page_size returns. 1067 | // 1068 | // @return: 0 on success, otherwise a error code to indicate the error. 1069 | // 1070 | DasError das_virt_mem_release(void* addr, uintptr_t size); 1071 | 1072 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 1073 | typedef void* DasMapFileHandle; // unused 1074 | #elif _WIN32 1075 | typedef HANDLE DasMapFileHandle; 1076 | #endif 1077 | 1078 | // 1079 | // maps a file to the virtual address space of the process. 1080 | // 1081 | // @param(requested_addr): see the same parameter in das_virt_mem_reserve for more info. 1082 | // 1083 | // @param(file_handle): the handle to the file opened with das_file_open 1084 | // 1085 | // @param(protection): what the memory is allowed to be used for. 1086 | // this must match with the file_handle's access rights 1087 | // see das_file_open and DasFileFlags. 1088 | // 1089 | // @param(offset): offset into the file in bytes this does not have to be aligned in anyway. 1090 | // internally it will round this down to the nearest reserve_align and map that. 1091 | // 1092 | // @param(size): size of the file in bytes starting from the offset that you wish to map into the address space. 1093 | // this does not have to be aligned in anyway but it will round this up to the page size internally and map that. 1094 | // 1095 | // @param(addr_out) a pointer to a value that is set to the address pointing to the offset into the file that was requested 1096 | // when this function returns successfully. 1097 | // 1098 | // @param(map_file_handle_out): a pointer that is set apon success to the handle of the map file. 1099 | // this is only used in Windows and must be passed into das_virt_mem_unmap_file to unmap correctly. 1100 | // 1101 | // @return: 0 on success, otherwise a error code to indicate the error. 1102 | // 1103 | DasError das_virt_mem_map_file(void* requested_addr, DasFileHandle file_handle, DasVirtMemProtection protection, uint64_t offset, uintptr_t size, void** addr_out, DasMapFileHandle* map_file_handle_out); 1104 | 1105 | 1106 | // 1107 | // unmaps a file that was mapped into the virtual address space by das_virt_mem_map_file. 1108 | // 1109 | // @param(addr): the address that was returned when mapping the file. 1110 | // 1111 | // @param(size): the size that was used when mapping the file. 1112 | // 1113 | // @param(map_file_handle): the map file handle that was returned when mapping the file. 1114 | // 1115 | // @return: 0 on success, otherwise a error code to indicate the error. 1116 | // 1117 | DasError das_virt_mem_unmap_file(void* addr, uintptr_t size, DasMapFileHandle map_file_handle); 1118 | 1119 | // =========================================================================== 1120 | // 1121 | // 1122 | // Linear Allocator 1123 | // 1124 | // 1125 | // =========================================================================== 1126 | // 1127 | // this linear allocator directly reserve address space and commits chunks 1128 | // of memory as and when they are needed. so you can reserve 2GBs and no 1129 | // memory is taken up on the system. 1130 | // all allocated memory is zeroed by the OS when the commit new chunks. 1131 | // 1132 | 1133 | typedef struct { 1134 | void* address_space; 1135 | uintptr_t pos; 1136 | uintptr_t commited_size; 1137 | uintptr_t commit_grow_size; 1138 | uintptr_t reserved_size; 1139 | } DasLinearAlctor; 1140 | 1141 | // 1142 | // initializes the linear allocator and reserves the address space 1143 | // needed to store @param(reserved_size) in bytes. 1144 | // 1145 | // @param(alctor): a pointer the linear allocator structure to initialize. 1146 | // 1147 | // @param(reserved_size): the maximum size the linear allocator can expand to in bytes. 1148 | // 1149 | // @param(commit_grow_size): the amount of memory that is commit when the linear allocator needs to grow 1150 | // 1151 | // @return: 0 on success, otherwise a error code to indicate the error. 1152 | // 1153 | DasError DasLinearAlctor_init(DasLinearAlctor* alctor, uintptr_t reserved_size, uintptr_t commit_grow_size); 1154 | 1155 | // 1156 | // deinitializes the linear allocator and release the address space back to the OS 1157 | // 1158 | // @param(alctor): a pointer the linear allocator structure. 1159 | // 1160 | // @return: 0 on success, otherwise a error code to indicate the error. 1161 | // 1162 | DasError DasLinearAlctor_deinit(DasLinearAlctor* alctor); 1163 | 1164 | // 1165 | // this is the allocator alloc function used in the DasAlctor interface. 1166 | // 1167 | // reset: set the next allocation position back to 0 and decommit all existing memory back to the OS 1168 | // 1169 | // alloc: try to bump up the next allocation position if there is enough commited memory and return the pointer to the zeroed memory. 1170 | // if go past the commited memory then try to commit more if it has not reache the maximum reserved size already. 1171 | // if we have exhausted the reserve size, then the allocation fails. 1172 | // 1173 | // realloc: if this was the previous allocation then try to extend the allocation in place. 1174 | // if not then allocate new memory and copy the old allocation there. 1175 | // 1176 | // dealloc: do nothing 1177 | // 1178 | void* DasLinearAlctor_alloc_fn(void* alctor_data, void* ptr, uintptr_t old_size, uintptr_t size, uintptr_t align); 1179 | 1180 | // 1181 | // creates an instance of the DasAlctor interface using a DasLinearAlctor. 1182 | #define DasLinearAlctor_as_das(linear_alctor_ptr) \ 1183 | (DasAlctor){ .fn = DasLinearAlctor_alloc_fn, .data = linear_alctor_ptr }; 1184 | 1185 | // =========================================================================== 1186 | // 1187 | // 1188 | // Pool 1189 | // 1190 | // 1191 | // =========================================================================== 1192 | // an optimized way of allocating lots of the same element type quickly. 1193 | // the allocated elements are linked so can be iterated. 1194 | // elements can be accessed by a validated identifier, index or pointer. 1195 | // 1196 | // it doesn't make sense making this allocator use the DasAlctor interface. 1197 | // it is very limited only being able to allocate elements of the same type. 1198 | // and the validated identifier should be the main way the elements are accessed. 1199 | // 1200 | 1201 | // 1202 | // this type contains an is allocated bit, a counter and an index. 1203 | // the number of bits the index uses is passed in to most pool functions. 1204 | // the allocated bit is at the MSB. 1205 | // this means the counter bits can be workout by removing the index bits and allocated bit from the equation. 1206 | // 1207 | // eg. index_bits = 20 1208 | // 1209 | // index mask: 0x000fffff 1210 | // counter mask: 0x7ff00000 1211 | // is allocated bit: 0x80000000 1212 | // 1213 | typedef uint32_t DasPoolElmtId; 1214 | #define DasPoolElmtId_is_allocated_bit_MASK 0x80000000 1215 | #define DasPoolElmtId_counter_mask(index_bits) (~(((1 << index_bits) - 1) | DasPoolElmtId_is_allocated_bit_MASK)) 1216 | 1217 | #define DasPoolElmtId_idx(Type, id) (((id).raw & ((1 << Type##_index_bits) - 1)) - 1) 1218 | #define DasPoolElmtId_counter(Type, id) (((id).raw & DasPoolElmtId_counter_mask(Type##_index_bits)) >> Type##_index_bits) 1219 | 1220 | // 1221 | // use this to typedef a element identifier for your pool type. 1222 | // there is an index bits variable and null identifier created by concatenating 1223 | // type name. for typedef_DasPoolElmtId(TestId, 20) 1224 | // TestId_index_bits and TestId_null will be defined. 1225 | // 1226 | // @param(Type): the name you wish to call the type 1227 | // 1228 | // @param(index_bits): the number of index bits you wish to use for the identifier. 1229 | // the number of counter bits is worked out from the rest. see DasPoolElmtId documentation. 1230 | // 1231 | #define typedef_DasPoolElmtId(Type, index_bits) \ 1232 | static uint32_t Type##_index_bits = index_bits; \ 1233 | typedef union Type Type; \ 1234 | union Type { DasPoolElmtId Type##_raw; DasPoolElmtId raw; }; \ 1235 | static Type Type##_null = { .raw = 0 } 1236 | 1237 | // 1238 | // the internal record to link to the previous and next item. 1239 | // can be in a free list or an allocated list. 1240 | // 1241 | typedef struct _DasPoolRecord _DasPoolRecord; 1242 | struct _DasPoolRecord { 1243 | uint32_t prev_id; 1244 | // this next_id contains the counter and allocated bit for the element this record structure refers to. 1245 | DasPoolElmtId next_id; 1246 | }; 1247 | 1248 | // 1249 | // the internal pool 1250 | // WARNING: this must match the typedef'd pool below 1251 | // 1252 | typedef struct _DasPool _DasPool; 1253 | struct _DasPool { 1254 | /* 1255 | // the data layout of the 'address_space' field 1256 | 1257 | T elements[reserved_cap] 1258 | _DasPoolRecord records[reserved_cap] 1259 | 1260 | // an _DasPoolRecord.next_id stores the is_allocated_bit, a counter and an index in the same integer. 1261 | // the index points to the next allocated index when it is allocated and 1262 | // will point to the the next free index when it is not allocated. 1263 | */ 1264 | void* address_space; 1265 | uint32_t count; 1266 | uint32_t cap; 1267 | uint32_t commited_cap; 1268 | uint32_t commit_grow_count; 1269 | uint32_t reserved_cap; 1270 | uint32_t page_size; 1271 | uint32_t free_list_head_id; 1272 | uint32_t alloced_list_head_id; 1273 | uint32_t alloced_list_tail_id: 31; 1274 | uint32_t order_free_list_on_dealloc: 1; 1275 | }; 1276 | 1277 | // 1278 | // macro to use the typedef'd pool 1279 | // 1280 | // @param(IdType): the name of the element identifier made with typedef_DasPoolElmtId 1281 | // 1282 | // @param(T): the type you wish to use in the element pool 1283 | // 1284 | #define DasPool(IdType, T) DasPool_##IdType##_##T 1285 | 1286 | // 1287 | // use this to typedef pool for a identifier and element type. 1288 | // refer to the type using the DasPool macro. 1289 | // 1290 | // @param(IdType): the name of the element identifier made with typedef_DasPoolElmtId 1291 | // 1292 | // @param(T): the type you wish to use in the element pool 1293 | // 1294 | #define typedef_DasPool(IdType, T) \ 1295 | typedef struct { \ 1296 | T* IdType##_address_space; \ 1297 | uint32_t count; \ 1298 | uint32_t cap; \ 1299 | uint32_t commited_cap; \ 1300 | uint32_t commit_grow_count; \ 1301 | uint32_t reserved_cap; \ 1302 | uint32_t page_size; \ 1303 | uint32_t free_list_head_id; \ 1304 | uint32_t alloced_list_head_id; \ 1305 | uint32_t alloced_list_tail_id: 31; \ 1306 | uint32_t order_free_list_on_dealloc: 1; \ 1307 | } DasPool_##IdType##_##T 1308 | 1309 | // 1310 | // initializes the pool and reserves the address space needed to store @param(reserved_cap) number of elements. 1311 | // 1312 | // @param(IdType): the name of the element identifier made with typedef_DasPoolElmtId 1313 | // 1314 | // @param(pool): a pointer to the pool structure 1315 | // 1316 | // @param(reserved_cap): the suggested maximum number of elements the pool can expand to in bytes. 1317 | // this will be round up so that the virtual address space is reserved with a size aligned to reserve align. 1318 | // you can get the round up value in DasPool.reserved_cap 1319 | // 1320 | // @param(commit_grow_count): the suggested amount of elements to grow the commited memory of the pool when it needs to grow. 1321 | // this will be round up so that the commit grow size is aligned to the page size. 1322 | // you can get the round up value in DasPool.commit_grow_count. 1323 | // this new number will not be the exact grow count if the element size is not directly divisble by page size. 1324 | // this is due to the remainder not being stored in the integer. 1325 | // 1326 | // @return: 0 on success, otherwise a error code to indicate the error. 1327 | // 1328 | #define DasPool_init(IdType, pool, reserved_cap, commit_grow_count) \ 1329 | _DasPool_init((_DasPool*)pool, reserved_cap, commit_grow_count, sizeof(*(pool)->IdType##_address_space)) 1330 | DasError _DasPool_init(_DasPool* pool, uint32_t reserved_cap, uint32_t commit_grow_count, uintptr_t elmt_size); 1331 | 1332 | // 1333 | // deinitializes the pool by releasing the address space back to the OS and zeroing the pool structure 1334 | // 1335 | // @param(IdType): the name of the element identifier made with typedef_DasPoolElmtId 1336 | // 1337 | // @param(pool): a pointer to the pool structure 1338 | // 1339 | // @return: 0 on success, otherwise a error code to indicate the error. 1340 | // 1341 | #define DasPool_deinit(IdType, pool) \ 1342 | _DasPool_deinit((_DasPool*)pool, sizeof(*(pool)->IdType##_address_space)) 1343 | DasError _DasPool_deinit(_DasPool* pool, uintptr_t elmt_size); 1344 | 1345 | // 1346 | // reset the pool as if it has just been initialized. all commited memory is decommited 1347 | // but the address_space is still reserved. 1348 | // 1349 | // @param(IdType): the name of the element identifier made with typedef_DasPoolElmtId 1350 | // 1351 | // @param(pool): a pointer to the pool structure 1352 | // 1353 | // @return: 0 on success, otherwise a error code to indicate the error. 1354 | // 1355 | #define DasPool_reset(IdType, pool) \ 1356 | _DasPool_reset((_DasPool*)pool, sizeof(*(pool)->IdType##_address_space)) 1357 | DasError _DasPool_reset(_DasPool* pool, uintptr_t elmt_size); 1358 | 1359 | // 1360 | // does a DasPool_reset and then initializes the pool with an array of elements. 1361 | // 1362 | // @param(IdType): the name of the element identifier made with typedef_DasPoolElmtId 1363 | // 1364 | // @param(pool): a pointer to the pool structure 1365 | // 1366 | // @param(elmts): a pointer to the array of elements 1367 | // 1368 | // @param(count): the number of elements in the array 1369 | // 1370 | // @return: 0 on success, otherwise a error code to indicate the error. 1371 | // 1372 | #define DasPool_reset_and_populate(IdType, pool, elmts, count) \ 1373 | _DasPool_reset_and_populate((_DasPool*)pool, elmts, count, sizeof(*(pool)->IdType##_address_space)) 1374 | DasError _DasPool_reset_and_populate(_DasPool* pool, void* elmts, uint32_t count, uintptr_t elmt_size); 1375 | 1376 | // 1377 | // allocates a zeroed element from the pool. the new element will be pushed on to 1378 | // the tail of the allocated linked list. 1379 | // the pool will try to allocate by using the head of the free list, 1380 | // otherwise it will extend the capacity. 1381 | // 1382 | // @param(IdType): the name of the element identifier made with typedef_DasPoolElmtId 1383 | // 1384 | // @param(pool): a pointer to the pool structure 1385 | // 1386 | // @param(id_out): a pointer that is set apon success to the identifier of this allocation 1387 | // 1388 | // @return: a pointer to the new zeroed element but a value of NULL if allocation failed. 1389 | // 1390 | #define DasPool_alloc(IdType, pool, id_out) \ 1391 | _DasPool_alloc((_DasPool*)pool, &(id_out)->IdType##_raw, sizeof(*(pool)->IdType##_address_space), IdType##_index_bits) 1392 | void* _DasPool_alloc(_DasPool* pool, DasPoolElmtId* id_out, uintptr_t elmt_size, uint32_t index_bits); 1393 | 1394 | // 1395 | // deallocates an element from the pool. the element will be removed from the allocated linked list 1396 | // and will be pushed on to the head of the free list. 1397 | // internally the element's counter will be incremented so the next allocation that 1398 | // uses the same memory location will have a different counter. 1399 | // 1400 | // @param(IdType): the name of the element identifier made with typedef_DasPoolElmtId 1401 | // 1402 | // @param(pool): a pointer to the pool structure 1403 | // 1404 | // @param(elmt_id): the element identifier you wish to deallocate 1405 | // 1406 | #define DasPool_dealloc(IdType, pool, elmt_id) \ 1407 | _DasPool_dealloc((_DasPool*)pool, elmt_id.IdType##_raw, sizeof(*(pool)->IdType##_address_space), IdType##_index_bits) 1408 | void _DasPool_dealloc(_DasPool* pool, DasPoolElmtId elmt_id, uintptr_t elmt_size, uint32_t index_bits); 1409 | 1410 | // 1411 | // gets the pointer for the element with the provided element identifier. 1412 | // aborts if the identifier is invalid (already freed or out of bounds). 1413 | // 1414 | // @param(IdType): the name of the element identifier made with typedef_DasPoolElmtId 1415 | // 1416 | // @param(pool): a pointer to the pool structure 1417 | // 1418 | // @param(elmt_id): the element identifier you wish to get a pointer to 1419 | // 1420 | // @return: a pointer to the element 1421 | // 1422 | #define DasPool_id_to_ptr(IdType, pool, elmt_id) \ 1423 | (_DasPool_id_to_ptr((_DasPool*)pool, elmt_id.IdType##_raw, sizeof(*(pool)->IdType##_address_space), IdType##_index_bits)) 1424 | void* _DasPool_id_to_ptr(_DasPool* pool, DasPoolElmtId elmt_id, uintptr_t elmt_size, uint32_t index_bits); 1425 | 1426 | // 1427 | // gets the index for the element with the provided element identifier. 1428 | // aborts if the identifier is invalid (already freed or out of bounds). 1429 | // 1430 | // @param(IdType): the name of the element identifier made with typedef_DasPoolElmtId 1431 | // 1432 | // @param(pool): a pointer to the pool structure 1433 | // 1434 | // @param(elmt_id): the element identifier you wish to get a pointer to 1435 | // 1436 | // @return: the index of the element 1437 | // 1438 | #define DasPool_id_to_idx(IdType, pool, elmt_id) \ 1439 | _DasPool_id_to_idx((_DasPool*)pool, elmt_id.IdType##_raw, sizeof(*(pool)->IdType##_address_space), IdType##_index_bits) 1440 | uint32_t _DasPool_id_to_idx(_DasPool* pool, DasPoolElmtId elmt_id, uintptr_t elmt_size, uint32_t index_bits); 1441 | 1442 | // 1443 | // gets the identifier for the element with the provided element pointer. 1444 | // aborts if the pointer is out of bounds or the element at the pointer is not allocated. 1445 | // 1446 | // @param(IdType): the name of the element identifier made with typedef_DasPoolElmtId 1447 | // 1448 | // @param(pool): a pointer to the pool structure 1449 | // 1450 | // @param(ptr): the pointer to the element you wish to get an identifier for 1451 | // 1452 | // @return: the identifier to the element 1453 | // 1454 | #define DasPool_ptr_to_id(IdType, pool, ptr) \ 1455 | ((IdType) { .IdType##_raw = _DasPool_ptr_to_id((_DasPool*)pool, ptr, sizeof(*(pool)->IdType##_address_space), IdType##_index_bits) }) 1456 | DasPoolElmtId _DasPool_ptr_to_id(_DasPool* pool, void* ptr, uintptr_t elmt_size, uint32_t index_bits); 1457 | 1458 | // 1459 | // gets the index for the element with the provided element pointer. 1460 | // aborts if the pointer is out of bounds or the element at the pointer is not allocated. 1461 | // 1462 | // @param(IdType): the name of the element identifier made with typedef_DasPoolElmtId 1463 | // 1464 | // @param(pool): a pointer to the pool structure 1465 | // 1466 | // @param(ptr): the pointer to the element you wish to get an index for 1467 | // 1468 | // @return: the index of the element 1469 | // 1470 | #define DasPool_ptr_to_idx(IdType, pool, ptr) \ 1471 | _DasPool_ptr_to_idx((_DasPool*)pool, ptr, sizeof(*(pool)->IdType##_address_space), IdType##_index_bits) 1472 | uint32_t _DasPool_ptr_to_idx(_DasPool* pool, void* ptr, uintptr_t elmt_size, uint32_t index_bits); 1473 | 1474 | // 1475 | // gets the pointer for the element with the provided element index. 1476 | // aborts if the index is out of bounds or the element at the index is not allocated. 1477 | // 1478 | // @param(IdType): the name of the element identifier made with typedef_DasPoolElmtId 1479 | // 1480 | // @param(pool): a pointer to the pool structure 1481 | // 1482 | // @param(idx): the index of the element you wish to get a pointer to 1483 | // 1484 | // @return: a pointer to the element 1485 | // 1486 | #define DasPool_idx_to_ptr(IdType, pool, idx) \ 1487 | (_DasPool_idx_to_ptr((_DasPool*)pool, idx, sizeof(*(pool)->IdType##_address_space))) 1488 | void* _DasPool_idx_to_ptr(_DasPool* pool, uint32_t idx, uintptr_t elmt_size); 1489 | 1490 | // 1491 | // gets the identifier for the element with the provided element index. 1492 | // aborts if the index is out of bounds or the element at the index is not allocated. 1493 | // 1494 | // @param(IdType): the name of the element identifier made with typedef_DasPoolElmtId 1495 | // 1496 | // @param(pool): a pointer to the pool structure 1497 | // 1498 | // @param(idx): the index of the element you wish to get a identifier for 1499 | // 1500 | // @return: the identifier of the element 1501 | // 1502 | #define DasPool_idx_to_id(IdType, pool, idx) \ 1503 | ((IdType) { .IdType##_raw = _DasPool_idx_to_id((_DasPool*)pool, idx, sizeof(*(pool)->IdType##_address_space), IdType##_index_bits) }) 1504 | DasPoolElmtId _DasPool_idx_to_id(_DasPool* pool, uint32_t idx, uintptr_t elmt_size, uint32_t index_bits); 1505 | 1506 | // 1507 | // goes to the next allocated element in the linked list from the provided element identifier. 1508 | // if @param(elmt_id) is the null identifier then the start of the allocated linked list is returned. 1509 | // see typedef_DasPoolElmtId for how to get the null identifier. 1510 | // 1511 | // @param(IdType): the name of the element identifier made with typedef_DasPoolElmtId 1512 | // 1513 | // @param(pool): a pointer to the pool structure 1514 | // 1515 | // @param(elmt_id): the element identifier to move from 1516 | // 1517 | // @return: the identifier of the next element, the null identifier will be returned if the element 1518 | // at the end of the allocated linked list was the @param(elmt_id) 1519 | // 1520 | #define DasPool_iter_next(IdType, pool, elmt_id) \ 1521 | ((IdType) { .IdType##_raw = _DasPool_iter_next((_DasPool*)pool, elmt_id.IdType##_raw, sizeof(*(pool)->IdType##_address_space), IdType##_index_bits) }) 1522 | DasPoolElmtId _DasPool_iter_next(_DasPool* pool, DasPoolElmtId elmt_id, uintptr_t elmt_size, uint32_t index_bits); 1523 | 1524 | // 1525 | // goes to the previous allocated element in the linked list from the provided element identifier. 1526 | // if @param(elmt_id) is the null identifier then the end of the allocated linked list is returned. 1527 | // see typedef_DasPoolElmtId for how to get the null identifier. 1528 | // 1529 | // @param(IdType): the name of the element identifier made with typedef_DasPoolElmtId 1530 | // 1531 | // @param(pool): a pointer to the pool structure 1532 | // 1533 | // @param(elmt_id): the element identifier to move from 1534 | // 1535 | // @return: the identifier of the previous element, the null identifier will be returned if the element 1536 | // at the start of the allocated linked list was the @param(elmt_id) 1537 | // 1538 | #define DasPool_iter_prev(IdType, pool, elmt_id) \ 1539 | ((IdType) { .IdType##_raw = _DasPool_iter_prev((_DasPool*)pool, elmt_id.IdType##_raw, sizeof(*(pool)->IdType##_address_space), IdType##_index_bits) }) 1540 | DasPoolElmtId _DasPool_iter_prev(_DasPool* pool, DasPoolElmtId elmt_id, uintptr_t elmt_size, uint32_t index_bits); 1541 | 1542 | // 1543 | // decrements the internal counter of the element by 1. this will invalidate 1544 | // the current identifier and restore the previous identifier at the same index 1545 | // as this one. 1546 | // 1547 | // @param(IdType): the name of the element identifier made with typedef_DasPoolElmtId 1548 | // 1549 | // @param(pool): a pointer to the pool structure 1550 | // 1551 | // @param(elmt_id): the element identifier you wish to decrement 1552 | // 1553 | // @return: the new identifier as a result of decrementing the counter 1554 | // 1555 | #define DasPool_decrement_record_counter(IdType, pool, elmt_id) \ 1556 | ((IdType) { .IdType##_raw = _DasPool_decrement_record_counter((_DasPool*)pool, elmt_id.IdType##_raw, sizeof(*(pool)->IdType##_address_space), IdType##_index_bits) }) 1557 | DasPoolElmtId _DasPool_decrement_record_counter(_DasPool* pool, DasPoolElmtId elmt_id, uintptr_t elmt_size, uint32_t index_bits); 1558 | 1559 | // 1560 | // see if an element at the provided index is allocated or not. 1561 | // 1562 | // @param(IdType): the name of the element identifier made with typedef_DasPoolElmtId 1563 | // 1564 | // @param(pool): a pointer to the pool structure 1565 | // 1566 | // @param(idx): the index of the element you wish to check 1567 | // 1568 | // @return: das_true if the element is allocated, otherwise das_false is returned 1569 | // 1570 | #define DasPool_is_idx_allocated(IdType, pool, idx) \ 1571 | _DasPool_is_idx_allocated((_DasPool*)pool, idx, sizeof(*(pool)->IdType##_address_space)) 1572 | DasBool _DasPool_is_idx_allocated(_DasPool* pool, uint32_t idx, uintptr_t elmt_size); 1573 | 1574 | // 1575 | // see if the supplied element identifier is valid and can be used to get an element. 1576 | // 1577 | // @param(IdType): the name of the element identifier made with typedef_DasPoolElmtId 1578 | // 1579 | // @param(pool): a pointer to the pool structure 1580 | // 1581 | // @param(elmt_id): the element identifier you wish to check 1582 | // 1583 | // @return: das_true if the element identifier is valid, otherwise das_false is returned 1584 | // 1585 | #define DasPool_is_id_valid(IdType, pool, elmt_id) \ 1586 | _DasPool_is_id_valid((_DasPool*)pool, elmt_id.IdType##_raw, sizeof(*(pool)->IdType##_address_space), IdType##_index_bits) 1587 | DasBool _DasPool_is_id_valid(_DasPool* pool, DasPoolElmtId elmt_id, uintptr_t elmt_size, uint32_t index_bits); 1588 | 1589 | #endif 1590 | 1591 | -------------------------------------------------------------------------------- /das_examples.c: -------------------------------------------------------------------------------- 1 | #include "das.h" 2 | #include "das.c" 3 | 4 | void stk_example() { 5 | // a zeroed stack is the default, 6 | // you can actually start pushing elements directly on this. 7 | DasStk(int) stk = NULL; 8 | 9 | 10 | // you can also preallocate the stack to hold atleast 6 int elements. 11 | DasStk_init_with_cap(&stk, 6); 12 | 13 | // or maybe use a custom allocator (advanced) 14 | DasStk_init_with_alctor(&stk, 6, DasAlctor_system); 15 | 16 | 17 | 18 | 19 | // pushes 0 on the end of the stack. 20 | // our stack then looks like this: [ 0 ] 21 | int elmt = 0; 22 | DasStk_push(&stk, &elmt); 23 | 24 | 25 | 26 | // passing a NULL pointer makes room at the end of the stack 27 | // this function also returns a pointer to the new element. 28 | // so we can then write to the element through the pointer instead. 29 | // this is useful for pushing on big data structures, 30 | // so you don't have to construct it on the call stack first. 31 | // our stack then looks like this: [ 0, 1 ] 32 | int* uninit_elmt = DasStk_push(&stk, NULL); 33 | *uninit_elmt = 1; 34 | 35 | 36 | 37 | // pushes 2, 3, 4 on the end of the stack. 38 | // our stack then looks like this: [ 0, 1, 2, 3, 4 ] 39 | for (int i = 2; i < 5; i += 1) { 40 | DasStk_push(&stk, &i); 41 | } 42 | 43 | 44 | 45 | // get the number of elements in the stack with the DasStk_count macro 46 | // this will print: DasStk_count(&stk) = 5 47 | printf("DasStk_count(&stk) = %zu\n", DasStk_count(&stk)); 48 | 49 | 50 | 51 | // get the number of elements the stack can hold before reallocating with the DasStk_cap macro. 52 | // this will print: DasStk_cap(&stk) = 6 or DasStk_min_cap 53 | printf("DasStk_cap(&stk) = %zu\n", DasStk_cap(&stk)); 54 | 55 | 56 | 57 | // the DasStk_data macro returns a pointer to the first element in the stack. 58 | // and elements are stored sequentially. 59 | // so DasStk_data(&stk)[0] is our value 0 60 | // and DasStk_data(&stk)[1] is our value 1 61 | // this is an UNSAFE way of accessing data, since it does not check the size of stack. 62 | // this way you can access uninitialized memory or go out of the stack's memory boundary. 63 | printf("[0] = %d and [1] = %d\n", DasStk_data(&stk)[0], DasStk_data(&stk)[1]); 64 | 65 | 66 | 67 | // the SAFE way to get a pointer to the stack is to use the DasStk_get macro. 68 | // this will abort the program if you provide an index that is goes out of bounds. 69 | // since DasStk_count(&stk) == 5, our highest index is 4 and any more will cause an abort. 70 | // be aware that keeping the pointer and calling more DasStk functions to cause 71 | // a reallocation is UNSAFE. the DasStk_data(&stk) pointer is likely change when memory is 72 | // reallocated. 73 | // FYI pushing, inserting and resizing the stack can cause a reallocation. 74 | printf("[2] = %d and [3] = %d\n", *DasStk_get(&stk, 2), *DasStk_get(&stk, 3)); 75 | 76 | 77 | 78 | // pushes this array of 5 elements on the end of the stack. 79 | // our stack then looks like this: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] 80 | // NULL can also be passed to push_many if you don't wish to copy anything right away. 81 | // a pointer is returned to the first new element. 82 | int five_to_nine[] = { 5, 6, 7, 8, 9 }; 83 | int* new_elmts = DasStk_push_many(&stk, five_to_nine, 5); 84 | 85 | 86 | 87 | // pops the element off the end of the stack and we do nothing with the value. 88 | // our stack then looks like this: [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ] 89 | DasStk_pop(&stk); 90 | 91 | 92 | 93 | // pops the element off the end of the stack. 94 | // but before we do, we put the element that will be removed, into a variable. 95 | // our stack then looks like this: [ 0, 1, 2, 3, 4, 5, 6, 7 ] 96 | // popped_value will be set to 8 97 | int popped_value = *DasStk_get_last(&stk); 98 | DasStk_pop(&stk); 99 | 100 | 101 | 102 | // pops the elements off the end of the stack into an array. 103 | // but before we do, we copy the elements that will be removed, into an array 104 | // our stack then looks like this: [ 0, 1, 2, 3, 4 ] 105 | // int_buf look like this: [ 5, 6, 7 ] 106 | int int_buf[3] = {0}; 107 | das_copy_elmts(int_buf, DasStk_get_back(&stk, 2), 3); 108 | DasStk_pop_many(&stk, 3); 109 | 110 | 111 | 112 | // removes an element from the stack by copying it's right neighbouring elements to the left. 113 | // so remove the index of 2. 114 | // our stack then looks like this: [ 0, 1, 3, 4 ] 115 | DasStk_remove_shift(&stk, 2); 116 | 117 | 118 | 119 | // removes an element from the stack by copying it's right neighbouring elements to the left. 120 | // so remove the index of 2. 121 | // our stack then looks like this: [ 0, 1, 4 ] 122 | // before we remove, we store the element in the removed_value variable. 123 | int removed_value = *DasStk_get(&stk, 2); 124 | DasStk_remove_shift(&stk, 2); 125 | 126 | 127 | 128 | // removes elements from the stack by copying it's right neighbouring elements to the left. 129 | // so remove 2 elements from the index of 0. 130 | // our stack then looks like this: [ 4 ] 131 | // pair will be set to [ 0, 1 ] 132 | int pair[2] = {0}; 133 | das_copy_elmts(pair, DasStk_get(&stk, 0), 2); 134 | DasStk_remove_shift_range(&stk, 0, 2); 135 | 136 | 137 | 138 | // clear the stack by setting the count to 0 139 | DasStk_clear(&stk); 140 | 141 | 142 | 143 | // lets get our stack to look like this again: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] 144 | int zero_to_nine[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 145 | DasStk_push_many(&stk, zero_to_nine, 10); 146 | 147 | 148 | 149 | // removes an element from the stack by replacing it with the element at the end. 150 | // so remove the index of 4. 151 | // our stack then looks like this: [ 0, 1, 2, 3, 9, 5, 6, 7, 8 ] 152 | // this does not maintain the same order but is faster than a shift. 153 | // as we are only copying elements from the end to replace the ones we remove. 154 | // as opposed to shifting all of the right neighbouring elements. 155 | DasStk_remove_swap(&stk, 4); 156 | 157 | 158 | 159 | // removes an element from the stack by replacing it with the element at the end. 160 | // copyings the removed element to a variable. 161 | // so remove the index of 4. 162 | // our stack then looks like this: [ 0, 1, 2, 3, 8, 5, 6, 7 ] 163 | // removed_value will be set to 9 164 | removed_value = *DasStk_get(&stk, 4); 165 | DasStk_remove_swap(&stk, 4); 166 | 167 | 168 | 169 | // removes an element from the stack by replacing it with the element at the end. 170 | // copyings the removed element to a variable. 171 | // so elements from the index of 3 to 5 exclusively. 172 | // our stack then looks like this: [ 0, 1, 2, 6, 7, 5 ] 173 | // pair will be set to [ 3, 8 ] 174 | das_copy_elmts(pair, DasStk_get(&stk, 3), 2); 175 | DasStk_remove_swap_range(&stk, 3, 5); 176 | 177 | 178 | 179 | // insert an element at an index by shifting the elements from that point to the right. 180 | // insert with an index of 3 181 | // our stack then looks like this: [ 0, 1, 2, 77, 6, 7, 5 ] 182 | int insert_value = 77; 183 | DasStk_insert(&stk, 3, &insert_value); 184 | 185 | 186 | 187 | // make room for an element at an index by shifting the elements from that point to the right. 188 | // get a pointer back so you can write to the new location. 189 | // this is useful for inserting big data structures, 190 | // so you don't have to construct it on the call stack first. 191 | // our stack then looks like this: [ 0, 1, 67, 2, 77, 6, 7, 5 ] 192 | int* elmt_insert_ptr = DasStk_insert(&stk, 2, NULL); 193 | *elmt_insert_ptr = 67; 194 | 195 | // 196 | // we also have DasStk_insert_many that works like DasStk_insert 197 | // but for adding multiple elements like DasStk_push_many. 198 | // 199 | 200 | 201 | } 202 | 203 | typedef struct { 204 | int a, b; 205 | } CustomType; 206 | 207 | // 208 | // to use a custom type with DasStk you need to create a stack type for that type. 209 | // use this macro to do that. 210 | typedef_DasStk(CustomType); 211 | 212 | // now we can have a CustomType stack 213 | DasStk(CustomType) custom_type_stk; 214 | 215 | // 216 | // you have to make sure the type names are a single string with no spaces or symbols. 217 | // an error will be thrown if you try to use a pointer in typedef_DasStk directly. 218 | typedef CustomType* CustomTypePtr; 219 | typedef_DasStk(CustomTypePtr); 220 | 221 | // now we can have a CustomType* stack 222 | DasStk(CustomTypePtr) custom_type_ptr_stk; 223 | 224 | 225 | // an error will be thrown if you use any type qualifier with typedef_DasStk directly. 226 | typedef unsigned int unsigned_int; 227 | typedef_DasStk(unsigned_int); 228 | 229 | // now we can have a unsigned int stack 230 | DasStk(CustomTypePtr) unsigned_int_stk; 231 | 232 | void deque_example() { 233 | // 234 | // a double ended queue implemented as a ring buffer. 235 | // it allows you to push and pop from both end and not suffer the performance issues a stack will. 236 | // elements are store sequentially like a stack but can be in two halfs. 237 | // internally the structure will keep pointers to the front and back of the deque. 238 | // 239 | // so a deque can have the following elements: 240 | // [ 0, 1, 2, 3, 4, 5, 6, 7 ] 241 | // 242 | // the value 0 is the front of the deque and is accessed with index 0 243 | // the value 7 is the back of the deque and is accessed with index 7 244 | // 245 | // in memory the deque can be stored like so: 246 | // B F 247 | // [ 5, 6, 7, . . . . 0, 1, 2, 3, 4, ] 248 | // 249 | // F is the front pointer that always points to the first element. 250 | // B is the back pointer that always points to the element after the element at the back. 251 | // 252 | // a zeroed deque is the default, 253 | // you can actually start pushing elements directly on this. 254 | DasDeque(int) deque = NULL; 255 | 256 | 257 | 258 | // initializes the deque to hold 6 int elements before needing to reallocate. 259 | DasDeque_init_with_cap(&deque, 6); 260 | 261 | // or maybe use a custom allocator (advanced) 262 | DasDeque_init_with_alctor(&deque, 6, DasAlctor_system); 263 | 264 | 265 | 266 | // push elements 0 to 10 on the back of the deque one by one. 267 | // deque data will look like: [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] 268 | for (int i = 0; i < 10; i += 1) { 269 | DasDeque_push_back(&deque, &i); 270 | } 271 | 272 | 273 | 274 | // we can pop multiple elements off the front. 275 | // deque data will look like: [ 3, 4, 5, 6, 7, 8, 9 ] 276 | // next_batch will look like: [ 0, 1, 2 ] 277 | int next_batch[3] = {0}; 278 | DasDeque_read(&deque, 0, next_batch, 3); 279 | DasDeque_pop_front_many(&deque, 3); 280 | 281 | 282 | 283 | // we can also pop from the back as well. 284 | // deque data will look like: [ 3, 4, 5, 6, 7, 8 ] 285 | // popped_elmt will be 9 286 | int popped_elmt = *DasDeque_get_last(&deque); 287 | DasDeque_pop_back(&deque); 288 | 289 | 290 | 291 | // we can then push a value on the front. 292 | // deque data will look like: [ 9, 3, 4, 5, 6, 7, 8 ] 293 | // popped_elmt will be 9 294 | DasDeque_push_front(&deque, &popped_elmt); 295 | 296 | 297 | // get 3rd element in the queue. 298 | // you cannot trust that the previous or next element will neighbour this one in memory. 299 | // so call this function for every index you wish to get. 300 | // or use the DasDeque_read/write functions. 301 | int* third_elmt = DasDeque_get(&deque, 2); 302 | 303 | 304 | 305 | // because this is not a stack, we have to get the 306 | // number of elements a different way. 307 | printf("deque.count = %zu\n", DasDeque_count(&deque)); 308 | 309 | 310 | 311 | // the capacity also works the same way. 312 | printf("deque.cap = %zu\n", DasDeque_cap(&deque)); 313 | } 314 | 315 | 316 | // 317 | // to use a custom type with DasDeque you need to create a deque type for that type. 318 | // use this macro to do that. 319 | typedef_DasDeque(CustomType); 320 | 321 | // now we can have a CustomType deque 322 | DasDeque(CustomType) custom_type_deque; 323 | 324 | // 325 | // you have to make sure the type names are a single string with no spaces or symbols. 326 | // an error will be thrown if you try to use a pointer in typedef_DasDeque directly. 327 | typedef CustomType* CustomTypePtr; 328 | typedef_DasDeque(CustomTypePtr); 329 | 330 | // now we can have a CustomType* deque 331 | DasDeque(CustomTypePtr) custom_type_ptr_deque; 332 | 333 | 334 | // an error will be thrown if you use any type qualifier with typedef_DasDeque directly. 335 | typedef unsigned int unsigned_int; 336 | typedef_DasDeque(unsigned_int); 337 | 338 | // now we can have a unsigned int deque 339 | DasDeque(CustomTypePtr) unsigned_int_deque; 340 | 341 | 342 | typedef struct { 343 | void* data; 344 | int whole_number; 345 | float number; 346 | double x; 347 | double y; 348 | } SomeStruct; 349 | 350 | void alloc_example() { 351 | // 352 | // internally the DasStk and DasDeque use the allocation API to 353 | // re/de/allocate their buffers that hold elements. 354 | // 355 | 356 | // dynamically allocate an element of a type using the default allocator, like so: 357 | SomeStruct* some_struct = das_alloc_elmt(SomeStruct, DasAlctor_default); 358 | 359 | 360 | 361 | // data will be uninitialized unless the allocator explicitly zeros it for you. 362 | // by default it does not, so we will initialize the memory here by zeroing it. 363 | das_zero_elmt(some_struct); 364 | 365 | 366 | 367 | // ... later on, deallocate the memory like so 368 | das_dealloc_elmt(SomeStruct, DasAlctor_default, some_struct); 369 | 370 | 371 | 372 | // dynamically allocate an array of 12 elements, like so: 373 | SomeStruct* some_array = das_alloc_array(SomeStruct, DasAlctor_default, 12); 374 | memset(some_array, 0, sizeof(SomeStruct) * 12); 375 | 376 | 377 | 378 | // data is stored sequentially in an array. 379 | // setting all the bytes in the fifth element 0xac. 380 | SomeStruct* ptr_to_fifth_elmt = &some_array[4]; 381 | memset(ptr_to_fifth_elmt, 0xac, sizeof(SomeStruct)); 382 | 383 | 384 | 385 | // dynamically reallocate the array from 12 to 20 elements, like so: 386 | some_array = das_realloc_array(SomeStruct, DasAlctor_default, some_array, 12, 20); 387 | 388 | 389 | // get the new pointer to the fifth element become the pointer probably changed. 390 | ptr_to_fifth_elmt = &some_array[4]; 391 | 392 | int whole_number = 0xacacacac; 393 | das_assert( 394 | memcmp(&ptr_to_fifth_elmt->whole_number, &whole_number, sizeof(int)) == 0, 395 | "will not fail since: reallocating preserves the original memory"); 396 | 397 | 398 | 399 | // deallocate an array of 20 elements, like so: 400 | das_dealloc_array(SomeStruct, DasAlctor_default, some_array, 20); 401 | 402 | 403 | 404 | // dynamically allocate 200 integers that are aligned to 1024. 405 | // void* das_alloc(uintptr_t size_in_bytes, uintptr_t align_in_bytes) 406 | int* ints_big_align = (int*)das_alloc(DasAlctor_default, 200 * sizeof(int), 1024); 407 | das_assert( 408 | (uintptr_t)ints_big_align % 1024 == 0, 409 | "will not fail since: alignment means that the pointer is a multiple of 1024"); 410 | 411 | 412 | 413 | // reallocation works like the array one but with bytes instead 414 | // void* das_realloc(void* ptr, uintptr_t old_size_in_bytes, uintptr_t size_in_bytes, uintptr_t align_in_bytes) 415 | ints_big_align = das_realloc(DasAlctor_default, ints_big_align, 200 * sizeof(int), 400 * sizeof(int), 1024); 416 | 417 | 418 | 419 | // dynamically deallocate 400 integers that are aligned to 1024. 420 | das_dealloc(DasAlctor_default, ints_big_align, 400 * sizeof(int), 1024); 421 | } 422 | 423 | 424 | 425 | 426 | // 427 | // as you may have noticed, we dont have any error checking for out of memory. 428 | // this is because a callback is called when an allocator fails to allocate memory. 429 | // this is to keep user code simple by not having to explicitly handle errors all the time. 430 | // also means you dont have to try and return this up a call stack and architect most of your functions 431 | // to handle when out of memory happens. 432 | // 433 | // here is a example out of memory handler. ideally the application should only be writing these. 434 | // but maybe there are use cases for libraries/frameworks to do this. 435 | // 436 | // 'data' is a custom userdata pointer that can be used for anything we like. 437 | // 'alctor' is a pointer to the current allocator, so you can manipulate this if you need. 438 | // 'size' is the required amount for the allocation to succeed. 439 | // return das_true if you think you have solved the problem. 440 | // it will try to allocate again and then will aborts on another failure. 441 | // return das_false and the program will abort. 442 | DasBool basic_out_of_mem_handler(void* data, DasAlctor* alctor, uintptr_t size) { 443 | // 444 | // here we gracefully close the application by saving all the state we need. 445 | // and then return das_false. 446 | // or 447 | // we can try to resolve the problems by manipulating the allocator. 448 | // and then return das_true. 449 | // 450 | 451 | return das_true; 452 | } 453 | 454 | 455 | 456 | // 457 | // here is a implementation of a linear allocator. 458 | // structurely it looks like a stack but will only grow until it is manually reset. 459 | // this is pretty much, the most simple memory allocator. 460 | // 461 | typedef struct { 462 | void* data; 463 | uint32_t pos; 464 | uint32_t size; 465 | } LinearAlctor; 466 | 467 | void* LinearAlctor_alloc_fn(void* alctor_data, void* ptr, uintptr_t old_size, uintptr_t size, uintptr_t align) { 468 | LinearAlctor* alctor = (LinearAlctor*)alctor_data; 469 | if (!ptr && size == 0) { 470 | // reset 471 | alctor->pos = 0; 472 | } else if (!ptr) { 473 | // allocate 474 | ptr = das_ptr_add(alctor->data, alctor->pos); 475 | ptr = das_ptr_round_up_align(ptr, align); 476 | uint32_t next_pos = das_ptr_diff(ptr, alctor->data) + size; 477 | if (next_pos <= alctor->size) { 478 | alctor->pos = next_pos; 479 | return ptr; 480 | } else { 481 | return NULL; 482 | } 483 | } else if (ptr && size > 0) { 484 | // reallocate 485 | 486 | // check if the ptr is the last allocation to resize in place 487 | if (das_ptr_add(alctor->data, alctor->pos - old_size) == ptr) { 488 | uint32_t next_pos = das_ptr_diff(ptr, alctor->data) + size; 489 | if (next_pos <= alctor->size) { 490 | alctor->pos = next_pos; 491 | return ptr; 492 | } 493 | } 494 | 495 | // if we cannot extend in place, then just allocate a new block. 496 | void* new_ptr = LinearAlctor_alloc_fn(alctor, NULL, 0, size, align); 497 | if (new_ptr == NULL) { return NULL; } 498 | 499 | memcpy(new_ptr, ptr, size < old_size ? size : old_size); 500 | return new_ptr; 501 | } else { 502 | // deallocate 503 | // do nothing 504 | return NULL; 505 | } 506 | 507 | return NULL; 508 | } 509 | 510 | DasAlctor LinearAlctor_as_das(LinearAlctor* alctor) { 511 | return (DasAlctor){ .fn = LinearAlctor_alloc_fn, .data = alctor }; 512 | } 513 | 514 | void custom_allocator_example() { 515 | // the buffer is zeroed, so the linear allocator will return zeroed memory 516 | char buffer[1024] = {0}; 517 | LinearAlctor la = { .data = buffer, .pos = 0, .size = sizeof(buffer) }; 518 | DasAlctor alctor = LinearAlctor_as_das(&la); 519 | 520 | float* floats = das_alloc_array(float, alctor, 256); 521 | for (int i = 0; i < 256; i += 1) { 522 | das_assert(floats[i] == 0.f, "will not fail since: linear allocator memory is zero"); 523 | } 524 | } 525 | 526 | int main(int argc, char** argv) { 527 | stk_example(); 528 | deque_example(); 529 | alloc_example(); 530 | custom_allocator_example(); 531 | 532 | return 0; 533 | } 534 | 535 | -------------------------------------------------------------------------------- /das_test.c: -------------------------------------------------------------------------------- 1 | #include "das.h" 2 | #include "das.c" 3 | 4 | void stk_test() { 5 | DasStk(int) stk = NULL; 6 | 7 | int elmt = 0; 8 | DasStk_push(&stk, &elmt); 9 | das_assert(memcmp(DasStk_data(&stk), &elmt, sizeof(int)) == 0, "test failed: DasStk_push"); 10 | 11 | for (int i = 1; i < 10; i += 1) { 12 | DasStk_push(&stk, &i); 13 | } 14 | 15 | int b[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 16 | das_assert(DasStk_count(&stk) == 10, "test failed: DasStk_push cause resize capacity"); 17 | das_assert(memcmp(DasStk_data(&stk), b, DasStk_count(&stk) * sizeof(int)) == 0, "test failed: DasStk_push cause resize capacity"); 18 | 19 | int popped_elmt = *DasStk_get_last(&stk); 20 | DasStk_pop(&stk); 21 | das_assert(popped_elmt == 9, "test failed: DasStk_pop"); 22 | das_assert(DasStk_count(&stk) == 9, "test failed: DasStk_pop"); 23 | int c[] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; 24 | das_assert(memcmp(DasStk_data(&stk), c, DasStk_count(&stk) * sizeof(int)) == 0, "test failed: DasStk_pop"); 25 | 26 | popped_elmt = *DasStk_get(&stk, 4); 27 | DasStk_remove_shift(&stk, 4); 28 | das_assert(popped_elmt == 4, "test failed: DasStk_remove_shift middle"); 29 | das_assert(DasStk_count(&stk) == 8, "test failed: DasStk_remove_shift middle"); 30 | int d[] = {0, 1, 2, 3, 5, 6, 7, 8}; 31 | das_assert(memcmp(DasStk_data(&stk), d, DasStk_count(&stk) * sizeof(int)) == 0, "test failed: DasStk_remove_shift middle"); 32 | 33 | popped_elmt = *DasStk_get(&stk, 0); 34 | DasStk_remove_shift(&stk, 0); 35 | das_assert(popped_elmt == 0, "test failed: DasStk_remove_shift start"); 36 | das_assert(DasStk_count(&stk) == 7, "test failed: DasStk_remove_shift start"); 37 | int e[] = {1, 2, 3, 5, 6, 7, 8}; 38 | das_assert(memcmp(DasStk_data(&stk), e, DasStk_count(&stk) * sizeof(int)) == 0, "test failed: DasStk_remove_shift start"); 39 | 40 | popped_elmt = *DasStk_get(&stk, 6); 41 | DasStk_remove_shift(&stk, 6); 42 | das_assert(popped_elmt == 8, "test failed: DasStk_remove_shift end"); 43 | das_assert(DasStk_count(&stk) == 6, "test failed: DasStk_remove_shift end"); 44 | int f[] = {1, 2, 3, 5, 6, 7}; 45 | das_assert(memcmp(DasStk_data(&stk), f, DasStk_count(&stk) * sizeof(int)) == 0, "test failed: DasStk_remove_shift end"); 46 | 47 | popped_elmt = *DasStk_get(&stk, 2); 48 | DasStk_remove_swap(&stk, 2); 49 | das_assert(popped_elmt == 3, "test failed: DasStk_remove_swap middle"); 50 | das_assert(DasStk_count(&stk) == 5, "test failed: DasStk_remove_swap middle"); 51 | int g[] = {1, 2, 7, 5, 6}; 52 | das_assert(memcmp(DasStk_data(&stk), g, DasStk_count(&stk) * sizeof(int)) == 0, "test failed: DasStk_remove_swap middle"); 53 | 54 | popped_elmt = *DasStk_get(&stk, 0); 55 | DasStk_remove_swap(&stk, 0); 56 | das_assert(popped_elmt == 1, "test failed: DasStk_remove_swap start"); 57 | das_assert(DasStk_count(&stk) == 4, "test failed: DasStk_remove_swap start"); 58 | int h[] = {6, 2, 7, 5}; 59 | das_assert(memcmp(DasStk_data(&stk), h, DasStk_count(&stk) * sizeof(int)) == 0, "test failed: DasStk_remove_swap start"); 60 | 61 | popped_elmt = *DasStk_get(&stk, 3); 62 | DasStk_remove_swap(&stk, 3); 63 | das_assert(popped_elmt == 5, "test failed: DasStk_remove_swap end"); 64 | das_assert(DasStk_count(&stk) == 3, "test failed: DasStk_remove_swap end"); 65 | int i[] = {6, 2, 7}; 66 | das_assert(memcmp(DasStk_data(&stk), i, DasStk_count(&stk) * sizeof(int)) == 0, "test failed: DasStk_remove_swap end"); 67 | 68 | elmt = 77; 69 | DasStk_insert(&stk, 2, &elmt); 70 | das_assert(DasStk_count(&stk) == 4, "test failed: DasStk_insert middle"); 71 | int j[] = {6, 2, 77, 7}; 72 | das_assert(memcmp(DasStk_data(&stk), j, DasStk_count(&stk) * sizeof(int)) == 0, "test failed: DasStk_insert middle"); 73 | 74 | elmt = 88; 75 | DasStk_insert(&stk, 0, &elmt); 76 | das_assert(DasStk_count(&stk) == 5, "test failed: DasStk_insert start"); 77 | int k[] = {88, 6, 2, 77, 7}; 78 | das_assert(memcmp(DasStk_data(&stk), k, DasStk_count(&stk) * sizeof(int)) == 0, "test failed: DasStk_insert start"); 79 | 80 | elmt = 99; 81 | DasStk_insert(&stk, 5, &elmt); 82 | das_assert(DasStk_count(&stk) == 6, "test failed: DasStk_insert end"); 83 | int l[] = {88, 6, 2, 77, 7, 99}; 84 | das_assert(memcmp(DasStk_data(&stk), l, DasStk_count(&stk) * sizeof(int)) == 0, "test failed: DasStk_insert end"); 85 | } 86 | 87 | void deque_test() { 88 | DasDeque(int) deque = NULL; 89 | DasDeque_resize_cap(&deque, 6); 90 | das_assert(deque && deque->front_idx == 0 && deque->back_idx == 0 && deque->cap >= 6, "test failed: DasDeque_resize_cap"); 91 | 92 | for (int i = 0; i < 10; i += 1) { 93 | DasDeque_push_front(&deque, &i); 94 | das_assert(DasDeque_count(&deque) == i + 1, "test failed: DasDeque_push_front enough to resize the capacity"); 95 | } 96 | 97 | // deque data should look like: 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 98 | for (int i = 0; i < 10; i += 1) { 99 | das_assert(*DasDeque_get(&deque, i) == 9 - i, "test failed: DasDeque_push_front enough to resize the capacity"); 100 | } 101 | 102 | for (int i = 0; i < 10; i += 1) { 103 | int elmt = *DasDeque_get(&deque, 0); 104 | DasDeque_pop_front(&deque); 105 | das_assert(elmt == 9 - i, "test failed: DasDeque_pop_front"); 106 | das_assert(DasDeque_count(&deque) == 9 - i, "test failed: DasDeque_pop_front"); 107 | } 108 | 109 | 110 | DasDeque_deinit(&deque); 111 | DasDeque_resize_cap(&deque, 6); 112 | das_assert(deque && deque->front_idx == 0 && deque->back_idx == 0 && deque->cap >= 6, "test failed: DasDeque_resize_cap"); 113 | 114 | for (int i = 0; i < 10; i += 1) { 115 | DasDeque_push_back(&deque, &i); 116 | das_assert(DasDeque_count(&deque) == i + 1, "test failed: DasDeque_push_back enough to resize the capacity"); 117 | } 118 | 119 | // deque data should look like: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 120 | for (int i = 0; i < 10; i += 1) { 121 | das_assert(*DasDeque_get(&deque, i) == i, "test failed: DasDeque_push_front enough to resize the capacity"); 122 | } 123 | 124 | for (int i = 0; i < 10; i += 1) { 125 | int elmt = *DasDeque_get_back(&deque, 0); 126 | DasDeque_pop_back(&deque); 127 | das_assert(elmt == 9 - i, "test failed: DasDeque_pop_back"); 128 | das_assert(DasDeque_count(&deque) == 9 - i, "test failed: DasDeque_pop_back"); 129 | } 130 | } 131 | 132 | typedef struct { 133 | void* data; 134 | uint32_t pos; 135 | uint32_t size; 136 | } LinearAlctor; 137 | 138 | void* LinearAlctor_alloc_fn(void* alctor_data, void* ptr, uintptr_t old_size, uintptr_t size, uintptr_t align) { 139 | LinearAlctor* alctor = (LinearAlctor*)alctor_data; 140 | if (!ptr && size == 0) { 141 | // reset 142 | alctor->pos = 0; 143 | } else if (!ptr) { 144 | // allocate 145 | ptr = das_ptr_add(alctor->data, alctor->pos); 146 | ptr = das_ptr_round_up_align(ptr, align); 147 | uint32_t next_pos = das_ptr_diff(ptr, alctor->data) + size; 148 | if (next_pos <= alctor->size) { 149 | alctor->pos = next_pos; 150 | return ptr; 151 | } else { 152 | return NULL; 153 | } 154 | } else if (ptr && size > 0) { 155 | // reallocate 156 | 157 | // check if the ptr is the last allocation to resize in place 158 | if (das_ptr_add(alctor->data, alctor->pos - old_size) == ptr) { 159 | uint32_t next_pos = das_ptr_diff(ptr, alctor->data) + size; 160 | if (next_pos <= alctor->size) { 161 | alctor->pos = next_pos; 162 | return ptr; 163 | } 164 | } 165 | 166 | // if we cannot extend in place, then just allocate a new block. 167 | void* new_ptr = LinearAlctor_alloc_fn(alctor, NULL, 0, size, align); 168 | if (new_ptr == NULL) { return NULL; } 169 | 170 | memcpy(new_ptr, ptr, size < old_size ? size : old_size); 171 | return new_ptr; 172 | } else { 173 | // deallocate 174 | // do nothing 175 | return NULL; 176 | } 177 | 178 | return NULL; 179 | } 180 | 181 | DasAlctor LinearAlctor_as_das(LinearAlctor* alctor) { 182 | return (DasAlctor){ .fn = LinearAlctor_alloc_fn, .data = alctor }; 183 | } 184 | 185 | typedef struct { 186 | void* data; 187 | int whole_number; 188 | float number; 189 | double x; 190 | double y; 191 | } SomeStruct; 192 | 193 | void alloc_test() { 194 | DasAlctor alctor = DasAlctor_default; 195 | SomeStruct* some = das_alloc_elmt(SomeStruct, alctor); 196 | 197 | int* ints = das_alloc_array(int, alctor, 200); 198 | memset(ints, 0xac, 200 * sizeof(int)); 199 | 200 | int* ints_big_align = (int*)das_alloc(alctor, 200 * sizeof(int), 1024); 201 | memset(ints_big_align, 0xef, 200 * sizeof(int)); 202 | das_assert((uintptr_t)ints_big_align % 1024 == 0, "test failed: trying to alignment our int array to 1024"); 203 | 204 | { 205 | // the buffer is zeroed, so the linear allocator will return zeroed memory 206 | char buffer[1024] = {0}; 207 | LinearAlctor la = { .data = buffer, .pos = 0, .size = sizeof(buffer) }; 208 | 209 | // make the allocations functions use the linear allocator 210 | alctor = LinearAlctor_as_das(&la); 211 | 212 | float* floats = das_alloc_array(float, alctor, 256); 213 | for (int i = 0; i < 256; i += 1) { 214 | das_assert(floats[i] == 0, "test failed: linear allocator memory should be zero"); 215 | 216 | float* buffer_float = (float*)(buffer + (i * sizeof(float))); 217 | das_assert(&floats[i] == buffer_float, "test failed: linear allocator pointers should match"); 218 | } 219 | 220 | das_assert(la.pos == la.size, "test failed: we should have consumed all of the linear allocators memory"); 221 | 222 | // restore the system allocator 223 | alctor = DasAlctor_default; 224 | } 225 | 226 | das_dealloc_elmt(SomeStruct, alctor, some); 227 | 228 | ints = das_realloc_array(int, alctor, ints, 200, 400); 229 | for (int i = 0; i < 200; i += 1) { 230 | das_assert(ints[i] == 0xacacacac, "test failed: reallocation has not preserved the memory"); 231 | } 232 | 233 | 234 | ints_big_align = (int*)das_realloc(alctor, ints_big_align, 200 * sizeof(int), 400 * sizeof(int), 1024); 235 | for (int i = 0; i < 200; i += 1) { 236 | das_assert(ints_big_align[i] == 0xefefefef, "test failed: reallocation has not preserved the memory"); 237 | } 238 | 239 | das_dealloc(alctor, ints_big_align, 400 * sizeof(int), 1024); 240 | das_dealloc_array(int, alctor, ints, 400); 241 | } 242 | 243 | void virt_mem_tests() { 244 | uintptr_t reserve_align; 245 | uintptr_t page_size; 246 | DasError error = das_virt_mem_page_size(&page_size, &reserve_align); 247 | das_assert(error == 0, "failed to get the page size: 0x%x", error); 248 | 249 | // 250 | // tests that need to fail cannot be checked without 251 | // segfault handlers and this is messy across different OSs. 252 | // maybe in the future we can do this. but not now. 253 | // so for now just increment this macro and manually test these. 254 | // maybe this can be passed through in the command-line with the -D flags. 255 | #define RUN_FAIL_TEST 0 256 | 257 | // 258 | // reserved MBs and grow by commiting KBs at a time 259 | DasLinearAlctor la_alctor = {0}; 260 | uintptr_t reserved_size = reserve_align * 1024; 261 | uintptr_t commit_grow_size = page_size; 262 | error = DasLinearAlctor_init(&la_alctor, reserved_size, commit_grow_size); 263 | das_assert(error == 0, "failed to initial linear allocator: 0x%x", error); 264 | 265 | DasAlctor alctor = DasLinearAlctor_as_das(&la_alctor); 266 | 267 | #if RUN_FAIL_TEST == 1 268 | // 269 | // writing this byte will crash since memory has not been commited 270 | printf("RUN_FAIL_TEST %u: we should get a SIGSEGV here\n", RUN_FAIL_TEST); 271 | memset(la_alctor.address_space, 0xac, 1); 272 | #endif 273 | 274 | // 275 | // this tests reserved memory and then committing it later 276 | void* ptr = das_alloc(alctor, commit_grow_size * 2, 1); 277 | memset(ptr, 0xac, commit_grow_size * 2); 278 | 279 | #if RUN_FAIL_TEST == 2 280 | printf("RUN_FAIL_TEST %u: we should get a SIGSEGV here\n", RUN_FAIL_TEST); 281 | // writing 1 more byte over will crash the program 282 | memset(ptr, 0xac, commit_grow_size * 2 + 1); 283 | #endif 284 | 285 | // 286 | // reset the allocator. 287 | // this will decommit all the pages and start from the beginning 288 | das_alloc_reset(alctor); 289 | 290 | #if RUN_FAIL_TEST == 3 291 | printf("RUN_FAIL_TEST %u: we should get a SIGSEGV here\n", RUN_FAIL_TEST); 292 | // 293 | // writing this byte will crash since memory has been decommitted 294 | memset(la_alctor.address_space, 0xac, 1); 295 | #endif 296 | 297 | // 298 | // this tests committing the memory that we previously decommitted. 299 | ptr = das_alloc(alctor, commit_grow_size * 2, 1); 300 | das_assert(ptr == la_alctor.address_space, "reset should make our allocator start from the beginning again"); 301 | for (uintptr_t i = 0; i < commit_grow_size * 2; i += 1) { 302 | uint8_t byte = *(uint8_t*)das_ptr_add(ptr, i); 303 | das_assert( 304 | byte == 0, 305 | "memory should be zero after we decommit and commit the memory again... got 0x%x at %zu", 306 | byte, i 307 | ); 308 | } 309 | 310 | // 311 | // committing 3 pages and marking the middle as read only 312 | reserved_size = das_round_up_nearest_multiple_u(page_size * 3, reserve_align); 313 | error = das_virt_mem_reserve(NULL, reserved_size, &ptr); 314 | error = das_virt_mem_commit(ptr, page_size * 3, DasVirtMemProtection_read_write); 315 | void* first_page = ptr; 316 | void* middle_page = das_ptr_add(ptr, page_size); 317 | void* last_page = das_ptr_add(ptr, page_size * 2); 318 | error = das_virt_mem_protection_set(middle_page, page_size, DasVirtMemProtection_read); 319 | 320 | // 321 | // test writing to the surrounding pages that have read write access. 322 | memset(first_page, 0xac, page_size); 323 | memset(last_page, 0xac, page_size); 324 | 325 | // 326 | // test reading from the middle page as the protection should be read only 327 | das_assert(*(uint8_t*)middle_page == 0, "newly commited memory should be 0"); 328 | 329 | #if RUN_FAIL_TEST == 4 330 | printf("RUN_FAIL_TEST %u: we should get a SIGSEGV here\n", RUN_FAIL_TEST); 331 | // 332 | // writing this byte will crash since memory it has read only protection 333 | memset(middle_page, 0xac, 1); 334 | #endif 335 | 336 | // 337 | // release the memory and unreserve the address space 338 | error = das_virt_mem_release(ptr, reserved_size); 339 | 340 | #if RUN_FAIL_TEST == 5 341 | printf("RUN_FAIL_TEST %u: we should get a SIGSEGV here\n", RUN_FAIL_TEST); 342 | // 343 | // writing this byte will crash since memory it has been released/unmapped 344 | memset(first_page, 0xac, 1); 345 | #endif 346 | 347 | char* path = "das_test.c"; 348 | DasFileHandle file_handle; 349 | error = das_file_open(path, DasFileFlags_read, &file_handle); 350 | das_assert(error == 0, "error opening file at %s : 0x%x", path, error); 351 | 352 | // 353 | // test mapping the file of the source code of this executable and reading the first line. 354 | // the offset starts after the first character to test the offset functionality. 355 | // the size is short and not a page size to test that out too. 356 | uint64_t this_file_map_offset = 1; 357 | uintptr_t this_file_map_size = 32; 358 | DasMapFileHandle this_file_map_file_handle; 359 | void* this_file_mem; 360 | error = das_virt_mem_map_file( 361 | NULL, file_handle, DasVirtMemProtection_read, 362 | this_file_map_offset, this_file_map_size, &this_file_mem, &this_file_map_file_handle); 363 | 364 | das_assert(error == 0, "failed to map the source code of the executable"); 365 | char* first_line_of_code = "include \"das.h\""; 366 | das_assert( 367 | strncmp(this_file_mem, first_line_of_code, strlen(first_line_of_code)) == 0, 368 | "failed testing mapping the source code of the exe and reading the first line"); 369 | 370 | 371 | error = das_virt_mem_unmap_file(this_file_mem, this_file_map_size, this_file_map_file_handle); 372 | das_assert(error == 0, "failed testing closing the memory mapped file of the source code"); 373 | 374 | das_file_close(file_handle); 375 | 376 | #undef RUN_FAIL_TEST 377 | } 378 | 379 | typedef struct Entity Entity; 380 | struct Entity { 381 | char data[64]; 382 | }; 383 | 384 | typedef_DasPoolElmtId(EntityId, 20); 385 | typedef_DasPool(EntityId, Entity); 386 | 387 | void pool_tests() { 388 | DasPool(EntityId, Entity) pool; 389 | 390 | uintptr_t max_entities_count = 50000; // 3.2MBs 391 | uintptr_t entities_grow_size = 256; // 16KB 392 | 393 | DasPool_init(EntityId, &pool, max_entities_count, entities_grow_size); 394 | max_entities_count = pool.reserved_cap; 395 | 396 | { 397 | // 398 | // allocated the maximum number of entities. 399 | // 400 | for (uint32_t i = 0; i < max_entities_count; i += 1) { 401 | EntityId id; 402 | das_assert( 403 | DasPool_alloc(EntityId, &pool, &id), 404 | "allocation should not fail" 405 | ); 406 | } 407 | } 408 | 409 | 410 | { 411 | // 412 | // make sure the allocated linked list is setup in order. 413 | // 414 | 415 | EntityId id = EntityId_null; 416 | uint32_t expected_idx = 0; 417 | uint32_t stop_at_idx = 20; 418 | while (1) { 419 | id = DasPool_iter_next(EntityId, &pool, id); 420 | if (id.raw == 0) 421 | break; 422 | 423 | uint32_t idx = DasPool_id_to_idx(EntityId, &pool, id); 424 | das_assert(idx == expected_idx, "when iterating forwards we expected in index of %u but got %u\n", expected_idx, idx); 425 | if (idx == stop_at_idx) break; 426 | expected_idx += 1; 427 | } 428 | 429 | id = EntityId_null; 430 | expected_idx = max_entities_count - 1; 431 | stop_at_idx = max_entities_count - 20; 432 | while (1) { 433 | id = DasPool_iter_prev(EntityId, &pool, id); 434 | if (id.raw == 0) 435 | break; 436 | 437 | uint32_t idx = DasPool_id_to_idx(EntityId, &pool, id); 438 | das_assert(idx == expected_idx, "when iterating backwards we expected in index of %u but got %u\n", expected_idx, idx); 439 | if (idx == stop_at_idx) break; 440 | expected_idx -= 1; 441 | } 442 | } 443 | 444 | { 445 | // 446 | // ensure the counter will wrap around to 0 when it overflows 447 | // 448 | uint32_t counter_mask = DasPoolElmtId_counter_mask(EntityId_index_bits); 449 | uint32_t counter_max = counter_mask >> EntityId_index_bits; 450 | EntityId id = EntityId_null; 451 | id = DasPool_iter_next(EntityId, &pool, id); 452 | uint32_t expected_counter = 0; 453 | uint32_t found_zero_count = 0; 454 | while (found_zero_count < 2) { 455 | uint32_t counter = (id.raw & counter_mask) >> EntityId_index_bits; 456 | das_assert(counter == expected_counter, "expected counter to be %u but got %u\n", expected_counter, counter); 457 | 458 | DasPool_dealloc(EntityId, &pool, id); 459 | 460 | DasPool_alloc(EntityId, &pool, &id); 461 | if (expected_counter == 0) 462 | found_zero_count += 1; 463 | 464 | if (expected_counter == counter_max) { 465 | expected_counter = 0; 466 | } else { 467 | expected_counter += 1; 468 | } 469 | } 470 | } 471 | } 472 | 473 | int main(int argc, char** argv) { 474 | alloc_test(); 475 | stk_test(); 476 | deque_test(); 477 | virt_mem_tests(); 478 | pool_tests(); 479 | 480 | printf("all tests were successful\n"); 481 | return 0; 482 | } 483 | 484 | -------------------------------------------------------------------------------- /run_tests.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | ECHO running das_test with clang... 3 | IF EXIST "das_test.exe" ( 4 | DEL das_test.exe 5 | ) 6 | 7 | clang.exe -g -o das_test.exe das_test.c 8 | 9 | IF EXIST "das_test.exe" ( 10 | das_test.exe 11 | ) 12 | 13 | ECHO running das_test with Visual Studio cl... 14 | IF EXIST "das_test.exe" ( 15 | DEL das_test.exe 16 | ) 17 | 18 | cl.exe das_test.c 19 | 20 | IF EXIST "das_test.exe" ( 21 | das_test.exe 22 | ) 23 | 24 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if test -f "das_test"; then 4 | rm das_test 5 | fi 6 | 7 | echo "running das_test on gcc..." 8 | gcc -Wpedantic -Werror -g -o das_test das_test.c 9 | 10 | if test -f "das_test"; then 11 | ./das_test 12 | fi 13 | 14 | if test -f "das_test"; then 15 | rm das_test 16 | echo "running das_test on clang..." 17 | clang -Wpedantic -Werror -g -o das_test das_test.c 18 | fi 19 | 20 | if test -f "das_test"; then 21 | ./das_test 22 | fi 23 | 24 | --------------------------------------------------------------------------------