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