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 /* 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
version(GNU)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
version(Windows)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 {
version(D_InlineAsm_X86)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 }
version(D_InlineAsm_X86_64)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 }
version(X86)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 }
version(X86_64)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 }
version(PPC)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 }
version(PPC64)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 }
version(MIPS_O32)143 else version (MIPS_O32)
144 {
145 version (Posix)
146 {
147 version = AsmMIPS_O32_Posix;
148 version = AsmExternal;
149 }
150 }
version(AArch64)151 else version (AArch64)
152 {
153 version (Posix)
154 {
155 version = AsmAArch64_Posix;
156 version = AsmExternal;
157 version = AlignFiberStackTo16Byte;
158 }
159 }
version(ARM)160 else version (ARM)
161 {
162 version (Posix)
163 {
164 version = AsmARM_Posix;
165 version = AsmExternal;
166 }
167 }
version(SPARC)168 else version (SPARC)
169 {
170 // NOTE: The SPARC ABI specifies only doubleword alignment.
171 version = AlignFiberStackTo16Byte;
172 }
version(SPARC64)173 else version (SPARC64)
174 {
175 version = AlignFiberStackTo16Byte;
176 }
177
version(Posix)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
fiber_entryPoint()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
version(AsmExternal)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
fiber_switchContext(void ** oldp,void * newp)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;
version(OSX)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 */
function()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 */
delegate()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 */
~this()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
call(Rethrow rethrow)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
callImpl()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 */
reset()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
reset(void function ()fn)798 final void reset( void function() fn ) nothrow @nogc
799 {
800 reset();
801 m_call = fn;
802 }
803
804 /// ditto
reset(void delegate ()dg)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 */
state()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 */
yield()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 */
yieldAndThrow(Throwable t)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 */
getThis()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
version(Posix)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 //
run()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 //
allocStack(size_t sz,size_t guardPageSize)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
version(Windows)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
version(StackGrowsDown)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 //
freeStack()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
version(Windows)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 //
initStack()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
push(size_t val)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.
version(AlignFiberStackTo16Byte)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
version(AsmX86_Windows)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 }
version(AsmX86_64_Windows)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 }
version(AsmX86_Posix)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 }
version(AsmX86_64_Posix)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 }
version(AsmPPC_Posix)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 }
version(AsmPPC_Darwin)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 }
version(AsmMIPS_O32_Posix)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 }
version(AsmAArch64_Posix)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 }
version(AsmARM_Posix)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 }
version(GNU_AsmX86_Windows)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 }
version(GNU_AsmX86_64_Windows)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 //
setThis(Fiber f)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 //
switchIn()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 //
switchOut()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 {
this()1675 this()
1676 {
1677 super( &run );
1678 }
1679
1680 private :
run()1681 void run()
1682 {
1683 counter += 2;
1684 }
1685 }
1686
fiberFunc()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
version(CoreUnittest)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;
version(OSX)1787 version (OSX)
1788 {
1789 version (X86) version = UnsafeFiberMigration;
1790 version (X86_64) version = UnsafeFiberMigration;
1791 }
1792
version(UnsafeFiberMigration)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
version(FiberMigrationUnittest)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 }
catch(Throwable t)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 {
throwAndYield(string msg)1915 static void throwAndYield(string msg) {
1916 try {
1917 throw new Exception(msg);
1918 } finally {
1919 Fiber.yield();
1920 }
1921 }
1922
fiber(string name)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
foo()1955 static void foo()
1956 {
1957 method = "foo";
1958 }
1959
bar()1960 void bar()
1961 {
1962 method = "bar";
1963 }
1964
expect(Fiber fib,string s)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
unreferencedThreadObject()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 {
this(int value)2019 this(int value)
2020 {
2021 _value = value;
2022 }
2023
bar()2024 int bar()
2025 {
2026 return _value;
2027 }
2028
2029 int _value;
2030 }
2031
collect()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
collect2()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
recurse(size_t cnt)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
version(AsmX86_64_Windows)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
version(D_InlineAsm_X86_64)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