├── .gitignore ├── LICENSE ├── README.md └── src └── rmem.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | zlib License 2 | 3 | Copyright (c) 2019-2024 Kevin 'Assyrianic' Yonan (@assyrianic) and reviewed by Ramon Santamaria (@raysan5) 4 | 5 | This software is provided "as-is", without any express or implied warranty. In no event 6 | will the authors be held liable for any damages arising from the use of this software. 7 | 8 | Permission is granted to anyone to use this software for any purpose, including commercial 9 | applications, and to alter it and redistribute it freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not claim that you 12 | wrote the original software. If you use this software in a product, an acknowledgment 13 | in the product documentation would be appreciated but is not required. 14 | 15 | 2. Altered source versions must be plainly marked as such, and must not be misrepresented 16 | as being the original software. 17 | 18 | 3. This notice may not be removed or altered from any source distribution. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rmem 2 | 3 | A set portable memory structures to be used with any engine/framework. 4 | 5 | By Kevin 'Assyrianic' Yonan ([@assyrianic](https://github.com/assyrianic)) 6 | 7 | rmem implements 3 types of memory structures: 8 | 9 | - [Memory Pool](#memory-pool) 10 | - [Object Pool](#object-pool) 11 | - [Double-Ended aka Bi(furcated) Stack](#bistack) 12 | 13 | ## Memory Pool 14 | 15 | The Memory Pool is a quick, efficient, and minimal free list & stack-based allocator. 16 | 17 | Memory Pool's purpose is the following list: 18 | 19 | * A quicker, efficient memory allocator alternative to `malloc` and friends. 20 | * Reduce the possibilities of memory leaks for beginner developers. 21 | * Being able to flexibly range check memory if necessary. 22 | 23 | ### Data Implementation 24 | 25 | The memory pool encapsulates two public structs: 26 | 27 | * `freeList` which is an abstracted doubly linked list consisting of a `head` and `tail` pointer to `MemNode` 28 | * A `len` that tracks the amount of nodes the linked list holds. 29 | * an array of doubly linked lists that store fixed size blocks called `buckets`. 30 | ```c 31 | typedef struct MemNode { 32 | size_t size; 33 | MemNode *next, *prev; 34 | } MemNode; 35 | 36 | typedef struct AllocList { 37 | MemNode *head, *tail; 38 | size_t len; 39 | } AllocList; 40 | ``` 41 | 42 | `arena` which is an array-based buffer consisting of... 43 | * a byte pointer to the entire array `mem` 44 | * a base pointer `offs` which changes position when allocating, deallocating, or defragging, 45 | * and `size` which tracks how large the entire buffer is. 46 | ```c 47 | typedef struct Arena { 48 | uintptr_t mem, offs; 49 | size_t size; 50 | } Arena; 51 | ``` 52 | 53 | ```c 54 | typedef struct MemPool { 55 | AllocList large, buckets[MEMPOOL_BUCKET_SIZE]; 56 | Arena arena; 57 | } MemPool; 58 | ``` 59 | 60 | ### Usage 61 | 62 | The memory pool is designed to be used as a direct object. 63 | 64 | We have two constructor functions: 65 | ```c 66 | MemPool CreateMemPool(size_t bytes); 67 | MemPool CreateMemPoolFromBuffer(void *buf, size_t bytes); 68 | ``` 69 | To which you create a `MemPool` instance and give the function a max amount of memory you wish or require for your data. 70 | Remember not to exceed that memory amount or the allocation functions of the allocator will give you a NULL pointer. 71 | 72 | So we create a pool that will malloc 10K bytes. 73 | ```c 74 | MemPool pool = CreateMemPool(10000); 75 | ``` 76 | 77 | When we finish using the pool's memory, we clean it up by using `DestroyMemPool`. 78 | ```c 79 | DestroyMemPool(&pool); 80 | ``` 81 | 82 | Alternatively, if you're not in a position to use any kind of dynamic allocation from the operating system, you have the option to utilize an existing buffer as memory for the mempool: 83 | ```c 84 | char mem[64000]; 85 | MemPool pool = CreateMemPoolFromBuffer(mem, sizeof mem); 86 | ``` 87 | 88 | To allocate from the pool, we have two functions: 89 | ```c 90 | void *MemPoolAlloc(MemPool *mempool, size_t bytes); 91 | void *MemPoolRealloc(MemPool *mempool, void *ptr, size_t bytes); 92 | ``` 93 | 94 | `MemPoolAlloc` returns a (zeroed) pointer to a memory block. 95 | ```c 96 | // allocate an int pointer. 97 | int *i = MemPoolAlloc(&pool, sizeof *i); 98 | ``` 99 | 100 | `MemPoolRealloc` works similar but it takes an existing pointers and resizes its data. Please note that if you resize a smaller size, the data WILL BE TRUNCATED/CUT OFF. 101 | If the `ptr` argument for `MemPoolRealloc` is `NULL`, it will work just like a call to `MemPoolAlloc`. 102 | ```c 103 | // allocate an int pointer. 104 | int *i = MemPoolRealloc(&pool, NULL, sizeof *i); 105 | 106 | // resize the pointer into an int array of 10 cells! 107 | i = MemPoolRealloc(&pool, i, sizeof *i * 10); 108 | ``` 109 | 110 | To deallocate memory back to the pool, there's also two functions: 111 | ```c 112 | void MemPoolFree(MemPool *mempool, void *ptr); 113 | void MemPoolCleanUp(MemPool *mempool, void **ptrref); 114 | ``` 115 | 116 | `MemPoolFree` will deallocate the pointer data back to the memory pool. 117 | ```c 118 | // i is now deallocated! Remember that i is NOT NULL! 119 | MemPoolFree(&pool, i); 120 | ``` 121 | 122 | `MemPoolCleanUp` instead takes a pointer to an allocated pointer and then calls `MemPoolFree` for that pointer and then sets it to NULL. 123 | ```c 124 | // deallocates i and sets the pointer to NULL. 125 | MemPoolCleanUp(&pool, (void **)&i); 126 | // i is now NULL. 127 | ``` 128 | 129 | Using `MemPoolCleanUp` is basically a shorthand way of doing this code: 130 | ```c 131 | // i is now deallocated! Remember that i is NOT NULL! 132 | MemPoolFree(&pool, i); 133 | 134 | // i is now NULL obviously. 135 | i = NULL; 136 | ``` 137 | 138 | If there's a moment when you want to not only free all your allocated data but refresh the allocator in its entirety, there's: 139 | 140 | ```c 141 | void MemPoolReset(MemPool *mempool); 142 | ``` 143 | which will completely re-merge all freelist blocks and zero out the allocator's buffer memory. 144 | Make sure to `NULL` all living pointers when doing this. 145 | 146 | Finally, to get the total amount of memory remaining (to make sure you don't accidentally over-allocate), you utilize this function: 147 | ```c 148 | size_t GetMemPoolFreeMemory(const MemPool mempool); 149 | ``` 150 | 151 | ## Object Pool 152 | 153 | The Object Pool is a fast and minimal fixed-size allocator. 154 | 155 | Object Pool was created as a complement to the Memory Pool. 156 | Due to the general purpose nature of Memory Pool, memory block fragmentations can affect allocation and deallocation speeds. Because of this, the Object pool succeeds by having no fragmentation and accommodating for allocating fixed-size data while the memory pool accommodates allocating variadic/differently sized data. 157 | 158 | ### Data Implementation 159 | 160 | The object pool is implemented as a hybrid array-stack of cells that are large enough to hold the size of your data at initialization: 161 | ```c 162 | typedef struct ObjPool { 163 | uintptr_t mem, offs; 164 | size_t objSize, freeBlocks, memSize; 165 | } ObjPool; 166 | ``` 167 | 168 | ### Usage 169 | 170 | The object pool is designed to be used as a direct object. 171 | We have two constructor functions: 172 | ```c 173 | ObjPool CreateObjPool(size_t objsize, size_t len); 174 | ObjPool CreateObjPoolFromBuffer(void *buf, size_t objsize, size_t len); 175 | ``` 176 | 177 | To which you create a `ObjPool` instance and give the size of your object and how many objects for the pool to hold. 178 | So assume we have a vector struct like: 179 | ```c 180 | typedef struct vec3D { 181 | float x,y,z; 182 | } vec3D_t; 183 | ``` 184 | ... which will have a size of 12 bytes. 185 | 186 | Now let's create a pool of 3D vectors that holds about 100 3D vectors. 187 | ```c 188 | ObjPool vector_pool = CreateObjPool(sizeof(vec3D_t), 100); 189 | ``` 190 | 191 | Alternatively, if for any reason that you cannot use dynamic memory allocation, you have the option of using an existing buffer for the object pool: 192 | ```c 193 | vec3D_t vectors[100]; 194 | ObjPool vector_pool = CreateObjPoolFromBuffer(vectors, sizeof(vec3D_t), 1[&vector] - 0[&vector]); 195 | ``` 196 | The buffer MUST be aligned to the size of `size_t` AND the object size must not be smaller than a `size_t` either. 197 | 198 | Next, we start our operations by allocating which will always allocate ONE object... 199 | If you need to allocate something like an array of these objects, then you'll have to make an object pool for the array of objects or use Memory Pool. 200 | 201 | Allocation is very simple nonetheless! 202 | ```c 203 | vec3D_t *origin = ObjPoolAlloc(&vector_pool); 204 | origin->x = -0.5f; 205 | origin->y = +0.5f; 206 | origin->z = 0.f; 207 | ``` 208 | 209 | Deallocation itself is also very simple. There's two deallocation functions available: 210 | ```c 211 | void ObjPoolFree(ObjPool *objpool, void *ptr); 212 | void ObjPoolCleanUp(ObjPool *objpool, void **ptrref); 213 | ``` 214 | 215 | `ObjPoolFree` will deallocate the object pointer data back to the memory pool. 216 | ```c 217 | ObjPoolFree(&vector_pool, origin); 218 | ``` 219 | 220 | Like Memory Pool, the Object Pool also comes with a convenient clean up function that takes a pointer to an allocated pointer, frees it, and sets the pointer to NULL for you! 221 | ```c 222 | ObjPoolCleanUp(&vector_pool, (void **)&origin); 223 | ``` 224 | 225 | Which of course is equivalent to: 226 | ```c 227 | ObjPoolFree(&vector_pool, origin), origin = NULL; 228 | ``` 229 | 230 | 231 | Once you're free with the object pool, you dispose of it by using `DestroyObjPool`: 232 | ```c 233 | DestroyObjPool(&vector_pool); 234 | ``` 235 | 236 | ## BiStack 237 | 238 | The BiStack is a fast & efficient bifurcated stack "bi-stack" allocator. 239 | 240 | BiStack's purpose is the following list: 241 | 242 | * A quick, efficient way of allocating temporary, dynamically-sizeable memory during various operations. 243 | * Bifurcated to allow certain temporary data to have a different lifetime from other, temporary data. 244 | 245 | ### Data Implementation 246 | 247 | The bifurcated stack encapsulates one public struct: 248 | * `mem` which is an unsigned integer large enough to represent a pointer; the pointer value it holds is the memory address of the backing buffer. 249 | * `front` holds a pointer value representing the first aka _front_ portion of the bifurcated stack. 250 | * `back` holds a pointer value representing the second aka _back_ portion of the bifurcated stack. 251 | * and a `size` that holds the amount of bytes of the backing buffer. 252 | ```c 253 | typedef struct BiStack { 254 | uintptr_t mem, front, back; 255 | size_t size; 256 | } BiStack; 257 | ``` 258 | 259 | ### Usage 260 | 261 | The bi-stack is designed to be used as a direct object. 262 | There are two constructor functions: 263 | ```c 264 | BiStack CreateBiStack(size_t len); 265 | BiStack CreateBiStackFromBuffer(void *buf, size_t len); 266 | ``` 267 | To which you create a `BiStack` instance and give the function a max amount of memory you wish or require for your data. 268 | Remember not to exceed that memory amount or the allocation functions of the allocator will give you a NULL pointer. 269 | 270 | So we create a bistack that will malloc an internal buffer of 10K bytes. 271 | ```c 272 | BiStack bistack = CreateBiStack(10000); 273 | ``` 274 | 275 | When the bi-stack is no longer needed, we clean it up by using `DestroyBiStack`. 276 | ```c 277 | DestroyBiStack(&bistack); 278 | ``` 279 | 280 | Alternatively, if you're not in a position to use any kind of dynamic allocation from the operating system, you have the option to utilize an existing buffer as memory for the bistack: 281 | ```c 282 | char mem[64000]; 283 | BiStack pool = CreateBiStackFromBuffer(mem, sizeof mem); 284 | ``` 285 | 286 | To allocate from the bistack, we have two functions: 287 | ```c 288 | void *BiStackAllocFront(BiStack *destack, size_t len); 289 | void *BiStackAllocBack(BiStack *destack, size_t len); 290 | ``` 291 | 292 | **NOTE**: _The two allocator functions do **NOT** zero memory._ 293 | ```c 294 | // allocate an int pointer from the front. 295 | int *i = BiStackAllocFront(&bistack, sizeof *i); 296 | 297 | // allocate a float pointer from the back. 298 | float *f = BiStackAllocBack(&bistack, sizeof *f); 299 | ``` 300 | 301 | Unlike the other allocators, the Bi-stack does not require you free given pointers back to the allocator. However, you will need to reset the bi-stack in order to "free" the data back to the bi-stack. Here are three functions that resets the bi-stack: 302 | ```c 303 | void BiStackResetFront(BiStack *destack); 304 | void BiStackResetBack(BiStack *destack); 305 | void BiStackResetAll(BiStack *destack); 306 | ``` 307 | `BiStackResetFront` will reset allocation data for the front portion of the bi-stack. So ALL pointers given from the front portion can be overwritten if any new pointers allocated also point to those parts. Same deal for `BiStackResetBack` and the back portion of the bi-stack. 308 | 309 | If both portions need to be reset, `BiStackResetAll` will do the job. 310 | 311 | An important caveat with the bi-stack is that if the back portion or the front portion collide with one another, you will be given a `NULL` pointer. To check if they will collide, `intptr_t BiStackMargins(BiStack destack);` is used to get the difference between how far, in bytes, the back portion is from the front. 312 | 313 | The way this works is that, when allocating from the front portion, the `front` member is increased. When allocating the back portion, the `back` member is decreased. If the front portion reaches the back, that means the front portion of the bi-stack is out of memory, same deal for the back portion if it reaches the front. Thus, `BiStackMargins` provides you the difference between these two portions; a number of 0 or less means that one of the portions has reached the other and a reset is necessary. 314 | 315 | ## License 316 | 317 | `rmem` is licensed under an unmodified zlib/libpng license, which is an OSI-certified, BSD-like license that allows static linking with closed source software. Check [LICENSE](LICENSE) for further details. 318 | -------------------------------------------------------------------------------- /src/rmem.h: -------------------------------------------------------------------------------- 1 | /********************************************************************************************** 2 | * 3 | * rmem v1.3 - memory pool and objects pool 4 | * 5 | * DESCRIPTION: 6 | * A quick, efficient, and minimal free list and arena-based allocator 7 | * 8 | * POURPOSE: 9 | * - A quicker, efficient memory allocator alternative to 'malloc()' and friends. 10 | * - Reduce the possibilities of memory leaks for beginner developers using raylib. 11 | * - Being able to flexibly range check memory if necessary. 12 | * 13 | * CONFIGURATION: 14 | * #define RMEM_IMPLEMENTATION 15 | * Generates the implementation of the library into the included file. 16 | * If not defined, the library is in header only mode and can be included in other headers 17 | * or source files without problems. But only ONE file should hold the implementation. 18 | * 19 | * DOCUMENTATION: 20 | * raylib Wiki: https://github.com/raysan5/raylib/wiki/raylib-memory-pool 21 | * Usage example with raylib: https://github.com/raysan5/raylib/issues/1329 22 | * 23 | * VERSIONS HISTORY: 24 | * 1.3 Several changes: 25 | * Optimizations of allocators 26 | * Renamed 'Stack' to 'Arena' 27 | * Replaced certain define constants with an anonymous enum 28 | * Refactored MemPool to no longer require active or deferred defragging 29 | * 1.2 Addition of bidirectional arena 30 | * 1.1 Bug patches for the mempool and addition of object pool 31 | * 1.0 First version 32 | * 33 | * 34 | * LICENSE: zlib/libpng 35 | * 36 | * Copyright (c) 2019 Kevin 'Assyrianic' Yonan (@assyrianic) and reviewed by Ramon Santamaria (@raysan5) 37 | * 38 | * This software is provided "as-is", without any express or implied warranty. In no event 39 | * will the authors be held liable for any damages arising from the use of this software. 40 | * 41 | * Permission is granted to anyone to use this software for any purpose, including commercial 42 | * applications, and to alter it and redistribute it freely, subject to the following restrictions: 43 | * 44 | * 1. The origin of this software must not be misrepresented; you must not claim that you 45 | * wrote the original software. If you use this software in a product, an acknowledgment 46 | * in the product documentation would be appreciated but is not required. 47 | * 48 | * 2. Altered source versions must be plainly marked as such, and must not be misrepresented 49 | * as being the original software. 50 | * 51 | * 3. This notice may not be removed or altered from any source distribution. 52 | * 53 | **********************************************************************************************/ 54 | 55 | #ifndef RMEM_H 56 | #define RMEM_H 57 | 58 | #include 59 | #include 60 | 61 | //---------------------------------------------------------------------------------- 62 | // Defines and Macros 63 | //---------------------------------------------------------------------------------- 64 | #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) 65 | #define RMEMAPI __declspec(dllexport) // We are building library as a Win32 shared library (.dll) 66 | #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) 67 | #define RMEMAPI __declspec(dllimport) // We are using library as a Win32 shared library (.dll) 68 | #else 69 | #define RMEMAPI // We are building or using library as a static library (or Linux shared library) 70 | #endif 71 | 72 | #define RMEM_VERSION "v1.3" // changelog at bottom of header. 73 | 74 | //---------------------------------------------------------------------------------- 75 | // Types and Structures Definition 76 | //---------------------------------------------------------------------------------- 77 | 78 | enum { 79 | MEMPOOL_BUCKET_SIZE = 8, 80 | MEMPOOL_BUCKET_BITS = (sizeof(uintptr_t) >> 1) + 1, 81 | MEM_SPLIT_THRESHOLD = sizeof(uintptr_t) * 4 82 | }; 83 | 84 | // Memory pool node 85 | typedef struct MemNode MemNode; 86 | struct MemNode { 87 | size_t size; 88 | MemNode *next, *prev; 89 | }; 90 | 91 | // Freelist implementation 92 | typedef struct AllocList { 93 | MemNode *head, *tail; 94 | size_t len; 95 | } AllocList; 96 | 97 | // Arena allocator 98 | typedef struct Arena { 99 | uintptr_t mem, offs; 100 | size_t size; 101 | } Arena; 102 | 103 | // Memory pool 104 | typedef struct MemPool { 105 | AllocList large, buckets[MEMPOOL_BUCKET_SIZE]; 106 | Arena arena; 107 | } MemPool; 108 | 109 | // Object pool 110 | typedef struct ObjPool { 111 | uintptr_t mem, offs; 112 | size_t objSize, freeBlocks, memSize; 113 | } ObjPool; 114 | 115 | // Double-ended stack (aka Deque) 116 | typedef struct BiStack { 117 | uintptr_t mem, front, back; 118 | size_t size; 119 | } BiStack; 120 | 121 | 122 | #if defined(__cplusplus) 123 | extern "C" { // Prevents name mangling of functions 124 | #endif 125 | 126 | //------------------------------------------------------------------------------------ 127 | // Functions Declaration - Memory Pool 128 | //------------------------------------------------------------------------------------ 129 | RMEMAPI MemPool CreateMemPool(size_t bytes); 130 | RMEMAPI MemPool CreateMemPoolFromBuffer(void *buf, size_t bytes); 131 | RMEMAPI void DestroyMemPool(MemPool *mempool); 132 | 133 | RMEMAPI void *MemPoolAlloc(MemPool *mempool, size_t bytes); 134 | RMEMAPI void *MemPoolRealloc(MemPool *mempool, void *ptr, size_t bytes); 135 | RMEMAPI void MemPoolFree(MemPool *mempool, void *ptr); 136 | RMEMAPI void MemPoolCleanUp(MemPool *mempool, void **ptrref); 137 | RMEMAPI void MemPoolReset(MemPool *mempool); 138 | RMEMAPI size_t GetMemPoolFreeMemory(const MemPool mempool); 139 | 140 | //------------------------------------------------------------------------------------ 141 | // Functions Declaration - Object Pool 142 | //------------------------------------------------------------------------------------ 143 | RMEMAPI ObjPool CreateObjPool(size_t objsize, size_t len); 144 | RMEMAPI ObjPool CreateObjPoolFromBuffer(void *buf, size_t objsize, size_t len); 145 | RMEMAPI void DestroyObjPool(ObjPool *objpool); 146 | 147 | RMEMAPI void *ObjPoolAlloc(ObjPool *objpool); 148 | RMEMAPI void ObjPoolFree(ObjPool *objpool, void *ptr); 149 | RMEMAPI void ObjPoolCleanUp(ObjPool *objpool, void **ptrref); 150 | 151 | //------------------------------------------------------------------------------------ 152 | // Functions Declaration - Double-Ended Stack 153 | //------------------------------------------------------------------------------------ 154 | RMEMAPI BiStack CreateBiStack(size_t len); 155 | RMEMAPI BiStack CreateBiStackFromBuffer(void *buf, size_t len); 156 | RMEMAPI void DestroyBiStack(BiStack *destack); 157 | 158 | RMEMAPI void *BiStackAllocFront(BiStack *destack, size_t len); 159 | RMEMAPI void *BiStackAllocBack(BiStack *destack, size_t len); 160 | 161 | RMEMAPI void BiStackResetFront(BiStack *destack); 162 | RMEMAPI void BiStackResetBack(BiStack *destack); 163 | RMEMAPI void BiStackResetAll(BiStack *destack); 164 | 165 | RMEMAPI intptr_t BiStackMargins(BiStack destack); 166 | 167 | #ifdef __cplusplus 168 | } 169 | #endif 170 | 171 | #endif // RMEM_H 172 | 173 | /*********************************************************************************** 174 | * 175 | * RMEM IMPLEMENTATION 176 | * 177 | ************************************************************************************/ 178 | 179 | #if defined(RMEM_IMPLEMENTATION) 180 | 181 | #include // Required for: malloc(), calloc(), free() 182 | #include // Required for: memset(), memcpy(), memmove() 183 | 184 | //---------------------------------------------------------------------------------- 185 | // Defines and Macros 186 | //---------------------------------------------------------------------------------- 187 | 188 | // Make sure restrict type qualifier for pointers is defined 189 | // NOTE: Not supported by C++, it is a C only keyword 190 | #if defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__) || defined(_MSC_VER) 191 | #ifndef restrict 192 | #define restrict __restrict 193 | #endif 194 | #endif 195 | 196 | //---------------------------------------------------------------------------------- 197 | // Global Variables Definition 198 | //---------------------------------------------------------------------------------- 199 | // ... 200 | 201 | //---------------------------------------------------------------------------------- 202 | // Module specific Functions Declaration 203 | //---------------------------------------------------------------------------------- 204 | static inline size_t __AlignSize(const size_t size, const size_t align) 205 | { 206 | return (size + (align - 1)) & -align; 207 | } 208 | 209 | static MemNode *__SplitMemNode(MemNode *const node, const size_t bytes) 210 | { 211 | uintptr_t n = ( uintptr_t )node; 212 | MemNode *const r = ( MemNode* )(n + (node->size - bytes)); 213 | node->size -= bytes; 214 | r->size = bytes; 215 | 216 | return r; 217 | } 218 | 219 | static void __InsertMemNodeBefore(AllocList *const list, MemNode *const insert, MemNode *const curr) 220 | { 221 | insert->next = curr; 222 | 223 | if (curr->prev==NULL) list->head = insert; 224 | else 225 | { 226 | insert->prev = curr->prev; 227 | curr->prev->next = insert; 228 | } 229 | 230 | curr->prev = insert; 231 | } 232 | 233 | static void __ReplaceMemNode(MemNode *const old, MemNode *const replace) 234 | { 235 | replace->prev = old->prev; 236 | replace->next = old->next; 237 | 238 | if (old->prev != NULL) old->prev->next = replace; 239 | if (old->next != NULL) old->next->prev = replace; 240 | } 241 | 242 | 243 | static MemNode *__RemoveMemNode(AllocList *const list, MemNode *const node) 244 | { 245 | if (node->prev != NULL) node->prev->next = node->next; 246 | else 247 | { 248 | list->head = node->next; 249 | if (list->head != NULL) list->head->prev = NULL; 250 | else list->tail = NULL; 251 | } 252 | 253 | if (node->next != NULL) node->next->prev = node->prev; 254 | else 255 | { 256 | list->tail = node->prev; 257 | if (list->tail != NULL) list->tail->next = NULL; 258 | else list->head = NULL; 259 | } 260 | 261 | list->len--; 262 | 263 | return node; 264 | } 265 | 266 | static MemNode *__FindMemNode(AllocList *const list, const size_t bytes) 267 | { 268 | for (MemNode *node = list->head; node != NULL; node = node->next) 269 | { 270 | if (node->size < bytes) continue; 271 | 272 | // Close in size - reduce fragmentation by not splitting 273 | else if (node->size <= bytes + MEM_SPLIT_THRESHOLD) return __RemoveMemNode(list, node); 274 | else return __SplitMemNode(node, bytes); 275 | } 276 | 277 | return NULL; 278 | } 279 | 280 | static void __InsertMemNode(MemPool *const mempool, AllocList *const list, MemNode *const node, const bool is_bucket) 281 | { 282 | if (list->head == NULL) 283 | { 284 | list->head = node; 285 | list->len++; 286 | } 287 | else 288 | { 289 | for (MemNode *iter = list->head; iter != NULL; iter = iter->next) 290 | { 291 | if ((uintptr_t)iter == mempool->arena.offs) 292 | { 293 | mempool->arena.offs += iter->size; 294 | __RemoveMemNode(list, iter); 295 | iter = list->head; 296 | 297 | if (iter == NULL) 298 | { 299 | list->head = node; 300 | return; 301 | } 302 | } 303 | 304 | const uintptr_t inode = (uintptr_t)node; 305 | const uintptr_t iiter = (uintptr_t)iter; 306 | const uintptr_t iter_end = iiter + iter->size; 307 | const uintptr_t node_end = inode + node->size; 308 | 309 | if (iter == node) return; 310 | else if (iter < node) 311 | { 312 | // node was coalesced prior. 313 | if (iter_end > inode) return; 314 | else if ((iter_end == inode) && !is_bucket) 315 | { 316 | // if we can coalesce, do so. 317 | iter->size += node->size; 318 | 319 | return; 320 | } 321 | else if (iter->next == NULL) 322 | { 323 | // we reached the end of the free list -> append the node 324 | iter->next = node; 325 | node->prev = iter; 326 | list->len++; 327 | 328 | return; 329 | } 330 | } 331 | else if (iter > node) 332 | { 333 | // Address sort, lowest to highest aka ascending order. 334 | if (iiter < node_end) return; 335 | else if ((iter == list->head) && !is_bucket) 336 | { 337 | if (iter_end == inode) iter->size += node->size; 338 | else if (node_end == iiter) 339 | { 340 | node->size += list->head->size; 341 | node->next = list->head->next; 342 | node->prev = NULL; 343 | list->head = node; 344 | } 345 | else 346 | { 347 | node->next = iter; 348 | node->prev = NULL; 349 | iter->prev = node; 350 | list->head = node; 351 | list->len++; 352 | } 353 | 354 | return; 355 | } 356 | else if ((iter_end == inode) && !is_bucket) 357 | { 358 | // if we can coalesce, do so. 359 | iter->size += node->size; 360 | return; 361 | } 362 | else 363 | { 364 | __InsertMemNodeBefore(list, node, iter); 365 | list->len++; 366 | return; 367 | } 368 | } 369 | } 370 | } 371 | } 372 | 373 | //---------------------------------------------------------------------------------- 374 | // Module Functions Definition - Memory Pool 375 | //---------------------------------------------------------------------------------- 376 | 377 | MemPool CreateMemPool(const size_t size) 378 | { 379 | MemPool mempool = { 0 }; 380 | 381 | if (size == 0) return mempool; 382 | else 383 | { 384 | // Align the mempool size to at least the size of an alloc node. 385 | uint8_t *const restrict buf = malloc(size*sizeof *buf); 386 | 387 | if (buf==NULL) return mempool; 388 | else 389 | { 390 | mempool.arena.size = size; 391 | mempool.arena.mem = (uintptr_t)buf; 392 | mempool.arena.offs = mempool.arena.mem + mempool.arena.size; 393 | 394 | return mempool; 395 | } 396 | } 397 | } 398 | 399 | MemPool CreateMemPoolFromBuffer(void *const restrict buf, const size_t size) 400 | { 401 | MemPool mempool = { 0 }; 402 | 403 | if ((size == 0) || (buf == NULL) || (size <= sizeof(MemNode))) return mempool; 404 | else 405 | { 406 | mempool.arena.size = size; 407 | mempool.arena.mem = (uintptr_t)buf; 408 | mempool.arena.offs = mempool.arena.mem + mempool.arena.size; 409 | 410 | return mempool; 411 | } 412 | } 413 | 414 | void DestroyMemPool(MemPool *const restrict mempool) 415 | { 416 | if (mempool->arena.mem == 0) return; 417 | else 418 | { 419 | void *const restrict ptr = (void *)mempool->arena.mem; 420 | free(ptr); 421 | *mempool = (MemPool){ 0 }; 422 | } 423 | } 424 | 425 | void *MemPoolAlloc(MemPool *const mempool, const size_t size) 426 | { 427 | if ((size == 0) || (size > mempool->arena.size)) return NULL; 428 | else 429 | { 430 | MemNode *new_mem = NULL; 431 | const size_t ALLOC_SIZE = __AlignSize(size + sizeof *new_mem, sizeof(intptr_t)); 432 | const size_t BUCKET_SLOT = (ALLOC_SIZE >> MEMPOOL_BUCKET_BITS) - 1; 433 | 434 | // If the size is small enough, let's check if our buckets has a fitting memory block. 435 | if (BUCKET_SLOT < MEMPOOL_BUCKET_SIZE) 436 | { 437 | new_mem = __FindMemNode(&mempool->buckets[BUCKET_SLOT], ALLOC_SIZE); 438 | } 439 | else if (mempool->large.head != NULL) 440 | { 441 | new_mem = __FindMemNode(&mempool->large, ALLOC_SIZE); 442 | } 443 | 444 | if (new_mem == NULL) 445 | { 446 | // not enough memory to support the size! 447 | if ((mempool->arena.offs - ALLOC_SIZE) < mempool->arena.mem) return NULL; 448 | else 449 | { 450 | // Couldn't allocate from a freelist, allocate from available mempool. 451 | // Subtract allocation size from the mempool. 452 | mempool->arena.offs -= ALLOC_SIZE; 453 | 454 | // Use the available mempool space as the new node. 455 | new_mem = ( MemNode* )mempool->arena.offs; 456 | new_mem->size = ALLOC_SIZE; 457 | } 458 | } 459 | 460 | // Visual of the allocation block. 461 | // -------------- 462 | // | mem size | lowest addr of block 463 | // | next node | 12 byte (32-bit) header 464 | // | prev node | 24 byte (64-bit) header 465 | // |------------| 466 | // | alloc'd | 467 | // | memory | 468 | // | space | highest addr of block 469 | // -------------- 470 | new_mem->next = new_mem->prev = NULL; 471 | uint8_t *const restrict final_mem = (uint8_t *)new_mem + sizeof *new_mem; 472 | 473 | return memset(final_mem, 0, new_mem->size - sizeof *new_mem); 474 | } 475 | } 476 | 477 | void *MemPoolRealloc(MemPool *const restrict mempool, void *const ptr, const size_t size) 478 | { 479 | if (size > mempool->arena.size) return NULL; 480 | // NULL ptr should make this work like regular Allocation 481 | else if (ptr == NULL) return MemPoolAlloc(mempool, size); 482 | else if ((uintptr_t)ptr - sizeof(MemNode) < mempool->arena.mem) return NULL; 483 | else 484 | { 485 | MemNode *const node = (MemNode *)((uint8_t *)ptr - sizeof *node); 486 | const size_t NODE_SIZE = sizeof *node; 487 | uint8_t *const resized_block = MemPoolAlloc(mempool, size); 488 | 489 | if (resized_block == NULL) return NULL; 490 | else 491 | { 492 | MemNode *const resized = (MemNode *)(resized_block - sizeof *resized); 493 | memmove(resized_block, ptr, (node->size > resized->size)? (resized->size - NODE_SIZE) : (node->size - NODE_SIZE)); 494 | MemPoolFree(mempool, ptr); 495 | 496 | return resized_block; 497 | } 498 | } 499 | } 500 | 501 | void MemPoolFree(MemPool *const restrict mempool, void *const ptr) 502 | { 503 | const uintptr_t p = (uintptr_t)ptr; 504 | 505 | if ((ptr == NULL) || (p - sizeof(MemNode) < mempool->arena.mem)) return; 506 | else 507 | { 508 | // Behind the actual pointer data is the allocation info. 509 | const uintptr_t block = p - sizeof(MemNode); 510 | MemNode *const mem_node = ( MemNode* )block; 511 | const size_t BUCKET_SLOT = (mem_node->size >> MEMPOOL_BUCKET_BITS) - 1; 512 | 513 | // Make sure the pointer data is valid. 514 | if ((block < mempool->arena.offs) || 515 | ((block - mempool->arena.mem) > mempool->arena.size) || 516 | (mem_node->size == 0) || 517 | (mem_node->size > mempool->arena.size)) return; 518 | // If the mem_node is right at the arena offs, then merge it back to the arena. 519 | else if (block == mempool->arena.offs) 520 | { 521 | mempool->arena.offs += mem_node->size; 522 | } 523 | else 524 | { 525 | // try to place it into bucket or large freelist. 526 | struct AllocList *const l = (BUCKET_SLOT < MEMPOOL_BUCKET_SIZE) ? &mempool->buckets[BUCKET_SLOT] : &mempool->large; 527 | __InsertMemNode(mempool, l, mem_node, (BUCKET_SLOT < MEMPOOL_BUCKET_SIZE)); 528 | } 529 | } 530 | } 531 | 532 | void MemPoolCleanUp(MemPool *const restrict mempool, void **const ptrref) 533 | { 534 | if ((ptrref == NULL) || (*ptrref == NULL)) return; 535 | else 536 | { 537 | MemPoolFree(mempool, *ptrref); 538 | *ptrref = NULL; 539 | } 540 | } 541 | 542 | size_t GetMemPoolFreeMemory(const MemPool mempool) 543 | { 544 | size_t total_remaining = mempool.arena.offs - mempool.arena.mem; 545 | 546 | for (MemNode *n = mempool.large.head; n != NULL; n = n->next) total_remaining += n->size; 547 | 548 | for (size_t i = 0; i < MEMPOOL_BUCKET_SIZE; i++) for (MemNode *n = mempool.buckets[i].head; n != NULL; n = n->next) total_remaining += n->size; 549 | 550 | return total_remaining; 551 | } 552 | 553 | void MemPoolReset(MemPool *const mempool) 554 | { 555 | mempool->large.head = mempool->large.tail = NULL; 556 | mempool->large.len = 0; 557 | 558 | for (size_t i = 0; i < MEMPOOL_BUCKET_SIZE; i++) 559 | { 560 | mempool->buckets[i].head = mempool->buckets[i].tail = NULL; 561 | mempool->buckets[i].len = 0; 562 | } 563 | 564 | mempool->arena.offs = mempool->arena.mem + mempool->arena.size; 565 | } 566 | 567 | //---------------------------------------------------------------------------------- 568 | // Module Functions Definition - Object Pool 569 | //---------------------------------------------------------------------------------- 570 | 571 | ObjPool CreateObjPool(const size_t objsize, const size_t len) 572 | { 573 | ObjPool objpool = { 0 }; 574 | 575 | if ((len == 0) || (objsize == 0)) return objpool; 576 | else 577 | { 578 | const size_t aligned_size = __AlignSize(objsize, sizeof(size_t)); 579 | uint8_t *const restrict buf = calloc(len, aligned_size); 580 | 581 | if (buf == NULL) return objpool; 582 | objpool.objSize = aligned_size; 583 | objpool.memSize = objpool.freeBlocks = len; 584 | objpool.mem = (uintptr_t)buf; 585 | 586 | for (size_t i=0; imem == 0) return; 625 | else 626 | { 627 | void *const restrict ptr = (void *)objpool->mem; 628 | free(ptr); 629 | 630 | *objpool = (ObjPool){ 0 }; 631 | } 632 | } 633 | 634 | void *ObjPoolAlloc(ObjPool *const objpool) 635 | { 636 | if (objpool->freeBlocks > 0) 637 | { 638 | // For first allocation, head points to the very first index. 639 | // Head = &pool[0]; 640 | // ret = Head == ret = &pool[0]; 641 | size_t *const restrict block = (size_t *)objpool->offs; 642 | objpool->freeBlocks--; 643 | 644 | // After allocating, we set head to the address of the index that *Head holds. 645 | // Head = &pool[*Head * pool.objsize]; 646 | objpool->offs = (objpool->freeBlocks != 0)? objpool->mem + (*block*objpool->objSize) : 0; 647 | 648 | return memset(block, 0, objpool->objSize); 649 | } 650 | else return NULL; 651 | } 652 | 653 | void ObjPoolFree(ObjPool *const restrict objpool, void *const ptr) 654 | { 655 | uintptr_t block = (uintptr_t)ptr; 656 | 657 | if ((ptr == NULL) || (block < objpool->mem) || (block > objpool->mem + objpool->memSize*objpool->objSize)) return; 658 | else 659 | { 660 | // When we free our pointer, we recycle the pointer space to store the previous index and then we push it as our new head. 661 | // *p = index of Head in relation to the buffer; 662 | // Head = p; 663 | size_t *const restrict index = (size_t *)block; 664 | *index = (objpool->offs != 0)? (objpool->offs - objpool->mem)/objpool->objSize : objpool->memSize; 665 | objpool->offs = block; 666 | objpool->freeBlocks++; 667 | } 668 | } 669 | 670 | void ObjPoolCleanUp(ObjPool *const restrict objpool, void **const restrict ptrref) 671 | { 672 | if (ptrref == NULL) return; 673 | else 674 | { 675 | ObjPoolFree(objpool, *ptrref); 676 | *ptrref = NULL; 677 | } 678 | } 679 | 680 | 681 | //---------------------------------------------------------------------------------- 682 | // Module Functions Definition - Double-Ended Stack 683 | //---------------------------------------------------------------------------------- 684 | 685 | BiStack CreateBiStack(const size_t len) 686 | { 687 | BiStack destack = { 0 }; 688 | 689 | if (len == 0) return destack; 690 | 691 | uint8_t *const buf = malloc(len*sizeof *buf); 692 | if (buf == NULL) return destack; 693 | destack.size = len; 694 | destack.mem = (uintptr_t)buf; 695 | destack.front = destack.mem; 696 | destack.back = destack.mem + len; 697 | 698 | return destack; 699 | } 700 | 701 | BiStack CreateBiStackFromBuffer(void *const buf, const size_t len) 702 | { 703 | BiStack destack = { 0 }; 704 | 705 | if ((len == 0) || (buf == NULL)) return destack; 706 | else 707 | { 708 | destack.size = len; 709 | destack.mem = destack.front = (uintptr_t)buf; 710 | destack.back = destack.mem + len; 711 | 712 | return destack; 713 | } 714 | } 715 | 716 | void DestroyBiStack(BiStack *const restrict destack) 717 | { 718 | if (destack->mem == 0) return; 719 | else 720 | { 721 | uint8_t *const restrict buf = (uint8_t *)destack->mem; 722 | free(buf); 723 | *destack = (BiStack){ 0 }; 724 | } 725 | } 726 | 727 | void *BiStackAllocFront(BiStack *const restrict destack, const size_t len) 728 | { 729 | if (destack->mem == 0) return NULL; 730 | else 731 | { 732 | const size_t ALIGNED_LEN = __AlignSize(len, sizeof(uintptr_t)); 733 | // front end arena is too high! 734 | if (destack->front + ALIGNED_LEN >= destack->back) return NULL; 735 | else 736 | { 737 | uint8_t *const restrict ptr = (uint8_t *)destack->front; 738 | destack->front += ALIGNED_LEN; 739 | 740 | return ptr; 741 | } 742 | } 743 | } 744 | 745 | void *BiStackAllocBack(BiStack *const restrict destack, const size_t len) 746 | { 747 | if (destack->mem == 0) return NULL; 748 | else 749 | { 750 | const size_t ALIGNED_LEN = __AlignSize(len, sizeof(uintptr_t)); 751 | // back end arena is too low 752 | if (destack->back - ALIGNED_LEN <= destack->front) return NULL; 753 | else 754 | { 755 | destack->back -= ALIGNED_LEN; 756 | uint8_t *const restrict ptr = (uint8_t *)destack->back; 757 | 758 | return ptr; 759 | } 760 | } 761 | } 762 | 763 | void BiStackResetFront(BiStack *const destack) 764 | { 765 | if (destack->mem == 0) return; 766 | else destack->front = destack->mem; 767 | } 768 | 769 | void BiStackResetBack(BiStack *const destack) 770 | { 771 | if (destack->mem == 0) return; 772 | else destack->back = destack->mem + destack->size; 773 | } 774 | 775 | void BiStackResetAll(BiStack *const destack) 776 | { 777 | BiStackResetBack(destack); 778 | BiStackResetFront(destack); 779 | } 780 | 781 | inline intptr_t BiStackMargins(const BiStack destack) 782 | { 783 | return destack.back - destack.front; 784 | } 785 | 786 | #endif // RMEM_IMPLEMENTATION 787 | --------------------------------------------------------------------------------