Wolfgang Thallergcc 15.1.0ada185d1 year agoHistory
1091 lines · 32.5 KB · D
RawDownload
/** * Base fiber module provides OS-indepedent part of 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/base.d) */ /* NOTE: This file has been patched from the original DMD distribution to * work with the GDC compiler. */module core.thread.fiber.base; package:version (GNU)    import gcc.config; import core.thread.fiber;import core.thread.threadbase;import core.thread.threadgroup;import core.thread.types;import core.thread.context; import core.memory : pageSize; package{    import core.atomic : atomicStore, cas, MemoryOrder;    import core.exception : onOutOfMemoryError;    import core.stdc.stdlib : abort;     extern (C) void fiber_entryPoint() nothrow    {        FiberBase obj = FiberBase.getThis();        assert( obj );         assert( ThreadBase.getThis().m_curr is obj.m_ctxt );        atomicStore!(MemoryOrder.raw)(*cast(shared)&ThreadBase.getThis().m_lock, false);        obj.m_ctxt.tstack = obj.m_ctxt.bstack;        obj.m_state = FiberBase.State.EXEC;         try        {            obj.run();        }        catch ( Throwable t )        {            obj.m_unhandled = t;        }         static if ( __traits( compiles, ucontext_t ) )          obj.m_ucur = &obj.m_utxt;         obj.m_state = Fiber.State.TERM;        obj.switchOut();    }} ///////////////////////////////////////////////////////////////////////////////// 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 *     {stack pointer} = {stack pointer} +- ({FPRegisters}.sizeof + {return address}.sizeof} *     pop {Floating point registers} *     pop {return Address} *     pop {registers with pointers} *     jump to {return Address} * * So the question now is which registers need to be saved? This depends on the specific * architecture ABI of course, but here are some general guidelines: * - If a register is callee-save (if the callee modifies the register it must saved and *   restored by the callee) it needs to be saved/restored in switchContext * - If a register is caller-save it needn't be saved/restored. (Calling fiber_switchContext *   is a function call and the compiler therefore already must save these registers before *   calling fiber_switchContext) * - Argument registers used for passing parameters to functions needn't be saved/restored * - The return register needn't be saved/restored (fiber_switchContext hasn't got a return type) * - All scratch registers needn't be saved/restored * - The link register usually needn't be saved/restored (but sometimes it must be cleared - *   see below for details) * - The frame pointer register - if it exists - is usually callee-save * - All current implementations do not save control registers * * What happens on the first switch into a Fiber? We never saved a state for this fiber before, * but the initial state is prepared in the initStack routine. (This routine will also be called * when a Fiber is being resetted). initStack must produce exactly the same stack layout as the * part of fiber_switchContext which saves the registers. Pay special attention to set the stack * pointer correctly if you use the GC optimization mentioned before. the return Address saved in * initStack must be the address of fiber_entrypoint. * * There's now a small but important difference between the first context switch into a fiber and * further context switches. On the first switch, Fiber.call is used and the returnAddress in * fiber_switchContext will point to fiber_entrypoint. The important thing here is that this jump * is a _function call_, we call fiber_entrypoint by jumping before it's function prologue. On later * calls, the user used yield() in a function, and therefore the return address points into a user * function, after the yield call. So here the jump in fiber_switchContext is a _function return_, * not a function call! * * The most important result of this is that on entering a function, i.e. fiber_entrypoint, we * would have to provide a return address / set the link register once fiber_entrypoint * returns. Now fiber_entrypoint does never return and therefore the actual value of the return * address / link register is never read/used and therefore doesn't matter. When fiber_switchContext * performs a _function return_ the value in the link register doesn't matter either. * However, the link register will still be saved to the stack in fiber_entrypoint and some * exception handling / stack unwinding code might read it from this stack location and crash. * The exact solution depends on your architecture, but see the ARM implementation for a way * to deal with this issue. * * The ARM implementation is meant to be used as a kind of documented example implementation. * Look there for a concrete example. * * FIXME: fiber_entrypoint might benefit from a @noreturn attribute, but D doesn't have one. */ /** * This class provides a cooperative concurrency mechanism integrated with the * threading and garbage collection functionality.  Calling a fiber may be * considered a blocking operation that returns when the fiber yields (via * Fiber.yield()).  Execution occurs within the context of the calling thread * so synchronization is not necessary to guarantee memory visibility so long * as the same thread calls the fiber each time.  Please note that there is no * requirement that a fiber be bound to one specific thread.  Rather, fibers * may be freely passed between threads so long as they are not currently * executing.  Like threads, a new fiber thread may be created using either * derivation or composition, as in the following example. * * Warning: * Status registers are not saved by the current implementations. This means * floating point exception status bits (overflow, divide by 0), rounding mode * and similar stuff is set per-thread, not per Fiber! * * Warning: * On ARM FPU registers are not saved if druntime was compiled as ARM_SoftFloat. * If such a build is used on a ARM_SoftFP system which actually has got a FPU * and other libraries are using the FPU registers (other code is compiled * as ARM_SoftFP) this can cause problems. Druntime must be compiled as * ARM_SoftFP in this case. * * Authors: Based on a design by Mikola Lysenko. */class FiberBase{    /**     * Initializes a fiber object which is associated with a static     * D function.     *     * Params:     *  fn = The fiber function.     *  sz = The stack size for this fiber.     *  guardPageSize = size of the guard page to trap fiber's stack     *                  overflows. Beware that using this will increase     *                  the number of mmaped regions on platforms using mmap     *                  so an OS-imposed limit may be hit.     *     * In:     *  fn must not be null.     */    this( void function() fn, size_t sz, size_t guardPageSize ) nothrow    in    {        assert( fn );    }    do    {        allocStack( sz, guardPageSize );        reset( fn );    }      /**     * Initializes a fiber object which is associated with a dynamic     * D function.     *     * Params:     *  dg = The fiber function.     *  sz = The stack size for this fiber.     *  guardPageSize = size of the guard page to trap fiber's stack     *                  overflows. Beware that using this will increase     *                  the number of mmaped regions on platforms using mmap     *                  so an OS-imposed limit may be hit.     *     * In:     *  dg must not be null.     */    this( void delegate() dg, size_t sz, size_t guardPageSize ) nothrow    {        allocStack( sz, guardPageSize );        reset( cast(void delegate() const) dg );    }      /**     * Cleans up any remaining resources used by this object.     */    ~this() nothrow @nogc    {        // NOTE: A live reference to this object will exist on its associated        //       stack from the first time its call() method has been called        //       until its execution completes with State.TERM.  Thus, the only        //       times this dtor should be called are either if the fiber has        //       terminated (and therefore has no active stack) or if the user        //       explicitly deletes this object.  The latter case is an error        //       but is not easily tested for, since State.HOLD may imply that        //       the fiber was just created but has never been run.  There is        //       not a compelling case to create a State.INIT just to offer a        //       means of ensuring the user isn't violating this object's        //       contract, so for now this requirement will be enforced by        //       documentation only.        freeStack();    }      ///////////////////////////////////////////////////////////////////////////    // General Actions    ///////////////////////////////////////////////////////////////////////////      /**     * Transfers execution to this fiber object.  The calling context will be     * suspended until the fiber calls Fiber.yield() or until it terminates     * via an unhandled exception.     *     * Params:     *  rethrow = Rethrow any unhandled exception which may have caused this     *            fiber to terminate.     *     * In:     *  This fiber must be in state HOLD.     *     * Throws:     *  Any exception not handled by the joined thread.     *     * Returns:     *  Any exception not handled by this fiber if rethrow = false, null     *  otherwise.     */    // Not marked with any attributes, even though `nothrow @nogc` works    // because it calls arbitrary user code. Most of the implementation    // is already `@nogc nothrow`, but in order for `Fiber.call` to    // propagate the attributes of the user's function, the Fiber    // class needs to be templated.    final Throwable call( Rethrow rethrow = Rethrow.yes )    {        return rethrow ? call!(Rethrow.yes)() : call!(Rethrow.no);    }     /// ditto    final Throwable call( Rethrow rethrow )()    {        callImpl();        if ( m_unhandled )        {            Throwable t = m_unhandled;            m_unhandled = null;            static if ( rethrow )                throw t;            else                return t;        }        return null;    }     private void callImpl() nothrow @nogc    in    {        assert( m_state == State.HOLD );    }    do    {        FiberBase cur = getThis();         static if ( __traits( compiles, ucontext_t ) )            m_ucur = cur ? &cur.m_utxt : &Fiber.sm_utxt;         setThis( this );        this.switchIn();        setThis( cur );         static if ( __traits( compiles, ucontext_t ) )            m_ucur = null;         // NOTE: If the fiber has terminated then the stack pointers must be        //       reset.  This ensures that the stack for this fiber is not        //       scanned if the fiber has terminated.  This is necessary to        //       prevent any references lingering on the stack from delaying        //       the collection of otherwise dead objects.  The most notable        //       being the current object, which is referenced at the top of        //       fiber_entryPoint.        if ( m_state == State.TERM )        {            m_ctxt.tstack = m_ctxt.bstack;        }    }     /// Flag to control rethrow behavior of $(D $(LREF call))    enum Rethrow : bool { no, yes }     /**     * Resets this fiber so that it may be re-used, optionally with a     * new function/delegate.  This routine should only be called for     * fibers that have terminated, as doing otherwise could result in     * scope-dependent functionality that is not executed.     * Stack-based classes, for example, may not be cleaned up     * properly if a fiber is reset before it has terminated.     *     * In:     *  This fiber must be in state TERM or HOLD.     */    final void reset() nothrow @nogc    in    {        assert( m_state == State.TERM || m_state == State.HOLD );    }    do    {        m_ctxt.tstack = m_ctxt.bstack;        m_state = State.HOLD;        initStack();        m_unhandled = null;    }     /// ditto    final void reset( void function() fn ) nothrow @nogc    {        reset();        m_call  = fn;    }     /// ditto    final void reset( void delegate() dg ) nothrow @nogc    {        reset();        m_call  = dg;    }     ///////////////////////////////////////////////////////////////////////////    // General Properties    ///////////////////////////////////////////////////////////////////////////      /// A fiber may occupy one of three states: HOLD, EXEC, and TERM.    enum State    {        /** The HOLD state applies to any fiber that is suspended and ready to        be called. */        HOLD,        /** The EXEC state will be set for any fiber that is currently        executing. */        EXEC,        /** The TERM state is set when a fiber terminates. Once a fiber        terminates, it must be reset before it may be called again. */        TERM    }      /**     * Gets the current state of this fiber.     *     * Returns:     *  The state of this fiber as an enumerated value.     */    final @property State state() const @safe pure nothrow @nogc    {        return m_state;    }      ///////////////////////////////////////////////////////////////////////////    // Actions on Calling Fiber    ///////////////////////////////////////////////////////////////////////////      /**     * Forces a context switch to occur away from the calling fiber.     */    static void yield() nothrow @nogc    {        FiberBase cur = getThis();        assert( cur, "Fiber.yield() called with no active fiber" );        assert( cur.m_state == State.EXEC );         static if ( __traits( compiles, ucontext_t ) )          cur.m_ucur = &cur.m_utxt;         cur.m_state = State.HOLD;        cur.switchOut();        cur.m_state = State.EXEC;    }      /**     * Forces a context switch to occur away from the calling fiber and then     * throws obj in the calling fiber.     *     * Params:     *  t = The object to throw.     *     * In:     *  t must not be null.     */    static void yieldAndThrow( Throwable t ) nothrow @nogc    in    {        assert( t );    }    do    {        FiberBase cur = getThis();        assert( cur, "Fiber.yield() called with no active fiber" );        assert( cur.m_state == State.EXEC );         static if ( __traits( compiles, ucontext_t ) )          cur.m_ucur = &cur.m_utxt;         cur.m_unhandled = t;        cur.m_state = State.HOLD;        cur.switchOut();        cur.m_state = State.EXEC;    }      ///////////////////////////////////////////////////////////////////////////    // Fiber Accessors    ///////////////////////////////////////////////////////////////////////////      /**     * Provides a reference to the calling fiber or null if no fiber is     * currently active.     *

Lines 1–500 of 1091.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t