├── .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 |
--------------------------------------------------------------------------------