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 }