mem_allocator.c (11435B)
1 /* Copyright (C) 2013-2023, 2025 Vincent Forest (vaplv@free.fr) 2 * 3 * The RSys library is free software: you can redistribute it and/or modify 4 * it under the terms of the GNU General Public License as published 5 * by the Free Software Foundation, either version 3 of the License, or 6 * (at your option) any later version. 7 * 8 * The RSys library is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License 14 * along with the RSys library. If not, see <http://www.gnu.org/licenses/>. */ 15 16 #define _POSIX_C_SOURCE 200112L /* snprintf support */ 17 #include "math.h" 18 #include "mem_allocator.h" 19 #include "mutex.h" 20 21 #include <errno.h> 22 #if defined(OS_MACH) 23 #include <malloc/malloc.h> 24 #else 25 #include <malloc.h> 26 #endif 27 #include <string.h> 28 29 #ifdef OS_WINDOWS 30 /* On Windows the _aligned_msize function is not defined. The size is thus 31 * stored into the memory block header */ 32 #define MEM_HEADER_SIZE (2 * sizeof(size_t)) 33 #include "io_c99.h" 34 #endif 35 36 struct alloc_counter { 37 ATOMIC nb_allocs; 38 ATOMIC allocated_size; 39 }; 40 41 /******************************************************************************* 42 * Common allocation functions 43 ******************************************************************************/ 44 static struct alloc_counter g_alloc_counter = { 0, 0 }; 45 46 void* 47 mem_alloc(const size_t size) 48 { 49 void* mem = NULL; 50 if(size) { 51 #if defined(OS_UNIX) || defined (OS_MACH) 52 mem = malloc(size); 53 if(mem) { 54 ATOMIC_ADD(&g_alloc_counter.allocated_size, mem_size(mem)); 55 ATOMIC_INCR(&g_alloc_counter.nb_allocs); 56 } 57 #elif defined(OS_WINDOWS) 58 const size_t DEFAULT_ALIGNMENT = 16; 59 mem = _aligned_offset_malloc 60 (size + MEM_HEADER_SIZE, DEFAULT_ALIGNMENT, MEM_HEADER_SIZE); 61 if(mem) { 62 ((size_t*)mem)[0] = DEFAULT_ALIGNMENT; 63 ((size_t*)mem)[1] = size + MEM_HEADER_SIZE; 64 mem = ((char*)mem) + MEM_HEADER_SIZE; 65 ATOMIC_ADD(&g_alloc_counter.allocated_size, mem_size(mem)); 66 ATOMIC_INCR(&g_alloc_counter.nb_allocs); 67 } 68 #else 69 #error "Unsupported OS" 70 #endif 71 } 72 return mem; 73 } 74 75 void* 76 mem_calloc(const size_t nelmts, const size_t size) 77 { 78 void* mem = NULL; 79 const size_t alloc_size = nelmts * size; 80 mem = mem_alloc(alloc_size); 81 if(mem) { 82 memset(mem, 0, alloc_size); 83 } 84 return mem; 85 } 86 87 void* 88 mem_realloc(void* mem, const size_t size) 89 { 90 void* new_mem = NULL; 91 92 if(mem == NULL) { 93 new_mem = mem_alloc(size); 94 } else if(size == 0) { 95 mem_rm(mem); 96 } else { 97 const size_t old_size = mem_size(mem); 98 99 ASSERT 100 ( old_size < SIZE_MAX 101 && g_alloc_counter.allocated_size >= (int64_t)old_size); 102 ATOMIC_SUB( &g_alloc_counter.allocated_size, old_size); 103 104 #if defined(OS_WINDOWS) 105 mem = ((char*)mem) - MEM_HEADER_SIZE; 106 new_mem = _aligned_offset_realloc 107 (mem, size + MEM_HEADER_SIZE, ((size_t*)mem)[0], MEM_HEADER_SIZE); 108 if(new_mem) { 109 ((size_t*)new_mem)[1] = size + MEM_HEADER_SIZE; 110 new_mem = ((char*)new_mem) + MEM_HEADER_SIZE; 111 } 112 #elif defined(OS_UNIX) || defined(OS_MACH) 113 new_mem = realloc( mem, size ); 114 #else 115 #error "Unsupported OS" 116 #endif 117 ATOMIC_ADD(&g_alloc_counter.allocated_size, mem_size(new_mem)); 118 } 119 return new_mem; 120 121 } 122 void* 123 mem_alloc_aligned(const size_t size, const size_t alignment) 124 { 125 void* mem = NULL; 126 127 if(size 128 && IS_POW2( alignment ) 129 && alignment <= 32768 /* 32 KB */) { 130 #if defined(OS_WINDOWS) 131 mem = _aligned_offset_malloc 132 (size + MEM_HEADER_SIZE, alignment, MEM_HEADER_SIZE); 133 if(mem) { 134 ((size_t*)mem)[0] = alignment; 135 ((size_t*)mem)[1] = size + MEM_HEADER_SIZE; 136 mem = ((char*)mem) + MEM_HEADER_SIZE; 137 ATOMIC_ADD(&g_alloc_counter.allocated_size, mem_size(mem)); 138 ATOMIC_INCR(&g_alloc_counter.nb_allocs); 139 } 140 #elif defined(OS_UNIX) || defined(OS_MACH) 141 const int result = posix_memalign 142 (&mem, (alignment < sizeof(void*)) ? sizeof(void*) : alignment, size); 143 (void)result; /* avoid warning in Release */ 144 /* The following assert may not occur due to previous conditions */ 145 ASSERT(result != EINVAL); 146 ASSERT((result != ENOMEM) || (mem == NULL)); 147 if(mem) { 148 ATOMIC_ADD(&g_alloc_counter.allocated_size, mem_size(mem)); 149 ATOMIC_INCR(&g_alloc_counter.nb_allocs); 150 } 151 #else 152 #error "Unsupported OS" 153 #endif 154 } 155 return mem; 156 } 157 158 void 159 mem_rm(void* mem) 160 { 161 if(mem) { 162 ASSERT 163 ( g_alloc_counter.nb_allocs != 0 164 && mem_size(mem) < SIZE_MAX 165 && g_alloc_counter.allocated_size >= (int64_t)mem_size(mem)); 166 ATOMIC_SUB(&g_alloc_counter.allocated_size, mem_size(mem)); 167 ATOMIC_DECR(&g_alloc_counter.nb_allocs); 168 #if defined(OS_WINDOWS) 169 mem = ((char*)mem) - MEM_HEADER_SIZE; 170 _aligned_free( mem ); 171 #elif defined(OS_UNIX) || defined(OS_MACH) 172 free( mem ); 173 #else 174 #error "Unsupported OS" 175 #endif 176 } 177 } 178 179 size_t 180 mem_size(void* mem) 181 { 182 size_t mem_size = 0; 183 if(mem) { 184 #if defined(OS_WINDOWS) 185 void* raw_mem = ((char*)mem) - MEM_HEADER_SIZE; 186 mem_size = ((size_t*)raw_mem)[1]; 187 #elif defined(OS_UNIX) 188 mem_size = malloc_usable_size(mem); 189 #elif defined(OS_MACH) 190 mem_size = malloc_size(mem); 191 #else 192 #error "Unsupported OS" 193 #endif 194 } 195 return mem_size; 196 } 197 198 size_t 199 mem_allocated_size(void) 200 { 201 return (size_t)g_alloc_counter.allocated_size; 202 } 203 204 /******************************************************************************* 205 * Default allocator functions 206 ******************************************************************************/ 207 #define TRACK_DEFAULT_ALLOC /* Enable the tracking of default allocations */ 208 209 static void* 210 default_alloc 211 (void* data, 212 const size_t size, 213 const char* filename, 214 const unsigned int fileline) 215 { 216 void* mem = NULL; 217 218 (void)filename; 219 (void)fileline; 220 221 if(size) { 222 mem = mem_alloc(size); 223 #ifndef TRACK_DEFAULT_ALLOC 224 (void)data; 225 #else 226 ASSERT(data); 227 if(mem) { 228 struct alloc_counter* counter = data; 229 const size_t size_mem = mem_size(mem); 230 ATOMIC_ADD(&counter->allocated_size, size_mem); 231 ATOMIC_INCR(&counter->nb_allocs); 232 } 233 #endif /* TRACK_DEFAULT_ALLOC */ 234 } 235 return mem; 236 } 237 238 static void 239 default_free(void* data, void* mem) 240 { 241 if(mem) { 242 #ifndef TRACK_DEFAULT_ALLOC 243 (void)data; 244 #else 245 struct alloc_counter* counter = data; 246 size_t size_mem = mem_size(mem); 247 ASSERT 248 ( (data != NULL) 249 & (counter->nb_allocs != 0) 250 & (counter->allocated_size >= (int64_t)size_mem)); 251 252 ATOMIC_SUB(&counter->allocated_size, size_mem); 253 ATOMIC_DECR(&counter->nb_allocs); 254 #endif /* TRACK_DEFAULT_ALLOC */ 255 mem_rm(mem); 256 } 257 } 258 259 static void* 260 default_calloc 261 (void* data, 262 const size_t nbelmts, 263 const size_t size, 264 const char* filename, 265 const unsigned int fileline) 266 { 267 void* mem = NULL; 268 const size_t alloc_size = nbelmts * size; 269 270 mem = default_alloc(data, alloc_size, filename, fileline); 271 if(mem) { 272 memset(mem, 0, alloc_size); 273 } 274 return mem; 275 } 276 277 static void* 278 default_realloc 279 (void* data, 280 void* mem, 281 const size_t size, 282 const char* filename, 283 const unsigned int fileline) 284 { 285 void* new_mem = NULL; 286 287 #ifndef TRACK_DEFAULT_ALLOC 288 (void)data; 289 (void)filename; 290 (void)fileline; 291 new_mem = mem_realloc(mem, size); 292 #else 293 ASSERT(data); 294 if(!mem) { 295 new_mem = default_alloc(data, size, filename, fileline); 296 } else { 297 if(size == 0) { 298 default_free(data, mem); 299 } else { 300 struct alloc_counter* counter = data; 301 const size_t size_old = mem_size(mem); 302 size_t size_new = 0; 303 304 ASSERT(counter->allocated_size >= (int64_t)size_old); 305 ATOMIC_SUB(&counter->allocated_size, size_old); 306 307 new_mem = mem_realloc(mem, size); 308 size_new = mem_size(new_mem); 309 ATOMIC_ADD(&counter->allocated_size, size_new); 310 } 311 } 312 #endif /* TRACK_DEFAULT_ALLOC */ 313 return new_mem; 314 } 315 316 static void* 317 default_alloc_aligned 318 (void* data, 319 const size_t size, 320 const size_t alignment, 321 const char* filename, 322 const unsigned int fileline) 323 { 324 void* mem = NULL; 325 326 (void)filename; 327 (void)fileline; 328 329 if(size && IS_POW2(alignment)) { 330 mem = mem_alloc_aligned(size, alignment); 331 #ifndef TRACK_DEFAULT_ALLOC 332 (void)data; 333 #else 334 ASSERT(data); 335 if(mem) { 336 struct alloc_counter* counter = data; 337 const size_t size_mem = mem_size(mem); 338 ATOMIC_ADD(&counter->allocated_size, size_mem); 339 ATOMIC_INCR(&counter->nb_allocs); 340 } 341 #endif /* TRACK_DEFAULT_ALLOC */ 342 } 343 return mem; 344 } 345 346 static size_t 347 default_mem_size(void* data, void* mem) 348 { 349 (void)data; 350 return mem_size(mem); 351 } 352 353 static size_t 354 default_allocated_size(const void* data) 355 { 356 #ifndef TRACK_DEFAULT_ALLOC 357 (void)data; 358 return 0; 359 #else 360 const struct alloc_counter* counter = data; 361 ASSERT(counter != NULL); 362 return (size_t)counter->allocated_size; 363 #endif /* TRACK_DEFAULT_ALLOC */ 364 } 365 366 static size_t 367 default_dump 368 (const void* data, 369 char* dump, 370 const size_t max_dump_len) 371 { 372 #ifndef TRACK_DEFAULT_ALLOC 373 (void)data; 374 if(dump && max_dump_len) 375 dump[0] = '\0'; 376 return 0; 377 378 #else 379 const struct alloc_counter* counter = data; 380 size_t dump_len = 0; 381 int len = 0; 382 383 ASSERT(counter && (!max_dump_len || dump)); 384 385 len = snprintf 386 (dump, 387 max_dump_len, 388 "%lu bytes allocated in %lu allocations.", 389 (unsigned long)counter->allocated_size, 390 (unsigned long)counter->nb_allocs); 391 ASSERT(len >= 0); 392 dump_len = (size_t)len; 393 394 if((size_t)len >= (max_dump_len - 1)) /* -1 <=> null char. */ 395 dump[max_dump_len-1] = '\0'; 396 397 return dump_len; 398 #endif 399 } 400 401 /******************************************************************************* 402 * Default allocator 403 ******************************************************************************/ 404 static struct alloc_counter default_alloc_counter = {0, 0}; 405 406 struct mem_allocator mem_default_allocator = { 407 default_alloc, 408 default_calloc, 409 default_realloc, 410 default_alloc_aligned, 411 default_free, 412 default_mem_size, 413 default_allocated_size, 414 default_dump, 415 (void*)&default_alloc_counter 416 }; 417 418 /******************************************************************************* 419 * Regular allocator 420 ******************************************************************************/ 421 res_T 422 mem_init_regular_allocator(struct mem_allocator* allocator) 423 { 424 struct alloc_counter* counter = NULL; 425 res_T res = RES_OK; 426 427 if(!allocator) { 428 res = RES_BAD_ARG; 429 goto error; 430 } 431 memset(allocator, 0, sizeof(struct mem_allocator)); 432 433 counter = mem_calloc(1, sizeof(struct alloc_counter)); 434 if(!counter) { 435 res = RES_MEM_ERR; 436 goto error; 437 } 438 439 allocator->alloc = default_alloc; 440 allocator->calloc = default_calloc; 441 allocator->realloc = default_realloc; 442 allocator->mem_size = default_mem_size; 443 allocator->alloc_aligned = default_alloc_aligned; 444 allocator->rm = default_free; 445 allocator->allocated_size = default_allocated_size; 446 allocator->dump = default_dump; 447 allocator->data = (void*)counter; 448 449 exit: 450 return res; 451 error: 452 if(allocator) mem_shutdown_regular_allocator(allocator); 453 goto exit; 454 } 455 456 void 457 mem_shutdown_regular_allocator(struct mem_allocator* allocator) 458 { 459 struct alloc_counter* counter; 460 ASSERT(allocator); 461 462 counter = allocator->data; 463 if(counter) { 464 ASSERT(!counter->allocated_size && !counter->nb_allocs); 465 mem_rm(counter); 466 } 467 memset(allocator, 0, sizeof(struct mem_allocator)); 468 }