1 /** 2 Additional Memory Handling. 3 4 Functions that build on the numem hooks to implement higher level memory 5 managment functionality. 6 7 Copyright: 8 Copyright © 2023-2025, Kitsunebi Games 9 Copyright © 2023-2025, Inochi2D Project 10 11 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 12 Authors: Luna Nielsen, Guillaume Piolat 13 */ 14 module numem.core.memory; 15 import numem.core.hooks; 16 import numem.core.traits; 17 import numem.core.math; 18 import numem.lifetime; 19 20 /** 21 System pointer size. 22 */ 23 enum size_t ALIGN_PTR_SIZE = (void*).sizeof; 24 25 /** 26 Allocates enough memory to contain Type T. 27 28 Returns: 29 Newly allocated memory or $(D null) on failure. 30 To avoid a memory leak, free the memory with $(D nu_free). 31 32 Notes: 33 Given the implementation of $(D nu_malloc) and $(D nu_free) may be 34 independent of the libc allocator, memory allocated with 35 $(D nu_malloc) should $(B always) be freed with $(D nu_free)! 36 */ 37 ref void[AllocSize!T] nu_mallocT(T)() @nogc nothrow @trusted { 38 return nu_malloc(AllocSize!T)[0..AllocSize!T]; 39 } 40 41 /** 42 Gets the storage space used by $(D object). 43 44 Params: 45 object = The object to get the storage space of. 46 47 Returns: 48 The storage of the provided object; cast to a static 49 void array reference. 50 */ 51 ref void[AllocSize!T] nu_storageT(T)(ref T object) @nogc nothrow @trusted { 52 static if (AllocSize!T == T.sizeof) 53 return object; 54 else { 55 return (cast(void*)object)[0..AllocSize!T]; 56 } 57 } 58 59 /** 60 Resizes a slice to be of the given size and alignment. 61 If the slice is not yet allocated, it will be. 62 63 When creating a slice with complex types you may wish to chain the resize 64 operation with $(D numem.lifetime.nogc_initialize). 65 66 Set $(D length) to $(D 0) to free the buffer. 67 68 Params: 69 buffer = The buffer to resize. 70 length = The length of the buffer (in elements.) 71 alignment = The alignment of the buffer (in bytes.) 72 73 Notes: 74 $(UL 75 $(LI 76 Resizing the buffer to be smaller than it was originally will 77 cause the elements to be deleted; if, and only if the type 78 is an aggregate value type (aka. $(D struct) or $(D union)) 79 and said type has an elaborate destructor. 80 ) 81 $(LI 82 Class pointers will NOT be deleted, this must be done 83 manually. 84 ) 85 $(LI 86 The memory allocated by nu_resize will NOT be initialized, 87 you must chain it with $(D numem.lifetime.nogc_initialize) 88 if the types rely on interior pointers. 89 ) 90 ) 91 92 Threadsafety: 93 The underlying data will, if possible be updated atomically, however 94 this does $(B NOT) prevent you from accessing stale references 95 elsewhere. If you wish to access a slice across threads, you should 96 use synchronisation primitives such as a mutex. 97 98 Returns: 99 The resized buffer. 100 */ 101 ref T[] nu_resize(T)(ref T[] buffer, size_t length, int alignment = 1) @nogc { 102 static if (hasElaborateDestructor!T && !isHeapAllocated!T) { 103 import numem.lifetime : nogc_delete; 104 105 if (length < buffer.length) { 106 107 static if (isRefcounted!T) { 108 foreach(i; length..buffer.length) { 109 buffer[i].nu_release(); 110 } 111 } else { 112 113 // Handle destructor invocation. 114 nogc_delete!(T, false)(buffer[length..buffer.length]); 115 } 116 117 // Handle buffer deletion. 118 if (length == 0) { 119 if (buffer.length > 0) 120 nu_aligned_free(cast(void*)buffer.ptr, alignment); 121 122 buffer = null; 123 return buffer; 124 } 125 } 126 127 } else { 128 129 // No destructors, just free normally. 130 if (length == 0) { 131 if (buffer.length > 0) 132 nu_aligned_free(cast(void*)buffer.ptr, alignment); 133 134 buffer = null; 135 return buffer; 136 } 137 } 138 139 T* ptr = cast(T*)nu_aligned_realloc(cast(void*)buffer.ptr, T.sizeof * length, alignment); 140 buffer = ptr !is null ? ptr[0..length] : null; 141 return buffer; 142 } 143 144 /** 145 Allocates and initializes a new slice. 146 147 Params: 148 count = The number of elements to allocate. 149 150 Returns: 151 The allocated array or a zero-length array on 152 error. 153 */ 154 T[] nu_malloca(T)(size_t count) { 155 T[] tmp; 156 tmp = tmp.nu_resize(count); 157 nogc_initialize(tmp[0..count]); 158 return tmp; 159 } 160 161 /** 162 Frees a slice. 163 164 Notes: 165 This function will call destructors for types that have destructors 166 (implicit or otherwise). you may use $(D nogc_zeroinit) to zero fill the 167 slice before freeing if need be. 168 169 Params: 170 slice = The slice to free. 171 */ 172 void nu_freea(T)(ref T[] slice) { 173 static if (isRefcounted!T) { 174 foreach(i; 0..slice.length) { 175 nu_release(slice[i]); 176 } 177 } else static if (hasAnyDestructor!T) { 178 nogc_delete(slice[0..$]); 179 } 180 181 nu_free(cast(void*)slice.ptr); 182 slice = null; 183 } 184 185 /** 186 Creates a shallow duplicate of the given buffer. 187 188 Params: 189 buffer = Buffer to duplicate. 190 191 Memorysafety: 192 This function copies data out of the string into a new 193 memory allocation; as such it has to be freed. 194 It is otherwise safe, in that it won't modify 195 the original memory provided. 196 197 Returns: 198 Duplicated slice, must be freed with $(D nu_resize) 199 */ 200 inout(T)[] nu_dup(T)(inout(T)[] buffer) @nogc @trusted { 201 T[] buf; 202 203 buf.nu_resize(buffer.length); 204 nu_memcpy(cast(void*)buf.ptr, cast(void*)buffer.ptr, buf.length*T.sizeof); 205 return cast(inout(T)[])buf; 206 } 207 208 /** 209 Creates a shallow immutable duplicate of the given buffer. 210 211 Params: 212 buffer = Buffer to duplicate. 213 214 Memorysafety: 215 This function copies data out of the slice into a new 216 memory allocation; as such it has to be freed. 217 It is otherwise safe, in that it won't modify 218 the original memory provided. 219 220 Returns: 221 Duplicated slice, must be freed with $(D nu_resize) 222 */ 223 immutable(T)[] nu_idup(T)(inout(T)[] buffer) @nogc @trusted { 224 return cast(immutable(T)[])nu_dup(buffer); 225 } 226 227 /** 228 Gets the retain function for type T and calls it. 229 */ 230 T nu_retain(T)(auto ref T value) @nogc @trusted 231 if (isRefcounted!T) { 232 static if (isObjectiveC!T) { 233 return cast(T)value.retain(); 234 } else static if(isCOMClass!T) { 235 value.AddRef(); 236 return cast(T)value; 237 } else { 238 static foreach(rcName; rcRetainNames) { 239 static if (!is(__found) && __traits(hasMember, T, rcName)) { 240 static if (is(ReturnType!(typeof(__traits(getMember, value, rcName))) : T)) 241 return __traits(getMember, value, rcName)(); 242 else { 243 __traits(getMember, value, rcName)(); 244 return cast(T)value; 245 } 246 247 enum __found = true; 248 } 249 } 250 } 251 } 252 253 /** 254 Calls the $(D release) function of reference counted type 255 $(D T). 256 257 Params: 258 value = The value to reduce the reference count of. 259 260 Returns: 261 If possible, the new state of $(D value). 262 */ 263 T nu_release(T)(auto ref T value) @nogc @trusted 264 if (isRefcounted!T) { 265 static if (isObjectiveC!T) { 266 return cast(T)value.release(); 267 } else static if(isCOMClass!T) { 268 value.Release(); 269 return cast(T)value; 270 } else { 271 static foreach(rcName; rcReleaseNames) { 272 static if (!is(__found) && __traits(hasMember, T, rcName)) { 273 static if (is(ReturnType!(typeof(__traits(getMember, value, rcName))) : T)) 274 return __traits(getMember, value, rcName)(); 275 else { 276 __traits(getMember, value, rcName)(); 277 return cast(T)value; 278 } 279 280 enum __found = true; 281 } 282 } 283 } 284 } 285 286 /** 287 Appends a null terminator at the end of the string, 288 resizes the memory allocation if need be. 289 290 Params: 291 text = string to add a null-terminator to, in-place 292 293 Memorysafety: 294 This function is not memory safe, in that if you attempt 295 to use it on string literals it may lead to memory corruption 296 or crashes. This is meant to be used internally. It may reallocate 297 the underlying memory of the provided string, as such all prior 298 string references should be assumed to be invalid. 299 300 Returns: 301 Slice of the null-terminated string, the null terminator is hidden. 302 */ 303 inout(T)[] nu_terminate(T)(ref inout(T)[] text) @nogc @system 304 if (is(T == char) || is(T == wchar) || is(T == dchar)) { 305 306 // Early escape, empty string. 307 if (text.length == 0) 308 return text; 309 310 // Early escape out, already terminated. 311 if (text[$-1] == '\0') 312 return text[0..$-1]; 313 314 size_t termOffset = text.length; 315 316 // Resize by 1, add null terminator. 317 // Sometimes this won't be needed, if extra memory was 318 // already allocated. 319 text.nu_resize(text.length+1); 320 (cast(T*)text.ptr)[termOffset] = '\0'; 321 text = text[0..$-1]; 322 323 // Return length _without_ null terminator by slicing it out. 324 // The memory allocation is otherwise still the same. 325 return text; 326 } 327 328 /** 329 Swaps around 2 values of the same type. 330 331 The swap will be performed either by using move constructors, 332 or by direct memory blitting, bypassing copy construction. 333 334 Params: 335 a = First item to swap 336 b = Second item to swap. 337 */ 338 void nu_swap(T)(ref T a, ref T b) { 339 static if (is(typeof((ref T a, ref T b) { a.moveTo(b); b.moveTo(a); }))) { 340 auto tmp = a.move; 341 b.moveTo(a); 342 tmp.moveTo(a); 343 } else { 344 T tmp; 345 nu_memmove(&tmp, &a, T.sizeof); 346 nu_memmove(&a, &b, T.sizeof); 347 nu_memmove(&b, &tmp, T.sizeof); 348 } 349 } 350 351 /** 352 Gets whether 2 memory ranges are overlapping. 353 354 Params: 355 a = Start address of first range. 356 aLength = Length of first range, in bytes. 357 b = Start address of second range. 358 bLength = Length of second range, in bytes. 359 360 Returns: 361 $(D true) if range $(D a) and $(D b) overlaps, $(D false) otherwise. 362 It is assumed that start points at $(D null) and lengths of $(D 0) never 363 overlap. 364 365 Examples: 366 --- 367 int[] arr1 = [1, 2, 3, 4]; 368 int[] arr2 = arr1[1..$-1]; 369 370 size_t arr1len = arr1.length*int.sizeof; 371 size_t arr2len = arr2.length*int.sizeof; 372 373 // Test all iterations that are supported. 374 assert(nu_is_overlapping(arr1.ptr, arr1len, arr2.ptr, arr2len)); 375 assert(!nu_is_overlapping(arr1.ptr, arr1len, arr2.ptr, 0)); 376 assert(!nu_is_overlapping(arr1.ptr, 0, arr2.ptr, arr2len)); 377 assert(!nu_is_overlapping(null, arr1len, arr2.ptr, arr2len)); 378 assert(!nu_is_overlapping(arr1.ptr, arr1len, null, arr2len)); 379 --- 380 381 */ 382 export 383 extern(C) 384 bool nu_is_overlapping(void* a, size_t aLength, void* b, size_t bLength) @nogc nothrow { 385 386 // Early exit, null don't overlap. 387 if (a is null || b is null) 388 return false; 389 390 // Early exit, no length. 391 if (aLength == 0 || bLength == 0) 392 return false; 393 394 void* aEnd = a+aLength; 395 void* bEnd = b+bLength; 396 397 // Overlap occurs if src is within [dst..dstEnd] 398 // or dst is within [src..srcEnd] 399 if (a >= b && a < bEnd) 400 return true; 401 402 if (b >= a && b < aEnd) 403 return true; 404 405 return false; 406 } 407 408 /** 409 Gets the amount of bytes to allocate when requesting a specific amount of memory 410 with a given alignment. 411 412 Params: 413 request = How many bytes to allocate 414 alignment = The alignment of the requested allocation, 415 in bytes. 416 417 Returns: 418 $(D request) aligned to $(D alignment), taking in to account 419 pointer alignment requirements. 420 */ 421 export 422 extern(C) 423 size_t nu_aligned_size(size_t request, size_t alignment) nothrow @nogc @safe pure { 424 return nu_alignup(request, nu_alignup(alignment, ALIGN_PTR_SIZE * 2)); 425 } 426 427 /** 428 Realigns $(D ptr) to the next increment of $(D alignment) 429 430 Params: 431 ptr = A pointer 432 alignment = The alignment to adjust the pointer to. 433 434 Returns: 435 The next aligned pointer. 436 */ 437 export 438 extern(C) 439 void* nu_realign(void* ptr, size_t alignment) nothrow @nogc @trusted pure { 440 return cast(void*)nu_alignup!size_t(cast(size_t)ptr, alignment); 441 } 442 443 /** 444 Allocates memory with a given alignment. 445 446 Params: 447 size = The size of the allocation, in bytes. 448 alignment = The alignment of the allocation, in bytes. 449 450 Returns: 451 A new aligned pointer, $(D null) on failure. 452 453 See_Also: 454 $(D nu_aligned_realloc) 455 $(D nu_aligned_free) 456 */ 457 export 458 extern(C) 459 void* nu_aligned_alloc(size_t size, size_t alignment) nothrow @nogc pure { 460 assert(alignment != 0); 461 462 // Shortcut for tight alignment. 463 if (alignment == 1) 464 return nu_malloc(size); 465 466 size_t request = nu_aligned_size(size, alignment); 467 void* raw = nu_malloc(request); 468 469 return __nu_store_aligned_ptr(raw, size, alignment); 470 } 471 472 /** 473 Reallocates memory with a given alignment. 474 475 Params: 476 ptr = Pointer to prior allocation made with $(D nu_aligned_alloc) 477 size = The size of the allocation, in bytes. 478 alignment = The alignment of the allocation, in bytes. 479 480 Returns: 481 The address of the pointer after the reallocation, $(D null) on failure. 482 483 Notes: 484 The alignment provided $(B HAS) to match the original alignment of $(D ptr) 485 486 See_Also: 487 $(D nu_aligned_alloc) 488 $(D nu_aligned_free) 489 */ 490 export 491 extern(C) 492 void* nu_aligned_realloc(void* ptr, size_t size, size_t alignment) nothrow @nogc pure { 493 return __nu_aligned_realloc!true(ptr, size, alignment); 494 } 495 496 /** 497 Reallocates memory with a given alignment. 498 499 Params: 500 ptr = Pointer to prior allocation made with $(D nu_aligned_alloc) 501 size = The size of the allocation, in bytes. 502 alignment = The alignment of the allocation, in bytes. 503 504 Returns: 505 The address of the pointer after the reallocation, $(D null) on failure. 506 507 Notes: 508 The alignment provided $(B HAS) to match the original alignment of $(D ptr) 509 510 See_Also: 511 $(D nu_aligned_alloc) 512 $(D nu_aligned_free) 513 */ 514 export 515 extern(C) 516 void* nu_aligned_realloc_destructive(void* ptr, size_t size, size_t alignment) nothrow @nogc pure { 517 return __nu_aligned_realloc!false(ptr, size, alignment); 518 } 519 520 /** 521 Frees aligned memory. 522 523 Params: 524 ptr = Pointer to prior allocation made with $(D nu_aligned_alloc) 525 alignment = The alignment of the allocation, in bytes. 526 527 See_Also: 528 $(D nu_aligned_alloc) 529 $(D nu_aligned_realloc) 530 */ 531 export 532 extern(C) 533 void nu_aligned_free(void* ptr, size_t alignment) nothrow @nogc pure { 534 535 // Handle null case. 536 if (!ptr) 537 return; 538 539 // Handle unaligned memory. 540 if (alignment == 1) 541 return nu_free(ptr); 542 543 assert(alignment != 0); 544 assert(nu_is_aligned(ptr, alignment)); 545 546 void** rawLocation = cast(void**)(ptr - ALIGN_PTR_SIZE); 547 nu_free(*rawLocation); 548 } 549 550 private 551 void* __nu_store_aligned_ptr(void* ptr, size_t size, size_t alignment) nothrow @nogc pure { 552 553 // Handle null case. 554 if (!ptr) 555 return null; 556 557 void* start = ptr + ALIGN_PTR_SIZE * 2; 558 void* aligned = nu_realign(start, alignment); 559 560 // Update the location. 561 void** rawLocation = cast(void**)(aligned - ALIGN_PTR_SIZE); 562 nu_atomic_store_ptr(cast(void**)rawLocation, ptr); 563 564 // Update the size. 565 size_t* sizeLocation = cast(size_t*)(aligned - 2 * ALIGN_PTR_SIZE); 566 nu_atomic_store_ptr(cast(void**)sizeLocation, cast(void*)size); 567 568 assert(nu_is_aligned(aligned, alignment)); 569 return aligned; 570 } 571 572 private 573 void* __nu_aligned_realloc(bool preserveIfResized)(void* aligned, size_t size, size_t alignment) nothrow @nogc pure { 574 575 // Use normal realloc if there's no alignment. 576 if (alignment == 1) 577 return nu_realloc(aligned, size); 578 579 // Create if doesn't exist. 580 if (aligned is null) 581 return nu_aligned_alloc(size, alignment); 582 583 assert(alignment != 0); 584 assert(nu_is_aligned(aligned, alignment)); 585 586 size_t prevSize = *cast(size_t*)(aligned - ALIGN_PTR_SIZE * 2); 587 size_t prevRequest = nu_aligned_size(prevSize, alignment); 588 size_t request = nu_aligned_size(size, alignment); 589 590 // Ensure alignment matches. 591 assert(prevRequest - request == prevSize - size); 592 593 // Heuristic: if a requested size is within 50% to 100% of what is already allocated 594 // then exit with the same pointer 595 if ((prevRequest < request * 4) && (request <= prevRequest)) 596 return aligned; 597 598 void* newptr = nu_malloc(request); 599 if (request > 0 && newptr is null) 600 return null; 601 602 void* newAligned = __nu_store_aligned_ptr(newptr, size, alignment); 603 604 static if (preserveIfResized) { 605 size_t minSize = nu_min(size, prevSize); 606 nu_memcpy(newAligned, aligned, minSize); 607 } 608 nu_aligned_free(aligned, alignment); 609 610 assert(nu_is_aligned(newAligned, alignment)); 611 return newAligned; 612 }