1 /** 2 * The fiber module provides OS-indepedent lightweight threads aka fibers. 3 * 4 * Copyright: Copyright Sean Kelly 2005 - 2012. 5 * License: Distributed under the 6 * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). 7 * (See accompanying file LICENSE) 8 * Authors: Sean Kelly, Walter Bright, Alex Rønne Petersen, Martin Nowak 9 * Source: $(DRUNTIMESRC core/thread/fiber.d) 10 */ 11 12 module core.thread.fiber; 13 14 import core.thread.osthread; 15 import core.thread.threadgroup; 16 import core.thread.types; 17 import core.thread.context; 18 19 import core.memory : pageSize; 20 21 version(WebAssembly) {} else: 22 23 /////////////////////////////////////////////////////////////////////////////// 24 // Fiber Platform Detection 25 /////////////////////////////////////////////////////////////////////////////// 26 27 version (GNU) 28 { 29 import gcc.builtins; 30 version (GNU_StackGrowsDown) 31 version = StackGrowsDown; 32 } 33 else 34 { 35 // this should be true for most architectures 36 version = StackGrowsDown; 37 } 38 39 version (Windows) 40 { 41 import core.stdc.stdlib : malloc, free; 42 import core.sys.windows.winbase; 43 import core.sys.windows.winnt; 44 } 45 46 private 47 { 48 version (D_InlineAsm_X86) 49 { 50 version (Windows) 51 version = AsmX86_Windows; 52 else version (Posix) 53 version = AsmX86_Posix; 54 55 version = AlignFiberStackTo16Byte; 56 } 57 else version (D_InlineAsm_X86_64) 58 { 59 version (Windows) 60 { 61 version = AsmX86_64_Windows; 62 version = AlignFiberStackTo16Byte; 63 } 64 else version (Posix) 65 { 66 version = AsmX86_64_Posix; 67 version = AlignFiberStackTo16Byte; 68 } 69 } 70 else version (PPC) 71 { 72 version (OSX) 73 { 74 version = AsmPPC_Darwin; 75 version = AsmExternal; 76 version = AlignFiberStackTo16Byte; 77 } 78 else version (Posix) 79 { 80 version = AsmPPC_Posix; 81 version = AsmExternal; 82 } 83 } 84 else version (PPC64) 85 { 86 version (OSX) 87 { 88 version = AsmPPC_Darwin; 89 version = AsmExternal; 90 version = AlignFiberStackTo16Byte; 91 } 92 else version (Posix) 93 { 94 version = AlignFiberStackTo16Byte; 95 } 96 } 97 else version (MIPS_O32) 98 { 99 version (Posix) 100 { 101 version = AsmMIPS_O32_Posix; 102 version = AsmExternal; 103 } 104 } 105 else version (AArch64) 106 { 107 version (Posix) 108 { 109 version = AsmAArch64_Posix; 110 version = AsmExternal; 111 version = AlignFiberStackTo16Byte; 112 } 113 } 114 else version (ARM) 115 { 116 version (Posix) 117 { 118 version = AsmARM_Posix; 119 version = AsmExternal; 120 } 121 } 122 else version (SPARC) 123 { 124 // NOTE: The SPARC ABI specifies only doubleword alignment. 125 version = AlignFiberStackTo16Byte; 126 } 127 else version (SPARC64) 128 { 129 version = AlignFiberStackTo16Byte; 130 } 131 else version (LoongArch64) 132 { 133 version (Posix) 134 { 135 version = AsmLoongArch64_Posix; 136 version = AsmExternal; 137 version = AlignFiberStackTo16Byte; 138 } 139 } 140 141 version (Posix) 142 { 143 version (AsmX86_Windows) {} else 144 version (AsmX86_Posix) {} else 145 version (AsmX86_64_Windows) {} else 146 version (AsmX86_64_Posix) {} else 147 version (AsmExternal) {} else 148 { 149 // NOTE: The ucontext implementation requires architecture specific 150 // data definitions to operate so testing for it must be done 151 // by checking for the existence of ucontext_t rather than by 152 // a version identifier. Please note that this is considered 153 // an obsolescent feature according to the POSIX spec, so a 154 // custom solution is still preferred. 155 import core.sys.posix.ucontext; 156 } 157 } 158 } 159 160 /////////////////////////////////////////////////////////////////////////////// 161 // Fiber Entry Point and Context Switch 162 /////////////////////////////////////////////////////////////////////////////// 163 164 private 165 { 166 import core.atomic : atomicStore, cas, MemoryOrder; 167 import core.exception : onOutOfMemoryError; 168 import core.stdc.stdlib : abort; 169 170 extern (C) void fiber_entryPoint() nothrow 171 { 172 Fiber obj = Fiber.getThis(); 173 assert( obj ); 174 175 assert( Thread.getThis().m_curr is obj.m_ctxt ); 176 atomicStore!(MemoryOrder.raw)(*cast(shared)&Thread.getThis().m_lock, false); 177 obj.m_ctxt.tstack = obj.m_ctxt.bstack; 178 obj.m_state = Fiber.State.EXEC; 179 180 try 181 { 182 obj.run(); 183 } 184 catch ( Throwable t ) 185 { 186 obj.m_unhandled = t; 187 } 188 189 static if ( __traits( compiles, ucontext_t ) ) 190 obj.m_ucur = &obj.m_utxt; 191 192 obj.m_state = Fiber.State.TERM; 193 obj.switchOut(); 194 } 195 196 // Look above the definition of 'class Fiber' for some information about the implementation of this routine 197 version (AsmExternal) 198 { 199 extern (C) void fiber_switchContext( void** oldp, void* newp ) nothrow @nogc; 200 version (AArch64) 201 extern (C) void fiber_trampoline() nothrow; 202 } 203 else 204 extern (C) void fiber_switchContext( void** oldp, void* newp ) nothrow @nogc 205 { 206 // NOTE: The data pushed and popped in this routine must match the 207 // default stack created by Fiber.initStack or the initial 208 // switch into a new context will fail. 209 210 version (AsmX86_Windows) 211 { 212 asm pure nothrow @nogc 213 { 214 naked; 215 216 // save current stack state 217 push EBP; 218 mov EBP, ESP; 219 push EDI; 220 push ESI; 221 push EBX; 222 push dword ptr FS:[0]; 223 push dword ptr FS:[4]; 224 push dword ptr FS:[8]; 225 push EAX; 226 227 // store oldp again with more accurate address 228 mov EAX, dword ptr 8[EBP]; 229 mov [EAX], ESP; 230 // load newp to begin context switch 231 mov ESP, dword ptr 12[EBP]; 232 233 // load saved state from new stack 234 pop EAX; 235 pop dword ptr FS:[8]; 236 pop dword ptr FS:[4]; 237 pop dword ptr FS:[0]; 238 pop EBX; 239 pop ESI; 240 pop EDI; 241 pop EBP; 242 243 // 'return' to complete switch 244 pop ECX; 245 jmp ECX; 246 } 247 } 248 else version (AsmX86_64_Windows) 249 { 250 asm pure nothrow @nogc 251 { 252 naked; 253 254 // save current stack state 255 // NOTE: When changing the layout of registers on the stack, 256 // make sure that the XMM registers are still aligned. 257 // On function entry, the stack is guaranteed to not 258 // be aligned to 16 bytes because of the return address 259 // on the stack. 260 push RBP; 261 mov RBP, RSP; 262 push R12; 263 push R13; 264 push R14; 265 push R15; 266 push RDI; 267 push RSI; 268 // 7 registers = 56 bytes; stack is now aligned to 16 bytes 269 sub RSP, 160; 270 movdqa [RSP + 144], XMM6; 271 movdqa [RSP + 128], XMM7; 272 movdqa [RSP + 112], XMM8; 273 movdqa [RSP + 96], XMM9; 274 movdqa [RSP + 80], XMM10; 275 movdqa [RSP + 64], XMM11; 276 movdqa [RSP + 48], XMM12; 277 movdqa [RSP + 32], XMM13; 278 movdqa [RSP + 16], XMM14; 279 movdqa [RSP], XMM15; 280 push RBX; 281 xor RAX,RAX; 282 push qword ptr GS:[RAX]; 283 push qword ptr GS:8[RAX]; 284 push qword ptr GS:16[RAX]; 285 286 // store oldp 287 mov [RCX], RSP; 288 // load newp to begin context switch 289 mov RSP, RDX; 290 291 // load saved state from new stack 292 pop qword ptr GS:16[RAX]; 293 pop qword ptr GS:8[RAX]; 294 pop qword ptr GS:[RAX]; 295 pop RBX; 296 movdqa XMM15, [RSP]; 297 movdqa XMM14, [RSP + 16]; 298 movdqa XMM13, [RSP + 32]; 299 movdqa XMM12, [RSP + 48]; 300 movdqa XMM11, [RSP + 64]; 301 movdqa XMM10, [RSP + 80]; 302 movdqa XMM9, [RSP + 96]; 303 movdqa XMM8, [RSP + 112]; 304 movdqa XMM7, [RSP + 128]; 305 movdqa XMM6, [RSP + 144]; 306 add RSP, 160; 307 pop RSI; 308 pop RDI; 309 pop R15; 310 pop R14; 311 pop R13; 312 pop R12; 313 pop RBP; 314 315 // 'return' to complete switch 316 pop RCX; 317 jmp RCX; 318 } 319 } 320 else version (AsmX86_Posix) 321 { 322 asm pure nothrow @nogc 323 { 324 naked; 325 326 // save current stack state 327 push EBP; 328 mov EBP, ESP; 329 push EDI; 330 push ESI; 331 push EBX; 332 push EAX; 333 334 // store oldp again with more accurate address 335 mov EAX, dword ptr 8[EBP]; 336 mov [EAX], ESP; 337 // load newp to begin context switch 338 mov ESP, dword ptr 12[EBP]; 339 340 // load saved state from new stack 341 pop EAX; 342 pop EBX; 343 pop ESI; 344 pop EDI; 345 pop EBP; 346 347 // 'return' to complete switch 348 pop ECX; 349 jmp ECX; 350 } 351 } 352 else version (AsmX86_64_Posix) 353 { 354 asm pure nothrow @nogc 355 { 356 naked; 357 358 // save current stack state 359 push RBP; 360 mov RBP, RSP; 361 push RBX; 362 push R12; 363 push R13; 364 push R14; 365 push R15; 366 367 // store oldp 368 mov [RDI], RSP; 369 // load newp to begin context switch 370 mov RSP, RSI; 371 372 // load saved state from new stack 373 pop R15; 374 pop R14; 375 pop R13; 376 pop R12; 377 pop RBX; 378 pop RBP; 379 380 // 'return' to complete switch 381 pop RCX; 382 jmp RCX; 383 } 384 } 385 else static if ( __traits( compiles, ucontext_t ) ) 386 { 387 Fiber cfib = Fiber.getThis(); 388 void* ucur = cfib.m_ucur; 389 390 *oldp = &ucur; 391 swapcontext( **(cast(ucontext_t***) oldp), 392 *(cast(ucontext_t**) newp) ); 393 } 394 else version (FreeStanding) 395 assert(0); // FIXME this should be possible tbh 396 else 397 static assert(0, "Not implemented"); 398 } 399 } 400 401 402 /////////////////////////////////////////////////////////////////////////////// 403 // Fiber 404 /////////////////////////////////////////////////////////////////////////////// 405 /* 406 * Documentation of Fiber internals: 407 * 408 * The main routines to implement when porting Fibers to new architectures are 409 * fiber_switchContext and initStack. Some version constants have to be defined 410 * for the new platform as well, search for "Fiber Platform Detection and Memory Allocation". 411 * 412 * Fibers are based on a concept called 'Context'. A Context describes the execution 413 * state of a Fiber or main thread which is fully described by the stack, some 414 * registers and a return address at which the Fiber/Thread should continue executing. 415 * Please note that not only each Fiber has a Context, but each thread also has got a 416 * Context which describes the threads stack and state. If you call Fiber fib; fib.call 417 * the first time in a thread you switch from Threads Context into the Fibers Context. 418 * If you call fib.yield in that Fiber you switch out of the Fibers context and back 419 * into the Thread Context. (However, this is not always the case. You can call a Fiber 420 * from within another Fiber, then you switch Contexts between the Fibers and the Thread 421 * Context is not involved) 422 * 423 * In all current implementations the registers and the return address are actually 424 * saved on a Contexts stack. 425 * 426 * The fiber_switchContext routine has got two parameters: 427 * void** a: This is the _location_ where we have to store the current stack pointer, 428 * the stack pointer of the currently executing Context (Fiber or Thread). 429 * void* b: This is the pointer to the stack of the Context which we want to switch into. 430 * Note that we get the same pointer here as the one we stored into the void** a 431 * in a previous call to fiber_switchContext. 432 * 433 * In the simplest case, a fiber_switchContext rountine looks like this: 434 * fiber_switchContext: 435 * push {return Address} 436 * push {registers} 437 * copy {stack pointer} into {location pointed to by a} 438 * //We have now switch to the stack of a different Context! 439 * copy {b} into {stack pointer} 440 * pop {registers} 441 * pop {return Address} 442 * jump to {return Address} 443 * 444 * The GC uses the value returned in parameter a to scan the Fibers stack. It scans from 445 * the stack base to that value. As the GC dislikes false pointers we can actually optimize 446 * this a little: By storing registers which can not contain references to memory managed 447 * by the GC outside of the region marked by the stack base pointer and the stack pointer 448 * saved in fiber_switchContext we can prevent the GC from scanning them. 449 * Such registers are usually floating point registers and the return address. In order to 450 * implement this, we return a modified stack pointer from fiber_switchContext. However, 451 * we have to remember that when we restore the registers from the stack! 452 * 453 * --------------------------- <= Stack Base 454 * | Frame | <= Many other stack frames 455 * | Frame | 456 * |-------------------------| <= The last stack frame. This one is created by fiber_switchContext 457 * | registers with pointers | 458 * | | <= Stack pointer. GC stops scanning here 459 * | return address | 460 * |floating point registers | 461 * --------------------------- <= Real Stack End 462 * 463 * fiber_switchContext: 464 * push {registers with pointers} 465 * copy {stack pointer} into {location pointed to by a} 466 * push {return Address} 467 * push {Floating point registers} 468 * //We have now switch to the stack of a different Context! 469 * copy {b} into {stack pointer} 470 * //We now have to adjust the stack pointer to point to 'Real Stack End' so we can pop 471 * //the FP registers 472 * //+ or - depends on if your stack grows downwards or upwards 473 * {stack pointer} = {stack pointer} +- ({FPRegisters}.sizeof + {return address}.sizeof} 474 * pop {Floating point registers} 475 * pop {return Address} 476 * pop {registers with pointers} 477 * jump to {return Address} 478 * 479 * So the question now is which registers need to be saved? This depends on the specific 480 * architecture ABI of course, but here are some general guidelines: 481 * - If a register is callee-save (if the callee modifies the register it must saved and 482 * restored by the callee) it needs to be saved/restored in switchContext 483 * - If a register is caller-save it needn't be saved/restored. (Calling fiber_switchContext 484 * is a function call and the compiler therefore already must save these registers before 485 * calling fiber_switchContext) 486 * - Argument registers used for passing parameters to functions needn't be saved/restored 487 * - The return register needn't be saved/restored (fiber_switchContext hasn't got a return type) 488 * - All scratch registers needn't be saved/restored 489 * - The link register usually needn't be saved/restored (but sometimes it must be cleared - 490 * see below for details) 491 * - The frame pointer register - if it exists - is usually callee-save 492 * - All current implementations do not save control registers 493 * 494 * What happens on the first switch into a Fiber? We never saved a state for this fiber before, 495 * but the initial state is prepared in the initStack routine. (This routine will also be called 496 * when a Fiber is being resetted). initStack must produce exactly the same stack layout as the 497 * part of fiber_switchContext which saves the registers. Pay special attention to set the stack 498 * pointer correctly if you use the GC optimization mentioned before. the return Address saved in 499 * initStack must be the address of fiber_entrypoint. 500 * 501 * There's now a small but important difference between the first context switch into a fiber and 502 * further context switches. On the first switch, Fiber.call is used and the returnAddress in 503 * fiber_switchContext will point to fiber_entrypoint. The important thing here is that this jump 504 * is a _function call_, we call fiber_entrypoint by jumping before it's function prologue. On later 505 * calls, the user used yield() in a function, and therefore the return address points into a user 506 * function, after the yield call. So here the jump in fiber_switchContext is a _function return_, 507 * not a function call! 508 * 509 * The most important result of this is that on entering a function, i.e. fiber_entrypoint, we 510 * would have to provide a return address / set the link register once fiber_entrypoint 511 * returns. Now fiber_entrypoint does never return and therefore the actual value of the return 512 * address / link register is never read/used and therefore doesn't matter. When fiber_switchContext 513 * performs a _function return_ the value in the link register doesn't matter either. 514 * However, the link register will still be saved to the stack in fiber_entrypoint and some 515 * exception handling / stack unwinding code might read it from this stack location and crash. 516 * The exact solution depends on your architecture, but see the ARM implementation for a way 517 * to deal with this issue. 518 * 519 * The ARM implementation is meant to be used as a kind of documented example implementation. 520 * Look there for a concrete example. 521 * 522 * FIXME: fiber_entrypoint might benefit from a @noreturn attribute, but D doesn't have one. 523 */ 524 525 /** 526 * This class provides a cooperative concurrency mechanism integrated with the 527 * threading and garbage collection functionality. Calling a fiber may be 528 * considered a blocking operation that returns when the fiber yields (via 529 * Fiber.yield()). Execution occurs within the context of the calling thread 530 * so synchronization is not necessary to guarantee memory visibility so long 531 * as the same thread calls the fiber each time. Please note that there is no 532 * requirement that a fiber be bound to one specific thread. Rather, fibers 533 * may be freely passed between threads so long as they are not currently 534 * executing. Like threads, a new fiber thread may be created using either 535 * derivation or composition, as in the following example. 536 * 537 * Warning: 538 * Status registers are not saved by the current implementations. This means 539 * floating point exception status bits (overflow, divide by 0), rounding mode 540 * and similar stuff is set per-thread, not per Fiber! 541 * 542 * Warning: 543 * On ARM FPU registers are not saved if druntime was compiled as ARM_SoftFloat. 544 * If such a build is used on a ARM_SoftFP system which actually has got a FPU 545 * and other libraries are using the FPU registers (other code is compiled 546 * as ARM_SoftFP) this can cause problems. Druntime must be compiled as 547 * ARM_SoftFP in this case. 548 * 549 * Authors: Based on a design by Mikola Lysenko. 550 */ 551 class Fiber 552 { 553 /////////////////////////////////////////////////////////////////////////// 554 // Initialization 555 /////////////////////////////////////////////////////////////////////////// 556 557 version (Windows) 558 // exception handling walks the stack, invoking DbgHelp.dll which 559 // needs up to 16k of stack space depending on the version of DbgHelp.dll, 560 // the existence of debug symbols and other conditions. Avoid causing 561 // stack overflows by defaulting to a larger stack size 562 enum defaultStackPages = 8; 563 else version (OSX) 564 { 565 version (X86_64) 566 // libunwind on macOS 11 now requires more stack space than 16k, so 567 // default to a larger stack size. This is only applied to X86 as 568 // the pageSize is still 4k, however on AArch64 it is 16k. 569 enum defaultStackPages = 8; 570 else 571 enum defaultStackPages = 4; 572 } 573 else 574 enum defaultStackPages = 4; 575 576 /** 577 * Initializes a fiber object which is associated with a static 578 * D function. 579 * 580 * Params: 581 * fn = The fiber function. 582 * sz = The stack size for this fiber. 583 * guardPageSize = size of the guard page to trap fiber's stack 584 * overflows. Beware that using this will increase 585 * the number of mmaped regions on platforms using mmap 586 * so an OS-imposed limit may be hit. 587 * 588 * In: 589 * fn must not be null. 590 */ 591 this( void function() fn, size_t sz = pageSize * defaultStackPages, 592 size_t guardPageSize = pageSize ) nothrow 593 in 594 { 595 assert( fn ); 596 } 597 do 598 { 599 allocStack( sz, guardPageSize ); 600 reset( fn ); 601 } 602 603 604 /** 605 * Initializes a fiber object which is associated with a dynamic 606 * D function. 607 * 608 * Params: 609 * dg = The fiber function. 610 * sz = The stack size for this fiber. 611 * guardPageSize = size of the guard page to trap fiber's stack 612 * overflows. Beware that using this will increase 613 * the number of mmaped regions on platforms using mmap 614 * so an OS-imposed limit may be hit. 615 * 616 * In: 617 * dg must not be null. 618 */ 619 this( void delegate() dg, size_t sz = pageSize * defaultStackPages, 620 size_t guardPageSize = pageSize ) nothrow 621 { 622 allocStack( sz, guardPageSize ); 623 reset( cast(void delegate() const) dg ); 624 } 625 626 627 /** 628 * Cleans up any remaining resources used by this object. 629 */ 630 ~this() nothrow @nogc @system 631 { 632 // NOTE: A live reference to this object will exist on its associated 633 // stack from the first time its call() method has been called 634 // until its execution completes with State.TERM. Thus, the only 635 // times this dtor should be called are either if the fiber has 636 // terminated (and therefore has no active stack) or if the user 637 // explicitly deletes this object. The latter case is an error 638 // but is not easily tested for, since State.HOLD may imply that 639 // the fiber was just created but has never been run. There is 640 // not a compelling case to create a State.INIT just to offer a 641 // means of ensuring the user isn't violating this object's 642 // contract, so for now this requirement will be enforced by 643 // documentation only. 644 freeStack(); 645 } 646 647 648 /////////////////////////////////////////////////////////////////////////// 649 // General Actions 650 /////////////////////////////////////////////////////////////////////////// 651 652 653 /** 654 * Transfers execution to this fiber object. The calling context will be 655 * suspended until the fiber calls Fiber.yield() or until it terminates 656 * via an unhandled exception. 657 * 658 * Params: 659 * rethrow = Rethrow any unhandled exception which may have caused this 660 * fiber to terminate. 661 * 662 * In: 663 * This fiber must be in state HOLD. 664 * 665 * Throws: 666 * Any exception not handled by the joined thread. 667 * 668 * Returns: 669 * Any exception not handled by this fiber if rethrow = false, null 670 * otherwise. 671 */ 672 // Not marked with any attributes, even though `nothrow @nogc` works 673 // because it calls arbitrary user code. Most of the implementation 674 // is already `@nogc nothrow`, but in order for `Fiber.call` to 675 // propagate the attributes of the user's function, the Fiber 676 // class needs to be templated. 677 final Throwable call( Rethrow rethrow = Rethrow.yes ) 678 { 679 return rethrow ? call!(Rethrow.yes)() : call!(Rethrow.no); 680 } 681 682 /// ditto 683 final Throwable call( Rethrow rethrow )() 684 { 685 callImpl(); 686 if ( m_unhandled ) 687 { 688 Throwable t = m_unhandled; 689 m_unhandled = null; 690 static if ( rethrow ) 691 throw t; 692 else 693 return t; 694 } 695 return null; 696 } 697 698 private void callImpl() nothrow @nogc 699 in 700 { 701 assert( m_state == State.HOLD ); 702 } 703 do 704 { 705 Fiber cur = getThis(); 706 707 static if ( __traits( compiles, ucontext_t ) ) 708 m_ucur = cur ? &cur.m_utxt : &Fiber.sm_utxt; 709 710 setThis( this ); 711 this.switchIn(); 712 setThis( cur ); 713 714 static if ( __traits( compiles, ucontext_t ) ) 715 m_ucur = null; 716 717 // NOTE: If the fiber has terminated then the stack pointers must be 718 // reset. This ensures that the stack for this fiber is not 719 // scanned if the fiber has terminated. This is necessary to 720 // prevent any references lingering on the stack from delaying 721 // the collection of otherwise dead objects. The most notable 722 // being the current object, which is referenced at the top of 723 // fiber_entryPoint. 724 if ( m_state == State.TERM ) 725 { 726 m_ctxt.tstack = m_ctxt.bstack; 727 } 728 } 729 730 /// Flag to control rethrow behavior of $(D $(LREF call)) 731 enum Rethrow : bool { no, yes } 732 733 /** 734 * Resets this fiber so that it may be re-used, optionally with a 735 * new function/delegate. This routine should only be called for 736 * fibers that have terminated, as doing otherwise could result in 737 * scope-dependent functionality that is not executed. 738 * Stack-based classes, for example, may not be cleaned up 739 * properly if a fiber is reset before it has terminated. 740 * 741 * In: 742 * This fiber must be in state TERM or HOLD. 743 */ 744 final void reset() nothrow @nogc 745 in 746 { 747 assert( m_state == State.TERM || m_state == State.HOLD ); 748 } 749 do 750 { 751 m_ctxt.tstack = m_ctxt.bstack; 752 m_state = State.HOLD; 753 initStack(); 754 m_unhandled = null; 755 } 756 757 /// ditto 758 final void reset( void function() fn ) nothrow @nogc 759 { 760 reset(); 761 m_call = fn; 762 } 763 764 /// ditto 765 final void reset( void delegate() dg ) nothrow @nogc 766 { 767 reset(); 768 m_call = dg; 769 } 770 771 /////////////////////////////////////////////////////////////////////////// 772 // General Properties 773 /////////////////////////////////////////////////////////////////////////// 774 775 776 /// A fiber may occupy one of three states: HOLD, EXEC, and TERM. 777 enum State 778 { 779 /** The HOLD state applies to any fiber that is suspended and ready to 780 be called. */ 781 HOLD, 782 /** The EXEC state will be set for any fiber that is currently 783 executing. */ 784 EXEC, 785 /** The TERM state is set when a fiber terminates. Once a fiber 786 terminates, it must be reset before it may be called again. */ 787 TERM 788 } 789 790 791 /** 792 * Gets the current state of this fiber. 793 * 794 * Returns: 795 * The state of this fiber as an enumerated value. 796 */ 797 final @property State state() const @safe pure nothrow @nogc 798 { 799 return m_state; 800 } 801 802 803 /////////////////////////////////////////////////////////////////////////// 804 // Actions on Calling Fiber 805 /////////////////////////////////////////////////////////////////////////// 806 807 808 /** 809 * Forces a context switch to occur away from the calling fiber. 810 */ 811 static void yield() nothrow @nogc 812 { 813 Fiber cur = getThis(); 814 assert( cur, "Fiber.yield() called with no active fiber" ); 815 assert( cur.m_state == State.EXEC ); 816 817 static if ( __traits( compiles, ucontext_t ) ) 818 cur.m_ucur = &cur.m_utxt; 819 820 cur.m_state = State.HOLD; 821 cur.switchOut(); 822 cur.m_state = State.EXEC; 823 } 824 825 826 /** 827 * Forces a context switch to occur away from the calling fiber and then 828 * throws obj in the calling fiber. 829 * 830 * Params: 831 * t = The object to throw. 832 * 833 * In: 834 * t must not be null. 835 */ 836 static void yieldAndThrow( Throwable t ) nothrow @nogc 837 in 838 { 839 assert( t ); 840 } 841 do 842 { 843 Fiber cur = getThis(); 844 assert( cur, "Fiber.yield() called with no active fiber" ); 845 assert( cur.m_state == State.EXEC ); 846 847 static if ( __traits( compiles, ucontext_t ) ) 848 cur.m_ucur = &cur.m_utxt; 849 850 cur.m_unhandled = t; 851 cur.m_state = State.HOLD; 852 cur.switchOut(); 853 cur.m_state = State.EXEC; 854 } 855 856 857 /////////////////////////////////////////////////////////////////////////// 858 // Fiber Accessors 859 /////////////////////////////////////////////////////////////////////////// 860 861 862 /** 863 * Provides a reference to the calling fiber or null if no fiber is 864 * currently active. 865 * 866 * Returns: 867 * The fiber object representing the calling fiber or null if no fiber 868 * is currently active within this thread. The result of deleting this object is undefined. 869 */ 870 static Fiber getThis() @safe nothrow @nogc 871 { 872 return sm_this; 873 } 874 875 876 /////////////////////////////////////////////////////////////////////////// 877 // Static Initialization 878 /////////////////////////////////////////////////////////////////////////// 879 880 881 version (Posix) 882 { 883 static this() 884 { 885 static if ( __traits( compiles, ucontext_t ) ) 886 { 887 int status = getcontext( &sm_utxt ); 888 assert( status == 0 ); 889 } 890 } 891 } 892 893 private: 894 895 // 896 // Fiber entry point. Invokes the function or delegate passed on 897 // construction (if any). 898 // 899 final void run() 900 { 901 m_call(); 902 } 903 904 // 905 // Standard fiber data 906 // 907 Callable m_call; 908 bool m_isRunning; 909 Throwable m_unhandled; 910 State m_state; 911 912 913 private: 914 /////////////////////////////////////////////////////////////////////////// 915 // Stack Management 916 /////////////////////////////////////////////////////////////////////////// 917 918 919 // 920 // Allocate a new stack for this fiber. 921 // 922 final void allocStack( size_t sz, size_t guardPageSize ) nothrow @system 923 in 924 { 925 assert( !m_pmem && !m_ctxt ); 926 } 927 do 928 { 929 // adjust alloc size to a multiple of pageSize 930 sz += pageSize - 1; 931 sz -= sz % pageSize; 932 933 // NOTE: This instance of Thread.Context is dynamic so Fiber objects 934 // can be collected by the GC so long as no user level references 935 // to the object exist. If m_ctxt were not dynamic then its 936 // presence in the global context list would be enough to keep 937 // this object alive indefinitely. An alternative to allocating 938 // room for this struct explicitly would be to mash it into the 939 // base of the stack being allocated below. However, doing so 940 // requires too much special logic to be worthwhile. 941 m_ctxt = new StackContext; 942 943 version (Windows) 944 { 945 // reserve memory for stack 946 m_pmem = VirtualAlloc( null, 947 sz + guardPageSize, 948 MEM_RESERVE, 949 PAGE_NOACCESS ); 950 if ( !m_pmem ) 951 onOutOfMemoryError(); 952 953 version (StackGrowsDown) 954 { 955 void* stack = m_pmem + guardPageSize; 956 void* guard = m_pmem; 957 void* pbase = stack + sz; 958 } 959 else 960 { 961 void* stack = m_pmem; 962 void* guard = m_pmem + sz; 963 void* pbase = stack; 964 } 965 966 // allocate reserved stack segment 967 stack = VirtualAlloc( stack, 968 sz, 969 MEM_COMMIT, 970 PAGE_READWRITE ); 971 if ( !stack ) 972 onOutOfMemoryError(); 973 974 if (guardPageSize) 975 { 976 // allocate reserved guard page 977 guard = VirtualAlloc( guard, 978 guardPageSize, 979 MEM_COMMIT, 980 PAGE_READWRITE | PAGE_GUARD ); 981 if ( !guard ) 982 onOutOfMemoryError(); 983 } 984 985 m_ctxt.bstack = pbase; 986 m_ctxt.tstack = pbase; 987 m_size = sz; 988 } 989 else 990 { 991 version (Posix) import core.sys.posix.sys.mman; // mmap, MAP_ANON 992 993 static if ( __traits( compiles, ucontext_t ) ) 994 { 995 // Stack size must be at least the minimum allowable by the OS. 996 if (sz < MINSIGSTKSZ) 997 sz = MINSIGSTKSZ; 998 } 999 1000 static if ( __traits( compiles, mmap ) ) 1001 { 1002 // Allocate more for the memory guard 1003 sz += guardPageSize; 1004 1005 int mmap_flags = MAP_PRIVATE | MAP_ANON; 1006 version (OpenBSD) 1007 mmap_flags |= MAP_STACK; 1008 1009 m_pmem = mmap( null, 1010 sz, 1011 PROT_READ | PROT_WRITE, 1012 mmap_flags, 1013 -1, 1014 0 ); 1015 if ( m_pmem == MAP_FAILED ) 1016 m_pmem = null; 1017 } 1018 else static if ( __traits( compiles, valloc ) ) 1019 { 1020 m_pmem = valloc( sz ); 1021 } 1022 else static if ( __traits( compiles, malloc ) ) 1023 { 1024 m_pmem = malloc( sz ); 1025 } 1026 else 1027 { 1028 m_pmem = null; 1029 } 1030 1031 if ( !m_pmem ) 1032 onOutOfMemoryError(); 1033 1034 version (StackGrowsDown) 1035 { 1036 m_ctxt.bstack = m_pmem + sz; 1037 m_ctxt.tstack = m_pmem + sz; 1038 void* guard = m_pmem; 1039 } 1040 else 1041 { 1042 m_ctxt.bstack = m_pmem; 1043 m_ctxt.tstack = m_pmem; 1044 void* guard = m_pmem + sz - guardPageSize; 1045 } 1046 m_size = sz; 1047 1048 static if ( __traits( compiles, mmap ) ) 1049 { 1050 if (guardPageSize) 1051 { 1052 // protect end of stack 1053 if ( mprotect(guard, guardPageSize, PROT_NONE) == -1 ) 1054 abort(); 1055 } 1056 } 1057 else 1058 { 1059 // Supported only for mmap allocated memory - results are 1060 // undefined if applied to memory not obtained by mmap 1061 } 1062 } 1063 1064 Thread.add( m_ctxt ); 1065 } 1066 1067 1068 // 1069 // Free this fiber's stack. 1070 // 1071 final void freeStack() nothrow @nogc @system 1072 in 1073 { 1074 assert( m_pmem && m_ctxt ); 1075 } 1076 do 1077 { 1078 // NOTE: m_ctxt is guaranteed to be alive because it is held in the 1079 // global context list. 1080 Thread.slock.lock_nothrow(); 1081 scope(exit) Thread.slock.unlock_nothrow(); 1082 Thread.remove( m_ctxt ); 1083 1084 version (Windows) 1085 { 1086 VirtualFree( m_pmem, 0, MEM_RELEASE ); 1087 } 1088 else 1089 { 1090 import core.sys.posix.sys.mman; // munmap 1091 1092 static if ( __traits( compiles, mmap ) ) 1093 { 1094 munmap( m_pmem, m_size ); 1095 } 1096 else static if ( __traits( compiles, valloc ) ) 1097 { 1098 free( m_pmem ); 1099 } 1100 else static if ( __traits( compiles, malloc ) ) 1101 { 1102 free( m_pmem ); 1103 } 1104 } 1105 m_pmem = null; 1106 m_ctxt = null; 1107 } 1108 1109 1110 // 1111 // Initialize the allocated stack. 1112 // Look above the definition of 'class Fiber' for some information about the implementation of this routine 1113 // 1114 final void initStack() nothrow @nogc @system 1115 in 1116 { 1117 assert( m_ctxt.tstack && m_ctxt.tstack == m_ctxt.bstack ); 1118 assert( cast(size_t) m_ctxt.bstack % (void*).sizeof == 0 ); 1119 } 1120 do 1121 { 1122 void* pstack = m_ctxt.tstack; 1123 scope( exit ) m_ctxt.tstack = pstack; 1124 1125 void push( size_t val ) nothrow 1126 { 1127 version (StackGrowsDown) 1128 { 1129 pstack -= size_t.sizeof; 1130 *(cast(size_t*) pstack) = val; 1131 } 1132 else 1133 { 1134 pstack += size_t.sizeof; 1135 *(cast(size_t*) pstack) = val; 1136 } 1137 } 1138 1139 // NOTE: On OS X the stack must be 16-byte aligned according 1140 // to the IA-32 call spec. For x86_64 the stack also needs to 1141 // be aligned to 16-byte according to SysV AMD64 ABI. 1142 version (AlignFiberStackTo16Byte) 1143 { 1144 version (StackGrowsDown) 1145 { 1146 pstack = cast(void*)(cast(size_t)(pstack) - (cast(size_t)(pstack) & 0x0F)); 1147 } 1148 else 1149 { 1150 pstack = cast(void*)(cast(size_t)(pstack) + (cast(size_t)(pstack) & 0x0F)); 1151 } 1152 } 1153 1154 version (AsmX86_Windows) 1155 { 1156 version (StackGrowsDown) {} else static assert( false ); 1157 1158 // On Windows Server 2008 and 2008 R2, an exploit mitigation 1159 // technique known as SEHOP is activated by default. To avoid 1160 // hijacking of the exception handler chain, the presence of a 1161 // Windows-internal handler (ntdll.dll!FinalExceptionHandler) at 1162 // its end is tested by RaiseException. If it is not present, all 1163 // handlers are disregarded, and the program is thus aborted 1164 // (see http://blogs.technet.com/b/srd/archive/2009/02/02/ 1165 // preventing-the-exploitation-of-seh-overwrites-with-sehop.aspx). 1166 // For new threads, this handler is installed by Windows immediately 1167 // after creation. To make exception handling work in fibers, we 1168 // have to insert it for our new stacks manually as well. 1169 // 1170 // To do this, we first determine the handler by traversing the SEH 1171 // chain of the current thread until its end, and then construct a 1172 // registration block for the last handler on the newly created 1173 // thread. We then continue to push all the initial register values 1174 // for the first context switch as for the other implementations. 1175 // 1176 // Note that this handler is never actually invoked, as we install 1177 // our own one on top of it in the fiber entry point function. 1178 // Thus, it should not have any effects on OSes not implementing 1179 // exception chain verification. 1180 1181 alias fp_t = void function(); // Actual signature not relevant. 1182 static struct EXCEPTION_REGISTRATION 1183 { 1184 EXCEPTION_REGISTRATION* next; // sehChainEnd if last one. 1185 fp_t handler; 1186 } 1187 enum sehChainEnd = cast(EXCEPTION_REGISTRATION*) 0xFFFFFFFF; 1188 1189 __gshared static fp_t finalHandler = null; 1190 if ( finalHandler is null ) 1191 { 1192 static EXCEPTION_REGISTRATION* fs0() nothrow 1193 { 1194 asm pure nothrow @nogc 1195 { 1196 naked; 1197 mov EAX, FS:[0]; 1198 ret; 1199 } 1200 } 1201 auto reg = fs0(); 1202 while ( reg.next != sehChainEnd ) reg = reg.next; 1203 1204 // Benign races are okay here, just to avoid re-lookup on every 1205 // fiber creation. 1206 finalHandler = reg.handler; 1207 } 1208 1209 // When linking with /safeseh (supported by LDC, but not DMD) 1210 // the exception chain must not extend to the very top 1211 // of the stack, otherwise the exception chain is also considered 1212 // invalid. Reserving additional 4 bytes at the top of the stack will 1213 // keep the EXCEPTION_REGISTRATION below that limit 1214 size_t reserve = EXCEPTION_REGISTRATION.sizeof + 4; 1215 pstack -= reserve; 1216 *(cast(EXCEPTION_REGISTRATION*)pstack) = 1217 EXCEPTION_REGISTRATION( sehChainEnd, finalHandler ); 1218 auto pChainEnd = pstack; 1219 1220 push( cast(size_t) &fiber_entryPoint ); // EIP 1221 push( cast(size_t) m_ctxt.bstack - reserve ); // EBP 1222 push( 0x00000000 ); // EDI 1223 push( 0x00000000 ); // ESI 1224 push( 0x00000000 ); // EBX 1225 push( cast(size_t) pChainEnd ); // FS:[0] 1226 push( cast(size_t) m_ctxt.bstack ); // FS:[4] 1227 push( cast(size_t) m_ctxt.bstack - m_size ); // FS:[8] 1228 push( 0x00000000 ); // EAX 1229 } 1230 else version (AsmX86_64_Windows) 1231 { 1232 // Using this trampoline instead of the raw fiber_entryPoint 1233 // ensures that during context switches, source and destination 1234 // stacks have the same alignment. Otherwise, the stack would need 1235 // to be shifted by 8 bytes for the first call, as fiber_entryPoint 1236 // is an actual function expecting a stack which is not aligned 1237 // to 16 bytes. 1238 static void trampoline() 1239 { 1240 asm pure nothrow @nogc 1241 { 1242 naked; 1243 sub RSP, 32; // Shadow space (Win64 calling convention) 1244 call fiber_entryPoint; 1245 xor RCX, RCX; // This should never be reached, as 1246 jmp RCX; // fiber_entryPoint must never return. 1247 } 1248 } 1249 1250 push( cast(size_t) &trampoline ); // RIP 1251 push( 0x00000000_00000000 ); // RBP 1252 push( 0x00000000_00000000 ); // R12 1253 push( 0x00000000_00000000 ); // R13 1254 push( 0x00000000_00000000 ); // R14 1255 push( 0x00000000_00000000 ); // R15 1256 push( 0x00000000_00000000 ); // RDI 1257 push( 0x00000000_00000000 ); // RSI 1258 push( 0x00000000_00000000 ); // XMM6 (high) 1259 push( 0x00000000_00000000 ); // XMM6 (low) 1260 push( 0x00000000_00000000 ); // XMM7 (high) 1261 push( 0x00000000_00000000 ); // XMM7 (low) 1262 push( 0x00000000_00000000 ); // XMM8 (high) 1263 push( 0x00000000_00000000 ); // XMM8 (low) 1264 push( 0x00000000_00000000 ); // XMM9 (high) 1265 push( 0x00000000_00000000 ); // XMM9 (low) 1266 push( 0x00000000_00000000 ); // XMM10 (high) 1267 push( 0x00000000_00000000 ); // XMM10 (low) 1268 push( 0x00000000_00000000 ); // XMM11 (high) 1269 push( 0x00000000_00000000 ); // XMM11 (low) 1270 push( 0x00000000_00000000 ); // XMM12 (high) 1271 push( 0x00000000_00000000 ); // XMM12 (low) 1272 push( 0x00000000_00000000 ); // XMM13 (high) 1273 push( 0x00000000_00000000 ); // XMM13 (low) 1274 push( 0x00000000_00000000 ); // XMM14 (high) 1275 push( 0x00000000_00000000 ); // XMM14 (low) 1276 push( 0x00000000_00000000 ); // XMM15 (high) 1277 push( 0x00000000_00000000 ); // XMM15 (low) 1278 push( 0x00000000_00000000 ); // RBX 1279 push( 0xFFFFFFFF_FFFFFFFF ); // GS:[0] 1280 version (StackGrowsDown) 1281 { 1282 push( cast(size_t) m_ctxt.bstack ); // GS:[8] 1283 push( cast(size_t) m_ctxt.bstack - m_size ); // GS:[16] 1284 } 1285 else 1286 { 1287 push( cast(size_t) m_ctxt.bstack ); // GS:[8] 1288 push( cast(size_t) m_ctxt.bstack + m_size ); // GS:[16] 1289 } 1290 } 1291 else version (AsmX86_Posix) 1292 { 1293 push( 0x00000000 ); // Return address of fiber_entryPoint call 1294 push( cast(size_t) &fiber_entryPoint ); // EIP 1295 push( cast(size_t) m_ctxt.bstack ); // EBP 1296 push( 0x00000000 ); // EDI 1297 push( 0x00000000 ); // ESI 1298 push( 0x00000000 ); // EBX 1299 push( 0x00000000 ); // EAX 1300 } 1301 else version (AsmX86_64_Posix) 1302 { 1303 push( 0x00000000_00000000 ); // Return address of fiber_entryPoint call 1304 push( cast(size_t) &fiber_entryPoint ); // RIP 1305 push( cast(size_t) m_ctxt.bstack ); // RBP 1306 push( 0x00000000_00000000 ); // RBX 1307 push( 0x00000000_00000000 ); // R12 1308 push( 0x00000000_00000000 ); // R13 1309 push( 0x00000000_00000000 ); // R14 1310 push( 0x00000000_00000000 ); // R15 1311 } 1312 else version (AsmPPC_Posix) 1313 { 1314 version (StackGrowsDown) 1315 { 1316 pstack -= int.sizeof * 5; 1317 } 1318 else 1319 { 1320 pstack += int.sizeof * 5; 1321 } 1322 1323 push( cast(size_t) &fiber_entryPoint ); // link register 1324 push( 0x00000000 ); // control register 1325 push( 0x00000000 ); // old stack pointer 1326 1327 // GPR values 1328 version (StackGrowsDown) 1329 { 1330 pstack -= int.sizeof * 20; 1331 } 1332 else 1333 { 1334 pstack += int.sizeof * 20; 1335 } 1336 1337 assert( (cast(size_t) pstack & 0x0f) == 0 ); 1338 } 1339 else version (AsmPPC_Darwin) 1340 { 1341 version (StackGrowsDown) {} 1342 else static assert(false, "PowerPC Darwin only supports decrementing stacks"); 1343 1344 uint wsize = size_t.sizeof; 1345 1346 // linkage + regs + FPRs + VRs 1347 uint space = 8 * wsize + 20 * wsize + 18 * 8 + 12 * 16; 1348 (cast(ubyte*)pstack - space)[0 .. space] = 0; 1349 1350 pstack -= wsize * 6; 1351 *cast(size_t*)pstack = cast(size_t) &fiber_entryPoint; // LR 1352 pstack -= wsize * 22; 1353 1354 // On Darwin PPC64 pthread self is in R13 (which is reserved). 1355 // At present, it is not safe to migrate fibers between threads, but if that 1356 // changes, then updating the value of R13 will also need to be handled. 1357 version (PPC64) 1358 *cast(size_t*)(pstack + wsize) = cast(size_t) Thread.getThis().m_addr; 1359 assert( (cast(size_t) pstack & 0x0f) == 0 ); 1360 } 1361 else version (AsmMIPS_O32_Posix) 1362 { 1363 version (StackGrowsDown) {} 1364 else static assert(0); 1365 1366 /* We keep the FP registers and the return address below 1367 * the stack pointer, so they don't get scanned by the 1368 * GC. The last frame before swapping the stack pointer is 1369 * organized like the following. 1370 * 1371 * |-----------|<= frame pointer 1372 * | $gp | 1373 * | $s0-8 | 1374 * |-----------|<= stack pointer 1375 * | $ra | 1376 * | align(8) | 1377 * | $f20-30 | 1378 * |-----------| 1379 * 1380 */ 1381 enum SZ_GP = 10 * size_t.sizeof; // $gp + $s0-8 1382 enum SZ_RA = size_t.sizeof; // $ra 1383 version (MIPS_HardFloat) 1384 { 1385 enum SZ_FP = 6 * 8; // $f20-30 1386 enum ALIGN = -(SZ_FP + SZ_RA) & (8 - 1); 1387 } 1388 else 1389 { 1390 enum SZ_FP = 0; 1391 enum ALIGN = 0; 1392 } 1393 1394 enum BELOW = SZ_FP + ALIGN + SZ_RA; 1395 enum ABOVE = SZ_GP; 1396 enum SZ = BELOW + ABOVE; 1397 1398 (cast(ubyte*)pstack - SZ)[0 .. SZ] = 0; 1399 pstack -= ABOVE; 1400 *cast(size_t*)(pstack - SZ_RA) = cast(size_t)&fiber_entryPoint; 1401 } 1402 else version (AsmLoongArch64_Posix) 1403 { 1404 // Like others, FP registers and return address ($r1) are kept 1405 // below the saved stack top (tstack) to hide from GC scanning. 1406 // fiber_switchContext expects newp sp to look like this: 1407 // 10: $r21 (reserved) 1408 // 9: $r22 (frame pointer) 1409 // 8: $r23 1410 // ... 1411 // 0: $r31 <-- newp tstack 1412 // -1: $r1 (return address) [&fiber_entryPoint] 1413 // -2: $f24 1414 // ... 1415 // -9: $f31 1416 1417 version (StackGrowsDown) {} 1418 else 1419 static assert(false, "Only full descending stacks supported on LoongArch64"); 1420 1421 // Only need to set return address ($r1). Everything else is fine 1422 // zero initialized. 1423 pstack -= size_t.sizeof * 11; // skip past space reserved for $r21-$r31 1424 push (cast(size_t) &fiber_entryPoint); 1425 pstack += size_t.sizeof; // adjust sp (newp) above lr 1426 } 1427 else version (AsmAArch64_Posix) 1428 { 1429 // Like others, FP registers and return address (lr) are kept 1430 // below the saved stack top (tstack) to hide from GC scanning. 1431 // fiber_switchContext expects newp sp to look like this: 1432 // 19: x19 1433 // ... 1434 // 9: x29 (fp) <-- newp tstack 1435 // 8: x30 (lr) [&fiber_entryPoint] 1436 // 7: d8 1437 // ... 1438 // 0: d15 1439 1440 version (StackGrowsDown) {} 1441 else 1442 static assert(false, "Only full descending stacks supported on AArch64"); 1443 1444 // Only need to set return address (lr). Everything else is fine 1445 // zero initialized. 1446 pstack -= size_t.sizeof * 11; // skip past x19-x29 1447 push(cast(size_t) &fiber_trampoline); // see threadasm.S for docs 1448 pstack += size_t.sizeof; // adjust sp (newp) above lr 1449 } 1450 else version (AsmARM_Posix) 1451 { 1452 /* We keep the FP registers and the return address below 1453 * the stack pointer, so they don't get scanned by the 1454 * GC. The last frame before swapping the stack pointer is 1455 * organized like the following. 1456 * 1457 * | |-----------|<= 'frame starts here' 1458 * | | fp | (the actual frame pointer, r11 isn't 1459 * | | r10-r4 | updated and still points to the previous frame) 1460 * | |-----------|<= stack pointer 1461 * | | lr | 1462 * | | 4byte pad | 1463 * | | d15-d8 |(if FP supported) 1464 * | |-----------| 1465 * Y 1466 * stack grows down: The pointer value here is smaller than some lines above 1467 */ 1468 // frame pointer can be zero, r10-r4 also zero initialized 1469 version (StackGrowsDown) 1470 pstack -= int.sizeof * 8; 1471 else 1472 static assert(false, "Only full descending stacks supported on ARM"); 1473 1474 // link register 1475 push( cast(size_t) &fiber_entryPoint ); 1476 /* 1477 * We do not push padding and d15-d8 as those are zero initialized anyway 1478 * Position the stack pointer above the lr register 1479 */ 1480 pstack += int.sizeof * 1; 1481 } 1482 else static if ( __traits( compiles, ucontext_t ) ) 1483 { 1484 getcontext( &m_utxt ); 1485 m_utxt.uc_stack.ss_sp = m_pmem; 1486 m_utxt.uc_stack.ss_size = m_size; 1487 makecontext( &m_utxt, &fiber_entryPoint, 0 ); 1488 // NOTE: If ucontext is being used then the top of the stack will 1489 // be a pointer to the ucontext_t struct for that fiber. 1490 push( cast(size_t) &m_utxt ); 1491 } 1492 else version (FreeStanding) 1493 assert(0); 1494 else 1495 static assert(0, "Not implemented"); 1496 } 1497 1498 1499 StackContext* m_ctxt; 1500 size_t m_size; 1501 void* m_pmem; 1502 1503 static if ( __traits( compiles, ucontext_t ) ) 1504 { 1505 // NOTE: The static ucontext instance is used to represent the context 1506 // of the executing thread. 1507 static ucontext_t sm_utxt = void; 1508 ucontext_t m_utxt = void; 1509 ucontext_t* m_ucur = null; 1510 } 1511 1512 1513 private: 1514 /////////////////////////////////////////////////////////////////////////// 1515 // Storage of Active Fiber 1516 /////////////////////////////////////////////////////////////////////////// 1517 1518 1519 // 1520 // Sets a thread-local reference to the current fiber object. 1521 // 1522 static void setThis( Fiber f ) nothrow @nogc 1523 { 1524 sm_this = f; 1525 } 1526 1527 static Fiber sm_this; 1528 1529 1530 private: 1531 /////////////////////////////////////////////////////////////////////////// 1532 // Context Switching 1533 /////////////////////////////////////////////////////////////////////////// 1534 1535 1536 // 1537 // Switches into the stack held by this fiber. 1538 // 1539 final void switchIn() nothrow @nogc @system 1540 { 1541 Thread tobj = Thread.getThis(); 1542 void** oldp = &tobj.m_curr.tstack; 1543 void* newp = m_ctxt.tstack; 1544 1545 // NOTE: The order of operations here is very important. The current 1546 // stack top must be stored before m_lock is set, and pushContext 1547 // must not be called until after m_lock is set. This process 1548 // is intended to prevent a race condition with the suspend 1549 // mechanism used for garbage collection. If it is not followed, 1550 // a badly timed collection could cause the GC to scan from the 1551 // bottom of one stack to the top of another, or to miss scanning 1552 // a stack that still contains valid data. The old stack pointer 1553 // oldp will be set again before the context switch to guarantee 1554 // that it points to exactly the correct stack location so the 1555 // successive pop operations will succeed. 1556 *oldp = getStackTop(); 1557 atomicStore!(MemoryOrder.raw)(*cast(shared)&tobj.m_lock, true); 1558 tobj.pushContext( m_ctxt ); 1559 1560 fiber_switchContext( oldp, newp ); 1561 1562 // NOTE: As above, these operations must be performed in a strict order 1563 // to prevent Bad Things from happening. 1564 tobj.popContext(); 1565 atomicStore!(MemoryOrder.raw)(*cast(shared)&tobj.m_lock, false); 1566 tobj.m_curr.tstack = tobj.m_curr.bstack; 1567 } 1568 1569 1570 // 1571 // Switches out of the current stack and into the enclosing stack. 1572 // 1573 final void switchOut() nothrow @nogc @system 1574 { 1575 Thread tobj = Thread.getThis(); 1576 void** oldp = &m_ctxt.tstack; 1577 void* newp = tobj.m_curr.within.tstack; 1578 1579 // NOTE: The order of operations here is very important. The current 1580 // stack top must be stored before m_lock is set, and pushContext 1581 // must not be called until after m_lock is set. This process 1582 // is intended to prevent a race condition with the suspend 1583 // mechanism used for garbage collection. If it is not followed, 1584 // a badly timed collection could cause the GC to scan from the 1585 // bottom of one stack to the top of another, or to miss scanning 1586 // a stack that still contains valid data. The old stack pointer 1587 // oldp will be set again before the context switch to guarantee 1588 // that it points to exactly the correct stack location so the 1589 // successive pop operations will succeed. 1590 *oldp = getStackTop(); 1591 atomicStore!(MemoryOrder.raw)(*cast(shared)&tobj.m_lock, true); 1592 1593 fiber_switchContext( oldp, newp ); 1594 1595 // NOTE: As above, these operations must be performed in a strict order 1596 // to prevent Bad Things from happening. 1597 // NOTE: If use of this fiber is multiplexed across threads, the thread 1598 // executing here may be different from the one above, so get the 1599 // current thread handle before unlocking, etc. 1600 tobj = Thread.getThis(); 1601 atomicStore!(MemoryOrder.raw)(*cast(shared)&tobj.m_lock, false); 1602 tobj.m_curr.tstack = tobj.m_curr.bstack; 1603 } 1604 } 1605 1606 /// 1607 unittest { 1608 int counter; 1609 1610 class DerivedFiber : Fiber 1611 { 1612 this() 1613 { 1614 super( &run ); 1615 } 1616 1617 private : 1618 void run() 1619 { 1620 counter += 2; 1621 } 1622 } 1623 1624 void fiberFunc() 1625 { 1626 counter += 4; 1627 Fiber.yield(); 1628 counter += 8; 1629 } 1630 1631 // create instances of each type 1632 Fiber derived = new DerivedFiber(); 1633 Fiber composed = new Fiber( &fiberFunc ); 1634 1635 assert( counter == 0 ); 1636 1637 derived.call(); 1638 assert( counter == 2, "Derived fiber increment." ); 1639 1640 composed.call(); 1641 assert( counter == 6, "First composed fiber increment." ); 1642 1643 counter += 16; 1644 assert( counter == 22, "Calling context increment." ); 1645 1646 composed.call(); 1647 assert( counter == 30, "Second composed fiber increment." ); 1648 1649 // since each fiber has run to completion, each should have state TERM 1650 assert( derived.state == Fiber.State.TERM ); 1651 assert( composed.state == Fiber.State.TERM ); 1652 } 1653 1654 version (CoreUnittest) 1655 { 1656 class TestFiber : Fiber 1657 { 1658 this() 1659 { 1660 super(&run); 1661 } 1662 1663 void run() 1664 { 1665 foreach (i; 0 .. 1000) 1666 { 1667 sum += i; 1668 Fiber.yield(); 1669 } 1670 } 1671 1672 enum expSum = 1000 * 999 / 2; 1673 size_t sum; 1674 } 1675 1676 void runTen() 1677 { 1678 TestFiber[10] fibs; 1679 foreach (ref fib; fibs) 1680 fib = new TestFiber(); 1681 1682 bool cont; 1683 do { 1684 cont = false; 1685 foreach (fib; fibs) { 1686 if (fib.state == Fiber.State.HOLD) 1687 { 1688 fib.call(); 1689 cont |= fib.state != Fiber.State.TERM; 1690 } 1691 } 1692 } while (cont); 1693 1694 foreach (fib; fibs) 1695 { 1696 assert(fib.sum == TestFiber.expSum); 1697 } 1698 } 1699 } 1700 1701 1702 // Single thread running separate fibers 1703 unittest 1704 { 1705 runTen(); 1706 } 1707 1708 1709 // Multiple threads running separate fibers 1710 unittest 1711 { 1712 auto group = new ThreadGroup(); 1713 foreach (_; 0 .. 4) 1714 { 1715 group.create(&runTen); 1716 } 1717 group.joinAll(); 1718 } 1719 1720 1721 // Multiple threads running shared fibers 1722 unittest 1723 { 1724 shared bool[10] locks; 1725 TestFiber[10] fibs; 1726 1727 void runShared() 1728 { 1729 bool cont; 1730 do { 1731 cont = false; 1732 foreach (idx; 0 .. 10) 1733 { 1734 if (cas(&locks[idx], false, true)) 1735 { 1736 if (fibs[idx].state == Fiber.State.HOLD) 1737 { 1738 fibs[idx].call(); 1739 cont |= fibs[idx].state != Fiber.State.TERM; 1740 } 1741 locks[idx] = false; 1742 } 1743 else 1744 { 1745 cont = true; 1746 } 1747 } 1748 } while (cont); 1749 } 1750 1751 foreach (ref fib; fibs) 1752 { 1753 fib = new TestFiber(); 1754 } 1755 1756 auto group = new ThreadGroup(); 1757 foreach (_; 0 .. 4) 1758 { 1759 group.create(&runShared); 1760 } 1761 group.joinAll(); 1762 1763 foreach (fib; fibs) 1764 { 1765 assert(fib.sum == TestFiber.expSum); 1766 } 1767 } 1768 1769 1770 // Test exception handling inside fibers. 1771 unittest 1772 { 1773 enum MSG = "Test message."; 1774 string caughtMsg; 1775 (new Fiber({ 1776 try 1777 { 1778 throw new Exception(MSG); 1779 } 1780 catch (Exception e) 1781 { 1782 caughtMsg = e.msg; 1783 } 1784 })).call(); 1785 assert(caughtMsg == MSG); 1786 } 1787 1788 1789 unittest 1790 { 1791 int x = 0; 1792 1793 (new Fiber({ 1794 x++; 1795 })).call(); 1796 assert( x == 1 ); 1797 } 1798 1799 nothrow unittest 1800 { 1801 new Fiber({}).call!(Fiber.Rethrow.no)(); 1802 } 1803 1804 unittest 1805 { 1806 new Fiber({}).call(Fiber.Rethrow.yes); 1807 new Fiber({}).call(Fiber.Rethrow.no); 1808 } 1809 1810 unittest 1811 { 1812 enum MSG = "Test message."; 1813 1814 try 1815 { 1816 (new Fiber(function() { 1817 throw new Exception( MSG ); 1818 })).call(); 1819 assert( false, "Expected rethrown exception." ); 1820 } 1821 catch ( Throwable t ) 1822 { 1823 assert( t.msg == MSG ); 1824 } 1825 } 1826 1827 // Test exception chaining when switching contexts in finally blocks. 1828 unittest 1829 { 1830 static void throwAndYield(string msg) { 1831 try { 1832 throw new Exception(msg); 1833 } finally { 1834 Fiber.yield(); 1835 } 1836 } 1837 1838 static void fiber(string name) { 1839 try { 1840 try { 1841 throwAndYield(name ~ ".1"); 1842 } finally { 1843 throwAndYield(name ~ ".2"); 1844 } 1845 } catch (Exception e) { 1846 assert(e.msg == name ~ ".1"); 1847 assert(e.next); 1848 assert(e.next.msg == name ~ ".2"); 1849 assert(!e.next.next); 1850 } 1851 } 1852 1853 auto first = new Fiber(() => fiber("first")); 1854 auto second = new Fiber(() => fiber("second")); 1855 first.call(); 1856 second.call(); 1857 first.call(); 1858 second.call(); 1859 first.call(); 1860 second.call(); 1861 assert(first.state == Fiber.State.TERM); 1862 assert(second.state == Fiber.State.TERM); 1863 } 1864 1865 // Test Fiber resetting 1866 unittest 1867 { 1868 static string method; 1869 1870 static void foo() 1871 { 1872 method = "foo"; 1873 } 1874 1875 void bar() 1876 { 1877 method = "bar"; 1878 } 1879 1880 static void expect(Fiber fib, string s) 1881 { 1882 assert(fib.state == Fiber.State.HOLD); 1883 fib.call(); 1884 assert(fib.state == Fiber.State.TERM); 1885 assert(method == s); method = null; 1886 } 1887 auto fib = new Fiber(&foo); 1888 expect(fib, "foo"); 1889 1890 fib.reset(); 1891 expect(fib, "foo"); 1892 1893 fib.reset(&foo); 1894 expect(fib, "foo"); 1895 1896 fib.reset(&bar); 1897 expect(fib, "bar"); 1898 1899 fib.reset(function void(){method = "function";}); 1900 expect(fib, "function"); 1901 1902 fib.reset(delegate void(){method = "delegate";}); 1903 expect(fib, "delegate"); 1904 } 1905 1906 // Test unsafe reset in hold state 1907 unittest 1908 { 1909 auto fib = new Fiber(function {ubyte[2048] buf = void; Fiber.yield();}, 4096); 1910 foreach (_; 0 .. 10) 1911 { 1912 fib.call(); 1913 assert(fib.state == Fiber.State.HOLD); 1914 fib.reset(); 1915 } 1916 } 1917 1918 // stress testing GC stack scanning 1919 unittest 1920 { 1921 import core.memory; 1922 import core.time : dur; 1923 1924 static void unreferencedThreadObject() 1925 { 1926 static void sleep() { Thread.sleep(dur!"msecs"(100)); } 1927 auto thread = new Thread(&sleep).start(); 1928 } 1929 unreferencedThreadObject(); 1930 GC.collect(); 1931 1932 static class Foo 1933 { 1934 this(int value) 1935 { 1936 _value = value; 1937 } 1938 1939 int bar() 1940 { 1941 return _value; 1942 } 1943 1944 int _value; 1945 } 1946 1947 static void collect() 1948 { 1949 auto foo = new Foo(2); 1950 assert(foo.bar() == 2); 1951 GC.collect(); 1952 Fiber.yield(); 1953 GC.collect(); 1954 assert(foo.bar() == 2); 1955 } 1956 1957 auto fiber = new Fiber(&collect); 1958 1959 fiber.call(); 1960 GC.collect(); 1961 fiber.call(); 1962 1963 // thread reference 1964 auto foo = new Foo(2); 1965 1966 void collect2() 1967 { 1968 assert(foo.bar() == 2); 1969 GC.collect(); 1970 Fiber.yield(); 1971 GC.collect(); 1972 assert(foo.bar() == 2); 1973 } 1974 1975 fiber = new Fiber(&collect2); 1976 1977 fiber.call(); 1978 GC.collect(); 1979 fiber.call(); 1980 1981 static void recurse(size_t cnt) 1982 { 1983 --cnt; 1984 Fiber.yield(); 1985 if (cnt) 1986 { 1987 auto fib = new Fiber(() { recurse(cnt); }); 1988 fib.call(); 1989 GC.collect(); 1990 fib.call(); 1991 } 1992 } 1993 fiber = new Fiber(() { recurse(20); }); 1994 fiber.call(); 1995 } 1996 1997 1998 version (AsmX86_64_Windows) 1999 { 2000 // Test Windows x64 calling convention 2001 unittest 2002 { 2003 void testNonvolatileRegister(alias REG)() 2004 { 2005 auto zeroRegister = new Fiber(() { 2006 mixin("asm pure nothrow @nogc { naked; xor "~REG~", "~REG~"; ret; }"); 2007 }); 2008 long after; 2009 2010 mixin("asm pure nothrow @nogc { mov "~REG~", 0xFFFFFFFFFFFFFFFF; }"); 2011 zeroRegister.call(); 2012 mixin("asm pure nothrow @nogc { mov after, "~REG~"; }"); 2013 2014 assert(after == -1); 2015 } 2016 2017 void testNonvolatileRegisterSSE(alias REG)() 2018 { 2019 auto zeroRegister = new Fiber(() { 2020 mixin("asm pure nothrow @nogc { naked; xorpd "~REG~", "~REG~"; ret; }"); 2021 }); 2022 long[2] before = [0xFFFFFFFF_FFFFFFFF, 0xFFFFFFFF_FFFFFFFF], after; 2023 2024 mixin("asm pure nothrow @nogc { movdqu "~REG~", before; }"); 2025 zeroRegister.call(); 2026 mixin("asm pure nothrow @nogc { movdqu after, "~REG~"; }"); 2027 2028 assert(before == after); 2029 } 2030 2031 testNonvolatileRegister!("R12")(); 2032 testNonvolatileRegister!("R13")(); 2033 testNonvolatileRegister!("R14")(); 2034 testNonvolatileRegister!("R15")(); 2035 testNonvolatileRegister!("RDI")(); 2036 testNonvolatileRegister!("RSI")(); 2037 testNonvolatileRegister!("RBX")(); 2038 2039 testNonvolatileRegisterSSE!("XMM6")(); 2040 testNonvolatileRegisterSSE!("XMM7")(); 2041 testNonvolatileRegisterSSE!("XMM8")(); 2042 testNonvolatileRegisterSSE!("XMM9")(); 2043 testNonvolatileRegisterSSE!("XMM10")(); 2044 testNonvolatileRegisterSSE!("XMM11")(); 2045 testNonvolatileRegisterSSE!("XMM12")(); 2046 testNonvolatileRegisterSSE!("XMM13")(); 2047 testNonvolatileRegisterSSE!("XMM14")(); 2048 testNonvolatileRegisterSSE!("XMM15")(); 2049 } 2050 } 2051 2052 2053 version (D_InlineAsm_X86_64) 2054 { 2055 unittest 2056 { 2057 void testStackAlignment() 2058 { 2059 void* pRSP; 2060 asm pure nothrow @nogc 2061 { 2062 mov pRSP, RSP; 2063 } 2064 assert((cast(size_t)pRSP & 0xF) == 0); 2065 } 2066 2067 auto fib = new Fiber(&testStackAlignment); 2068 fib.call(); 2069 } 2070 }