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))));