/** * The fiber module provides lightweight threads aka fibers. * * Copyright: Copyright Sean Kelly 2005 - 2012. * License: Distributed under the * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). * (See accompanying file LICENSE) * Authors: Sean Kelly, Walter Bright, Alex Rønne Petersen, Martin Nowak * Source: $(DRUNTIMESRC core/thread/fiber/package.d) */ /* NOTE: This file has been patched from the original DMD distribution to * work with the GDC compiler. */module core.thread.fiber; import core.thread.context;import core.thread.fiber.base : fiber_entryPoint, FiberBase;import core.thread.threadbase;import core.thread.threadgroup;import core.thread.types; import core.memory : pageSize; ///////////////////////////////////////////////////////////////////////////////// Fiber Platform Detection/////////////////////////////////////////////////////////////////////////////// version (GNU){ import gcc.builtins; version (GNU_StackGrowsDown) version = StackGrowsDown;}else{ // this should be true for most architectures version = StackGrowsDown;} version (Windows){ import core.stdc.stdlib : free, malloc; import core.sys.windows.winbase; import core.sys.windows.winnt;} package{ version (D_InlineAsm_X86) { version (Windows) version = AsmX86_Windows; else version (Posix) version = AsmX86_Posix; version = AlignFiberStackTo16Byte; } else version (D_InlineAsm_X86_64) { version (Windows) { version = AsmX86_64_Windows; version = AlignFiberStackTo16Byte; } else version (Posix) { version = AsmX86_64_Posix; version = AlignFiberStackTo16Byte; } } else version (X86) { version = AlignFiberStackTo16Byte; version (GNU_CET) { // fiber_switchContext does not support shadow stack from // Intel CET. So use ucontext implementation. } else { version = AsmExternal; version (MinGW) version = GNU_AsmX86_Windows; else version (OSX) version = AsmX86_Posix; else version (Posix) version = AsmX86_Posix; } } else version (X86_64) { version = AlignFiberStackTo16Byte; version (GNU_CET) { // fiber_switchContext does not support shadow stack from // Intel CET. So use ucontext implementation. } else version (D_X32) { // let X32 be handled by ucontext swapcontext } else { version = AsmExternal; version (MinGW) version = GNU_AsmX86_64_Windows; else version (OSX) version = AsmX86_64_Posix; else version (Posix) version = AsmX86_64_Posix; } } else version (PPC) { version (OSX) { version = AsmPPC_Darwin; version = AsmExternal; version = AlignFiberStackTo16Byte; } else version (Posix) { version = AsmPPC_Posix; version = AsmExternal; } } else version (PPC64) { version (OSX) { version = AsmPPC_Darwin; version = AsmExternal; version = AlignFiberStackTo16Byte; } else version (Posix) { version = AlignFiberStackTo16Byte; } } else version (MIPS_O32) { version (Posix) { version = AsmMIPS_O32_Posix; version = AsmExternal; } } else version (MIPS_N64) { version (Posix) { version = AsmMIPS_N64_Posix; version = AsmExternal; } } else version (AArch64) { version (Posix) { version = AsmAArch64_Posix; version = AsmExternal; version = AlignFiberStackTo16Byte; } } else version (ARM) { version (Posix) { version = AsmARM_Posix; version = AsmExternal; } } else version (SPARC) { // NOTE: The SPARC ABI specifies only doubleword alignment. version = AlignFiberStackTo16Byte; } else version (SPARC64) { version = AlignFiberStackTo16Byte; } else version (LoongArch64) { version (Posix) { version = AsmLoongArch64_Posix; version = AsmExternal; version = AlignFiberStackTo16Byte; } } version (Posix) { version (AsmX86_Windows) {} else version (AsmX86_Posix) {} else version (AsmX86_64_Windows) {} else version (AsmX86_64_Posix) {} else version (AsmExternal) {} else { // NOTE: The ucontext implementation requires architecture specific // data definitions to operate so testing for it must be done // by checking for the existence of ucontext_t rather than by // a version identifier. Please note that this is considered // an obsolescent feature according to the POSIX spec, so a // custom solution is still preferred. import core.sys.posix.ucontext : getcontext, makecontext, MINSIGSTKSZ, swapcontext, ucontext_t; } }} ///////////////////////////////////////////////////////////////////////////////// Fiber Entry Point and Context Switch/////////////////////////////////////////////////////////////////////////////// package{ import core.atomic : atomicStore, cas, MemoryOrder; import core.exception : onOutOfMemoryError; import core.stdc.stdlib : abort; // Look above the definition of 'class Fiber' for some information about the implementation of this routine version (AsmExternal) { extern (C) void fiber_switchContext( void** oldp, void* newp ) nothrow @nogc; version (AArch64) extern (C) void fiber_trampoline() nothrow; } else extern (C) void fiber_switchContext( void** oldp, void* newp ) nothrow @nogc { // NOTE: The data pushed and popped in this routine must match the // default stack created by Fiber.initStack or the initial // switch into a new context will fail. version (AsmX86_Windows) { asm pure nothrow @nogc { naked; // save current stack state push EBP; mov EBP, ESP; push EDI; push ESI; push EBX; push dword ptr FS:[0]; push dword ptr FS:[4]; push dword ptr FS:[8]; push EAX; // store oldp again with more accurate address mov EAX, dword ptr 8[EBP]; mov [EAX], ESP; // load newp to begin context switch mov ESP, dword ptr 12[EBP]; // load saved state from new stack pop EAX; pop dword ptr FS:[8]; pop dword ptr FS:[4]; pop dword ptr FS:[0]; pop EBX; pop ESI; pop EDI; pop EBP; // 'return' to complete switch pop ECX; jmp ECX; } } else version (AsmX86_64_Windows) { asm pure nothrow @nogc { naked; // save current stack state // NOTE: When changing the layout of registers on the stack, // make sure that the XMM registers are still aligned. // On function entry, the stack is guaranteed to not // be aligned to 16 bytes because of the return address // on the stack. push RBP; mov RBP, RSP; push R12; push R13; push R14; push R15; push RDI; push RSI; // 7 registers = 56 bytes; stack is now aligned to 16 bytes sub RSP, 160; movdqa [RSP + 144], XMM6; movdqa [RSP + 128], XMM7; movdqa [RSP + 112], XMM8; movdqa [RSP + 96], XMM9; movdqa [RSP + 80], XMM10; movdqa [RSP + 64], XMM11; movdqa [RSP + 48], XMM12; movdqa [RSP + 32], XMM13; movdqa [RSP + 16], XMM14; movdqa [RSP], XMM15; push RBX; xor RAX,RAX; push qword ptr GS:[RAX]; push qword ptr GS:8[RAX]; push qword ptr GS:16[RAX]; // store oldp mov [RCX], RSP; // load newp to begin context switch mov RSP, RDX; // load saved state from new stack pop qword ptr GS:16[RAX]; pop qword ptr GS:8[RAX]; pop qword ptr GS:[RAX]; pop RBX; movdqa XMM15, [RSP]; movdqa XMM14, [RSP + 16]; movdqa XMM13, [RSP + 32]; movdqa XMM12, [RSP + 48]; movdqa XMM11, [RSP + 64]; movdqa XMM10, [RSP + 80]; movdqa XMM9, [RSP + 96]; movdqa XMM8, [RSP + 112]; movdqa XMM7, [RSP + 128]; movdqa XMM6, [RSP + 144]; add RSP, 160; pop RSI; pop RDI; pop R15; pop R14; pop R13; pop R12; pop RBP; // 'return' to complete switch pop RCX; jmp RCX; } } else version (AsmX86_Posix) { asm pure nothrow @nogc { naked; // save current stack state push EBP; mov EBP, ESP; push EDI; push ESI; push EBX; push EAX; // store oldp again with more accurate address mov EAX, dword ptr 8[EBP]; mov [EAX], ESP; // load newp to begin context switch mov ESP, dword ptr 12[EBP]; // load saved state from new stack pop EAX; pop EBX; pop ESI; pop EDI; pop EBP; // 'return' to complete switch pop ECX; jmp ECX; } } else version (AsmX86_64_Posix) { asm pure nothrow @nogc { naked; // save current stack state push RBP; mov RBP, RSP; push RBX; push R12; push R13; push R14; push R15; // store oldp mov [RDI], RSP; // load newp to begin context switch mov RSP, RSI; // load saved state from new stack pop R15; pop R14; pop R13; pop R12; pop RBX; pop RBP; // 'return' to complete switch pop RCX; jmp RCX; } } else static if ( __traits( compiles, ucontext_t ) ) { Fiber cfib = Fiber.getThis(); void* ucur = cfib.m_ucur; *oldp = &ucur; swapcontext( **(cast(ucontext_t***) oldp), *(cast(ucontext_t**) newp) ); } else static assert(0, "Not implemented"); }} ///////////////////////////////////////////////////////////////////////////////// Fiber////////////////////////////////////////////////////////////////////////////////* * Documentation of Fiber internals: * * The main routines to implement when porting Fibers to new architectures are * fiber_switchContext and initStack. Some version constants have to be defined * for the new platform as well, search for "Fiber Platform Detection and Memory Allocation". * * Fibers are based on a concept called 'Context'. A Context describes the execution * state of a Fiber or main thread which is fully described by the stack, some * registers and a return address at which the Fiber/Thread should continue executing. * Please note that not only each Fiber has a Context, but each thread also has got a * Context which describes the threads stack and state. If you call Fiber fib; fib.call * the first time in a thread you switch from Threads Context into the Fibers Context. * If you call fib.yield in that Fiber you switch out of the Fibers context and back * into the Thread Context. (However, this is not always the case. You can call a Fiber * from within another Fiber, then you switch Contexts between the Fibers and the Thread * Context is not involved) * * In all current implementations the registers and the return address are actually * saved on a Contexts stack. * * The fiber_switchContext routine has got two parameters: * void** a: This is the _location_ where we have to store the current stack pointer, * the stack pointer of the currently executing Context (Fiber or Thread). * void* b: This is the pointer to the stack of the Context which we want to switch into. * Note that we get the same pointer here as the one we stored into the void** a * in a previous call to fiber_switchContext. * * In the simplest case, a fiber_switchContext rountine looks like this: * fiber_switchContext: * push {return Address} * push {registers} * copy {stack pointer} into {location pointed to by a} * //We have now switch to the stack of a different Context! * copy {b} into {stack pointer} * pop {registers} * pop {return Address} * jump to {return Address} * * The GC uses the value returned in parameter a to scan the Fibers stack. It scans from * the stack base to that value. As the GC dislikes false pointers we can actually optimize * this a little: By storing registers which can not contain references to memory managed * by the GC outside of the region marked by the stack base pointer and the stack pointer * saved in fiber_switchContext we can prevent the GC from scanning them. * Such registers are usually floating point registers and the return address. In order to * implement this, we return a modified stack pointer from fiber_switchContext. However, * we have to remember that when we restore the registers from the stack! * * --------------------------- <= Stack Base * | Frame | <= Many other stack frames * | Frame | * |-------------------------| <= The last stack frame. This one is created by fiber_switchContext * | registers with pointers | * | | <= Stack pointer. GC stops scanning here * | return address | * |floating point registers | * --------------------------- <= Real Stack End * * fiber_switchContext: * push {registers with pointers} * copy {stack pointer} into {location pointed to by a} * push {return Address} * push {Floating point registers} * //We have now switch to the stack of a different Context! * copy {b} into {stack pointer} * //We now have to adjust the stack pointer to point to 'Real Stack End' so we can pop * //the FP registers * //+ or - depends on if your stack grows downwards or upwardsLines 1–500 of 1433. Load more
/** * The fiber module provides lightweight threads aka fibers. * * Copyright: Copyright Sean Kelly 2005 - 2012. * License: Distributed under the * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). * (See accompanying file LICENSE) * Authors: Sean Kelly, Walter Bright, Alex Rønne Petersen, Martin Nowak * Source: $(DRUNTIMESRC core/thread/fiber/package.d) */ /* NOTE: This file has been patched from the original DMD distribution to * work with the GDC compiler. */ module core.thread.fiber; import core.thread.context; import core.thread.fiber.base : fiber_entryPoint, FiberBase; import core.thread.threadbase; import core.thread.threadgroup; import core.thread.types; import core.memory : pageSize; /////////////////////////////////////////////////////////////////////////////// // Fiber Platform Detection /////////////////////////////////////////////////////////////////////////////// version (GNU) { import gcc.builtins; version (GNU_StackGrowsDown) version = StackGrowsDown; } else { // this should be true for most architectures version = StackGrowsDown; } version (Windows) { import core.stdc.stdlib : free, malloc; import core.sys.windows.winbase; import core.sys.windows.winnt; } package { version (D_InlineAsm_X86) { version (Windows) version = AsmX86_Windows; else version (Posix) version = AsmX86_Posix; version = AlignFiberStackTo16Byte; } else version (D_InlineAsm_X86_64) { version (Windows) { version = AsmX86_64_Windows; version = AlignFiberStackTo16Byte; } else version (Posix) { version = AsmX86_64_Posix; version = AlignFiberStackTo16Byte; } } else version (X86) { version = AlignFiberStackTo16Byte; version (GNU_CET) { // fiber_switchContext does not support shadow stack from // Intel CET. So use ucontext implementation. } else { version = AsmExternal; version (MinGW) version = GNU_AsmX86_Windows; else version (OSX) version = AsmX86_Posix; else version (Posix) version = AsmX86_Posix; } } else version (X86_64) { version = AlignFiberStackTo16Byte; version (GNU_CET) { // fiber_switchContext does not support shadow stack from // Intel CET. So use ucontext implementation. } else version (D_X32) { // let X32 be handled by ucontext swapcontext } else { version = AsmExternal; version (MinGW) version = GNU_AsmX86_64_Windows; else version (OSX) version = AsmX86_64_Posix; else version (Posix) version = AsmX86_64_Posix; } } else version (PPC) { version (OSX) { version = AsmPPC_Darwin; version = AsmExternal; version = AlignFiberStackTo16Byte; } else version (Posix) { version = AsmPPC_Posix; version = AsmExternal; } } else version (PPC64) { version (OSX) { version = AsmPPC_Darwin; version = AsmExternal; version = AlignFiberStackTo16Byte; } else version (Posix) { version = AlignFiberStackTo16Byte; } } else version (MIPS_O32) { version (Posix) { version = AsmMIPS_O32_Posix; version = AsmExternal; } } else version (MIPS_N64) { version (Posix) { version = AsmMIPS_N64_Posix; version = AsmExternal; } } else version (AArch64) { version (Posix) { version = AsmAArch64_Posix; version = AsmExternal; version = AlignFiberStackTo16Byte; } } else version (ARM) { version (Posix) { version = AsmARM_Posix; version = AsmExternal; } } else version (SPARC) { // NOTE: The SPARC ABI specifies only doubleword alignment. version = AlignFiberStackTo16Byte; } else version (SPARC64) { version = AlignFiberStackTo16Byte; } else version (LoongArch64) { version (Posix) { version = AsmLoongArch64_Posix; version = AsmExternal; version = AlignFiberStackTo16Byte; } } version (Posix) { version (AsmX86_Windows) {} else version (AsmX86_Posix) {} else version (AsmX86_64_Windows) {} else version (AsmX86_64_Posix) {} else version (AsmExternal) {} else { // NOTE: The ucontext implementation requires architecture specific // data definitions to operate so testing for it must be done // by checking for the existence of ucontext_t rather than by // a version identifier. Please note that this is considered // an obsolescent feature according to the POSIX spec, so a // custom solution is still preferred. import core.sys.posix.ucontext : getcontext, makecontext, MINSIGSTKSZ, swapcontext, ucontext_t; } } } /////////////////////////////////////////////////////////////////////////////// // Fiber Entry Point and Context Switch /////////////////////////////////////////////////////////////////////////////// package { import core.atomic : atomicStore, cas, MemoryOrder; import core.exception : onOutOfMemoryError; import core.stdc.stdlib : abort; // Look above the definition of 'class Fiber' for some information about the implementation of this routine version (AsmExternal) { extern (C) void fiber_switchContext( void** oldp, void* newp ) nothrow @nogc; version (AArch64) extern (C) void fiber_trampoline() nothrow; } else extern (C) void fiber_switchContext( void** oldp, void* newp ) nothrow @nogc { // NOTE: The data pushed and popped in this routine must match the // default stack created by Fiber.initStack or the initial // switch into a new context will fail. version (AsmX86_Windows) { asm pure nothrow @nogc { naked; // save current stack state push EBP; mov EBP, ESP; push EDI; push ESI; push EBX; push dword ptr FS:[0]; push dword ptr FS:[4]; push dword ptr FS:[8]; push EAX; // store oldp again with more accurate address mov EAX, dword ptr 8[EBP]; mov [EAX], ESP; // load newp to begin context switch mov ESP, dword ptr 12[EBP]; // load saved state from new stack pop EAX; pop dword ptr FS:[8]; pop dword ptr FS:[4]; pop dword ptr FS:[0]; pop EBX; pop ESI; pop EDI; pop EBP; // 'return' to complete switch pop ECX; jmp ECX; } } else version (AsmX86_64_Windows) { asm pure nothrow @nogc { naked; // save current stack state // NOTE: When changing the layout of registers on the stack, // make sure that the XMM registers are still aligned. // On function entry, the stack is guaranteed to not // be aligned to 16 bytes because of the return address // on the stack. push RBP; mov RBP, RSP; push R12; push R13; push R14; push R15; push RDI; push RSI; // 7 registers = 56 bytes; stack is now aligned to 16 bytes sub RSP, 160; movdqa [RSP + 144], XMM6; movdqa [RSP + 128], XMM7; movdqa [RSP + 112], XMM8; movdqa [RSP + 96], XMM9; movdqa [RSP + 80], XMM10; movdqa [RSP + 64], XMM11; movdqa [RSP + 48], XMM12; movdqa [RSP + 32], XMM13; movdqa [RSP + 16], XMM14; movdqa [RSP], XMM15; push RBX; xor RAX,RAX; push qword ptr GS:[RAX]; push qword ptr GS:8[RAX]; push qword ptr GS:16[RAX]; // store oldp mov [RCX], RSP; // load newp to begin context switch mov RSP, RDX; // load saved state from new stack pop qword ptr GS:16[RAX]; pop qword ptr GS:8[RAX]; pop qword ptr GS:[RAX]; pop RBX; movdqa XMM15, [RSP]; movdqa XMM14, [RSP + 16]; movdqa XMM13, [RSP + 32]; movdqa XMM12, [RSP + 48]; movdqa XMM11, [RSP + 64]; movdqa XMM10, [RSP + 80]; movdqa XMM9, [RSP + 96]; movdqa XMM8, [RSP + 112]; movdqa XMM7, [RSP + 128]; movdqa XMM6, [RSP + 144]; add RSP, 160; pop RSI; pop RDI; pop R15; pop R14; pop R13; pop R12; pop RBP; // 'return' to complete switch pop RCX; jmp RCX; } } else version (AsmX86_Posix) { asm pure nothrow @nogc { naked; // save current stack state push EBP; mov EBP, ESP; push EDI; push ESI; push EBX; push EAX; // store oldp again with more accurate address mov EAX, dword ptr 8[EBP]; mov [EAX], ESP; // load newp to begin context switch mov ESP, dword ptr 12[EBP]; // load saved state from new stack pop EAX; pop EBX; pop ESI; pop EDI; pop EBP; // 'return' to complete switch pop ECX; jmp ECX; } } else version (AsmX86_64_Posix) { asm pure nothrow @nogc { naked; // save current stack state push RBP; mov RBP, RSP; push RBX; push R12; push R13; push R14; push R15; // store oldp mov [RDI], RSP; // load newp to begin context switch mov RSP, RSI; // load saved state from new stack pop R15; pop R14; pop R13; pop R12; pop RBX; pop RBP; // 'return' to complete switch pop RCX; jmp RCX; } } else static if ( __traits( compiles, ucontext_t ) ) { Fiber cfib = Fiber.getThis(); void* ucur = cfib.m_ucur; *oldp = &ucur; swapcontext( **(cast(ucontext_t***) oldp), *(cast(ucontext_t**) newp) ); } else static assert(0, "Not implemented"); } } /////////////////////////////////////////////////////////////////////////////// // Fiber /////////////////////////////////////////////////////////////////////////////// /* * Documentation of Fiber internals: * * The main routines to implement when porting Fibers to new architectures are * fiber_switchContext and initStack. Some version constants have to be defined * for the new platform as well, search for "Fiber Platform Detection and Memory Allocation". * * Fibers are based on a concept called 'Context'. A Context describes the execution * state of a Fiber or main thread which is fully described by the stack, some * registers and a return address at which the Fiber/Thread should continue executing. * Please note that not only each Fiber has a Context, but each thread also has got a * Context which describes the threads stack and state. If you call Fiber fib; fib.call * the first time in a thread you switch from Threads Context into the Fibers Context. * If you call fib.yield in that Fiber you switch out of the Fibers context and back * into the Thread Context. (However, this is not always the case. You can call a Fiber * from within another Fiber, then you switch Contexts between the Fibers and the Thread * Context is not involved) * * In all current implementations the registers and the return address are actually * saved on a Contexts stack. * * The fiber_switchContext routine has got two parameters: * void** a: This is the _location_ where we have to store the current stack pointer, * the stack pointer of the currently executing Context (Fiber or Thread). * void* b: This is the pointer to the stack of the Context which we want to switch into. * Note that we get the same pointer here as the one we stored into the void** a * in a previous call to fiber_switchContext. * * In the simplest case, a fiber_switchContext rountine looks like this: * fiber_switchContext: * push {return Address} * push {registers} * copy {stack pointer} into {location pointed to by a} * //We have now switch to the stack of a different Context! * copy {b} into {stack pointer} * pop {registers} * pop {return Address} * jump to {return Address} * * The GC uses the value returned in parameter a to scan the Fibers stack. It scans from * the stack base to that value. As the GC dislikes false pointers we can actually optimize * this a little: By storing registers which can not contain references to memory managed * by the GC outside of the region marked by the stack base pointer and the stack pointer * saved in fiber_switchContext we can prevent the GC from scanning them. * Such registers are usually floating point registers and the return address. In order to * implement this, we return a modified stack pointer from fiber_switchContext. However, * we have to remember that when we restore the registers from the stack! * * --------------------------- <= Stack Base * | Frame | <= Many other stack frames * | Frame | * |-------------------------| <= The last stack frame. This one is created by fiber_switchContext * | registers with pointers | * | | <= Stack pointer. GC stops scanning here * | return address | * |floating point registers | * --------------------------- <= Real Stack End * * fiber_switchContext: * push {registers with pointers} * copy {stack pointer} into {location pointed to by a} * push {return Address} * push {Floating point registers} * //We have now switch to the stack of a different Context! * copy {b} into {stack pointer} * //We now have to adjust the stack pointer to point to 'Real Stack End' so we can pop * //the FP registers * //+ or - depends on if your stack grows downwards or upwards