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