1 /**
2     Numem Internal Lifetime Handling.
3 
4     This module implements the neccesary functionality to instantiate
5     complex D types, including handling of copy constructors,
6     destructors, moving, and copying.
7     
8     Copyright:
9         Copyright © 2023-2025, Kitsunebi Games
10         Copyright © 2023-2025, Inochi2D Project
11     
12     License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
13     Authors:   Luna Nielsen
14 */
15 module numem.core.lifetime;
16 import numem.core.hooks;
17 import numem.core.traits;
18 import numem.core.exception;
19 import numem.core.memory;
20 import numem.casting;
21 import numem.lifetime : nogc_construct, nogc_initialize, nogc_delete;
22 
23 // Deletion function signature.
24 private extern (D) alias fp_t = void function (Object) @nogc nothrow;
25 
26 // Helper which creates a destructor function that
27 // D likes.
28 private template xdtor(T) {
29     void xdtor(ref T obj) {
30         obj.__xdtor();
31     }
32 }
33 
34 /**
35     Initializes the memory at the specified chunk.
36 */
37 void initializeAt(T)(scope ref T chunk) @nogc nothrow @trusted {
38     static if (is(T == class)) {
39 
40         // NOTE: class counts as a pointer, so its normal init symbol
41         // in general circumstances is null, we don't want this, so class check
42         // should be first! Otherwise the chunk = T.init will mess us up.
43         const void[] initSym = __traits(initSymbol, T);
44         nu_memcpy(cast(void*)chunk, cast(void*)initSym.ptr, initSym.length);
45     } else static if (__traits(isZeroInit, T)) {
46 
47         nu_memset(cast(void*)&chunk, 0, T.sizeof);
48     } else static if (__traits(isScalar, T) || 
49         (T.sizeof <= 16 && !hasElaborateAssign!T && __traits(compiles, () { T chunk; chunk = T.init; }))) {
50 
51         // To avoid triggering postblits/move constructors we need to do a memcpy here as well.
52         // If the user wants to postblit after initialization, they should call the relevant postblit function.
53         T tmp = T.init;
54         nu_memcpy(cast(void*)&chunk, &tmp, T.sizeof);
55     } else static if (__traits(isStaticArray, T)) {
56 
57         foreach(i; 0..T.length)
58             initializeAt(chunk[i]);
59     } else {
60 
61         const void[] initSym = __traits(initSymbol, T);
62         nu_memcpy(cast(void*)&chunk, initSym.ptr, initSym.length);
63     }
64 }
65 
66 /**
67     Initializes the memory at the specified chunk, but ensures no 
68     context pointers are wiped.
69 */
70 void initializeAtNoCtx(T)(scope ref T chunk) @nogc nothrow @trusted {
71     static if (__traits(isZeroInit, T)) {
72 
73         nu_memset(cast(void*)&chunk, 0, T.sizeof);
74     } else static if (__traits(isScalar, T) || 
75         (T.sizeof <= 16 && !hasElaborateAssign!T && __traits(compiles, () { T chunk; chunk = T.init; }))) {
76 
77         // To avoid triggering postblits/move constructors we need to do a memcpy here as well.
78         // If the user wants to postblit after initialization, they should call the relevant postblit function.
79         T tmp = T.init;
80         nu_memcpy(cast(void*)&chunk, &tmp, T.sizeof);
81     } else static if (__traits(isStaticArray, T)) {
82 
83         foreach(i; 0..T.length)
84             initializeAt(chunk[i]);
85     } else {
86 
87         const void[] initSym = __traits(initSymbol, T);
88         nu_memcpy(cast(void*)&chunk, initSym.ptr, initSym.length);
89     }
90 }
91 
92 /**
93     Destroy element with a destructor.
94 */
95 @trusted
96 void destruct(T, bool reInit=true)(ref T obj_) @nogc {
97     alias RealT = Unref!T;
98 
99     // Handle custom destruction functions.
100     alias destroyWith = nu_getdestroywith!RealT;
101     static if (is(typeof(destroyWith))) {
102         destroyWith(obj_);
103     } else {
104         static if (isHeapAllocated!T) {
105             if (obj_ !is null) {
106                 static if (hasAnyDestructor!RealT) {
107                     static if (__traits(getLinkage, RealT) == "D") {
108                         auto cInfo = cast(ClassInfo)typeid(obj_);
109                         if (cInfo) {
110                             auto c = cInfo;
111 
112                             // Call destructors in order of most specific
113                             // to least-specific
114                             do {
115                                 if (c.destructor)
116                                     (cast(fp_t)c.destructor)(cast(Object)obj_);
117                             } while((c = c.base) !is null);
118                             
119                         } else {
120 
121                             // Item is a struct, we can destruct it directly.
122                             static if (__traits(hasMember, RealT, "__xdtor")) {
123                                 assumeNoGC(&obj_.__xdtor);
124                             } else static if (__traits(hasMember, RealT, "__dtor")) {
125                                 assumeNoGC(&obj_.__dtor);
126                             }
127                         }
128                     } else static if (__traits(getLinkage, RealT) == "C++") {
129 
130                         // C++ and Objective-C types may have D destructors declared
131                         // with extern(D), in that case, just call those.
132 
133                         static if (__traits(hasMember, RealT, "__xdtor")) {
134                             assumeNoGC(&xdtor!T, obj_);
135                         }
136                     } else static if (__traits(hasMember, RealT, "__xdtor")) {
137                         
138                         // Item is liekly a struct, we can destruct it directly.
139                         assumeNoGC(&xdtor!T, obj_);
140                     }
141                 }
142             }
143         } else {
144 
145             // Item is a struct, we can destruct it directly.
146             static if (__traits(hasMember, RealT, "__xdtor")) {
147                 assumeNoGC(&obj_.__xdtor);
148             } else static if (__traits(hasMember, RealT, "__dtor")) {
149                 assumeNoGC(&obj_.__dtor);
150             }
151         }
152     }
153 
154     static if (reInit)
155         initializeAt(obj_);
156 }
157 
158 /**
159     Runs constructor for the memory at dst
160 */
161 void emplace(T, UT, Args...)(ref UT dst, auto ref Args args) @nogc {
162 
163     enum bool usePlacementNew = (__VERSION__ >= 2111) 
164         && (is(T == class) || (args.length == 0));
165 
166     static if(usePlacementNew) {
167         static if (is(T == class)) {
168 
169             // NOTE: Since we need to handle inner-classes
170             // we need to initialize here instead of next to the ctor.
171             initializeAt(dst);
172 
173             // Shove it in a mixin, that more or less prevents the compiler
174             // complaining.
175             mixin(q{ dst = new(nu_storageT(dst)) T(args); });
176         }  else static if (args.length == 0) {
177             static assert(is(typeof({static T i;})),
178                 "Cannot emplace a " ~ T.stringof ~ ", its constructor is marked with @disable.");
179             
180             initializeAt(dst);
181         }
182         else 
183             static assert(false);
184 
185     } else {
186         enum isConstructibleOther =
187             (!is(T == struct) && Args.length == 1) ||                        // Primitives, enums, arrays.
188             (Args.length == 1 && is(typeof({T t = forward!(args[0]); }))) || // Conversions
189             is(typeof(T(forward!args)));                                     // General constructors.
190 
191         static if (is(T == class)) {
192 
193             static assert(!__traits(isAbstractClass, T), 
194                 T.stringof ~ " is abstract and can't be emplaced.");
195 
196             // NOTE: Since we need to handle inner-classes
197             // we need to initialize here instead of next to the ctor.
198             initializeAt(dst);
199             
200             static if (isInnerClass!T) {
201                 static assert(Args.length > 0,
202                     "Initializing an inner class requires a pointer to the outer class");
203                 
204                 static assert(is(Args[0] : typeof(T.outer)),
205                     "The first argument must be a pointer to the outer class");
206                 
207                 chunk.outer = args[0];
208                 alias fargs = args[1..$];
209                 alias fargsT = Args[1..$];
210 
211             } else {
212                 alias fargs = args;
213                 alias fargsT = Args;
214             }
215 
216             static if (is(typeof(dst.__ctor(forward!fargs)))) {
217                 dst.__ctor(forward!args);
218             } else {
219                 static assert(fargs.length == 0 && !is(typeof(&T.__ctor)),
220                     "No constructor for " ~ T.stringof ~ " found matching arguments "~fargsT.stringof~"!");
221             }
222 
223         } else static if (args.length == 0) {
224 
225             static assert(is(typeof({static T i;})),
226                 "Cannot emplace a " ~ T.stringof ~ ", its constructor is marked with @disable.");
227             initializeAt(dst);
228         } else static if (isConstructibleOther) {
229 
230             // Handler struct which forwards construction
231             // to the payload.
232             static struct S {
233                 T payload;
234                 this()(auto ref Args args) {
235                     static if (__traits(compiles, payload = forward!args))
236                         payload = forward!args;
237                     else
238                         payload = T(forward!args);
239                 }
240             }
241 
242             if (__ctfe) {
243                 static if (__traits(compiles, dst = T(forward!args)))
244                     dst = T(forward!args);
245                 else static if(args.length == 1 && __traits(compiles, dst = forward!(args[0])))
246                     dst = forward!(args[0]);
247                 else static assert(0,
248                     "Can't emplace " ~ T.stringof ~ " at compile-time using " ~ Args.stringof ~ ".");
249             } else {
250                 S* p = cast(S*)cast(void*)&dst;
251                 static if (UT.sizeof > 0)
252                     initializeAt(*p);
253                 
254                 p.__ctor(forward!args);
255             }
256         } else static if (is(typeof(dst.__ctor(forward!args)))) {
257             
258             initializeAt(dst);
259             chunk.__ctor(forward!args);
260         } else {
261             static assert(!(Args.length == 1 && is(Args[0] : T)),
262                 "Can't emplace a " ~ T.stringof ~ " because the postblit is disabled.");
263 
264             static assert(0, 
265                 "No constructor for " ~ T.stringof ~ " found matching arguments "~fargs.stringof~"!");
266         }
267     }
268 }
269 
270 /// ditto
271 void emplace(UT, Args...)(auto ref UT dst, auto ref Args args) @nogc {
272     emplace!(UT, UT, Args)(dst, forward!args);
273 }
274 
275 /**
276     Copies source to target.
277 */
278 void __copy(S, T)(ref S source, ref T target) @nogc @system {
279     static if (is(T == struct)) {
280         static if (!__traits(hasCopyConstructor, T))
281             __blit(target, source);
282         
283         static if (hasElaborateCopyConstructor!T)
284             __copy_postblit(source, target);
285     } else static if (is(T == E[n], E, size_t n)) {
286         
287         // Some kind of array or range.
288         static if (hasElaborateCopyConstructor!E) {
289             size_t i;
290             try {
291                 for(i = 0; i < n; i++)
292                     __copy(source[i], target[i]);
293             } catch(Exception ex) {
294                 while(i--) {
295                     auto ref_ = const_cast!(Unconst!(E)*)(&target[i]);
296                     destruct(ref_);
297                     nu_free(cast(void*)ref_);
298                 }
299                 throw e;
300             }
301         } else static if (!__traits(hasCopyConstructor, T))
302             __blit(target, source);
303     } else {
304         *(const_cast!(Unconst!(T)*)(&target)) = *const_cast!(Unconst!(T)*)(&source);
305     }
306 }
307 
308 /**
309     Moves $(D source) to $(D target), via destructive copy if neccesary.
310 
311     $(D source) will be reset to its init state after the move.
312 */
313 void __move(S, T)(ref S source, ref T target) @nogc @trusted {
314     static if (is(T == struct) && hasElaborateDestructor!T) {
315         if(&source is &target)
316             return;
317 
318         destruct!(T, false)(target);
319     }
320 
321     return __moveImpl(source, target);
322 }
323 
324 /// ditto
325 T __move(T)(ref return scope T source) @nogc @trusted {
326     T target = void;
327     __moveImpl(source, target);
328     return target;
329 }
330 
331 private
332 pragma(inline, true)
333 void __moveImpl(S, T)(ref S source, ref T target) @nogc @system {
334     static if(is(T == struct)) {
335         assert(&source !is &target, "Source and target must not be identical");
336         __blit(target, source);
337         
338         static if (hasElaborateMove!T)
339             __move_postblit(target, source);
340 
341         // If the source defines a destructor or a postblit the type needs to be
342         // obliterated to avoid double frees and undue aliasing.
343         static if (hasElaborateDestructor!T || hasElaborateCopyConstructor!T) {
344             initializeAtNoCtx(source);
345         }
346     } else static if (is(T == E[n], E, size_t n)) {
347         static if (!hasElaborateMove!T && 
348                    !hasElaborateDestructor!T && 
349                    !hasElaborateCopyConstructor!T) {
350             
351             assert(source.ptr !is target.ptr, "Source and target must not be identical");
352             __blit(target, source);
353             initializeAt(source);
354         } else {
355             foreach(i; 0..source.length) {
356                 __move(source[i], target[i]);
357             }
358         }
359     } else {
360         target = source;
361         initializeAt(source);
362     }
363 }
364 
365 /**
366     Blits instance $(D from) to location $(D to).
367 
368     Effectively this acts as a simple memory copy, 
369     a postblit needs to be run after to finalize the object.
370 */
371 pragma(inline, true)
372 void __blit(T)(ref T to, ref T from) @nogc @system nothrow {
373     nu_memcpy(const_cast!(Unqual!T*)(&to), const_cast!(Unqual!T*)(&from), AllocSize!T);
374 }
375 
376 /**
377     Runs postblit operations for a copy operation.
378 */
379 pragma(inline, true)
380 void __copy_postblit(S, T)(ref S source, ref T target) @nogc @system {
381     static if (__traits(hasPostblit, T)) {
382         dst.__xpostblit();
383     } else static if (__traits(hasCopyConstructor, T)) {
384 
385         // https://issues.dlang.org/show_bug.cgi?id=22766
386         initializeAt(target);
387 
388         // Copy context pointer if needed.
389         static if (__traits(isNested, T))
390             *(cast(void**)&target.tupleof[$-1]) = cast(void*) source.tupleof[$-1];
391         
392         // Invoke copy ctor.
393         target.__ctor(source);
394     }
395 }
396 
397 /**
398     Ported from D runtime, this function is released under the boost license.
399 
400     Recursively calls the $(D opPostMove) callbacks of a struct and its members if
401     they're defined.
402 
403     When moving a struct instance, the compiler emits a call to this function
404     after blitting the instance and before releasing the original instance's
405     memory.
406 
407     Params:
408         newLocation = reference to struct instance being moved into
409         oldLocation = reference to the original instance
410 
411     Notes:
412         $(D __move_postblit) will do nothing if the type does not support elaborate moves.
413 */
414 pragma(inline, true)
415 void __move_postblit(T)(ref T newLocation, ref T oldLocation) @nogc @system {
416     static if (is(T == struct)) {
417 
418         // Call __move_postblit for all members which have move semantics.
419         static foreach(i, M; typeof(T.tupleof)) {
420             static if (hasElaborateMove!T) {
421                 __move_postblit(newLocation.tupleof[i], oldLocation.tupleof[i]);
422             }
423         }
424 
425         static if (__traits(hasMember, T, "opPostMove")) {
426             static assert(is(typeof(T.init.opPostMove(lvalueOf!T))) &&
427                           !is(typeof(T.init.opPostMove(rvalueOf!T))),
428                 "`" ~ T.stringof ~ ".opPostMove` must take exactly one argument of type `" ~ T.stringof ~ "` by reference");
429         
430             newLocation.opPostMove(oldLocation);
431         }
432     } else static if (__traits(isStaticArray, T)) {
433         static if (T.length && hasElaborateMove!(typeof(newLocation[0]))) {
434             foreach(i; 0..T.length)
435                 __move_postblit(newLocation[i], oldLocation[i]);
436         }
437     }
438 }
439 
440 /**
441     Forwards function arguments while keeping $(D out), $(D ref), and $(D lazy) on
442     the parameters.
443 
444     Params:
445         args = a parameter list or an $(REF AliasSeq,std,meta).
446     
447     Returns:
448         An $(D AliasSeq) of $(D args) with $(D out), $(D ref), and $(D lazy) saved.
449 */
450 template forward(args...)
451 {
452     import core.internal.traits : AliasSeq;
453     import numem.object;
454 
455     template fwd(alias arg)
456     {
457         // by ref || lazy || const/immutable
458         static if (__traits(isRef,  arg) ||
459                    __traits(isOut,  arg) ||
460                    __traits(isLazy, arg) ||
461                    !is(typeof(__move(arg))))
462             alias fwd = arg;
463         // (r)value
464         else
465             @property auto fwd()
466             {
467                 version (DigitalMars) { /* @@BUG 23890@@ */ } else pragma(inline, true);
468                 return __move(arg);
469             }
470     }
471 
472     alias Result = AliasSeq!();
473     static foreach (arg; args)
474         Result = AliasSeq!(Result, fwd!arg);
475     static if (Result.length == 1)
476         alias forward = Result[0];
477     else
478         alias forward = Result;
479 }
480 
481 /**
482     UDA which allows specifying which functions numem should call when
483     destroying an object with $(D destruct).
484 */
485 struct nu_destroywith(alias handlerFunc) {
486 private:
487     alias Handler = handlerFunc;
488 }
489 
490 /**
491     UDA which allows specifying which functions numem should call when
492     autoreleasing an object with $(D nu_autorelease).
493 */
494 struct nu_autoreleasewith(alias handlerFunc) {
495 private:
496     alias Handler = handlerFunc;
497 }
498 
499 /**
500     Adds the given item to the topmost auto release pool.
501 
502     Params:
503         item =  The item to automatically be destroyed when the pool
504                 goes out of scope.
505     
506     Returns:
507         $(D true) if the item was successfully added to the pool,
508         $(D false) otherwise.
509 */
510 bool nu_autorelease(T)(T item) @trusted @nogc {
511     static if (isValidObjectiveC!T) {
512         item.autorelease();
513     } else {
514         alias autoreleaseWith = nu_getautoreleasewith!T;
515         
516         if (nu_arpool_stack.length > 0) {
517             nu_arpool_stack[$-1].push(
518                 nu_arpool_element(
519                     cast(void*)item,
520                     (void* obj) {
521                         T obj_ = cast(T)obj;
522                         static if (is(typeof(autoreleaseWith))) {
523                             autoreleaseWith(obj_);
524                         } else {
525                             nogc_delete!(T)(obj_);
526                         }
527                     }
528                 )
529             );
530             return true;
531         }
532         return false;
533     }
534 }
535 
536 /**
537     Pushes an auto release pool onto the pool stack.
538 
539     Returns:
540         A context pointer, meaning is arbitrary.
541 
542     Memorysafety:
543         $(D nu_autoreleasepool_push) and $(D nu_autoreleasepool_pop) are internal
544         API and are not safely used outside of the helpers.
545     
546     See_Also:
547         $(D numem.lifetime.autoreleasepool_scope), 
548         $(D numem.lifetime.autoreleasepool)
549 */
550 void* nu_autoreleasepool_push() @system @nogc {
551     nu_arpool_stack.nu_resize(nu_arpool_stack.length+1);
552     nogc_construct(nu_arpool_stack[$-1]);
553 
554     if (nuopt_autoreleasepool_push)
555         nu_arpool_stack[$-1].fctx = nuopt_autoreleasepool_push();
556 
557     
558     return cast(void*)&nu_arpool_stack[$-1];
559 }
560 
561 /**
562     Pops an auto release pool from the pool stack.
563 
564     Params:
565         ctx = A context pointer.
566 
567     Memorysafety:
568         $(D nu_autoreleasepool_push) and $(D nu_autoreleasepool_pop) are internal
569         API and are not safely used outside of the helpers.
570     
571     See_Also:
572         $(D numem.lifetime.autoreleasepool_scope), 
573         $(D numem.lifetime.autoreleasepool)
574 */
575 void nu_autoreleasepool_pop(void* ctx) @system @nogc {
576     if (nu_arpool_stack.length > 0) {
577         assert(ctx == &nu_arpool_stack[$-1], "Misaligned auto release pool sequence!");
578 
579         nu_arpool_stack.nu_resize(cast(ptrdiff_t)nu_arpool_stack.length-1);
580         if (nuopt_autoreleasepool_pop)
581             nuopt_autoreleasepool_pop(ctx);
582     }
583 }
584 
585 
586 //
587 //          INTERNAL
588 //
589 
590 private:
591 import numem.object : NuRefCounted;
592 
593 // auto-release pool stack.
594 __gshared nu_arpool_ctx[] nu_arpool_stack;
595 
596 // Handler function type.
597 alias nu_arpool_handler_t = void function(void*) @nogc;
598 
599 // Handlers within an auto-release pool context.
600 struct nu_arpool_element {
601     void* ptr;
602     nu_arpool_handler_t handler;
603 }
604 
605 // An autorelease pool context.
606 struct nu_arpool_ctx {
607 @nogc:
608     nu_arpool_element[] queue;
609     void* fctx;
610 
611     ~this() {
612         foreach_reverse(ref item; queue) {
613             item.handler(item.ptr);
614             nogc_initialize(item);
615         }
616         queue.nu_resize(0);
617     }
618 
619     void push(nu_arpool_element element) {
620         queue.nu_resize(queue.length+1);
621         queue[$-1] = element;
622     }
623 }
624 
625 // DESTROY UDA
626 
627 template nu_getdestroywith(T, A...) {
628     static if (is(typeof(__traits(getAttributes, Unref!T)))) {
629         static if (A.length == 0) {
630             alias attrs = __traits(getAttributes, Unref!T);
631 
632             static if (attrs.length > 0)
633                 alias nu_getdestroywith = nu_getdestroywith!(Unref!T, attrs);
634             else
635                 alias nu_getdestroywith = void;
636         } else static if (A.length == 1) {
637             static if (nu_isdestroywith!(T, A[0]))
638                 alias nu_getdestroywith = A[0].Handler;
639             else
640                 alias nu_getdestroywith = void;
641         } else static if (nu_isdestroywith!(T, A[0]))
642                 alias nu_getdestroywith = A[0].Handler;
643             else
644                 alias nu_getdestroywith = nu_getdestroywith!(T, A[1 .. $]);
645     } else {
646         alias nu_getdestroywith = void;
647     }
648 }
649 
650 enum nu_isdestroywith(T, alias H) = 
651     __traits(identifier, H) == __traits(identifier, nu_destroywith) &&
652     is(typeof(H.Handler)) && 
653     is(typeof((H.Handler(lvalueOf!T))));
654 
655 // AUTORELEASE UDA
656 
657 template nu_getautoreleasewith(T, A...) {
658     static if (is(typeof(__traits(getAttributes, Unref!T)))) {
659         static if (A.length == 0) {
660             alias attrs = __traits(getAttributes, Unref!T);
661 
662             static if (attrs.length > 0)
663                 alias nu_getautoreleasewith = nu_getautoreleasewith!(Unref!T, attrs);
664             else
665                 alias nu_getautoreleasewith = void;
666         } else static if (A.length == 1) {
667             static if (nu_isautoreleasewith!(T, A[0]))
668                 alias nu_getautoreleasewith = A[0].Handler;
669             else
670                 alias nu_getautoreleasewith = void;
671         } else static if (nu_isautoreleasewith!(T, A[0]))
672                 alias nu_getautoreleasewith = A[0].Handler;
673             else
674                 alias nu_getautoreleasewith = nu_getautoreleasewith!(T, A[1 .. $]);
675     } else {
676         alias nu_getautoreleasewith = void;
677     }
678 }
679 
680 enum nu_isautoreleasewith(T, alias H) = 
681     __traits(identifier, H) == __traits(identifier, nu_autoreleasewith) &&
682     is(typeof(H.Handler)) && 
683     is(typeof((H.Handler(lvalueOf!T))));