diff --git a/mak/COPY b/mak/COPY index 47f6ec3f2f..45e55e63fd 100644 --- a/mak/COPY +++ b/mak/COPY @@ -435,6 +435,7 @@ COPY=\ \ $(IMPDIR)\core\thread\fiber.d \ $(IMPDIR)\core\thread\osthread.d \ + $(IMPDIR)\core\thread\context.d \ $(IMPDIR)\core\thread\package.d \ \ $(IMPDIR)\etc\linux\memoryerror.d diff --git a/mak/DOCS b/mak/DOCS index 7892992f3e..fb09b703da 100644 --- a/mak/DOCS +++ b/mak/DOCS @@ -79,6 +79,7 @@ DOCS=\ \ $(DOCDIR)\core_thread_fiber.html \ $(DOCDIR)\core_thread_osthread.html \ + $(DOCDIR)\core_thread_context.html \ \ $(DOCDIR)\core_internal_array_capacity.html \ \ diff --git a/mak/SRCS b/mak/SRCS index 6237ad872a..67c6e915b5 100644 --- a/mak/SRCS +++ b/mak/SRCS @@ -433,6 +433,7 @@ SRCS=\ \ src\core\thread\fiber.d \ src\core\thread\osthread.d \ + src\core\thread\context.d \ src\core\thread\package.d \ \ src\gc\bits.d \ diff --git a/mak/WINDOWS b/mak/WINDOWS index 0ada094036..c8aa1575e4 100644 --- a/mak/WINDOWS +++ b/mak/WINDOWS @@ -1276,6 +1276,9 @@ $(IMPDIR)\core\thread\fiber.d : src\core\thread\fiber.d $(IMPDIR)\core\thread\osthread.d : src\core\thread\osthread.d copy $** $@ +$(IMPDIR)\core\thread\context.d : src\core\thread\context.d + copy $** $@ + $(IMPDIR)\core\thread\package.d : src\core\thread\package.d copy $** $@ diff --git a/src/core/thread/context.d b/src/core/thread/context.d new file mode 100644 index 0000000000..2bda556007 --- /dev/null +++ b/src/core/thread/context.d @@ -0,0 +1,619 @@ +/** + * Every thread / fiber needs stack context data structure. + * thread.context decouples this info into the StackContext struct. + * Also, Thread / Fiber have some common functionality which is provided + * as a super class for both in StackContextExecutor. + * Finally, GlobalStackContext contains commonly used global data. + * + * 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/osthread.d) + */ + +module core.thread.context; + +struct StackContext +{ + // bottom, top of stack + void* bstack, tstack; + + /// Slot for the EH implementation to keep some state for each stack + /// (will be necessary for exception chaining, etc.). Opaque as far as + /// we are concerned here. + void* ehContext; + + StackContext* within, next, prev; +} + + +/// Flag to control rethrow behavior of call, join and friends +enum Rethrow : bool { no, yes } + +/** +A class that represents a thread of execution that manages a stack. +This serves primarily as a superclass for Thread and Fiber. +*/ +class StackContextExecutor +{ + // + // The type of routine passed on thread/fiber construction. + // + enum Call + { + NO, + FN, + DG + } + + // Common standard data for Thread / Fiber. + Call m_call = Call.NO; + union + { + void function() m_fn; + void delegate() m_dg; + } + + StackContext* m_ctxt; + size_t m_size; + Throwable m_unhandled; + + + // Thread / Fiber entry point. Invokes the function or delegate passed on + // construction (if any). + final void run() + { + switch ( m_call ) + { + case Call.FN: + m_fn(); + break; + case Call.DG: + m_dg(); + break; + default: + break; + } + } + + final void pushContext( StackContext* c ) nothrow @nogc + in + { + assert( !c.within ); + } + do + { + m_ctxt.ehContext = swapContext(c.ehContext); + c.within = m_ctxt; + m_ctxt = c; + } + + final void popContext() nothrow @nogc + in + { + assert( m_ctxt && m_ctxt.within ); + } + do + { + StackContext* c = m_ctxt; + m_ctxt = c.within; + c.ehContext = swapContext(m_ctxt.ehContext); + c.within = null; + } + + final StackContext* topContext() nothrow @nogc + in + { + assert( m_ctxt ); + } + do + { + return m_ctxt; + } +} + +struct GlobalStackContext +{ + /////////////////////////////////////////////////////////////////////////// + // GC Scanning Support + /////////////////////////////////////////////////////////////////////////// + + import core.sync.mutex : Mutex; + + + // NOTE: The GC scanning process works like so: + // + // 1. Suspend all threads. + // 2. Scan the stacks of all suspended threads for roots. + // 3. Resume all threads. + // + // Step 1 and 3 require a list of all threads in the system, while + // step 2 requires a list of all thread stacks (each represented by + // a Context struct). Traditionally, there was one stack per thread + // and the Context structs were not necessary. However, Fibers have + // changed things so that each thread has its own 'main' stack plus + // an arbitrary number of nested stacks (normally referenced via + // m_curr). Also, there may be 'free-floating' stacks in the system, + // which are Fibers that are not currently executing on any specific + // thread but are still being processed and still contain valid + // roots. + // + // To support all of this, the StackContext struct has been created to + // represent a stack range, and a global list of StackContext structs has + // been added to enable scanning of these stack ranges. The lifetime + // (and presence in the StackContext list) of a thread's 'main' stack will + // be equivalent to the thread's lifetime. So the StackContext will be + // added to the list on thread entry, and removed from the list on + // thread exit (which is essentially the same as the presence of a + // Thread object in its own global list). The lifetime of a Fiber's + // context, however, will be tied to the lifetime of the Fiber object + // itself, and Fibers are expected to add/remove their Context struct + // on construction/deletion. + + + // + // All use of the global thread lists/array should synchronize on this lock. + // + // Careful as the GC acquires this lock after the GC lock to suspend all + // threads any GC usage with slock held can result in a deadlock through + // lock order inversion. + @property static Mutex slock() nothrow @nogc + { + return cast(Mutex)_locks[0].ptr; + } + + @property static Mutex criticalRegionLock() nothrow @nogc + { + return cast(Mutex)_locks[1].ptr; + } + + __gshared align(Mutex.alignof) void[__traits(classInstanceSize, Mutex)][2] _locks; + + static void initLocks() @nogc + { + foreach (ref lock; _locks) + { + lock[] = typeid(Mutex).initializer[]; + (cast(Mutex)lock.ptr).__ctor(); + } + } + + static void termLocks() @nogc + { + foreach (ref lock; _locks) + (cast(Mutex)lock.ptr).__dtor(); + } + + + + __gshared StackContext* sm_cbeg; + + import core.thread : Thread; + + __gshared Thread sm_tbeg; + __gshared size_t sm_tlen; + + // can't use core.internal.util.array in public code + __gshared Thread* pAboutToStart; + __gshared size_t nAboutToStart; + + + __gshared uint suspendDepth = 0; + + /////////////////////////////////////////////////////////////////////////// + // Global Context List Operations + /////////////////////////////////////////////////////////////////////////// + + + // + // Add a context to the global context list. + // + static void add(StackContext* c) nothrow @nogc + in + { + assert( c ); + assert( !c.next && !c.prev ); + } + do + { + slock.lock_nothrow(); + scope(exit) slock.unlock_nothrow(); + assert(!suspendDepth); // must be 0 b/c it's only set with slock held + + if (sm_cbeg) + { + c.next = sm_cbeg; + sm_cbeg.prev = c; + } + sm_cbeg = c; + } + + // + // Remove a context from the global context list. + // + // This assumes slock being acquired. This isn't done here to + // avoid double locking when called from remove(Thread) + static void remove(StackContext* c) nothrow @nogc + in + { + assert(c); + assert(c.next || c.prev); + } + do + { + if (c.prev) + c.prev.next = c.next; + if (c.next) + c.next.prev = c.prev; + if (sm_cbeg == c) + sm_cbeg = c.next; + // NOTE: Don't null out c.next or c.prev because opApply currently + // follows c.next after removing a node. This could be easily + // addressed by simply returning the next node from this + // function, however, a context should never be re-added to the + // list anyway and having next and prev be non-null is a good way + // to ensure that. + } + + /////////////////////////////////////////////////////////////////////////// + // Global Thread List Operations + /////////////////////////////////////////////////////////////////////////// + + + // + // Add a thread to the global thread list. + // + static void add(Thread t, bool rmAboutToStart = true) nothrow @nogc + in + { + assert(t); + assert(!t.next && !t.prev); + } + do + { + slock.lock_nothrow(); + scope(exit) slock.unlock_nothrow(); + assert(t.isRunning); // check this with slock to ensure pthread_create already returned + assert(!suspendDepth); // must be 0 b/c it's only set with slock held + + if (rmAboutToStart) + { + size_t idx = -1; + foreach (i, thr; pAboutToStart[0 .. nAboutToStart]) + { + if (thr is t) + { + idx = i; + break; + } + } + assert(idx != -1); + import core.stdc.string : memmove; + import core.stdc.stdlib : realloc; + memmove(pAboutToStart + idx, + pAboutToStart + idx + 1, Thread.sizeof * (nAboutToStart - idx - 1)); + + pAboutToStart = + cast(Thread*)realloc(pAboutToStart, Thread.sizeof * --nAboutToStart); + } + + if (sm_tbeg) + { + t.next = sm_tbeg; + sm_tbeg.prev = t; + } + sm_tbeg = t; + ++sm_tlen; + } + + + // + // Remove a thread from the global thread list. + // + static void remove(Thread t) nothrow @nogc + in + { + assert(t); + } + do + { + // Thread was already removed earlier, might happen b/c of thread_detachInstance + if (!t.next && !t.prev && (sm_tbeg !is t)) + return; + + slock.lock_nothrow(); + { + // NOTE: When a thread is removed from the global thread list its + // main context is invalid and should be removed as well. + // It is possible that t.m_curr could reference more + // than just the main context if the thread exited abnormally + // (if it was terminated), but we must assume that the user + // retains a reference to them and that they may be re-used + // elsewhere. Therefore, it is the responsibility of any + // object that creates contexts to clean them up properly + // when it is done with them. + remove(&t.m_main); + + if (t.prev) + t.prev.next = t.next; + if (t.next) + t.next.prev = t.prev; + if (sm_tbeg is t) + sm_tbeg = t.next; + t.prev = t.next = null; + --sm_tlen; + } + // NOTE: Don't null out t.next or t.prev because opApply currently + // follows t.next after removing a node. This could be easily + // addressed by simply returning the next node from this + // function, however, a thread should never be re-added to the + // list anyway and having next and prev be non-null is a good way + // to ensure that. + slock.unlock_nothrow(); + } +} + +version (OSX) + version = Darwin; +else version (iOS) + version = Darwin; +else version (TVOS) + version = Darwin; +else version (WatchOS) + version = Darwin; + +version (D_InlineAsm_X86) +{ + version (Windows) + version = AsmX86_Windows; + else version (Posix) + version = AsmX86_Posix; +} +else version (D_InlineAsm_X86_64) +{ + version (Windows) + { + version = AsmX86_64_Windows; + } + else version (Posix) + { + version = AsmX86_64_Posix; + } +} + +version (Posix) +{ + import core.sys.posix.pthread; + import core.sys.posix.unistd; + + 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; + } +} + +version (Solaris) +{ + import core.sys.solaris.sys.priocntl; + import core.sys.solaris.sys.types; + import core.sys.posix.sys.wait : idtype_t; +} + + +private +{ + /** + * Hook for whatever EH implementation is used to save/restore some data + * per stack. + * + * Params: + * newContext = The return value of the prior call to this function + * where the stack was last swapped out, or null when a fiber stack + * is switched in for the first time. + */ + extern(C) void* _d_eh_swapContext(void* newContext) nothrow @nogc; + + version (DigitalMars) + { + version (Windows) + alias swapContext = _d_eh_swapContext; + else + { + extern(C) void* _d_eh_swapContextDwarf(void* newContext) nothrow @nogc; + + void* swapContext(void* newContext) nothrow @nogc + { + /* Detect at runtime which scheme is being used. + * Eventually, determine it statically. + */ + static int which = 0; + final switch (which) + { + case 0: + { + assert(newContext == null); + auto p = _d_eh_swapContext(newContext); + auto pdwarf = _d_eh_swapContextDwarf(newContext); + if (p) + { + which = 1; + return p; + } + else if (pdwarf) + { + which = 2; + return pdwarf; + } + return null; + } + case 1: + return _d_eh_swapContext(newContext); + case 2: + return _d_eh_swapContextDwarf(newContext); + } + } + } + } + else + alias swapContext = _d_eh_swapContext; +} + +private +{ + extern (C) @nogc nothrow + { + version (CRuntime_Glibc) int pthread_getattr_np(pthread_t thread, pthread_attr_t* attr); + version (FreeBSD) int pthread_attr_get_np(pthread_t thread, pthread_attr_t* attr); + version (NetBSD) int pthread_attr_get_np(pthread_t thread, pthread_attr_t* attr); + version (OpenBSD) int pthread_stackseg_np(pthread_t thread, stack_t* sinfo); + version (DragonFlyBSD) int pthread_attr_get_np(pthread_t thread, pthread_attr_t* attr); + version (Solaris) int thr_stksegment(stack_t* stk); + version (CRuntime_Bionic) int pthread_getattr_np(pthread_t thid, pthread_attr_t* attr); + version (CRuntime_Musl) int pthread_getattr_np(pthread_t, pthread_attr_t*); + version (CRuntime_UClibc) int pthread_getattr_np(pthread_t thread, pthread_attr_t* attr); + } +} + +package(core.thread) +{ + void* getStackBottom() nothrow @nogc + { + version (Windows) + { + version (D_InlineAsm_X86) + asm pure nothrow @nogc { naked; mov EAX, FS:4; ret; } + else version (D_InlineAsm_X86_64) + asm pure nothrow @nogc + { naked; + mov RAX, 8; + mov RAX, GS:[RAX]; + ret; + } + else + static assert(false, "Architecture not supported."); + } + else version (Darwin) + { + import core.sys.darwin.pthread; + return pthread_get_stackaddr_np(pthread_self()); + } + else version (CRuntime_Glibc) + { + pthread_attr_t attr; + void* addr; size_t size; + + pthread_getattr_np(pthread_self(), &attr); + pthread_attr_getstack(&attr, &addr, &size); + pthread_attr_destroy(&attr); + version (StackGrowsDown) + addr += size; + return addr; + } + else version (FreeBSD) + { + pthread_attr_t attr; + void* addr; size_t size; + pthread_attr_init(&attr); + pthread_attr_get_np(pthread_self(), &attr); + pthread_attr_getstack(&attr, &addr, &size); + pthread_attr_destroy(&attr); + version (StackGrowsDown) + addr += size; + return addr; + } + else version (NetBSD) + { + pthread_attr_t attr; + void* addr; size_t size; + pthread_attr_init(&attr); + pthread_attr_get_np(pthread_self(), &attr); + pthread_attr_getstack(&attr, &addr, &size); + pthread_attr_destroy(&attr); + version (StackGrowsDown) + addr += size; + return addr; + } + else version (OpenBSD) + { + stack_t stk; + pthread_stackseg_np(pthread_self(), &stk); + return stk.ss_sp; + } + else version (DragonFlyBSD) + { + pthread_attr_t attr; + void* addr; size_t size; + pthread_attr_init(&attr); + pthread_attr_get_np(pthread_self(), &attr); + pthread_attr_getstack(&attr, &addr, &size); + pthread_attr_destroy(&attr); + version (StackGrowsDown) + addr += size; + return addr; + } + else version (Solaris) + { + stack_t stk; + thr_stksegment(&stk); + return stk.ss_sp; + } + else version (CRuntime_Bionic) + { + pthread_attr_t attr; + void* addr; size_t size; + pthread_getattr_np(pthread_self(), &attr); + pthread_attr_getstack(&attr, &addr, &size); + pthread_attr_destroy(&attr); + version (StackGrowsDown) + addr += size; + return addr; + } + else version (CRuntime_Musl) + { + pthread_attr_t attr; + void* addr; size_t size; + pthread_getattr_np(pthread_self(), &attr); + pthread_attr_getstack(&attr, &addr, &size); + pthread_attr_destroy(&attr); + version (StackGrowsDown) + addr += size; + return addr; + } + else version (CRuntime_UClibc) + { + pthread_attr_t attr; + void* addr; size_t size; + pthread_getattr_np(pthread_self(), &attr); + pthread_attr_getstack(&attr, &addr, &size); + pthread_attr_destroy(&attr); + version (StackGrowsDown) + addr += size; + return addr; + } + else + static assert(false, "Platform not supported."); + } + void* getStackTop() nothrow @nogc + { + version (D_InlineAsm_X86) + asm pure nothrow @nogc { naked; mov EAX, ESP; ret; } + else version (D_InlineAsm_X86_64) + asm pure nothrow @nogc { naked; mov RAX, RSP; ret; } + else version (GNU) + return __builtin_frame_address(0); + else + static assert(false, "Architecture not supported."); + } +} diff --git a/src/core/thread/fiber.d b/src/core/thread/fiber.d index 5eb15e7555..e400cb83d5 100644 --- a/src/core/thread/fiber.d +++ b/src/core/thread/fiber.d @@ -144,7 +144,7 @@ private Fiber obj = Fiber.getThis(); assert( obj ); - assert( Thread.getThis().m_curr is obj.m_ctxt ); + assert( Thread.getThis().m_ctxt is obj.m_ctxt ); atomicStore!(MemoryOrder.raw)(*cast(shared)&Thread.getThis().m_lock, false); obj.m_ctxt.tstack = obj.m_ctxt.bstack; obj.m_state = Fiber.State.EXEC; @@ -518,7 +518,10 @@ private * * Authors: Based on a design by Mikola Lysenko. */ -class Fiber + +import core.thread.context : StackContext, StackContextExecutor; + +class Fiber : StackContextExecutor { /////////////////////////////////////////////////////////////////////////// // Initialization @@ -614,6 +617,7 @@ class Fiber // General Actions /////////////////////////////////////////////////////////////////////////// + public import core.thread.context : Rethrow; /** * Transfers execution to this fiber object. The calling context will be @@ -639,7 +643,7 @@ class Fiber // 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 ) + final Throwable call(Rethrow rethrow = Rethrow.yes) { return rethrow ? call!(Rethrow.yes)() : call!(Rethrow.no); } @@ -663,20 +667,20 @@ class Fiber private void callImpl() nothrow @nogc in { - assert( m_state == State.HOLD ); + assert(m_state == State.HOLD); } do { - Fiber cur = getThis(); + Fiber cur = getThis(); - static if ( __traits( compiles, ucontext_t ) ) + static if (__traits(compiles, ucontext_t)) m_ucur = cur ? &cur.m_utxt : &Fiber.sm_utxt; - setThis( this ); + setThis(this); this.switchIn(); - setThis( cur ); + setThis(cur); - static if ( __traits( compiles, ucontext_t ) ) + static if (__traits(compiles, ucontext_t)) m_ucur = null; // NOTE: If the fiber has terminated then the stack pointers must be @@ -686,15 +690,12 @@ class Fiber // 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 ) + 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 @@ -709,7 +710,7 @@ class Fiber final void reset() nothrow @nogc in { - assert( m_state == State.TERM || m_state == State.HOLD ); + assert(m_state == State.TERM || m_state == State.HOLD); } do { @@ -866,50 +867,11 @@ private: m_call = Call.NO; } - - // - // Fiber entry point. Invokes the function or delegate passed on - // construction (if any). - // - final void run() - { - switch ( m_call ) - { - case Call.FN: - m_fn(); - break; - case Call.DG: - m_dg(); - break; - default: - break; - } - } - - private: - // - // The type of routine passed on fiber construction. - // - enum Call - { - NO, - FN, - DG - } - - // // Standard fiber data // - Call m_call; - union - { - void function() m_fn; - void delegate() m_dg; - } bool m_isRunning; - Throwable m_unhandled; State m_state; @@ -917,7 +879,7 @@ private: /////////////////////////////////////////////////////////////////////////// // Stack Management /////////////////////////////////////////////////////////////////////////// - + import core.thread.context : GlobalStackContext; // // Allocate a new stack for this fiber. @@ -941,7 +903,7 @@ private: // room for this struct explicitly would be to mash it into the // base of the stack being allocated below. However, doing so // requires too much special logic to be worthwhile. - m_ctxt = new Thread.Context; + m_ctxt = new StackContext; static if ( __traits( compiles, VirtualAlloc ) ) { @@ -1060,7 +1022,7 @@ private: } } - Thread.add( m_ctxt ); + GlobalStackContext.add( m_ctxt ); } @@ -1076,9 +1038,9 @@ private: { // NOTE: m_ctxt is guaranteed to be alive because it is held in the // global context list. - Thread.slock.lock_nothrow(); - scope(exit) Thread.slock.unlock_nothrow(); - Thread.remove( m_ctxt ); + GlobalStackContext.slock.lock_nothrow(); + scope(exit) GlobalStackContext.slock.unlock_nothrow(); + GlobalStackContext.remove( m_ctxt ); static if ( __traits( compiles, VirtualAlloc ) ) { @@ -1444,9 +1406,6 @@ private: static assert(0, "Not implemented"); } - - Thread.Context* m_ctxt; - size_t m_size; void* m_pmem; static if ( __traits( compiles, ucontext_t ) ) @@ -1481,6 +1440,7 @@ private: // Context Switching /////////////////////////////////////////////////////////////////////////// + import core.thread.context : getStackTop; // // Switches into the stack held by this fiber. @@ -1488,7 +1448,7 @@ private: final void switchIn() nothrow @nogc { Thread tobj = Thread.getThis(); - void** oldp = &tobj.m_curr.tstack; + void** oldp = &tobj.m_ctxt.tstack; void* newp = m_ctxt.tstack; // NOTE: The order of operations here is very important. The current @@ -1512,7 +1472,7 @@ private: // to prevent Bad Things from happening. tobj.popContext(); atomicStore!(MemoryOrder.raw)(*cast(shared)&tobj.m_lock, false); - tobj.m_curr.tstack = tobj.m_curr.bstack; + tobj.m_ctxt.tstack = tobj.m_ctxt.bstack; } @@ -1523,7 +1483,7 @@ private: { Thread tobj = Thread.getThis(); void** oldp = &m_ctxt.tstack; - void* newp = tobj.m_curr.within.tstack; + void* newp = tobj.m_ctxt.within.tstack; // NOTE: The order of operations here is very important. The current // stack top must be stored before m_lock is set, and pushContext @@ -1548,7 +1508,7 @@ private: // current thread handle before unlocking, etc. tobj = Thread.getThis(); atomicStore!(MemoryOrder.raw)(*cast(shared)&tobj.m_lock, false); - tobj.m_curr.tstack = tobj.m_curr.bstack; + tobj.m_ctxt.tstack = tobj.m_ctxt.bstack; } } @@ -1609,6 +1569,7 @@ version (unittest) super(&run); } + private: void run() { foreach (i; 0 .. 1000) diff --git a/src/core/thread/osthread.d b/src/core/thread/osthread.d index b46a6655fe..2c77a911cb 100644 --- a/src/core/thread/osthread.d +++ b/src/core/thread/osthread.d @@ -19,17 +19,19 @@ import core.exception : onOutOfMemoryError; // Platform Detection and Memory Allocation /////////////////////////////////////////////////////////////////////////////// -version (OSX) - version = Darwin; -else version (iOS) - version = Darwin; -else version (TVOS) - version = Darwin; -else version (WatchOS) - version = Darwin; + package(core.thread) { + version (OSX) + version = Darwin; + else version (iOS) + version = Darwin; + else version (TVOS) + version = Darwin; + else version (WatchOS) + version = Darwin; + version (D_InlineAsm_X86) { version (Windows) @@ -186,73 +188,6 @@ class ThreadError : Error } } -private -{ - import core.atomic, core.memory, core.sync.mutex; - - // - // exposed by compiler runtime - // - extern (C) void rt_moduleTlsCtor(); - extern (C) void rt_moduleTlsDtor(); - - /** - * Hook for whatever EH implementation is used to save/restore some data - * per stack. - * - * Params: - * newContext = The return value of the prior call to this function - * where the stack was last swapped out, or null when a fiber stack - * is switched in for the first time. - */ - extern(C) void* _d_eh_swapContext(void* newContext) nothrow @nogc; - - version (DigitalMars) - { - version (Windows) - alias swapContext = _d_eh_swapContext; - else - { - extern(C) void* _d_eh_swapContextDwarf(void* newContext) nothrow @nogc; - - void* swapContext(void* newContext) nothrow @nogc - { - /* Detect at runtime which scheme is being used. - * Eventually, determine it statically. - */ - static int which = 0; - final switch (which) - { - case 0: - { - assert(newContext == null); - auto p = _d_eh_swapContext(newContext); - auto pdwarf = _d_eh_swapContextDwarf(newContext); - if (p) - { - which = 1; - return p; - } - else if (pdwarf) - { - which = 2; - return pdwarf; - } - return null; - } - case 1: - return _d_eh_swapContext(newContext); - case 2: - return _d_eh_swapContextDwarf(newContext); - } - } - } - } - else - alias swapContext = _d_eh_swapContext; -} - - /////////////////////////////////////////////////////////////////////////////// // Thread Entry Point and Signal Handlers /////////////////////////////////////////////////////////////////////////////// @@ -285,18 +220,18 @@ version (Windows) Thread obj = cast(Thread) arg; assert( obj ); - assert( obj.m_curr is &obj.m_main ); + assert( obj.m_ctxt is &obj.m_main ); obj.m_main.bstack = getStackBottom(); obj.m_main.tstack = obj.m_main.bstack; obj.m_tlsgcdata = rt_tlsgc_init(); Thread.setThis(obj); - Thread.add(obj); + GlobalStackContext.add(obj); scope (exit) { - Thread.remove(obj); + GlobalStackContext.remove(obj); } - Thread.add(&obj.m_main); + GlobalStackContext.add(&obj.m_main); // NOTE: No GC allocations may occur until the stack pointers have // been set and Thread.getThis returns a valid reference to @@ -363,12 +298,18 @@ else version (Posix) import core.sys.posix.signal; import core.sys.posix.time; + import core.thread.context : GlobalStackContext; + version (Darwin) { import core.sys.darwin.mach.thread_act; import core.sys.darwin.pthread : pthread_mach_thread_np; } + // exposed by compiler runtime + extern (C) void rt_moduleTlsCtor(); + extern (C) void rt_moduleTlsDtor(); + // // Entry point for POSIX threads // @@ -394,20 +335,20 @@ else version (Posix) void function(void*) @nogc nothrow)(loadedLibraries); } - assert( obj.m_curr is &obj.m_main ); + assert( obj.m_ctxt is &obj.m_main ); obj.m_main.bstack = getStackBottom(); obj.m_main.tstack = obj.m_main.bstack; obj.m_tlsgcdata = rt_tlsgc_init(); atomicStore!(MemoryOrder.raw)(obj.m_isRunning, true); Thread.setThis(obj); // allocates lazy TLS (see Issue 11981) - Thread.add(obj); // can only receive signals from here on + GlobalStackContext.add(obj); // can only receive signals from here on scope (exit) { - Thread.remove(obj); + GlobalStackContext.remove(obj); atomicStore!(MemoryOrder.raw)(obj.m_isRunning, false); } - Thread.add(&obj.m_main); + GlobalStackContext.add(&obj.m_main); static extern (C) void thread_cleanupHandler( void* arg ) nothrow @nogc { @@ -517,7 +458,7 @@ else version (Posix) if ( !obj.m_lock ) { - obj.m_curr.tstack = getStackTop(); + obj.m_ctxt.tstack = getStackTop(); } sigset_t sigres = void; @@ -537,7 +478,7 @@ else version (Posix) if ( !obj.m_lock ) { - obj.m_curr.tstack = obj.m_curr.bstack; + obj.m_ctxt.tstack = obj.m_ctxt.bstack; } } @@ -600,11 +541,11 @@ else static assert( false, "Unknown threading implementation." ); } - /////////////////////////////////////////////////////////////////////////////// // Thread /////////////////////////////////////////////////////////////////////////////// +import core.thread.context; /** * This class encapsulates all threading functionality for the D @@ -614,7 +555,7 @@ else * A new thread may be created using either derivation or composition, as * in the following example. */ -class Thread +class Thread : StackContextExecutor { /////////////////////////////////////////////////////////////////////////// // Initialization @@ -642,7 +583,7 @@ class Thread this(sz); () @trusted { m_fn = fn; }(); m_call = Call.FN; - m_curr = &m_main; + m_ctxt = &m_main; } @@ -667,7 +608,7 @@ class Thread this(sz); () @trusted { m_dg = dg; }(); m_call = Call.DG; - m_curr = &m_main; + m_ctxt = &m_main; } @@ -677,7 +618,7 @@ class Thread ~this() nothrow @nogc { bool no_context = m_addr == m_addr.init; - bool not_registered = !next && !prev && (sm_tbeg !is this); + bool not_registered = !next && !prev && (GlobalStackContext.sm_tbeg !is this); if (no_context || not_registered) { @@ -741,7 +682,7 @@ class Thread if ( pthread_attr_init( &attr ) ) onThreadError( "Error initializing thread attributes" ); - if ( m_sz && pthread_attr_setstacksize( &attr, m_sz ) ) + if ( m_size && pthread_attr_setstacksize( &attr, m_size ) ) onThreadError( "Error initializing thread stack size" ); } @@ -756,18 +697,20 @@ class Thread // // Solution: Create the thread in suspended state and then // add and resume it with slock acquired - assert(m_sz <= uint.max, "m_sz must be less than or equal to uint.max"); - m_hndl = cast(HANDLE) _beginthreadex( null, cast(uint) m_sz, &thread_entryPoint, cast(void*) this, CREATE_SUSPENDED, &m_addr ); + assert(m_size <= uint.max, "m_size must be less than or equal to uint.max"); + m_hndl = cast(HANDLE) _beginthreadex( null, cast(uint) m_size, &thread_entryPoint, cast(void*) this, CREATE_SUSPENDED, &m_addr ); if ( cast(size_t) m_hndl == 0 ) onThreadError( "Error creating thread" ); } - slock.lock_nothrow(); - scope(exit) slock.unlock_nothrow(); + GlobalStackContext.slock.lock_nothrow(); + scope(exit) GlobalStackContext.slock.unlock_nothrow(); { - ++nAboutToStart; - pAboutToStart = cast(Thread*)realloc(pAboutToStart, Thread.sizeof * nAboutToStart); - pAboutToStart[nAboutToStart - 1] = this; + ++(GlobalStackContext.nAboutToStart); + GlobalStackContext.pAboutToStart = cast(Thread*)realloc( + GlobalStackContext.pAboutToStart, + Thread.sizeof * GlobalStackContext.nAboutToStart); + GlobalStackContext.pAboutToStart[GlobalStackContext.nAboutToStart - 1] = this; version (Windows) { if ( ResumeThread( m_hndl ) == -1 ) @@ -831,12 +774,12 @@ class Thread * Any exception not handled by this thread if rethrow = false, null * otherwise. */ - final Throwable join( bool rethrow = true ) + final Throwable join(Rethrow rethrow = Rethrow.yes) { version (Windows) { - if ( WaitForSingleObject( m_hndl, INFINITE ) != WAIT_OBJECT_0 ) - throw new ThreadException( "Unable to join thread" ); + if (WaitForSingleObject( m_hndl, INFINITE ) != WAIT_OBJECT_0) + throw new ThreadException("Unable to join thread"); // NOTE: m_addr must be cleared before m_hndl is closed to avoid // a race condition with isRunning. The operation is done // with atomicStore to prevent compiler reordering. @@ -1492,15 +1435,16 @@ class Thread Thread[] buf; while (true) { - immutable len = atomicLoad!(MemoryOrder.raw)(*cast(shared)&sm_tlen); + auto tlen_addr = &GlobalStackContext.sm_tlen; + immutable len = atomicLoad!(MemoryOrder.raw)(*cast(shared)tlen_addr); resize(buf, len); assert(buf.length == len); - synchronized (slock) + synchronized (GlobalStackContext.slock) { - if (len == sm_tlen) + if (len == GlobalStackContext.sm_tlen) { size_t pos; - for (Thread t = sm_tbeg; t; t = t.next) + for (Thread t = GlobalStackContext.sm_tbeg; t; t = t.next) buf[pos++] = t; return buf; } @@ -1531,45 +1475,13 @@ private: if (PTHREAD_STACK_MIN > sz) sz = PTHREAD_STACK_MIN; } - m_sz = sz; + m_size = sz; } m_call = Call.NO; - m_curr = &m_main; - } - - - // - // Thread entry point. Invokes the function or delegate passed on - // construction (if any). - // - final void run() - { - switch ( m_call ) - { - case Call.FN: - m_fn(); - break; - case Call.DG: - m_dg(); - break; - default: - break; - } + m_ctxt = &m_main; } - private: - // - // The type of routine passed on thread construction. - // - enum Call - { - NO, - FN, - DG - } - - // // Standard types // @@ -1613,21 +1525,13 @@ private: mach_port_t m_tmach; } ThreadID m_addr; - Call m_call; string m_name; - union - { - void function() m_fn; - void delegate() m_dg; - } - size_t m_sz; version (Posix) { shared bool m_isRunning; } bool m_isDaemon; bool m_isInCriticalRegion; - Throwable m_unhandled; version (Solaris) { @@ -1649,68 +1553,15 @@ private: } package(core.thread): - static struct Context - { - void* bstack, - tstack; - - /// Slot for the EH implementation to keep some state for each stack - /// (will be necessary for exception chaining, etc.). Opaque as far as - /// we are concerned here. - void* ehContext; - - Context* within; - Context* next, - prev; - } - - Context m_main; - Context* m_curr; - bool m_lock; - void* m_tlsgcdata; + StackContext m_main; + bool m_lock; + void* m_tlsgcdata; /////////////////////////////////////////////////////////////////////////// // Thread Context and GC Scanning Support /////////////////////////////////////////////////////////////////////////// - - final void pushContext( Context* c ) nothrow @nogc - in - { - assert( !c.within ); - } - do - { - m_curr.ehContext = swapContext(c.ehContext); - c.within = m_curr; - m_curr = c; - } - - - final void popContext() nothrow @nogc - in - { - assert( m_curr && m_curr.within ); - } - do - { - Context* c = m_curr; - m_curr = c.within; - c.ehContext = swapContext(m_curr.ehContext); - c.within = null; - } - -private: - - final Context* topContext() nothrow @nogc - in - { - assert( m_curr ); - } - do - { - return m_curr; - } + private: version (Windows) { @@ -1747,239 +1598,11 @@ private: package(core.thread): - /////////////////////////////////////////////////////////////////////////// - // GC Scanning Support - /////////////////////////////////////////////////////////////////////////// - - - // NOTE: The GC scanning process works like so: - // - // 1. Suspend all threads. - // 2. Scan the stacks of all suspended threads for roots. - // 3. Resume all threads. - // - // Step 1 and 3 require a list of all threads in the system, while - // step 2 requires a list of all thread stacks (each represented by - // a Context struct). Traditionally, there was one stack per thread - // and the Context structs were not necessary. However, Fibers have - // changed things so that each thread has its own 'main' stack plus - // an arbitrary number of nested stacks (normally referenced via - // m_curr). Also, there may be 'free-floating' stacks in the system, - // which are Fibers that are not currently executing on any specific - // thread but are still being processed and still contain valid - // roots. - // - // To support all of this, the Context struct has been created to - // represent a stack range, and a global list of Context structs has - // been added to enable scanning of these stack ranges. The lifetime - // (and presence in the Context list) of a thread's 'main' stack will - // be equivalent to the thread's lifetime. So the Ccontext will be - // added to the list on thread entry, and removed from the list on - // thread exit (which is essentially the same as the presence of a - // Thread object in its own global list). The lifetime of a Fiber's - // context, however, will be tied to the lifetime of the Fiber object - // itself, and Fibers are expected to add/remove their Context struct - // on construction/deletion. - - - // - // All use of the global thread lists/array should synchronize on this lock. - // - // Careful as the GC acquires this lock after the GC lock to suspend all - // threads any GC usage with slock held can result in a deadlock through - // lock order inversion. - @property static Mutex slock() nothrow @nogc - { - return cast(Mutex)_locks[0].ptr; - } - - @property static Mutex criticalRegionLock() nothrow @nogc - { - return cast(Mutex)_locks[1].ptr; - } - - __gshared align(Mutex.alignof) void[__traits(classInstanceSize, Mutex)][2] _locks; - - static void initLocks() @nogc - { - foreach (ref lock; _locks) - { - lock[] = typeid(Mutex).initializer[]; - (cast(Mutex)lock.ptr).__ctor(); - } - } - - static void termLocks() @nogc - { - foreach (ref lock; _locks) - (cast(Mutex)lock.ptr).__dtor(); - } - - __gshared Context* sm_cbeg; - - __gshared Thread sm_tbeg; - __gshared size_t sm_tlen; - - // can't use core.internal.util.array in public code - __gshared Thread* pAboutToStart; - __gshared size_t nAboutToStart; - // // Used for ordering threads in the global thread list. // Thread prev; Thread next; - - - /////////////////////////////////////////////////////////////////////////// - // Global Context List Operations - /////////////////////////////////////////////////////////////////////////// - - - // - // Add a context to the global context list. - // - static void add( Context* c ) nothrow @nogc - in - { - assert( c ); - assert( !c.next && !c.prev ); - } - do - { - slock.lock_nothrow(); - scope(exit) slock.unlock_nothrow(); - assert(!suspendDepth); // must be 0 b/c it's only set with slock held - - if (sm_cbeg) - { - c.next = sm_cbeg; - sm_cbeg.prev = c; - } - sm_cbeg = c; - } - - // - // Remove a context from the global context list. - // - // This assumes slock being acquired. This isn't done here to - // avoid double locking when called from remove(Thread) - static void remove( Context* c ) nothrow @nogc - in - { - assert( c ); - assert( c.next || c.prev ); - } - do - { - if ( c.prev ) - c.prev.next = c.next; - if ( c.next ) - c.next.prev = c.prev; - if ( sm_cbeg == c ) - sm_cbeg = c.next; - // NOTE: Don't null out c.next or c.prev because opApply currently - // follows c.next after removing a node. This could be easily - // addressed by simply returning the next node from this - // function, however, a context should never be re-added to the - // list anyway and having next and prev be non-null is a good way - // to ensure that. - } - - - /////////////////////////////////////////////////////////////////////////// - // Global Thread List Operations - /////////////////////////////////////////////////////////////////////////// - - - // - // Add a thread to the global thread list. - // - static void add( Thread t, bool rmAboutToStart = true ) nothrow @nogc - in - { - assert( t ); - assert( !t.next && !t.prev ); - } - do - { - slock.lock_nothrow(); - scope(exit) slock.unlock_nothrow(); - assert(t.isRunning); // check this with slock to ensure pthread_create already returned - assert(!suspendDepth); // must be 0 b/c it's only set with slock held - - if (rmAboutToStart) - { - size_t idx = -1; - foreach (i, thr; pAboutToStart[0 .. nAboutToStart]) - { - if (thr is t) - { - idx = i; - break; - } - } - assert(idx != -1); - import core.stdc.string : memmove; - memmove(pAboutToStart + idx, pAboutToStart + idx + 1, Thread.sizeof * (nAboutToStart - idx - 1)); - pAboutToStart = - cast(Thread*)realloc(pAboutToStart, Thread.sizeof * --nAboutToStart); - } - - if (sm_tbeg) - { - t.next = sm_tbeg; - sm_tbeg.prev = t; - } - sm_tbeg = t; - ++sm_tlen; - } - - - // - // Remove a thread from the global thread list. - // - static void remove( Thread t ) nothrow @nogc - in - { - assert( t ); - } - do - { - // Thread was already removed earlier, might happen b/c of thread_detachInstance - if (!t.next && !t.prev && (sm_tbeg !is t)) - return; - - slock.lock_nothrow(); - { - // NOTE: When a thread is removed from the global thread list its - // main context is invalid and should be removed as well. - // It is possible that t.m_curr could reference more - // than just the main context if the thread exited abnormally - // (if it was terminated), but we must assume that the user - // retains a reference to them and that they may be re-used - // elsewhere. Therefore, it is the responsibility of any - // object that creates contexts to clean them up properly - // when it is done with them. - remove( &t.m_main ); - - if ( t.prev ) - t.prev.next = t.next; - if ( t.next ) - t.next.prev = t.prev; - if ( sm_tbeg is t ) - sm_tbeg = t.next; - t.prev = t.next = null; - --sm_tlen; - } - // NOTE: Don't null out t.next or t.prev because opApply currently - // follows t.next after removing a node. This could be easily - // addressed by simply returning the next node from this - // function, however, a thread should never be re-added to the - // list anyway and having next and prev be non-null is a good way - // to ensure that. - slock.unlock_nothrow(); - } } /// @@ -2102,7 +1725,7 @@ extern (C) void thread_init() @nogc // functions to detect the condition and return immediately. initLowlevelThreads(); - Thread.initLocks(); + GlobalStackContext.initLocks(); // The Android VM runtime intercepts SIGUSR1 and apparently doesn't allow // its signal handler to run, so swap the two signals on Android, since @@ -2191,14 +1814,14 @@ extern (C) void thread_term() @nogc (cast(ubyte[])_mainThreadStore)[] = 0; Thread.sm_main = null; - assert(Thread.sm_tbeg && Thread.sm_tlen == 1); - assert(!Thread.nAboutToStart); - if (Thread.pAboutToStart) // in case realloc(p, 0) doesn't return null + assert(GlobalStackContext.sm_tbeg && GlobalStackContext.sm_tlen == 1); + assert(!GlobalStackContext.nAboutToStart); + if (GlobalStackContext.pAboutToStart) // in case realloc(p, 0) doesn't return null { - free(Thread.pAboutToStart); - Thread.pAboutToStart = null; + free(GlobalStackContext.pAboutToStart); + GlobalStackContext.pAboutToStart = null; } - Thread.termLocks(); + GlobalStackContext.termLocks(); termLowlevelThreads(); } @@ -2232,8 +1855,8 @@ extern (C) Thread thread_attachThis() private Thread attachThread(Thread thisThread) @nogc { - Thread.Context* thisContext = &thisThread.m_main; - assert( thisContext == thisThread.m_curr ); + StackContext* thisContext = &thisThread.m_main; + assert( thisContext == thisThread.m_ctxt ); version (Windows) { @@ -2260,8 +1883,8 @@ private Thread attachThread(Thread thisThread) @nogc assert( thisThread.m_tmach != thisThread.m_tmach.init ); } - Thread.add( thisThread, false ); - Thread.add( thisContext ); + GlobalStackContext.add( thisThread, false ); + GlobalStackContext.add( thisContext ); if ( Thread.sm_main !is null ) multiThreadedFlag = true; return thisThread; @@ -2296,8 +1919,8 @@ version (Windows) return t; Thread thisThread = new Thread(); - Thread.Context* thisContext = &thisThread.m_main; - assert( thisContext == thisThread.m_curr ); + StackContext* thisContext = &thisThread.m_main; + assert( thisContext == thisThread.m_ctxt ); thisThread.m_addr = addr; thisContext.bstack = bstack; @@ -2321,8 +1944,8 @@ version (Windows) }); } - Thread.add( thisThread, false ); - Thread.add( thisContext ); + GlobalStackContext.add( thisThread, false ); + GlobalStackContext.add( thisContext ); if ( Thread.sm_main !is null ) multiThreadedFlag = true; return thisThread; @@ -2344,7 +1967,7 @@ version (Windows) extern (C) void thread_detachThis() nothrow @nogc { if (auto t = Thread.getThis()) - Thread.remove(t); + GlobalStackContext.remove(t); } @@ -2362,14 +1985,14 @@ extern (C) void thread_detachThis() nothrow @nogc extern (C) void thread_detachByAddr( ThreadID addr ) { if ( auto t = thread_findByAddr( addr ) ) - Thread.remove( t ); + GlobalStackContext.remove( t ); } /// ditto extern (C) void thread_detachInstance( Thread t ) nothrow @nogc { - Thread.remove( t ); + GlobalStackContext.remove( t ); } @@ -2402,12 +2025,12 @@ unittest */ static Thread thread_findByAddr( ThreadID addr ) { - Thread.slock.lock_nothrow(); - scope(exit) Thread.slock.unlock_nothrow(); + GlobalStackContext.slock.lock_nothrow(); + scope(exit) GlobalStackContext.slock.unlock_nothrow(); // also return just spawned thread so that // DLL_THREAD_ATTACH knows it's a D thread - foreach (t; Thread.pAboutToStart[0 .. Thread.nAboutToStart]) + foreach (t; GlobalStackContext.pAboutToStart[0 .. GlobalStackContext.nAboutToStart]) if (t.m_addr == addr) return t; @@ -2443,23 +2066,23 @@ extern (C) void thread_setThis(Thread t) nothrow @nogc extern (C) void thread_joinAll() { Lagain: - Thread.slock.lock_nothrow(); + GlobalStackContext.slock.lock_nothrow(); // wait for just spawned threads - if (Thread.nAboutToStart) + if (GlobalStackContext.nAboutToStart) { - Thread.slock.unlock_nothrow(); + GlobalStackContext.slock.unlock_nothrow(); Thread.yield(); goto Lagain; } // join all non-daemon threads, the main thread is also a daemon - auto t = Thread.sm_tbeg; + auto t = GlobalStackContext.sm_tbeg; while (t) { if (!t.isRunning) { auto tn = t.next; - Thread.remove(t); + GlobalStackContext.remove(t); t = tn; } else if (t.isDaemon) @@ -2468,12 +2091,12 @@ extern (C) void thread_joinAll() } else { - Thread.slock.unlock_nothrow(); + GlobalStackContext.slock.unlock_nothrow(); t.join(); // might rethrow goto Lagain; // must restart iteration b/c of unlock } } - Thread.slock.unlock_nothrow(); + GlobalStackContext.slock.unlock_nothrow(); } @@ -2485,12 +2108,12 @@ shared static ~this() // NOTE: The functionality related to garbage collection must be minimally // operable after this dtor completes. Therefore, only minimal // cleanup may occur. - auto t = Thread.sm_tbeg; + auto t = GlobalStackContext.sm_tbeg; while (t) { auto tn = t.next; if (!t.isRunning) - Thread.remove(t); + GlobalStackContext.remove(t); t = tn; } } @@ -2587,9 +2210,6 @@ else } } -// Used for suspendAll/resumeAll below. -private __gshared uint suspendDepth = 0; - /** * Suspend the specified thread and load stack and register information for * use by thread_scanAll. If the supplied thread is the calling thread, @@ -2612,15 +2232,15 @@ private bool suspend( Thread t ) nothrow Lagain: if (!t.isRunning) { - Thread.remove(t); + GlobalStackContext.remove(t); return false; } else if (t.m_isInCriticalRegion) { - Thread.criticalRegionLock.unlock_nothrow(); + GlobalStackContext.criticalRegionLock.unlock_nothrow(); Thread.sleep(waittime); if (waittime < dur!"msecs"(10)) waittime *= 2; - Thread.criticalRegionLock.lock_nothrow(); + GlobalStackContext.criticalRegionLock.lock_nothrow(); goto Lagain; } @@ -2630,7 +2250,7 @@ private bool suspend( Thread t ) nothrow { if ( !t.isRunning ) { - Thread.remove( t ); + GlobalStackContext.remove( t ); return false; } onThreadError( "Unable to suspend thread" ); @@ -2644,7 +2264,7 @@ private bool suspend( Thread t ) nothrow version (X86) { if ( !t.m_lock ) - t.m_curr.tstack = cast(void*) context.Esp; + t.m_ctxt.tstack = cast(void*) context.Esp; // eax,ebx,ecx,edx,edi,esi,ebp,esp t.m_reg[0] = context.Eax; t.m_reg[1] = context.Ebx; @@ -2658,7 +2278,7 @@ private bool suspend( Thread t ) nothrow else version (X86_64) { if ( !t.m_lock ) - t.m_curr.tstack = cast(void*) context.Rsp; + t.m_ctxt.tstack = cast(void*) context.Rsp; // rax,rbx,rcx,rdx,rdi,rsi,rbp,rsp t.m_reg[0] = context.Rax; t.m_reg[1] = context.Rbx; @@ -2689,7 +2309,7 @@ private bool suspend( Thread t ) nothrow { if ( !t.isRunning ) { - Thread.remove( t ); + GlobalStackContext.remove( t ); return false; } onThreadError( "Unable to suspend thread" ); @@ -2703,7 +2323,7 @@ private bool suspend( Thread t ) nothrow if ( thread_get_state( t.m_tmach, x86_THREAD_STATE32, &state, &count ) != KERN_SUCCESS ) onThreadError( "Unable to load thread state" ); if ( !t.m_lock ) - t.m_curr.tstack = cast(void*) state.esp; + t.m_ctxt.tstack = cast(void*) state.esp; // eax,ebx,ecx,edx,edi,esi,ebp,esp t.m_reg[0] = state.eax; t.m_reg[1] = state.ebx; @@ -2722,7 +2342,7 @@ private bool suspend( Thread t ) nothrow if ( thread_get_state( t.m_tmach, x86_THREAD_STATE64, &state, &count ) != KERN_SUCCESS ) onThreadError( "Unable to load thread state" ); if ( !t.m_lock ) - t.m_curr.tstack = cast(void*) state.rsp; + t.m_ctxt.tstack = cast(void*) state.rsp; // rax,rbx,rcx,rdx,rdi,rsi,rbp,rsp t.m_reg[0] = state.rax; t.m_reg[1] = state.rbx; @@ -2755,7 +2375,7 @@ private bool suspend( Thread t ) nothrow { if ( !t.isRunning ) { - Thread.remove( t ); + GlobalStackContext.remove( t ); return false; } onThreadError( "Unable to suspend thread" ); @@ -2763,7 +2383,7 @@ private bool suspend( Thread t ) nothrow } else if ( !t.m_lock ) { - t.m_curr.tstack = getStackTop(); + t.m_ctxt.tstack = getStackTop(); } } return true; @@ -2788,28 +2408,28 @@ extern (C) void thread_suspendAll() nothrow // completes, with the assumption that no other GC memory has yet // been allocated by the system, and thus there is no risk of losing // data if the global thread list is empty. The check of - // Thread.sm_tbeg below is done to ensure thread_init has completed, + // GlobalStackContext.sm_tbeg below is done to ensure thread_init has completed, // and therefore that calling Thread.getThis will not result in an - // error. For the short time when Thread.sm_tbeg is null, there is + // error. For the short time when GlobalStackContext.sm_tbeg is null, there is // no reason not to simply call the multithreaded code below, with // the expectation that the foreach loop will never be entered. - if ( !multiThreadedFlag && Thread.sm_tbeg ) + if ( !multiThreadedFlag && GlobalStackContext.sm_tbeg ) { - if ( ++suspendDepth == 1 ) + if ( ++GlobalStackContext.suspendDepth == 1 ) suspend( Thread.getThis() ); return; } - Thread.slock.lock_nothrow(); + GlobalStackContext.slock.lock_nothrow(); { - if ( ++suspendDepth > 1 ) + if ( ++GlobalStackContext.suspendDepth > 1 ) return; - Thread.criticalRegionLock.lock_nothrow(); - scope (exit) Thread.criticalRegionLock.unlock_nothrow(); + GlobalStackContext.criticalRegionLock.lock_nothrow(); + scope (exit) GlobalStackContext.criticalRegionLock.unlock_nothrow(); size_t cnt; - auto t = Thread.sm_tbeg; + auto t = GlobalStackContext.sm_tbeg; while (t) { auto tn = t.next; @@ -2839,7 +2459,7 @@ extern (C) void thread_suspendAll() nothrow version (FreeBSD) { // avoid deadlocks, see Issue 13416 - t = Thread.sm_tbeg; + t = GlobalStackContext.sm_tbeg; while (t) { auto tn = t.next; @@ -2876,14 +2496,14 @@ private void resume( Thread t ) nothrow { if ( !t.isRunning ) { - Thread.remove( t ); + GlobalStackContext.remove( t ); return; } onThreadError( "Unable to resume thread" ); } if ( !t.m_lock ) - t.m_curr.tstack = t.m_curr.bstack; + t.m_ctxt.tstack = t.m_ctxt.bstack; t.m_reg[0 .. $] = 0; } else version (Darwin) @@ -2892,14 +2512,14 @@ private void resume( Thread t ) nothrow { if ( !t.isRunning ) { - Thread.remove( t ); + GlobalStackContext.remove( t ); return; } onThreadError( "Unable to resume thread" ); } if ( !t.m_lock ) - t.m_curr.tstack = t.m_curr.bstack; + t.m_ctxt.tstack = t.m_ctxt.bstack; t.m_reg[0 .. $] = 0; } else version (Posix) @@ -2910,7 +2530,7 @@ private void resume( Thread t ) nothrow { if ( !t.isRunning ) { - Thread.remove( t ); + GlobalStackContext.remove( t ); return; } onThreadError( "Unable to resume thread" ); @@ -2918,7 +2538,7 @@ private void resume( Thread t ) nothrow } else if ( !t.m_lock ) { - t.m_curr.tstack = t.m_curr.bstack; + t.m_ctxt.tstack = t.m_ctxt.bstack; } } } @@ -2937,24 +2557,24 @@ private void resume( Thread t ) nothrow extern (C) void thread_resumeAll() nothrow in { - assert( suspendDepth > 0 ); + assert( GlobalStackContext.suspendDepth > 0 ); } do { // NOTE: See thread_suspendAll for the logic behind this. - if ( !multiThreadedFlag && Thread.sm_tbeg ) + if ( !multiThreadedFlag && GlobalStackContext.sm_tbeg ) { - if ( --suspendDepth == 0 ) + if ( --GlobalStackContext.suspendDepth == 0 ) resume( Thread.getThis() ); return; } - scope(exit) Thread.slock.unlock_nothrow(); + scope(exit) GlobalStackContext.slock.unlock_nothrow(); { - if ( --suspendDepth > 0 ) + if ( --GlobalStackContext.suspendDepth > 0 ) return; - for ( Thread t = Thread.sm_tbeg; t; t = t.next ) + for ( Thread t = GlobalStackContext.sm_tbeg; t; t = t.next ) { // NOTE: We do not need to care about critical regions at all // here. thread_suspendAll takes care of everything. @@ -2988,7 +2608,7 @@ alias ScanAllThreadsTypeFn = void delegate(ScanType, void*, void*) nothrow; /// extern (C) void thread_scanAllType( scope ScanAllThreadsTypeFn scan ) nothrow in { - assert( suspendDepth > 0 ); + assert( GlobalStackContext.suspendDepth > 0 ); } do { @@ -3001,34 +2621,36 @@ private void scanAllTypeImpl( scope ScanAllThreadsTypeFn scan, void* curStackTop Thread thisThread = null; void* oldStackTop = null; - if ( Thread.sm_tbeg ) + if ( GlobalStackContext.sm_tbeg ) { thisThread = Thread.getThis(); if ( !thisThread.m_lock ) { - oldStackTop = thisThread.m_curr.tstack; - thisThread.m_curr.tstack = curStackTop; + oldStackTop = thisThread.m_ctxt.tstack; + thisThread.m_ctxt.tstack = curStackTop; } } scope( exit ) { - if ( Thread.sm_tbeg ) + if ( GlobalStackContext.sm_tbeg ) { if ( !thisThread.m_lock ) { - thisThread.m_curr.tstack = oldStackTop; + thisThread.m_ctxt.tstack = oldStackTop; } } } - // NOTE: Synchronizing on Thread.slock is not needed because this + // NOTE: Synchronizing on GlobalStackContext.slock is not needed because this // function may only be called after all other threads have // been suspended from within the same lock. - if (Thread.nAboutToStart) - scan(ScanType.stack, Thread.pAboutToStart, Thread.pAboutToStart + Thread.nAboutToStart); + if (GlobalStackContext.nAboutToStart) + scan(ScanType.stack, + GlobalStackContext.pAboutToStart, + GlobalStackContext.pAboutToStart + GlobalStackContext.nAboutToStart); - for ( Thread.Context* c = Thread.sm_cbeg; c; c = c.next ) + for ( StackContext* c = GlobalStackContext.sm_cbeg; c; c = c.next ) { version (StackGrowsDown) { @@ -3044,7 +2666,7 @@ private void scanAllTypeImpl( scope ScanAllThreadsTypeFn scan, void* curStackTop } } - for ( Thread t = Thread.sm_tbeg; t; t = t.next ) + for ( Thread t = GlobalStackContext.sm_tbeg; t; t = t.next ) { version (Windows) { @@ -3102,7 +2724,7 @@ in } do { - synchronized (Thread.criticalRegionLock) + synchronized (GlobalStackContext.criticalRegionLock) Thread.getThis().m_isInCriticalRegion = true; } @@ -3121,7 +2743,7 @@ in } do { - synchronized (Thread.criticalRegionLock) + synchronized (GlobalStackContext.criticalRegionLock) Thread.getThis().m_isInCriticalRegion = false; } @@ -3139,7 +2761,7 @@ in } do { - synchronized (Thread.criticalRegionLock) + synchronized (GlobalStackContext.criticalRegionLock) return Thread.getThis().m_isInCriticalRegion; } @@ -3214,12 +2836,12 @@ unittest thr.start(); sema.wait(); - synchronized (Thread.criticalRegionLock) + synchronized (GlobalStackContext.criticalRegionLock) assert(thr.m_isInCriticalRegion); semb.notify(); sema.wait(); - synchronized (Thread.criticalRegionLock) + synchronized (GlobalStackContext.criticalRegionLock) assert(!thr.m_isInCriticalRegion); semb.notify(); @@ -3282,7 +2904,7 @@ alias IsMarkedDg = int delegate( void* addr ) nothrow; /// The isMarked callback */ extern(C) void thread_processGCMarks( scope IsMarkedDg isMarked ) nothrow { - for ( Thread t = Thread.sm_tbeg; t; t = t.next ) + for ( Thread t = GlobalStackContext.sm_tbeg; t; t = t.next ) { /* Can be null if collection was triggered between adding a * thread and calling rt_tlsgc_init. @@ -3292,161 +2914,6 @@ extern(C) void thread_processGCMarks( scope IsMarkedDg isMarked ) nothrow } } - -extern (C) @nogc nothrow -{ - version (CRuntime_Glibc) int pthread_getattr_np(pthread_t thread, pthread_attr_t* attr); - version (FreeBSD) int pthread_attr_get_np(pthread_t thread, pthread_attr_t* attr); - version (NetBSD) int pthread_attr_get_np(pthread_t thread, pthread_attr_t* attr); - version (OpenBSD) int pthread_stackseg_np(pthread_t thread, stack_t* sinfo); - version (DragonFlyBSD) int pthread_attr_get_np(pthread_t thread, pthread_attr_t* attr); - version (Solaris) int thr_stksegment(stack_t* stk); - version (CRuntime_Bionic) int pthread_getattr_np(pthread_t thid, pthread_attr_t* attr); - version (CRuntime_Musl) int pthread_getattr_np(pthread_t, pthread_attr_t*); - version (CRuntime_UClibc) int pthread_getattr_np(pthread_t thread, pthread_attr_t* attr); -} - - -package(core.thread) void* getStackTop() nothrow @nogc -{ - version (D_InlineAsm_X86) - asm pure nothrow @nogc { naked; mov EAX, ESP; ret; } - else version (D_InlineAsm_X86_64) - asm pure nothrow @nogc { naked; mov RAX, RSP; ret; } - else version (GNU) - return __builtin_frame_address(0); - else - static assert(false, "Architecture not supported."); -} - - -package(core.thread) void* getStackBottom() nothrow @nogc -{ - version (Windows) - { - version (D_InlineAsm_X86) - asm pure nothrow @nogc { naked; mov EAX, FS:4; ret; } - else version (D_InlineAsm_X86_64) - asm pure nothrow @nogc - { naked; - mov RAX, 8; - mov RAX, GS:[RAX]; - ret; - } - else - static assert(false, "Architecture not supported."); - } - else version (Darwin) - { - import core.sys.darwin.pthread; - return pthread_get_stackaddr_np(pthread_self()); - } - else version (CRuntime_Glibc) - { - pthread_attr_t attr; - void* addr; size_t size; - - pthread_getattr_np(pthread_self(), &attr); - pthread_attr_getstack(&attr, &addr, &size); - pthread_attr_destroy(&attr); - version (StackGrowsDown) - addr += size; - return addr; - } - else version (FreeBSD) - { - pthread_attr_t attr; - void* addr; size_t size; - - pthread_attr_init(&attr); - pthread_attr_get_np(pthread_self(), &attr); - pthread_attr_getstack(&attr, &addr, &size); - pthread_attr_destroy(&attr); - version (StackGrowsDown) - addr += size; - return addr; - } - else version (NetBSD) - { - pthread_attr_t attr; - void* addr; size_t size; - - pthread_attr_init(&attr); - pthread_attr_get_np(pthread_self(), &attr); - pthread_attr_getstack(&attr, &addr, &size); - pthread_attr_destroy(&attr); - version (StackGrowsDown) - addr += size; - return addr; - } - else version (OpenBSD) - { - stack_t stk; - - pthread_stackseg_np(pthread_self(), &stk); - return stk.ss_sp; - } - else version (DragonFlyBSD) - { - pthread_attr_t attr; - void* addr; size_t size; - - pthread_attr_init(&attr); - pthread_attr_get_np(pthread_self(), &attr); - pthread_attr_getstack(&attr, &addr, &size); - pthread_attr_destroy(&attr); - version (StackGrowsDown) - addr += size; - return addr; - } - else version (Solaris) - { - stack_t stk; - - thr_stksegment(&stk); - return stk.ss_sp; - } - else version (CRuntime_Bionic) - { - pthread_attr_t attr; - void* addr; size_t size; - - pthread_getattr_np(pthread_self(), &attr); - pthread_attr_getstack(&attr, &addr, &size); - pthread_attr_destroy(&attr); - version (StackGrowsDown) - addr += size; - return addr; - } - else version (CRuntime_Musl) - { - pthread_attr_t attr; - void* addr; size_t size; - - pthread_getattr_np(pthread_self(), &attr); - pthread_attr_getstack(&attr, &addr, &size); - pthread_attr_destroy(&attr); - version (StackGrowsDown) - addr += size; - return addr; - } - else version (CRuntime_UClibc) - { - pthread_attr_t attr; - void* addr; size_t size; - - pthread_getattr_np(pthread_self(), &attr); - pthread_attr_getstack(&attr, &addr, &size); - pthread_attr_destroy(&attr); - version (StackGrowsDown) - addr += size; - return addr; - } - else - static assert(false, "Platform not supported."); -} - - /** * Returns the stack top of the currently active stack within the calling * thread. @@ -3532,11 +2999,11 @@ class ThreadGroup * Returns: * A reference to the newly created thread. */ - final Thread create( void delegate() dg ) + final Thread create(void delegate() dg) { - Thread t = new Thread( dg ).start(); + Thread t = new Thread(dg).start(); - synchronized( this ) + synchronized(this) { m_all[t] = t; } @@ -3553,14 +3020,14 @@ class ThreadGroup * In: * t must not be null. */ - final void add( Thread t ) + final void add(Thread t) in { - assert( t ); + assert(t); } do { - synchronized( this ) + synchronized(this) { m_all[t] = t; } @@ -3577,16 +3044,16 @@ class ThreadGroup * In: * t must not be null. */ - final void remove( Thread t ) + final void remove(Thread t) in { - assert( t ); + assert(t); } do { - synchronized( this ) + synchronized(this) { - m_all.remove( t ); + m_all.remove(t); } } @@ -3624,15 +3091,15 @@ class ThreadGroup * Throws: * Any exception not handled by the joined threads. */ - final void joinAll( bool rethrow = true ) + final void joinAll(Rethrow rethrow = Rethrow.yes) { - synchronized( this ) + synchronized(this) { // NOTE: This loop relies on the knowledge that m_all uses the // Thread object for both the key and the mapped value. - foreach ( Thread t; m_all.keys ) + foreach (Thread t; m_all.keys) { - t.join( rethrow ); + t.join(rethrow); } } } @@ -3706,6 +3173,8 @@ version (Posix) // lowlovel threading support private { + import core.atomic, core.memory, core.sync.mutex; + struct ll_ThreadData { ThreadID tid; diff --git a/test/thread/src/fiber_guard_page.d b/test/thread/src/fiber_guard_page.d index 212f4c857e..472e9108d2 100644 --- a/test/thread/src/fiber_guard_page.d +++ b/test/thread/src/fiber_guard_page.d @@ -21,21 +21,21 @@ void main() // allocate a page below (above) the fiber's stack to make stack overflows possible (w/o segfaulting) version (StackGrowsDown) { - static assert(__traits(identifier, test_fiber.tupleof[8]) == "m_pmem"); - auto stackBottom = test_fiber.tupleof[8]; - auto p = mmap(stackBottom - 8 * stackSize, 8 * stackSize, + static assert(__traits(identifier, test_fiber.tupleof[5]) == "m_pmem"); + auto stackBottom = test_fiber.tupleof[5]; + auto p = mmap(stackBottom - 5 * stackSize, 5 * stackSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); assert(p !is null, "failed to allocate page"); } else { - auto m_sz = test_fiber.tupleof[7]; - auto m_pmem = test_fiber.tupleof[8]; - static assert(__traits(identifier, test_fiber.tupleof[7]) == "m_size"); - static assert(__traits(identifier, test_fiber.tupleof[8]) == "m_pmem"); + auto m_sz = test_fiber.tupleof[4]; + auto m_pmem = test_fiber.tupleof[5]; + static assert(__traits(identifier, test_fiber.tupleof[4]) == "m_size"); + static assert(__traits(identifier, test_fiber.tupleof[5]) == "m_pmem"); auto stackTop = m_pmem + m_sz; - auto p = mmap(stackTop, 8 * stackSize, + auto p = mmap(stackTop, 5 * stackSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); assert(p !is null, "failed to allocate page"); }