| /** | |
| * The thread module provides support for thread creation and management. | |
| * | |
| * 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.d) | |
| */ | |
| /* NOTE: This file has been patched from the original DMD distribution to | |
| * work with the GDC compiler. | |
| */ | |
| module core.thread; | |
| public import core.time; // for Duration | |
| import core.exception : onOutOfMemoryError; | |
| version (OSX) | |
| version = Darwin; | |
| else version (iOS) | |
| version = Darwin; | |
| else version (TVOS) | |
| version = Darwin; | |
| else version (WatchOS) | |
| version = Darwin; | |
| private | |
| { | |
| // interface to rt.tlsgc | |
| import core.internal.traits : externDFunc; | |
| alias rt_tlsgc_init = externDFunc!("rt.tlsgc.init", void* function() nothrow @nogc); | |
| alias rt_tlsgc_destroy = externDFunc!("rt.tlsgc.destroy", void function(void*) nothrow @nogc); | |
| alias ScanDg = void delegate(void* pstart, void* pend) nothrow; | |
| alias rt_tlsgc_scan = | |
| externDFunc!("rt.tlsgc.scan", void function(void*, scope ScanDg) nothrow); | |
| alias rt_tlsgc_processGCMarks = | |
| externDFunc!("rt.tlsgc.processGCMarks", void function(void*, scope IsMarkedDg) nothrow); | |
| } | |
| version (Solaris) | |
| { | |
| import core.sys.solaris.sys.priocntl; | |
| import core.sys.solaris.sys.types; | |
| } | |
| version (GNU) | |
| { | |
| import gcc.builtins; | |
| version (GNU_StackGrowsDown) | |
| version = StackGrowsDown; | |
| } | |
| else | |
| { | |
| // this should be true for most architectures | |
| version = StackGrowsDown; | |
| } | |
| /** | |
| * Returns the process ID of the calling process, which is guaranteed to be | |
| * unique on the system. This call is always successful. | |
| * | |
| * Example: | |
| * --- | |
| * writefln("Current process id: %s", getpid()); | |
| * --- | |
| */ | |
| version (Posix) | |
| { | |
| alias getpid = core.sys.posix.unistd.getpid; | |
| } | |
| else version (Windows) | |
| { | |
| alias getpid = core.sys.windows.windows.GetCurrentProcessId; | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| // Thread and Fiber Exceptions | |
| /////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Base class for thread exceptions. | |
| */ | |
| class ThreadException : Exception | |
| { | |
| @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) | |
| { | |
| super(msg, file, line, next); | |
| } | |
| @safe pure nothrow this(string msg, Throwable next, string file = __FILE__, size_t line = __LINE__) | |
| { | |
| super(msg, file, line, next); | |
| } | |
| } | |
| /** | |
| * Base class for thread errors to be used for function inside GC when allocations are unavailable. | |
| */ | |
| class ThreadError : Error | |
| { | |
| @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) | |
| { | |
| super(msg, file, line, next); | |
| } | |
| @safe pure nothrow this(string msg, Throwable next, string file = __FILE__, size_t line = __LINE__) | |
| { | |
| super(msg, file, line, next); | |
| } | |
| } | |
| private | |
| { | |
| import core.atomic, core.memory, core.sync.mutex; | |
| // Handling unaligned mutexes are not supported on all platforms, so we must | |
| // ensure that the address of all shared data are appropriately aligned. | |
| import core.internal.traits : classInstanceAlignment; | |
| enum mutexAlign = classInstanceAlignment!Mutex; | |
| enum mutexClassInstanceSize = __traits(classInstanceSize, 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 | |
| /////////////////////////////////////////////////////////////////////////////// | |
| version (Windows) | |
| { | |
| private | |
| { | |
| import core.stdc.stdint : uintptr_t; // for _beginthreadex decl below | |
| import core.stdc.stdlib; // for malloc, atexit | |
| import core.sys.windows.windows; | |
| import core.sys.windows.threadaux; // for OpenThreadHandle | |
| extern (Windows) alias btex_fptr = uint function(void*); | |
| extern (C) uintptr_t _beginthreadex(void*, uint, btex_fptr, void*, uint, uint*) nothrow; | |
| // | |
| // Entry point for Windows threads | |
| // | |
| extern (Windows) uint thread_entryPoint( void* arg ) nothrow | |
| { | |
| Thread obj = cast(Thread) arg; | |
| assert( obj ); | |
| assert( obj.m_curr 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); | |
| scope (exit) | |
| { | |
| Thread.remove(obj); | |
| } | |
| Thread.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 | |
| // this thread object (this latter condition is not strictly | |
| // necessary on Windows but it should be followed for the | |
| // sake of consistency). | |
| // TODO: Consider putting an auto exception object here (using | |
| // alloca) forOutOfMemoryError plus something to track | |
| // whether an exception is in-flight? | |
| void append( Throwable t ) | |
| { | |
| if ( obj.m_unhandled is null ) | |
| obj.m_unhandled = t; | |
| else | |
| { | |
| Throwable last = obj.m_unhandled; | |
| while ( last.next !is null ) | |
| last = last.next; | |
| last.next = t; | |
| } | |
| } | |
| version (D_InlineAsm_X86) | |
| { | |
| asm nothrow @nogc { fninit; } | |
| } | |
| try | |
| { | |
| rt_moduleTlsCtor(); | |
| try | |
| { | |
| obj.run(); | |
| } | |
| catch ( Throwable t ) | |
| { | |
| append( t ); | |
| } | |
| rt_moduleTlsDtor(); | |
| } | |
| catch ( Throwable t ) | |
| { | |
| append( t ); | |
| } | |
| return 0; | |
| } | |
| HANDLE GetCurrentThreadHandle() nothrow @nogc | |
| { | |
| const uint DUPLICATE_SAME_ACCESS = 0x00000002; | |
| HANDLE curr = GetCurrentThread(), | |
| proc = GetCurrentProcess(), | |
| hndl; | |
| DuplicateHandle( proc, curr, proc, &hndl, 0, TRUE, DUPLICATE_SAME_ACCESS ); | |
| return hndl; | |
| } | |
| } | |
| } | |
| else version (Posix) | |
| { | |
| private | |
| { | |
| import core.stdc.errno; | |
| import core.sys.posix.semaphore; | |
| import core.sys.posix.stdlib; // for malloc, valloc, free, atexit | |
| import core.sys.posix.pthread; | |
| import core.sys.posix.signal; | |
| import core.sys.posix.time; | |
| version (Darwin) | |
| { | |
| import core.sys.darwin.mach.thread_act; | |
| import core.sys.darwin.pthread : pthread_mach_thread_np; | |
| } | |
| // | |
| // Entry point for POSIX threads | |
| // | |
| extern (C) void* thread_entryPoint( void* arg ) nothrow | |
| { | |
| version (Shared) | |
| { | |
| import rt.sections; | |
| Thread obj = cast(Thread)(cast(void**)arg)[0]; | |
| auto loadedLibraries = (cast(void**)arg)[1]; | |
| .free(arg); | |
| } | |
| else | |
| { | |
| Thread obj = cast(Thread)arg; | |
| } | |
| assert( obj ); | |
| // loadedLibraries need to be inherited from parent thread | |
| // before initilizing GC for TLS (rt_tlsgc_init) | |
| version (Shared) inheritLoadedLibraries(loadedLibraries); | |
| assert( obj.m_curr 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 | |
| scope (exit) | |
| { | |
| Thread.remove(obj); | |
| atomicStore!(MemoryOrder.raw)(obj.m_isRunning, false); | |
| } | |
| Thread.add(&obj.m_main); | |
| static extern (C) void thread_cleanupHandler( void* arg ) nothrow @nogc | |
| { | |
| Thread obj = cast(Thread) arg; | |
| assert( obj ); | |
| // NOTE: If the thread terminated abnormally, just set it as | |
| // not running and let thread_suspendAll remove it from | |
| // the thread list. This is safer and is consistent | |
| // with the Windows thread code. | |
| atomicStore!(MemoryOrder.raw)(obj.m_isRunning,false); | |
| } | |
| // NOTE: Using void to skip the initialization here relies on | |
| // knowledge of how pthread_cleanup is implemented. It may | |
| // not be appropriate for all platforms. However, it does | |
| // avoid the need to link the pthread module. If any | |
| // implementation actually requires default initialization | |
| // then pthread_cleanup should be restructured to maintain | |
| // the current lack of a link dependency. | |
| static if ( __traits( compiles, pthread_cleanup ) ) | |
| { | |
| pthread_cleanup cleanup = void; | |
| cleanup.push( &thread_cleanupHandler, cast(void*) obj ); | |
| } | |
| else static if ( __traits( compiles, pthread_cleanup_push ) ) | |
| { | |
| pthread_cleanup_push( &thread_cleanupHandler, cast(void*) obj ); | |
| } | |
| else | |
| { | |
| static assert( false, "Platform not supported." ); | |
| } | |
| // NOTE: No GC allocations may occur until the stack pointers have | |
| // been set and Thread.getThis returns a valid reference to | |
| // this thread object (this latter condition is not strictly | |
| // necessary on Windows but it should be followed for the | |
| // sake of consistency). | |
| // TODO: Consider putting an auto exception object here (using | |
| // alloca) forOutOfMemoryError plus something to track | |
| // whether an exception is in-flight? | |
| void append( Throwable t ) | |
| { | |
| if ( obj.m_unhandled is null ) | |
| obj.m_unhandled = t; | |
| else | |
| { | |
| Throwable last = obj.m_unhandled; | |
| while ( last.next !is null ) | |
| last = last.next; | |
| last.next = t; | |
| } | |
| } | |
| try | |
| { | |
| rt_moduleTlsCtor(); | |
| try | |
| { | |
| obj.run(); | |
| } | |
| catch ( Throwable t ) | |
| { | |
| append( t ); | |
| } | |
| rt_moduleTlsDtor(); | |
| version (Shared) cleanupLoadedLibraries(); | |
| } | |
| catch ( Throwable t ) | |
| { | |
| append( t ); | |
| } | |
| // NOTE: Normal cleanup is handled by scope(exit). | |
| static if ( __traits( compiles, pthread_cleanup ) ) | |
| { | |
| cleanup.pop( 0 ); | |
| } | |
| else static if ( __traits( compiles, pthread_cleanup_push ) ) | |
| { | |
| pthread_cleanup_pop( 0 ); | |
| } | |
| return null; | |
| } | |
| // | |
| // Used to track the number of suspended threads | |
| // | |
| __gshared sem_t suspendCount; | |
| extern (C) void thread_suspendHandler( int sig ) nothrow | |
| in | |
| { | |
| assert( sig == suspendSignalNumber ); | |
| } | |
| body | |
| { | |
| void op(void* sp) nothrow | |
| { | |
| // NOTE: Since registers are being pushed and popped from the | |
| // stack, any other stack data used by this function should | |
| // be gone before the stack cleanup code is called below. | |
| Thread obj = Thread.getThis(); | |
| assert(obj !is null); | |
| if ( !obj.m_lock ) | |
| { | |
| obj.m_curr.tstack = getStackTop(); | |
| } | |
| sigset_t sigres = void; | |
| int status; | |
| status = sigfillset( &sigres ); | |
| assert( status == 0 ); | |
| status = sigdelset( &sigres, resumeSignalNumber ); | |
| assert( status == 0 ); | |
| version (FreeBSD) obj.m_suspendagain = false; | |
| status = sem_post( &suspendCount ); | |
| assert( status == 0 ); | |
| sigsuspend( &sigres ); | |
| if ( !obj.m_lock ) | |
| { | |
| obj.m_curr.tstack = obj.m_curr.bstack; | |
| } | |
| } | |
| // avoid deadlocks on FreeBSD, see Issue 13416 | |
| version (FreeBSD) | |
| { | |
| auto obj = Thread.getThis(); | |
| if (THR_IN_CRITICAL(obj.m_addr)) | |
| { | |
| obj.m_suspendagain = true; | |
| if (sem_post(&suspendCount)) assert(0); | |
| return; | |
| } | |
| } | |
| callWithStackShell(&op); | |
| } | |
| extern (C) void thread_resumeHandler( int sig ) nothrow | |
| in | |
| { | |
| assert( sig == resumeSignalNumber ); | |
| } | |
| body | |
| { | |
| } | |
| // HACK libthr internal (thr_private.h) macro, used to | |
| // avoid deadlocks in signal handler, see Issue 13416 | |
| version (FreeBSD) bool THR_IN_CRITICAL(pthread_t p) nothrow @nogc | |
| { | |
| import core.sys.posix.config : c_long; | |
| import core.sys.posix.sys.types : lwpid_t; | |
| // If the begin of pthread would be changed in libthr (unlikely) | |
| // we'll run into undefined behavior, compare with thr_private.h. | |
| static struct pthread | |
| { | |
| c_long tid; | |
| static struct umutex { lwpid_t owner; uint flags; uint[2] ceilings; uint[4] spare; } | |
| umutex lock; | |
| uint cycle; | |
| int locklevel; | |
| int critical_count; | |
| // ... | |
| } | |
| auto priv = cast(pthread*)p; | |
| return priv.locklevel > 0 || priv.critical_count > 0; | |
| } | |
| } | |
| } | |
| else | |
| { | |
| // NOTE: This is the only place threading versions are checked. If a new | |
| // version is added, the module code will need to be searched for | |
| // places where version-specific code may be required. This can be | |
| // easily accomlished by searching for 'Windows' or 'Posix'. | |
| static assert( false, "Unknown threading implementation." ); | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| // Thread | |
| /////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * This class encapsulates all threading functionality for the D | |
| * programming language. As thread manipulation is a required facility | |
| * for garbage collection, all user threads should derive from this | |
| * class, and instances of this class should never be explicitly deleted. | |
| * A new thread may be created using either derivation or composition, as | |
| * in the following example. | |
| */ | |
| class Thread | |
| { | |
| /////////////////////////////////////////////////////////////////////////// | |
| // Initialization | |
| /////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Initializes a thread object which is associated with a static | |
| * D function. | |
| * | |
| * Params: | |
| * fn = The thread function. | |
| * sz = The stack size for this thread. | |
| * | |
| * In: | |
| * fn must not be null. | |
| */ | |
| this( void function() fn, size_t sz = 0 ) @safe pure nothrow @nogc | |
| in | |
| { | |
| assert( fn ); | |
| } | |
| body | |
| { | |
| this(sz); | |
| () @trusted { m_fn = fn; }(); | |
| m_call = Call.FN; | |
| m_curr = &m_main; | |
| } | |
| /** | |
| * Initializes a thread object which is associated with a dynamic | |
| * D function. | |
| * | |
| * Params: | |
| * dg = The thread function. | |
| * sz = The stack size for this thread. | |
| * | |
| * In: | |
| * dg must not be null. | |
| */ | |
| this( void delegate() dg, size_t sz = 0 ) @safe pure nothrow @nogc | |
| in | |
| { | |
| assert( dg ); | |
| } | |
| body | |
| { | |
| this(sz); | |
| () @trusted { m_dg = dg; }(); | |
| m_call = Call.DG; | |
| m_curr = &m_main; | |
| } | |
| /** | |
| * Cleans up any remaining resources used by this object. | |
| */ | |
| ~this() nothrow @nogc | |
| { | |
| if ( m_addr == m_addr.init ) | |
| { | |
| return; | |
| } | |
| version (Windows) | |
| { | |
| m_addr = m_addr.init; | |
| CloseHandle( m_hndl ); | |
| m_hndl = m_hndl.init; | |
| } | |
| else version (Posix) | |
| { | |
| pthread_detach( m_addr ); | |
| m_addr = m_addr.init; | |
| } | |
| version (Darwin) | |
| { | |
| m_tmach = m_tmach.init; | |
| } | |
| rt_tlsgc_destroy( m_tlsgcdata ); | |
| m_tlsgcdata = null; | |
| } | |
| /////////////////////////////////////////////////////////////////////////// | |
| // General Actions | |
| /////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Starts the thread and invokes the function or delegate passed upon | |
| * construction. | |
| * | |
| * In: | |
| * This routine may only be called once per thread instance. | |
| * | |
| * Throws: | |
| * ThreadException if the thread fails to start. | |
| */ | |
| final Thread start() nothrow | |
| in | |
| { | |
| assert( !next && !prev ); | |
| } | |
| body | |
| { | |
| auto wasThreaded = multiThreadedFlag; | |
| multiThreadedFlag = true; | |
| scope( failure ) | |
| { | |
| if ( !wasThreaded ) | |
| multiThreadedFlag = false; | |
| } | |
| version (Windows) {} else | |
| version (Posix) | |
| { | |
| pthread_attr_t attr; | |
| if ( pthread_attr_init( &attr ) ) | |
| onThreadError( "Error initializing thread attributes" ); | |
| if ( m_sz && pthread_attr_setstacksize( &attr, m_sz ) ) | |
| onThreadError( "Error initializing thread stack size" ); | |
| } | |
| version (Windows) | |
| { | |
| // NOTE: If a thread is just executing DllMain() | |
| // while another thread is started here, it holds an OS internal | |
| // lock that serializes DllMain with CreateThread. As the code | |
| // might request a synchronization on slock (e.g. in thread_findByAddr()), | |
| // we cannot hold that lock while creating the thread without | |
| // creating a deadlock | |
| // | |
| // 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 ); | |
| if ( cast(size_t) m_hndl == 0 ) | |
| onThreadError( "Error creating thread" ); | |
| } | |
| slock.lock_nothrow(); | |
| scope(exit) slock.unlock_nothrow(); | |
| { | |
| ++nAboutToStart; | |
| pAboutToStart = cast(Thread*)realloc(pAboutToStart, Thread.sizeof * nAboutToStart); | |
| pAboutToStart[nAboutToStart - 1] = this; | |
| version (Windows) | |
| { | |
| if ( ResumeThread( m_hndl ) == -1 ) | |
| onThreadError( "Error resuming thread" ); | |
| } | |
| else version (Posix) | |
| { | |
| // NOTE: This is also set to true by thread_entryPoint, but set it | |
| // here as well so the calling thread will see the isRunning | |
| // state immediately. | |
| atomicStore!(MemoryOrder.raw)(m_isRunning, true); | |
| scope( failure ) atomicStore!(MemoryOrder.raw)(m_isRunning, false); | |
| version (Shared) | |
| { | |
| import rt.sections; | |
| auto libs = pinLoadedLibraries(); | |
| auto ps = cast(void**).malloc(2 * size_t.sizeof); | |
| if (ps is null) onOutOfMemoryError(); | |
| ps[0] = cast(void*)this; | |
| ps[1] = cast(void*)libs; | |
| if ( pthread_create( &m_addr, &attr, &thread_entryPoint, ps ) != 0 ) | |
| { | |
| unpinLoadedLibraries(libs); | |
| .free(ps); | |
| onThreadError( "Error creating thread" ); | |
| } | |
| } | |
| else | |
| { | |
| if ( pthread_create( &m_addr, &attr, &thread_entryPoint, cast(void*) this ) != 0 ) | |
| onThreadError( "Error creating thread" ); | |
| } | |
| } | |
| version (Darwin) | |
| { | |
| m_tmach = pthread_mach_thread_np( m_addr ); | |
| if ( m_tmach == m_tmach.init ) | |
| onThreadError( "Error creating thread" ); | |
| } | |
| return this; | |
| } | |
| } | |
| /** | |
| * Waits for this thread to complete. If the thread terminated as the | |
| * result of an unhandled exception, this exception will be rethrown. | |
| * | |
| * Params: | |
| * rethrow = Rethrow any unhandled exception which may have caused this | |
| * thread to terminate. | |
| * | |
| * Throws: | |
| * ThreadException if the operation fails. | |
| * Any exception not handled by the joined thread. | |
| * | |
| * Returns: | |
| * Any exception not handled by this thread if rethrow = false, null | |
| * otherwise. | |
| */ | |
| final Throwable join( bool rethrow = true ) | |
| { | |
| version (Windows) | |
| { | |
| 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. | |
| atomicStore!(MemoryOrder.raw)(*cast(shared)&m_addr, m_addr.init); | |
| CloseHandle( m_hndl ); | |
| m_hndl = m_hndl.init; | |
| } | |
| else version (Posix) | |
| { | |
| if ( pthread_join( m_addr, null ) != 0 ) | |
| throw new ThreadException( "Unable to join thread" ); | |
| // NOTE: pthread_join acts as a substitute for pthread_detach, | |
| // which is normally called by the dtor. Setting m_addr | |
| // to zero ensures that pthread_detach will not be called | |
| // on object destruction. | |
| m_addr = m_addr.init; | |
| } | |
| if ( m_unhandled ) | |
| { | |
| if ( rethrow ) | |
| throw m_unhandled; | |
| return m_unhandled; | |
| } | |
| return null; | |
| } | |
| /////////////////////////////////////////////////////////////////////////// | |
| // General Properties | |
| /////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Gets the OS identifier for this thread. | |
| * | |
| * Returns: | |
| * If the thread hasn't been started yet, returns $(LREF ThreadID)$(D.init). | |
| * Otherwise, returns the result of $(D GetCurrentThreadId) on Windows, | |
| * and $(D pthread_self) on POSIX. | |
| * | |
| * The value is unique for the current process. | |
| */ | |
| final @property ThreadID id() @safe @nogc | |
| { | |
| synchronized( this ) | |
| { | |
| return m_addr; | |
| } | |
| } | |
| /** | |
| * Gets the user-readable label for this thread. | |
| * | |
| * Returns: | |
| * The name of this thread. | |
| */ | |
| final @property string name() @safe @nogc | |
| { | |
| synchronized( this ) | |
| { | |
| return m_name; | |
| } | |
| } | |
| /** | |
| * Sets the user-readable label for this thread. | |
| * | |
| * Params: | |
| * val = The new name of this thread. | |
| */ | |
| final @property void name( string val ) @safe @nogc | |
| { | |
| synchronized( this ) | |
| { | |
| m_name = val; | |
| } | |
| } | |
| /** | |
| * Gets the daemon status for this thread. While the runtime will wait for | |
| * all normal threads to complete before tearing down the process, daemon | |
| * threads are effectively ignored and thus will not prevent the process | |
| * from terminating. In effect, daemon threads will be terminated | |
| * automatically by the OS when the process exits. | |
| * | |
| * Returns: | |
| * true if this is a daemon thread. | |
| */ | |
| final @property bool isDaemon() @safe @nogc | |
| { | |
| synchronized( this ) | |
| { | |
| return m_isDaemon; | |
| } | |
| } | |
| /** | |
| * Sets the daemon status for this thread. While the runtime will wait for | |
| * all normal threads to complete before tearing down the process, daemon | |
| * threads are effectively ignored and thus will not prevent the process | |
| * from terminating. In effect, daemon threads will be terminated | |
| * automatically by the OS when the process exits. | |
| * | |
| * Params: | |
| * val = The new daemon status for this thread. | |
| */ | |
| final @property void isDaemon( bool val ) @safe @nogc | |
| { | |
| synchronized( this ) | |
| { | |
| m_isDaemon = val; | |
| } | |
| } | |
| /** | |
| * Tests whether this thread is running. | |
| * | |
| * Returns: | |
| * true if the thread is running, false if not. | |
| */ | |
| final @property bool isRunning() nothrow @nogc | |
| { | |
| if ( m_addr == m_addr.init ) | |
| { | |
| return false; | |
| } | |
| version (Windows) | |
| { | |
| uint ecode = 0; | |
| GetExitCodeThread( m_hndl, &ecode ); | |
| return ecode == STILL_ACTIVE; | |
| } | |
| else version (Posix) | |
| { | |
| return atomicLoad(m_isRunning); | |
| } | |
| } | |
| /////////////////////////////////////////////////////////////////////////// | |
| // Thread Priority Actions | |
| /////////////////////////////////////////////////////////////////////////// | |
| version (Windows) | |
| { | |
| @property static int PRIORITY_MIN() @nogc nothrow pure @safe | |
| { | |
| return THREAD_PRIORITY_IDLE; | |
| } | |
| @property static const(int) PRIORITY_MAX() @nogc nothrow pure @safe | |
| { | |
| return THREAD_PRIORITY_TIME_CRITICAL; | |
| } | |
| @property static int PRIORITY_DEFAULT() @nogc nothrow pure @safe | |
| { | |
| return THREAD_PRIORITY_NORMAL; | |
| } | |
| } | |
| else | |
| { | |
| private struct Priority | |
| { | |
| int PRIORITY_MIN = int.min; | |
| int PRIORITY_DEFAULT = int.min; | |
| int PRIORITY_MAX = int.min; | |
| } | |
| /* | |
| Lazily loads one of the members stored in a hidden global variable of | |
| type `Priority`. Upon the first access of either member, the entire | |
| `Priority` structure is initialized. Multiple initializations from | |
| different threads calling this function are tolerated. | |
| `which` must be one of `PRIORITY_MIN`, `PRIORITY_DEFAULT`, | |
| `PRIORITY_MAX`. | |
| */ | |
| private static int loadGlobal(string which)() | |
| { | |
| static shared Priority cache; | |
| auto local = atomicLoad(mixin("cache." ~ which)); | |
| if (local != local.min) return local; | |
| // There will be benign races | |
| cache = loadPriorities; | |
| return atomicLoad(mixin("cache." ~ which)); | |
| } | |
| /* | |
| Loads all priorities and returns them as a `Priority` structure. This | |
| function is thread-neutral. | |
| */ | |
| private static Priority loadPriorities() @nogc nothrow @trusted | |
| { | |
| Priority result; | |
| version (Solaris) | |
| { | |
| pcparms_t pcParms; | |
| pcinfo_t pcInfo; | |
| pcParms.pc_cid = PC_CLNULL; | |
| if (priocntl(idtype_t.P_PID, P_MYID, PC_GETPARMS, &pcParms) == -1) | |
| assert( 0, "Unable to get scheduling class" ); | |
| pcInfo.pc_cid = pcParms.pc_cid; | |
| // PC_GETCLINFO ignores the first two args, use dummy values | |
| if (priocntl(idtype_t.P_PID, 0, PC_GETCLINFO, &pcInfo) == -1) | |
| assert( 0, "Unable to get scheduling class info" ); | |
| pri_t* clparms = cast(pri_t*)&pcParms.pc_clparms; | |
| pri_t* clinfo = cast(pri_t*)&pcInfo.pc_clinfo; | |
| result.PRIORITY_MAX = clparms[0]; | |
| if (pcInfo.pc_clname == "RT") | |
| { | |
| m_isRTClass = true; | |
| // For RT class, just assume it can't be changed | |
| result.PRIORITY_MIN = clparms[0]; | |
| result.PRIORITY_DEFAULT = clparms[0]; | |
| } | |
| else | |
| { | |
| m_isRTClass = false; | |
| // For all other scheduling classes, there are | |
| // two key values -- uprilim and maxupri. | |
| // maxupri is the maximum possible priority defined | |
| // for the scheduling class, and valid priorities | |
| // range are in [-maxupri, maxupri]. | |
| // | |
| // However, uprilim is an upper limit that the | |
| // current thread can set for the current scheduling | |
| // class, which can be less than maxupri. As such, | |
| // use this value for priorityMax since this is | |
| // the effective maximum. | |
| // maxupri | |
| result.PRIORITY_MIN = -clinfo[0]; | |
| // by definition | |
| result.PRIORITY_DEFAULT = 0; | |
| } | |
| } | |
| else version (Posix) | |
| { | |
| int policy; | |
| sched_param param; | |
| pthread_getschedparam( pthread_self(), &policy, ¶m ) == 0 | |
| || assert(0, "Internal error in pthread_getschedparam"); | |
| result.PRIORITY_MIN = sched_get_priority_min( policy ); | |
| result.PRIORITY_MIN != -1 | |
| || assert(0, "Internal error in sched_get_priority_min"); | |
| result.PRIORITY_DEFAULT = param.sched_priority; | |
| result.PRIORITY_MAX = sched_get_priority_max( policy ); | |
| result.PRIORITY_MAX != -1 || | |
| assert(0, "Internal error in sched_get_priority_max"); | |
| } | |
| else | |
| { | |
| static assert(0, "Your code here."); | |
| } | |
| return result; | |
| } | |
| /** | |
| * The minimum scheduling priority that may be set for a thread. On | |
| * systems where multiple scheduling policies are defined, this value | |
| * represents the minimum valid priority for the scheduling policy of | |
| * the process. | |
| */ | |
| @property static int PRIORITY_MIN() @nogc nothrow pure @trusted | |
| { | |
| return (cast(int function() @nogc nothrow pure @safe) | |
| &loadGlobal!"PRIORITY_MIN")(); | |
| } | |
| /** | |
| * The maximum scheduling priority that may be set for a thread. On | |
| * systems where multiple scheduling policies are defined, this value | |
| * represents the maximum valid priority for the scheduling policy of | |
| * the process. | |
| */ | |
| @property static const(int) PRIORITY_MAX() @nogc nothrow pure @trusted | |
| { | |
| return (cast(int function() @nogc nothrow pure @safe) | |
| &loadGlobal!"PRIORITY_MAX")(); | |
| } | |
| /** | |
| * The default scheduling priority that is set for a thread. On | |
| * systems where multiple scheduling policies are defined, this value | |
| * represents the default priority for the scheduling policy of | |
| * the process. | |
| */ | |
| @property static int PRIORITY_DEFAULT() @nogc nothrow pure @trusted | |
| { | |
| return (cast(int function() @nogc nothrow pure @safe) | |
| &loadGlobal!"PRIORITY_DEFAULT")(); | |
| } | |
| } | |
| version (NetBSD) | |
| { | |
| //NetBSD does not support priority for default policy | |
| // and it is not possible change policy without root access | |
| int fakePriority = int.max; | |
| } | |
| /** | |
| * Gets the scheduling priority for the associated thread. | |
| * | |
| * Note: Getting the priority of a thread that already terminated | |
| * might return the default priority. | |
| * | |
| * Returns: | |
| * The scheduling priority of this thread. | |
| */ | |
| final @property int priority() | |
| { | |
| version (Windows) | |
| { | |
| return GetThreadPriority( m_hndl ); | |
| } | |
| else version (NetBSD) | |
| { | |
| return fakePriority==int.max? PRIORITY_DEFAULT : fakePriority; | |
| } | |
| else version (Posix) | |
| { | |
| int policy; | |
| sched_param param; | |
| if (auto err = pthread_getschedparam(m_addr, &policy, ¶m)) | |
| { | |
| // ignore error if thread is not running => Bugzilla 8960 | |
| if (!atomicLoad(m_isRunning)) return PRIORITY_DEFAULT; | |
| throw new ThreadException("Unable to get thread priority"); | |
| } | |
| return param.sched_priority; | |
| } | |
| } | |
| /** | |
| * Sets the scheduling priority for the associated thread. | |
| * | |
| * Note: Setting the priority of a thread that already terminated | |
| * might have no effect. | |
| * | |
| * Params: | |
| * val = The new scheduling priority of this thread. | |
| */ | |
| final @property void priority( int val ) | |
| in | |
| { | |
| assert(val >= PRIORITY_MIN); | |
| assert(val <= PRIORITY_MAX); | |
| } | |
| body | |
| { | |
| version (Windows) | |
| { | |
| if ( !SetThreadPriority( m_hndl, val ) ) | |
| throw new ThreadException( "Unable to set thread priority" ); | |
| } | |
| else version (Solaris) | |
| { | |
| // the pthread_setschedprio(3c) and pthread_setschedparam functions | |
| // are broken for the default (TS / time sharing) scheduling class. | |
| // instead, we use priocntl(2) which gives us the desired behavior. | |
| // We hardcode the min and max priorities to the current value | |
| // so this is a no-op for RT threads. | |
| if (m_isRTClass) | |
| return; | |
| pcparms_t pcparm; | |
| pcparm.pc_cid = PC_CLNULL; | |
| if (priocntl(idtype_t.P_LWPID, P_MYID, PC_GETPARMS, &pcparm) == -1) | |
| throw new ThreadException( "Unable to get scheduling class" ); | |
| pri_t* clparms = cast(pri_t*)&pcparm.pc_clparms; | |
| // clparms is filled in by the PC_GETPARMS call, only necessary | |
| // to adjust the element that contains the thread priority | |
| clparms[1] = cast(pri_t) val; | |
| if (priocntl(idtype_t.P_LWPID, P_MYID, PC_SETPARMS, &pcparm) == -1) | |
| throw new ThreadException( "Unable to set scheduling class" ); | |
| } | |
| else version (NetBSD) | |
| { | |
| fakePriority = val; | |
| } | |
| else version (Posix) | |
| { | |
| static if (__traits(compiles, pthread_setschedprio)) | |
| { | |
| if (auto err = pthread_setschedprio(m_addr, val)) | |
| { | |
| // ignore error if thread is not running => Bugzilla 8960 | |
| if (!atomicLoad(m_isRunning)) return; | |
| throw new ThreadException("Unable to set thread priority"); | |
| } | |
| } | |
| else | |
| { | |
| // NOTE: pthread_setschedprio is not implemented on Darwin, FreeBSD, OpenBSD, | |
| // or DragonFlyBSD, so use the more complicated get/set sequence below. | |
| int policy; | |
| sched_param param; | |
| if (auto err = pthread_getschedparam(m_addr, &policy, ¶m)) | |
| { | |
| // ignore error if thread is not running => Bugzilla 8960 | |
| if (!atomicLoad(m_isRunning)) return; | |
| throw new ThreadException("Unable to set thread priority"); | |
| } | |
| param.sched_priority = val; | |
| if (auto err = pthread_setschedparam(m_addr, policy, ¶m)) | |
| { | |
| // ignore error if thread is not running => Bugzilla 8960 | |
| if (!atomicLoad(m_isRunning)) return; | |
| throw new ThreadException("Unable to set thread priority"); | |
| } | |
| } | |
| } | |
| } | |
| unittest | |
| { | |
| auto thr = Thread.getThis(); | |
| immutable prio = thr.priority; | |
| scope (exit) thr.priority = prio; | |
| assert(prio == PRIORITY_DEFAULT); | |
| assert(prio >= PRIORITY_MIN && prio <= PRIORITY_MAX); | |
| thr.priority = PRIORITY_MIN; | |
| assert(thr.priority == PRIORITY_MIN); | |
| thr.priority = PRIORITY_MAX; | |
| assert(thr.priority == PRIORITY_MAX); | |
| } | |
| unittest // Bugzilla 8960 | |
| { | |
| import core.sync.semaphore; | |
| auto thr = new Thread({}); | |
| thr.start(); | |
| Thread.sleep(1.msecs); // wait a little so the thread likely has finished | |
| thr.priority = PRIORITY_MAX; // setting priority doesn't cause error | |
| auto prio = thr.priority; // getting priority doesn't cause error | |
| assert(prio >= PRIORITY_MIN && prio <= PRIORITY_MAX); | |
| } | |
| /////////////////////////////////////////////////////////////////////////// | |
| // Actions on Calling Thread | |
| /////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Suspends the calling thread for at least the supplied period. This may | |
| * result in multiple OS calls if period is greater than the maximum sleep | |
| * duration supported by the operating system. | |
| * | |
| * Params: | |
| * val = The minimum duration the calling thread should be suspended. | |
| * | |
| * In: | |
| * period must be non-negative. | |
| * | |
| * Example: | |
| * ------------------------------------------------------------------------ | |
| * | |
| * Thread.sleep( dur!("msecs")( 50 ) ); // sleep for 50 milliseconds | |
| * Thread.sleep( dur!("seconds")( 5 ) ); // sleep for 5 seconds | |
| * | |
| * ------------------------------------------------------------------------ | |
| */ | |
| static void sleep( Duration val ) @nogc nothrow | |
| in | |
| { | |
| assert( !val.isNegative ); | |
| } | |
| body | |
| { | |
| version (Windows) | |
| { | |
| auto maxSleepMillis = dur!("msecs")( uint.max - 1 ); | |
| // avoid a non-zero time to be round down to 0 | |
| if ( val > dur!"msecs"( 0 ) && val < dur!"msecs"( 1 ) ) | |
| val = dur!"msecs"( 1 ); | |
| // NOTE: In instances where all other threads in the process have a | |
| // lower priority than the current thread, the current thread | |
| // will not yield with a sleep time of zero. However, unlike | |
| // yield(), the user is not asking for a yield to occur but | |
| // only for execution to suspend for the requested interval. | |
| // Therefore, expected performance may not be met if a yield | |
| // is forced upon the user. | |
| while ( val > maxSleepMillis ) | |
| { | |
| Sleep( cast(uint) | |
| maxSleepMillis.total!"msecs" ); | |
| val -= maxSleepMillis; | |
| } | |
| Sleep( cast(uint) val.total!"msecs" ); | |
| } | |
| else version (Posix) | |
| { | |
| timespec tin = void; | |
| timespec tout = void; | |
| val.split!("seconds", "nsecs")(tin.tv_sec, tin.tv_nsec); | |
| if ( val.total!"seconds" > tin.tv_sec.max ) | |
| tin.tv_sec = tin.tv_sec.max; | |
| while ( true ) | |
| { | |
| if ( !nanosleep( &tin, &tout ) ) | |
| return; | |
| if ( errno != EINTR ) | |
| assert(0, "Unable to sleep for the specified duration"); | |
| tin = tout; | |
| } | |
| } | |
| } | |
| /** | |
| * Forces a context switch to occur away from the calling thread. | |
| */ | |
| static void yield() @nogc nothrow | |
| { | |
| version (Windows) | |
| SwitchToThread(); | |
| else version (Posix) | |
| sched_yield(); | |
| } | |
| /////////////////////////////////////////////////////////////////////////// | |
| // Thread Accessors | |
| /////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * Provides a reference to the calling thread. | |
| * | |
| * Returns: | |
| * The thread object representing the calling thread. The result of | |
| * deleting this object is undefined. If the current thread is not | |
| * attached to the runtime, a null reference is returned. | |
| */ | |
| static Thread getThis() @safe nothrow @nogc | |
| { | |
| // NOTE: This function may not be called until thread_init has | |
| // completed. See thread_suspendAll for more information | |
| // on why this might occur. | |
| return sm_this; | |
| } | |
| /** | |
| * Provides a list of all threads currently being tracked by the system. | |
| * Note that threads in the returned array might no longer run (see | |
| * $(D Thread.)$(LREF isRunning)). | |
| * | |
| * Returns: | |
| * An array containing references to all threads currently being | |
| * tracked by the system. The result of deleting any contained | |
| * objects is undefined. | |
| */ | |
| static Thread[] getAll() | |
| { | |
| static void resize(ref Thread[] buf, size_t nlen) | |
| { | |
| buf.length = nlen; | |
| } | |
| return getAllImpl!resize(); | |
| } | |
| /** | |
| * Operates on all threads currently being tracked by the system. The | |
| * result of deleting any Thread object is undefined. | |
| * Note that threads passed to the callback might no longer run (see | |
| * $(D Thread.)$(LREF isRunning)). | |
| * | |
| * Params: | |
| * dg = The supplied code as a delegate. | |
| * | |
| * Returns: | |
| * Zero if all elemented are visited, nonzero if not. | |
| */ | |
| static int opApply(scope int delegate(ref Thread) dg) | |
| { | |
| import core.stdc.stdlib : free, realloc; | |
| static void resize(ref Thread[] buf, size_t nlen) | |
| { | |
| buf = (cast(Thread*)realloc(buf.ptr, nlen * Thread.sizeof))[0 .. nlen]; | |
| } | |
| auto buf = getAllImpl!resize; | |
| scope(exit) if (buf.ptr) free(buf.ptr); | |
| foreach (t; buf) | |
| { | |
| if (auto res = dg(t)) | |
| return res; | |
| } | |
| return 0; | |
| } | |
| unittest | |
| { | |
| auto t1 = new Thread({ | |
| foreach (_; 0 .. 20) | |
| Thread.getAll; | |
| }).start; | |
| auto t2 = new Thread({ | |
| foreach (_; 0 .. 20) | |
| GC.collect; | |
| }).start; | |
| t1.join(); | |
| t2.join(); | |
| } | |
| private static Thread[] getAllImpl(alias resize)() | |
| { | |
| import core.atomic; | |
| Thread[] buf; | |
| while (true) | |
| { | |
| immutable len = atomicLoad!(MemoryOrder.raw)(*cast(shared)&sm_tlen); | |
| resize(buf, len); | |
| assert(buf.length == len); | |
| synchronized (slock) | |
| { | |
| if (len == sm_tlen) | |
| { | |
| size_t pos; | |
| for (Thread t = sm_tbeg; t; t = t.next) | |
| buf[pos++] = t; | |
| return buf; | |
| } | |
| } | |
| } | |
| } | |
| /////////////////////////////////////////////////////////////////////////// | |
| // Stuff That Should Go Away | |
| /////////////////////////////////////////////////////////////////////////// | |
| private: | |
| // | |
| // Initializes a thread object which has no associated executable function. | |
| // This is used for the main thread initialized in thread_init(). | |
| // | |
| this(size_t sz = 0) @safe pure nothrow @nogc | |
| { | |
| if (sz) | |
| { | |
| version (Posix) | |
| { | |
| // stack size must be a multiple of PAGESIZE | |
| sz += PAGESIZE - 1; | |
| sz -= sz % PAGESIZE; | |
| // and at least PTHREAD_STACK_MIN | |
| if (PTHREAD_STACK_MIN > sz) | |
| sz = PTHREAD_STACK_MIN; | |
| } | |
| m_sz = 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; | |
| } | |
| } | |
| private: | |
| // | |
| // The type of routine passed on thread construction. | |
| // | |
| enum Call | |
| { | |
| NO, | |
| FN, | |
| DG | |
| } | |
| // | |
| // Standard types | |
| // | |
| version (Windows) | |
| { | |
| alias TLSKey = uint; | |
| } | |
| else version (Posix) | |
| { | |
| alias TLSKey = pthread_key_t; | |
| } | |
| // | |
| // Local storage | |
| // | |
| static Thread sm_this; | |
| // | |
| // Main process thread | |
| // | |
| __gshared Thread sm_main; | |
| version (FreeBSD) | |
| { | |
| // set when suspend failed and should be retried, see Issue 13416 | |
| shared bool m_suspendagain; | |
| } | |
| // | |
| // Standard thread data | |
| // | |
| version (Windows) | |
| { | |
| HANDLE m_hndl; | |
| } | |
| else version (Darwin) | |
| { | |
| 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) | |
| { | |
| __gshared bool m_isRTClass; | |
| } | |
| private: | |
| /////////////////////////////////////////////////////////////////////////// | |
| // Storage of Active Thread | |
| /////////////////////////////////////////////////////////////////////////// | |
| // | |
| // Sets a thread-local reference to the current thread object. | |
| // | |
| static void setThis( Thread t ) nothrow @nogc | |
| { | |
| sm_this = t; | |
| } | |
| private: | |
| /////////////////////////////////////////////////////////////////////////// | |
| // Thread Context and GC Scanning Support | |
| /////////////////////////////////////////////////////////////////////////// | |
| final void pushContext( Context* c ) nothrow @nogc | |
| in | |
| { | |
| assert( !c.within ); | |
| } | |
| body | |
| { | |
| 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 ); | |
| } | |
| body | |
| { | |
| Context* c = m_curr; | |
| m_curr = c.within; | |
| c.ehContext = swapContext(m_curr.ehContext); | |
| c.within = null; | |
| } | |
| final Context* topContext() nothrow @nogc | |
| in | |
| { | |
| assert( m_curr ); | |
| } | |
| body | |
| { | |
| return m_curr; | |
| } | |
| 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; | |
| version (Windows) | |
| { | |
| version (X86) | |
| { | |
| uint[8] m_reg; // edi,esi,ebp,esp,ebx,edx,ecx,eax | |
| } | |
| else version (X86_64) | |
| { | |
| ulong[16] m_reg; // rdi,rsi,rbp,rsp,rbx,rdx,rcx,rax | |
| // r8,r9,r10,r11,r12,r13,r14,r15 | |
| } | |
| else | |
| { | |
| static assert(false, "Architecture not supported." ); | |
| } | |
| } | |
| else version (Darwin) | |
| { | |
| version (X86) | |
| { | |
| uint[8] m_reg; // edi,esi,ebp,esp,ebx,edx,ecx,eax | |
| } | |
| else version (X86_64) | |
| { | |
| ulong[16] m_reg; // rdi,rsi,rbp,rsp,rbx,rdx,rcx,rax | |
| // r8,r9,r10,r11,r12,r13,r14,r15 | |
| } | |
| else | |
| { | |
| static assert(false, "Architecture not supported." ); | |
| } | |
| } | |
| private: | |
| /////////////////////////////////////////////////////////////////////////// | |
| // 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)_slock.ptr; | |
| } | |
| @property static Mutex criticalRegionLock() nothrow @nogc | |
| { | |
| return cast(Mutex)_criticalRegionLock.ptr; | |
| } | |
| __gshared align(mutexAlign) void[mutexClassInstanceSize] _slock; | |
| __gshared align(mutexAlign) void[mutexClassInstanceSize] _criticalRegionLock; | |
| static void initLocks() | |
| { | |
| _slock[] = typeid(Mutex).initializer[]; | |
| (cast(Mutex)_slock.ptr).__ctor(); | |
| _criticalRegionLock[] = typeid(Mutex).initializer[]; | |
| (cast(Mutex)_criticalRegionLock.ptr).__ctor(); | |
| } | |
| static void termLocks() | |
| { | |
| (cast(Mutex)_slock.ptr).__dtor(); | |
| (cast(Mutex)_criticalRegionLock.ptr).__dtor(); | |
| } | |
| __gshared Context* sm_cbeg; | |
| __gshared Thread sm_tbeg; | |
| __gshared size_t sm_tlen; | |
| // can't use rt.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 ); | |
| } | |
| body | |
| { | |
| 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 ); | |
| } | |
| body | |
| { | |
| 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 ); | |
| } | |
| body | |
| { | |
| 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 ); | |
| } | |
| body | |
| { | |
| // Thread was already removed earlier, might happen b/c of thread_detachInstance | |
| if (!t.next && !t.prev) | |
| 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(); | |
| } | |
| } | |
| /// | |
| unittest | |
| { | |
| class DerivedThread : Thread | |
| { | |
| this() | |
| { | |
| super(&run); | |
| } | |
| private: | |
| void run() | |
| { | |
| // Derived thread running. | |
| } | |
| } | |
| void threadFunc() | |
| { | |
| // Composed thread running. | |
| } | |
| // create and start instances of each type | |
| auto derived = new DerivedThread().start(); | |
| auto composed = new Thread(&threadFunc).start(); | |
| new Thread({ | |
| // Codes to run in the newly created thread. | |
| }).start(); | |
| } | |
| unittest | |
| { | |
| int x = 0; | |
| new Thread( | |
| { | |
| x++; | |
| }).start().join(); | |
| assert( x == 1 ); | |
| } | |
| unittest | |
| { | |
| enum MSG = "Test message."; | |
| string caughtMsg; | |
| try | |
| { | |
| new Thread( | |
| { | |
| throw new Exception( MSG ); | |
| }).start().join(); | |
| assert( false, "Expected rethrown exception." ); | |
| } | |
| catch ( Throwable t ) | |
| { | |
| assert( t.msg == MSG ); | |
| } | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| // GC Support Routines | |
| /////////////////////////////////////////////////////////////////////////////// | |
| version (CoreDdoc) | |
| { | |
| /** | |
| * Instruct the thread module, when initialized, to use a different set of | |
| * signals besides SIGUSR1 and SIGUSR2 for suspension and resumption of threads. | |
| * This function should be called at most once, prior to thread_init(). | |
| * This function is Posix-only. | |
| */ | |
| extern (C) void thread_setGCSignals(int suspendSignalNo, int resumeSignalNo) nothrow @nogc | |
| { | |
| } | |
| } | |
| else version (Posix) | |
| { | |
| extern (C) void thread_setGCSignals(int suspendSignalNo, int resumeSignalNo) nothrow @nogc | |
| in | |
| { | |
| assert(suspendSignalNumber == 0); | |
| assert(resumeSignalNumber == 0); | |
| assert(suspendSignalNo != 0); | |
| assert(resumeSignalNo != 0); | |
| } | |
| out | |
| { | |
| assert(suspendSignalNumber != 0); | |
| assert(resumeSignalNumber != 0); | |
| } | |
| body | |
| { | |
| suspendSignalNumber = suspendSignalNo; | |
| resumeSignalNumber = resumeSignalNo; | |
| } | |
| } | |
| version (Posix) | |
| { | |
| __gshared int suspendSignalNumber; | |
| __gshared int resumeSignalNumber; | |
| } | |
| /** | |
| * Initializes the thread module. This function must be called by the | |
| * garbage collector on startup and before any other thread routines | |
| * are called. | |
| */ | |
| extern (C) void thread_init() | |
| { | |
| // NOTE: If thread_init itself performs any allocations then the thread | |
| // routines reserved for garbage collector use may be called while | |
| // thread_init is being processed. However, since no memory should | |
| // exist to be scanned at this point, it is sufficient for these | |
| // functions to detect the condition and return immediately. | |
| Thread.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 | |
| // thread_resumeHandler does nothing. | |
| version (Android) thread_setGCSignals(SIGUSR2, SIGUSR1); | |
| version (Darwin) | |
| { | |
| } | |
| else version (Posix) | |
| { | |
| if ( suspendSignalNumber == 0 ) | |
| { | |
| suspendSignalNumber = SIGUSR1; | |
| } | |
| if ( resumeSignalNumber == 0 ) | |
| { | |
| resumeSignalNumber = SIGUSR2; | |
| } | |
| int status; | |
| sigaction_t sigusr1 = void; | |
| sigaction_t sigusr2 = void; | |
| // This is a quick way to zero-initialize the structs without using | |
| // memset or creating a link dependency on their static initializer. | |
| (cast(byte*) &sigusr1)[0 .. sigaction_t.sizeof] = 0; | |
| (cast(byte*) &sigusr2)[0 .. sigaction_t.sizeof] = 0; | |
| // NOTE: SA_RESTART indicates that system calls should restart if they | |
| // are interrupted by a signal, but this is not available on all | |
| // Posix systems, even those that support multithreading. | |
| static if ( __traits( compiles, SA_RESTART ) ) | |
| sigusr1.sa_flags = SA_RESTART; | |
| else | |
| sigusr1.sa_flags = 0; | |
| sigusr1.sa_handler = &thread_suspendHandler; | |
| // NOTE: We want to ignore all signals while in this handler, so fill | |
| // sa_mask to indicate this. | |
| status = sigfillset( &sigusr1.sa_mask ); | |
| assert( status == 0 ); | |
| // NOTE: Since resumeSignalNumber should only be issued for threads within the | |
| // suspend handler, we don't want this signal to trigger a | |
| // restart. | |
| sigusr2.sa_flags = 0; | |
| sigusr2.sa_handler = &thread_resumeHandler; | |
| // NOTE: We want to ignore all signals while in this handler, so fill | |
| // sa_mask to indicate this. | |
| status = sigfillset( &sigusr2.sa_mask ); | |
| assert( status == 0 ); | |
| status = sigaction( suspendSignalNumber, &sigusr1, null ); | |
| assert( status == 0 ); | |
| status = sigaction( resumeSignalNumber, &sigusr2, null ); | |
| assert( status == 0 ); | |
| status = sem_init( &suspendCount, 0, 0 ); | |
| assert( status == 0 ); | |
| } | |
| Thread.sm_main = thread_attachThis(); | |
| } | |
| /** | |
| * Terminates the thread module. No other thread routine may be called | |
| * afterwards. | |
| */ | |
| extern (C) void thread_term() | |
| { | |
| assert(Thread.sm_tbeg && Thread.sm_tlen == 1); | |
| assert(!Thread.nAboutToStart); | |
| if (Thread.pAboutToStart) // in case realloc(p, 0) doesn't return null | |
| { | |
| free(Thread.pAboutToStart); | |
| Thread.pAboutToStart = null; | |
| } | |
| Thread.termLocks(); | |
| } | |
| /** | |
| * | |
| */ | |
| extern (C) bool thread_isMainThread() nothrow @nogc | |
| { | |
| return Thread.getThis() is Thread.sm_main; | |
| } | |
| /** | |
| * Registers the calling thread for use with the D Runtime. If this routine | |
| * is called for a thread which is already registered, no action is performed. | |
| * | |
| * NOTE: This routine does not run thread-local static constructors when called. | |
| * If full functionality as a D thread is desired, the following function | |
| * must be called after thread_attachThis: | |
| * | |
| * extern (C) void rt_moduleTlsCtor(); | |
| */ | |
| extern (C) Thread thread_attachThis() | |
| { | |
| GC.disable(); scope(exit) GC.enable(); | |
| if (auto t = Thread.getThis()) | |
| return t; | |
| Thread thisThread = new Thread(); | |
| Thread.Context* thisContext = &thisThread.m_main; | |
| assert( thisContext == thisThread.m_curr ); | |
| version (Windows) | |
| { | |
| thisThread.m_addr = GetCurrentThreadId(); | |
| thisThread.m_hndl = GetCurrentThreadHandle(); | |
| thisContext.bstack = getStackBottom(); | |
| thisContext.tstack = thisContext.bstack; | |
| } | |
| else version (Posix) | |
| { | |
| thisThread.m_addr = pthread_self(); | |
| thisContext.bstack = getStackBottom(); | |
| thisContext.tstack = thisContext.bstack; | |
| atomicStore!(MemoryOrder.raw)(thisThread.m_isRunning, true); | |
| } | |
| thisThread.m_isDaemon = true; | |
| thisThread.m_tlsgcdata = rt_tlsgc_init(); | |
| Thread.setThis( thisThread ); | |
| version (Darwin) | |
| { | |
| thisThread.m_tmach = pthread_mach_thread_np( thisThread.m_addr ); | |
| assert( thisThread.m_tmach != thisThread.m_tmach.init ); | |
| } | |
| Thread.add( thisThread, false ); | |
| Thread.add( thisContext ); | |
| if ( Thread.sm_main !is null ) | |
| multiThreadedFlag = true; | |
| return thisThread; | |
| } | |
| version (Windows) | |
| { | |
| // NOTE: These calls are not safe on Posix systems that use signals to | |
| // perform garbage collection. The suspendHandler uses getThis() | |
| // to get the thread handle so getThis() must be a simple call. | |
| // Mutexes can't safely be acquired inside signal handlers, and | |
| // even if they could, the mutex needed (Thread.slock) is held by | |
| // thread_suspendAll(). So in short, these routines will remain | |
| // Windows-specific. If they are truly needed elsewhere, the | |
| // suspendHandler will need a way to call a version of getThis() | |
| // that only does the TLS lookup without the fancy fallback stuff. | |
| /// ditto | |
| extern (C) Thread thread_attachByAddr( ThreadID addr ) | |
| { | |
| return thread_attachByAddrB( addr, getThreadStackBottom( addr ) ); | |
| } | |
| /// ditto | |
| extern (C) Thread thread_attachByAddrB( ThreadID addr, void* bstack ) | |
| { | |
| GC.disable(); scope(exit) GC.enable(); | |
| if (auto t = thread_findByAddr(addr)) | |
| return t; | |
| Thread thisThread = new Thread(); | |
| Thread.Context* thisContext = &thisThread.m_main; | |
| assert( thisContext == thisThread.m_curr ); | |
| thisThread.m_addr = addr; | |
| thisContext.bstack = bstack; | |
| thisContext.tstack = thisContext.bstack; | |
| thisThread.m_isDaemon = true; | |
| if ( addr == GetCurrentThreadId() ) | |
| { | |
| thisThread.m_hndl = GetCurrentThreadHandle(); | |
| thisThread.m_tlsgcdata = rt_tlsgc_init(); | |
| Thread.setThis( thisThread ); | |
| } | |
| else | |
| { | |
| thisThread.m_hndl = OpenThreadHandle( addr ); | |
| impersonate_thread(addr, | |
| { | |
| thisThread.m_tlsgcdata = rt_tlsgc_init(); | |
| Thread.setThis( thisThread ); | |
| }); | |
| } | |
| Thread.add( thisThread, false ); | |
| Thread.add( thisContext ); | |
| if ( Thread.sm_main !is null ) | |
| multiThreadedFlag = true; | |
| return thisThread; | |
| } | |
| } | |
| /** | |
| * Deregisters the calling thread from use with the runtime. If this routine | |
| * is called for a thread which is not registered, the result is undefined. | |
| * | |
| * NOTE: This routine does not run thread-local static destructors when called. | |
| * If full functionality as a D thread is desired, the following function | |
| * must be called after thread_detachThis, particularly if the thread is | |
| * being detached at some indeterminate time before program termination: | |
| * | |
| * $(D extern(C) void rt_moduleTlsDtor();) | |
| */ | |
| extern (C) void thread_detachThis() nothrow @nogc | |
| { | |
| if (auto t = Thread.getThis()) | |
| Thread.remove(t); | |
| } | |
| /** | |
| * Deregisters the given thread from use with the runtime. If this routine | |
| * is called for a thread which is not registered, the result is undefined. | |
| * | |
| * NOTE: This routine does not run thread-local static destructors when called. | |
| * If full functionality as a D thread is desired, the following function | |
| * must be called by the detached thread, particularly if the thread is | |
| * being detached at some indeterminate time before program termination: | |
| * | |
| * $(D extern(C) void rt_moduleTlsDtor();) | |
| */ | |
| extern (C) void thread_detachByAddr( ThreadID addr ) | |
| { | |
| if ( auto t = thread_findByAddr( addr ) ) | |
| Thread.remove( t ); | |
| } | |
| /// ditto | |
| extern (C) void thread_detachInstance( Thread t ) nothrow @nogc | |
| { | |
| Thread.remove( t ); | |
| } | |
| unittest | |
| { | |
| import core.sync.semaphore; | |
| auto sem = new Semaphore(); | |
| auto t = new Thread( | |
| { | |
| sem.notify(); | |
| Thread.sleep(100.msecs); | |
| }).start(); | |
| sem.wait(); // thread cannot be detached while being started | |
| thread_detachInstance(t); | |
| foreach (t2; Thread) | |
| assert(t !is t2); | |
| t.join(); | |
| } | |
| /** | |
| * Search the list of all threads for a thread with the given thread identifier. | |
| * | |
| * Params: | |
| * addr = The thread identifier to search for. | |
| * Returns: | |
| * The thread object associated with the thread identifier, null if not found. | |
| */ | |
| static Thread thread_findByAddr( ThreadID addr ) | |
| { | |
| Thread.slock.lock_nothrow(); | |
| scope(exit) Thread.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]) | |
| if (t.m_addr == addr) | |
| return t; | |
| foreach (t; Thread) | |
| if (t.m_addr == addr) | |
| return t; | |
| return null; | |
| } | |
| /** | |
| * Sets the current thread to a specific reference. Only to be used | |
| * when dealing with externally-created threads (in e.g. C code). | |
| * The primary use of this function is when Thread.getThis() must | |
| * return a sensible value in, for example, TLS destructors. In | |
| * other words, don't touch this unless you know what you're doing. | |
| * | |
| * Params: | |
| * t = A reference to the current thread. May be null. | |
| */ | |
| extern (C) void thread_setThis(Thread t) nothrow @nogc | |
| { | |
| Thread.setThis(t); | |
| } | |
| /** | |
| * Joins all non-daemon threads that are currently running. This is done by | |
| * performing successive scans through the thread list until a scan consists | |
| * of only daemon threads. | |
| */ | |
| extern (C) void thread_joinAll() | |
| { | |
| Lagain: | |
| Thread.slock.lock_nothrow(); | |
| // wait for just spawned threads | |
| if (Thread.nAboutToStart) | |
| { | |
| Thread.slock.unlock_nothrow(); | |
| Thread.yield(); | |
| goto Lagain; | |
| } | |
| // join all non-daemon threads, the main thread is also a daemon | |
| auto t = Thread.sm_tbeg; | |
| while (t) | |
| { | |
| if (!t.isRunning) | |
| { | |
| auto tn = t.next; | |
| Thread.remove(t); | |
| t = tn; | |
| } | |
| else if (t.isDaemon) | |
| { | |
| t = t.next; | |
| } | |
| else | |
| { | |
| Thread.slock.unlock_nothrow(); | |
| t.join(); // might rethrow | |
| goto Lagain; // must restart iteration b/c of unlock | |
| } | |
| } | |
| Thread.slock.unlock_nothrow(); | |
| } | |
| /** | |
| * Performs intermediate shutdown of the thread module. | |
| */ | |
| 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; | |
| while (t) | |
| { | |
| auto tn = t.next; | |
| if (!t.isRunning) | |
| Thread.remove(t); | |
| t = tn; | |
| } | |
| } | |
| // Used for needLock below. | |
| private __gshared bool multiThreadedFlag = false; | |
| version (PPC64) version = ExternStackShell; | |
| version (ExternStackShell) | |
| { | |
| extern(D) public void callWithStackShell(scope void delegate(void* sp) nothrow fn) nothrow; | |
| } | |
| else | |
| { | |
| // Calls the given delegate, passing the current thread's stack pointer to it. | |
| private void callWithStackShell(scope void delegate(void* sp) nothrow fn) nothrow | |
| in | |
| { | |
| assert(fn); | |
| } | |
| body | |
| { | |
| // The purpose of the 'shell' is to ensure all the registers get | |
| // put on the stack so they'll be scanned. We only need to push | |
| // the callee-save registers. | |
| void *sp = void; | |
| version (GNU) | |
| { | |
| __builtin_unwind_init(); | |
| sp = &sp; | |
| } | |
| else version (AsmX86_Posix) | |
| { | |
| size_t[3] regs = void; | |
| asm pure nothrow @nogc | |
| { | |
| mov [regs + 0 * 4], EBX; | |
| mov [regs + 1 * 4], ESI; | |
| mov [regs + 2 * 4], EDI; | |
| mov sp[EBP], ESP; | |
| } | |
| } | |
| else version (AsmX86_Windows) | |
| { | |
| size_t[3] regs = void; | |
| asm pure nothrow @nogc | |
| { | |
| mov [regs + 0 * 4], EBX; | |
| mov [regs + 1 * 4], ESI; | |
| mov [regs + 2 * 4], EDI; | |
| mov sp[EBP], ESP; | |
| } | |
| } | |
| else version (AsmX86_64_Posix) | |
| { | |
| size_t[5] regs = void; | |
| asm pure nothrow @nogc | |
| { | |
| mov [regs + 0 * 8], RBX; | |
| mov [regs + 1 * 8], R12; | |
| mov [regs + 2 * 8], R13; | |
| mov [regs + 3 * 8], R14; | |
| mov [regs + 4 * 8], R15; | |
| mov sp[RBP], RSP; | |
| } | |
| } | |
| else version (AsmX86_64_Windows) | |
| { | |
| size_t[7] regs = void; | |
| asm pure nothrow @nogc | |
| { | |
| mov [regs + 0 * 8], RBX; | |
| mov [regs + 1 * 8], RSI; | |
| mov [regs + 2 * 8], RDI; | |
| mov [regs + 3 * 8], R12; | |
| mov [regs + 4 * 8], R13; | |
| mov [regs + 5 * 8], R14; | |
| mov [regs + 6 * 8], R15; | |
| mov sp[RBP], RSP; | |
| } | |
| } | |
| else | |
| { | |
| static assert(false, "Architecture not supported."); | |
| } | |
| fn(sp); | |
| } | |
| } | |
| // 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, | |
| * stack and register information will be loaded but the thread will not | |
| * be suspended. If the suspend operation fails and the thread is not | |
| * running then it will be removed from the global thread list, otherwise | |
| * an exception will be thrown. | |
| * | |
| * Params: | |
| * t = The thread to suspend. | |
| * | |
| * Throws: | |
| * ThreadError if the suspend operation fails for a running thread. | |
| * Returns: | |
| * Whether the thread is now suspended (true) or terminated (false). | |
| */ | |
| private bool suspend( Thread t ) nothrow | |
| { | |
| Duration waittime = dur!"usecs"(10); | |
| Lagain: | |
| if (!t.isRunning) | |
| { | |
| Thread.remove(t); | |
| return false; | |
| } | |
| else if (t.m_isInCriticalRegion) | |
| { | |
| Thread.criticalRegionLock.unlock_nothrow(); | |
| Thread.sleep(waittime); | |
| if (waittime < dur!"msecs"(10)) waittime *= 2; | |
| Thread.criticalRegionLock.lock_nothrow(); | |
| goto Lagain; | |
| } | |
| version (Windows) | |
| { | |
| if ( t.m_addr != GetCurrentThreadId() && SuspendThread( t.m_hndl ) == 0xFFFFFFFF ) | |
| { | |
| if ( !t.isRunning ) | |
| { | |
| Thread.remove( t ); | |
| return false; | |
| } | |
| onThreadError( "Unable to suspend thread" ); | |
| } | |
| CONTEXT context = void; | |
| context.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL; | |
| if ( !GetThreadContext( t.m_hndl, &context ) ) | |
| onThreadError( "Unable to load thread context" ); | |
| version (X86) | |
| { | |
| if ( !t.m_lock ) | |
| t.m_curr.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; | |
| t.m_reg[2] = context.Ecx; | |
| t.m_reg[3] = context.Edx; | |
| t.m_reg[4] = context.Edi; | |
| t.m_reg[5] = context.Esi; | |
| t.m_reg[6] = context.Ebp; | |
| t.m_reg[7] = context.Esp; | |
| } | |
| else version (X86_64) | |
| { | |
| if ( !t.m_lock ) | |
| t.m_curr.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; | |
| t.m_reg[2] = context.Rcx; | |
| t.m_reg[3] = context.Rdx; | |
| t.m_reg[4] = context.Rdi; | |
| t.m_reg[5] = context.Rsi; | |
| t.m_reg[6] = context.Rbp; | |
| t.m_reg[7] = context.Rsp; | |
| // r8,r9,r10,r11,r12,r13,r14,r15 | |
| t.m_reg[8] = context.R8; | |
| t.m_reg[9] = context.R9; | |
| t.m_reg[10] = context.R10; | |
| t.m_reg[11] = context.R11; | |
| t.m_reg[12] = context.R12; | |
| t.m_reg[13] = context.R13; | |
| t.m_reg[14] = context.R14; | |
| t.m_reg[15] = context.R15; | |
| } | |
| else | |
| { | |
| static assert(false, "Architecture not supported." ); | |
| } | |
| } | |
| else version (Darwin) | |
| { | |
| if ( t.m_addr != pthread_self() && thread_suspend( t.m_tmach ) != KERN_SUCCESS ) | |
| { | |
| if ( !t.isRunning ) | |
| { | |
| Thread.remove( t ); | |
| return false; | |
| } | |
| onThreadError( "Unable to suspend thread" ); | |
| } | |
| version (X86) | |
| { | |
| x86_thread_state32_t state = void; | |
| mach_msg_type_number_t count = x86_THREAD_STATE32_COUNT; | |
| 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; | |
| // eax,ebx,ecx,edx,edi,esi,ebp,esp | |
| t.m_reg[0] = state.eax; | |
| t.m_reg[1] = state.ebx; | |
| t.m_reg[2] = state.ecx; | |
| t.m_reg[3] = state.edx; | |
| t.m_reg[4] = state.edi; | |
| t.m_reg[5] = state.esi; | |
| t.m_reg[6] = state.ebp; | |
| t.m_reg[7] = state.esp; | |
| } | |
| else version (X86_64) | |
| { | |
| x86_thread_state64_t state = void; | |
| mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT; | |
| 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; | |
| // rax,rbx,rcx,rdx,rdi,rsi,rbp,rsp | |
| t.m_reg[0] = state.rax; | |
| t.m_reg[1] = state.rbx; | |
| t.m_reg[2] = state.rcx; | |
| t.m_reg[3] = state.rdx; | |
| t.m_reg[4] = state.rdi; | |
| t.m_reg[5] = state.rsi; | |
| t.m_reg[6] = state.rbp; | |
| t.m_reg[7] = state.rsp; | |
| // r8,r9,r10,r11,r12,r13,r14,r15 | |
| t.m_reg[8] = state.r8; | |
| t.m_reg[9] = state.r9; | |
| t.m_reg[10] = state.r10; | |
| t.m_reg[11] = state.r11; | |
| t.m_reg[12] = state.r12; | |
| t.m_reg[13] = state.r13; | |
| t.m_reg[14] = state.r14; | |
| t.m_reg[15] = state.r15; | |
| } | |
| else | |
| { | |
| static assert(false, "Architecture not supported." ); | |
| } | |
| } | |
| else version (Posix) | |
| { | |
| if ( t.m_addr != pthread_self() ) | |
| { | |
| if ( pthread_kill( t.m_addr, suspendSignalNumber ) != 0 ) | |
| { | |
| if ( !t.isRunning ) | |
| { | |
| Thread.remove( t ); | |
| return false; | |
| } | |
| onThreadError( "Unable to suspend thread" ); | |
| } | |
| } | |
| else if ( !t.m_lock ) | |
| { | |
| t.m_curr.tstack = getStackTop(); | |
| } | |
| } | |
| return true; | |
| } | |
| /** | |
| * Suspend all threads but the calling thread for "stop the world" garbage | |
| * collection runs. This function may be called multiple times, and must | |
| * be followed by a matching number of calls to thread_resumeAll before | |
| * processing is resumed. | |
| * | |
| * Throws: | |
| * ThreadError if the suspend operation fails for a running thread. | |
| */ | |
| extern (C) void thread_suspendAll() nothrow | |
| { | |
| // NOTE: We've got an odd chicken & egg problem here, because while the GC | |
| // is required to call thread_init before calling any other thread | |
| // routines, thread_init may allocate memory which could in turn | |
| // trigger a collection. Thus, thread_suspendAll, thread_scanAll, | |
| // and thread_resumeAll must be callable before thread_init | |
| // 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, | |
| // and therefore that calling Thread.getThis will not result in an | |
| // error. For the short time when Thread.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 ( ++suspendDepth == 1 ) | |
| suspend( Thread.getThis() ); | |
| return; | |
| } | |
| Thread.slock.lock_nothrow(); | |
| { | |
| if ( ++suspendDepth > 1 ) | |
| return; | |
| Thread.criticalRegionLock.lock_nothrow(); | |
| scope (exit) Thread.criticalRegionLock.unlock_nothrow(); | |
| size_t cnt; | |
| auto t = Thread.sm_tbeg; | |
| while (t) | |
| { | |
| auto tn = t.next; | |
| if (suspend(t)) | |
| ++cnt; | |
| t = tn; | |
| } | |
| version (Darwin) | |
| {} | |
| else version (Posix) | |
| { | |
| // subtract own thread | |
| assert(cnt >= 1); | |
| --cnt; | |
| Lagain: | |
| // wait for semaphore notifications | |
| for (; cnt; --cnt) | |
| { | |
| while (sem_wait(&suspendCount) != 0) | |
| { | |
| if (errno != EINTR) | |
| onThreadError("Unable to wait for semaphore"); | |
| errno = 0; | |
| } | |
| } | |
| version (FreeBSD) | |
| { | |
| // avoid deadlocks, see Issue 13416 | |
| t = Thread.sm_tbeg; | |
| while (t) | |
| { | |
| auto tn = t.next; | |
| if (t.m_suspendagain && suspend(t)) | |
| ++cnt; | |
| t = tn; | |
| } | |
| if (cnt) | |
| goto Lagain; | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * Resume the specified thread and unload stack and register information. | |
| * If the supplied thread is the calling thread, stack and register | |
| * information will be unloaded but the thread will not be resumed. If | |
| * the resume operation fails and the thread is not running then it will | |
| * be removed from the global thread list, otherwise an exception will be | |
| * thrown. | |
| * | |
| * Params: | |
| * t = The thread to resume. | |
| * | |
| * Throws: | |
| * ThreadError if the resume fails for a running thread. | |
| */ | |
| private void resume( Thread t ) nothrow | |
| { | |
| version (Windows) | |
| { | |
| if ( t.m_addr != GetCurrentThreadId() && ResumeThread( t.m_hndl ) == 0xFFFFFFFF ) | |
| { | |
| if ( !t.isRunning ) | |
| { | |
| Thread.remove( t ); | |
| return; | |
| } | |
| onThreadError( "Unable to resume thread" ); | |
| } | |
| if ( !t.m_lock ) | |
| t.m_curr.tstack = t.m_curr.bstack; | |
| t.m_reg[0 .. $] = 0; | |
| } | |
| else version (Darwin) | |
| { | |
| if ( t.m_addr != pthread_self() && thread_resume( t.m_tmach ) != KERN_SUCCESS ) | |
| { | |
| if ( !t.isRunning ) | |
| { | |
| Thread.remove( t ); | |
| return; | |
| } | |
| onThreadError( "Unable to resume thread" ); | |
| } | |
| if ( !t.m_lock ) | |
| t.m_curr.tstack = t.m_curr.bstack; | |
| t.m_reg[0 .. $] = 0; | |
| } | |
| else version (Posix) | |
| { | |
| if ( t.m_addr != pthread_self() ) | |
| { | |
| if ( pthread_kill( t.m_addr, resumeSignalNumber ) != 0 ) | |
| { | |
| if ( !t.isRunning ) | |
| { | |
| Thread.remove( t ); | |
| return; | |
| } | |
| onThreadError( "Unable to resume thread" ); | |
| } | |
| } | |
| else if ( !t.m_lock ) | |
| { | |
| t.m_curr.tstack = t.m_curr.bstack; | |
| } | |
| } | |
| } | |
| /** | |
| * Resume all threads but the calling thread for "stop the world" garbage | |
| * collection runs. This function must be called once for each preceding | |
| * call to thread_suspendAll before the threads are actually resumed. | |
| * | |
| * In: | |
| * This routine must be preceded by a call to thread_suspendAll. | |
| * | |
| * Throws: | |
| * ThreadError if the resume operation fails for a running thread. | |
| */ | |
| extern (C) void thread_resumeAll() nothrow | |
| in | |
| { | |
| assert( suspendDepth > 0 ); | |
| } | |
| body | |
| { | |
| // NOTE: See thread_suspendAll for the logic behind this. | |
| if ( !multiThreadedFlag && Thread.sm_tbeg ) | |
| { | |
| if ( --suspendDepth == 0 ) | |
| resume( Thread.getThis() ); | |
| return; | |
| } | |
| scope(exit) Thread.slock.unlock_nothrow(); | |
| { | |
| if ( --suspendDepth > 0 ) | |
| return; | |
| for ( Thread t = Thread.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. | |
| resume( t ); | |
| } | |
| } | |
| } | |
| /** | |
| * Indicates the kind of scan being performed by $(D thread_scanAllType). | |
| */ | |
| enum ScanType | |
| { | |
| stack, /// The stack and/or registers are being scanned. | |
| tls, /// TLS data is being scanned. | |
| } | |
| alias ScanAllThreadsFn = void delegate(void*, void*) nothrow; /// The scanning function. | |
| alias ScanAllThreadsTypeFn = void delegate(ScanType, void*, void*) nothrow; /// ditto | |
| /** | |
| * The main entry point for garbage collection. The supplied delegate | |
| * will be passed ranges representing both stack and register values. | |
| * | |
| * Params: | |
| * scan = The scanner function. It should scan from p1 through p2 - 1. | |
| * | |
| * In: | |
| * This routine must be preceded by a call to thread_suspendAll. | |
| */ | |
| extern (C) void thread_scanAllType( scope ScanAllThreadsTypeFn scan ) nothrow | |
| in | |
| { | |
| assert( suspendDepth > 0 ); | |
| } | |
| body | |
| { | |
| callWithStackShell(sp => scanAllTypeImpl(scan, sp)); | |
| } | |
| private void scanAllTypeImpl( scope ScanAllThreadsTypeFn scan, void* curStackTop ) nothrow | |
| { | |
| Thread thisThread = null; | |
| void* oldStackTop = null; | |
| if ( Thread.sm_tbeg ) | |
| { | |
| thisThread = Thread.getThis(); | |
| if ( !thisThread.m_lock ) | |
| { | |
| oldStackTop = thisThread.m_curr.tstack; | |
| thisThread.m_curr.tstack = curStackTop; | |
| } | |
| } | |
| scope( exit ) | |
| { | |
| if ( Thread.sm_tbeg ) | |
| { | |
| if ( !thisThread.m_lock ) | |
| { | |
| thisThread.m_curr.tstack = oldStackTop; | |
| } | |
| } | |
| } | |
| // NOTE: Synchronizing on Thread.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); | |
| for ( Thread.Context* c = Thread.sm_cbeg; c; c = c.next ) | |
| { | |
| version (StackGrowsDown) | |
| { | |
| // NOTE: We can't index past the bottom of the stack | |
| // so don't do the "+1" for StackGrowsDown. | |
| if ( c.tstack && c.tstack < c.bstack ) | |
| scan( ScanType.stack, c.tstack, c.bstack ); | |
| } | |
| else | |
| { | |
| if ( c.bstack && c.bstack < c.tstack ) | |
| scan( ScanType.stack, c.bstack, c.tstack + 1 ); | |
| } | |
| } | |
| for ( Thread t = Thread.sm_tbeg; t; t = t.next ) | |
| { | |
| version (Windows) | |
| { | |
| // Ideally, we'd pass ScanType.regs or something like that, but this | |
| // would make portability annoying because it only makes sense on Windows. | |
| scan( ScanType.stack, t.m_reg.ptr, t.m_reg.ptr + t.m_reg.length ); | |
| } | |
| if (t.m_tlsgcdata !is null) | |
| rt_tlsgc_scan(t.m_tlsgcdata, (p1, p2) => scan(ScanType.tls, p1, p2)); | |
| } | |
| } | |
| /** | |
| * The main entry point for garbage collection. The supplied delegate | |
| * will be passed ranges representing both stack and register values. | |
| * | |
| * Params: | |
| * scan = The scanner function. It should scan from p1 through p2 - 1. | |
| * | |
| * In: | |
| * This routine must be preceded by a call to thread_suspendAll. | |
| */ | |
| extern (C) void thread_scanAll( scope ScanAllThreadsFn scan ) nothrow | |
| { | |
| thread_scanAllType((type, p1, p2) => scan(p1, p2)); | |
| } | |
| /** | |
| * Signals that the code following this call is a critical region. Any code in | |
| * this region must finish running before the calling thread can be suspended | |
| * by a call to thread_suspendAll. | |
| * | |
| * This function is, in particular, meant to help maintain garbage collector | |
| * invariants when a lock is not used. | |
| * | |
| * A critical region is exited with thread_exitCriticalRegion. | |
| * | |
| * $(RED Warning): | |
| * Using critical regions is extremely error-prone. For instance, using locks | |
| * inside a critical region can easily result in a deadlock when another thread | |
| * holding the lock already got suspended. | |
| * | |
| * The term and concept of a 'critical region' comes from | |
| * $(LINK2 https://github.com/mono/mono/blob/521f4a198e442573c400835ef19bbb36b60b0ebb/mono/metadata/sgen-gc.h#L925 Mono's SGen garbage collector). | |
| * | |
| * In: | |
| * The calling thread must be attached to the runtime. | |
| */ | |
| extern (C) void thread_enterCriticalRegion() @nogc | |
| in | |
| { | |
| assert(Thread.getThis()); | |
| } | |
| body | |
| { | |
| synchronized (Thread.criticalRegionLock) | |
| Thread.getThis().m_isInCriticalRegion = true; | |
| } | |
| /** | |
| * Signals that the calling thread is no longer in a critical region. Following | |
| * a call to this function, the thread can once again be suspended. | |
| * | |
| * In: | |
| * The calling thread must be attached to the runtime. | |
| */ | |
| extern (C) void thread_exitCriticalRegion() @nogc | |
| in | |
| { | |
| assert(Thread.getThis()); | |
| } | |
| body | |
| { | |
| synchronized (Thread.criticalRegionLock) | |
| Thread.getThis().m_isInCriticalRegion = false; | |
| } | |
| /** | |
| * Returns true if the current thread is in a critical region; otherwise, false. | |
| * | |
| * In: | |
| * The calling thread must be attached to the runtime. | |
| */ | |
| extern (C) bool thread_inCriticalRegion() @nogc | |
| in | |
| { | |
| assert(Thread.getThis()); | |
| } | |
| body | |
| { | |
| synchronized (Thread.criticalRegionLock) | |
| return Thread.getThis().m_isInCriticalRegion; | |
| } | |
| /** | |
| * A callback for thread errors in D during collections. Since an allocation is not possible | |
| * a preallocated ThreadError will be used as the Error instance | |
| * | |
| * Throws: | |
| * ThreadError. | |
| */ | |
| private void onThreadError(string msg = null, Throwable next = null) nothrow | |
| { | |
| __gshared ThreadError error = new ThreadError(null); | |
| error.msg = msg; | |
| error.next = next; | |
| import core.exception : SuppressTraceInfo; | |
| error.info = SuppressTraceInfo.instance; | |
| throw error; | |
| } | |
| unittest | |
| { | |
| assert(!thread_inCriticalRegion()); | |
| { | |
| thread_enterCriticalRegion(); | |
| scope (exit) | |
| thread_exitCriticalRegion(); | |
| assert(thread_inCriticalRegion()); | |
| } | |
| assert(!thread_inCriticalRegion()); | |
| } | |
| unittest | |
| { | |
| // NOTE: This entire test is based on the assumption that no | |
| // memory is allocated after the child thread is | |
| // started. If an allocation happens, a collection could | |
| // trigger, which would cause the synchronization below | |
| // to cause a deadlock. | |
| // NOTE: DO NOT USE LOCKS IN CRITICAL REGIONS IN NORMAL CODE. | |
| import core.sync.semaphore; | |
| auto sema = new Semaphore(), | |
| semb = new Semaphore(); | |
| auto thr = new Thread( | |
| { | |
| thread_enterCriticalRegion(); | |
| assert(thread_inCriticalRegion()); | |
| sema.notify(); | |
| semb.wait(); | |
| assert(thread_inCriticalRegion()); | |
| thread_exitCriticalRegion(); | |
| assert(!thread_inCriticalRegion()); | |
| sema.notify(); | |
| semb.wait(); | |
| assert(!thread_inCriticalRegion()); | |
| }); | |
| thr.start(); | |
| sema.wait(); | |
| synchronized (Thread.criticalRegionLock) | |
| assert(thr.m_isInCriticalRegion); | |
| semb.notify(); | |
| sema.wait(); | |
| synchronized (Thread.criticalRegionLock) | |
| assert(!thr.m_isInCriticalRegion); | |
| semb.notify(); | |
| thr.join(); | |
| } | |
| unittest | |
| { | |
| import core.sync.semaphore; | |
| shared bool inCriticalRegion; | |
| auto sema = new Semaphore(), | |
| semb = new Semaphore(); | |
| auto thr = new Thread( | |
| { | |
| thread_enterCriticalRegion(); | |
| inCriticalRegion = true; | |
| sema.notify(); | |
| semb.wait(); | |
| Thread.sleep(dur!"msecs"(1)); | |
| inCriticalRegion = false; | |
| thread_exitCriticalRegion(); | |
| }); | |
| thr.start(); | |
| sema.wait(); | |
| assert(inCriticalRegion); | |
| semb.notify(); | |
| thread_suspendAll(); | |
| assert(!inCriticalRegion); | |
| thread_resumeAll(); | |
| } | |
| /** | |
| * Indicates whether an address has been marked by the GC. | |
| */ | |
| enum IsMarked : int | |
| { | |
| no, /// Address is not marked. | |
| yes, /// Address is marked. | |
| unknown, /// Address is not managed by the GC. | |
| } | |
| alias IsMarkedDg = int delegate( void* addr ) nothrow; /// The isMarked callback function. | |
| /** | |
| * This routine allows the runtime to process any special per-thread handling | |
| * for the GC. This is needed for taking into account any memory that is | |
| * referenced by non-scanned pointers but is about to be freed. That currently | |
| * means the array append cache. | |
| * | |
| * Params: | |
| * isMarked = The function used to check if $(D addr) is marked. | |
| * | |
| * In: | |
| * This routine must be called just prior to resuming all threads. | |
| */ | |
| extern(C) void thread_processGCMarks( scope IsMarkedDg isMarked ) nothrow | |
| { | |
| for ( Thread t = Thread.sm_tbeg; t; t = t.next ) | |
| { | |
| /* Can be null if collection was triggered between adding a | |
| * thread and calling rt_tlsgc_init. | |
| */ | |
| if (t.m_tlsgcdata !is null) | |
| rt_tlsgc_processGCMarks(t.m_tlsgcdata, isMarked); | |
| } | |
| } | |
| 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); | |
| } | |
| private 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."); | |
| } | |
| private 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 version (GNU_InlineAsm) | |
| { | |
| void *bottom; | |
| version (X86) | |
| asm pure nothrow @nogc { "movl %%fs:4, %0;" : "=r" bottom; } | |
| else version (X86_64) | |
| asm pure nothrow @nogc { "movq %%gs:8, %0;" : "=r" bottom; } | |
| else | |
| static assert(false, "Platform not supported."); | |
| return bottom; | |
| } | |
| 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. | |
| * | |
| * In: | |
| * The calling thread must be attached to the runtime. | |
| * | |
| * Returns: | |
| * The address of the stack top. | |
| */ | |
| extern (C) void* thread_stackTop() nothrow @nogc | |
| in | |
| { | |
| // Not strictly required, but it gives us more flexibility. | |
| assert(Thread.getThis()); | |
| } | |
| body | |
| { | |
| return getStackTop(); | |
| } | |
| /** | |
| * Returns the stack bottom of the currently active stack within the calling | |
| * thread. | |
| * | |
| * In: | |
| * The calling thread must be attached to the runtime. | |
| * | |
| * Returns: | |
| * The address of the stack bottom. | |
| */ | |
| extern (C) void* thread_stackBottom() nothrow @nogc | |
| in | |
| { | |
| assert(Thread.getThis()); | |
| } | |
| body | |
| { | |
| return Thread.getThis().topContext().bstack; | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| // Thread Group | |
| /////////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * This class is intended to simplify certain common programming techniques. | |
| */ | |
| class ThreadGroup | |
| { | |
| /** | |
| * Creates and starts a new Thread object that executes fn and adds it to | |
| * the list of tracked threads. | |
| * | |
| * Params: | |
| * fn = The thread function. | |
| * | |
| * Returns: | |
| * A reference to the newly created thread. | |
| */ | |
| final Thread create( void function() fn ) | |
| { | |
| Thread t = new Thread( fn ).start(); | |
| synchronized( this ) | |
| { | |
| m_all[t] = t; | |
| } | |
| return t; | |
| } | |
| /** | |
| * Creates and starts a new Thread object that executes dg and adds it to | |
| * the list of tracked threads. | |
| * | |
| * Params: | |
| * dg = The thread function. | |
| * | |
| * Returns: | |
| * A reference to the newly created thread. | |
| */ | |
| final Thread create( void delegate() dg ) | |
| { | |
| Thread t = new Thread( dg ).start(); | |
| synchronized( this ) | |
| { | |
| m_all[t] = t; | |
| } | |
| return t; | |
| } | |
| /** | |
| * Add t to the list of tracked threads if it is not already being tracked. | |
| * | |
| * Params: | |
| * t = The thread to add. | |
| * | |
| * In: | |
| * t must not be null. | |
| */ | |
| final void add( Thread t ) | |
| in | |
| { | |
| assert( t ); | |
| } | |
| body | |
| { | |
| synchronized( this ) | |
| { | |
| m_all[t] = t; | |
| } | |
| } | |
| /** | |
| * Removes t from the list of tracked threads. No operation will be | |
| * performed if t is not currently being tracked by this object. | |
| * | |
| * Params: | |
| * t = The thread to remove. | |
| * | |
| * In: | |
| * t must not be null. | |
| */ | |
| final void remove( Thread t ) | |
| in | |
| { | |
| assert( t ); | |
| } | |
| body | |
| { | |
| synchronized( this ) | |
| { | |
| m_all.remove( t ); | |
| } | |
| } | |
| /** | |
| * Operates on all threads currently tracked by this object. | |
| */ | |
| final int opApply( scope int delegate( ref Thread ) dg ) | |
| { | |
| synchronized( this ) | |
| { | |
| int ret = 0; | |
| // 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 ) | |
| { | |
| ret = dg( t ); | |
| if ( ret ) | |
| break; | |
| } | |
| return ret; | |
| } | |
| } | |
| /** | |
| * Iteratively joins all tracked threads. This function will block add, | |
| * remove, and opApply until it completes. | |
| * | |
| * Params: | |
| * rethrow = Rethrow any unhandled exception which may have caused the | |
| * current thread to terminate. | |
| * | |
| * Throws: | |
| * Any exception not handled by the joined threads. | |
| */ | |
| final void joinAll( bool rethrow = true ) | |
| { | |
| 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 ) | |
| { | |
| t.join( rethrow ); | |
| } | |
| } | |
| } | |
| private: | |
| Thread[Thread] m_all; | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| // Fiber Platform Detection and Memory Allocation | |
| /////////////////////////////////////////////////////////////////////////////// | |
| private | |
| { | |
| version (D_InlineAsm_X86) | |
| { | |
| version (Windows) | |
| version = AsmX86_Windows; | |
| else version (Posix) | |
| version = AsmX86_Posix; | |
| version (Darwin) | |
| 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 = AsmExternal; | |
| version (MinGW) | |
| { | |
| version = GNU_AsmX86_Windows; | |
| version = AlignFiberStackTo16Byte; | |
| } | |
| else version (Posix) | |
| { | |
| version = AsmX86_Posix; | |
| version (OSX) | |
| version = AlignFiberStackTo16Byte; | |
| } | |
| } | |
| else version (X86_64) | |
| { | |
| version (D_X32) | |
| { | |
| // let X32 be handled by ucontext swapcontext | |
| } | |
| else | |
| { | |
| version = AsmExternal; | |
| version = AlignFiberStackTo16Byte; | |
| version (MinGW) | |
| version = GNU_AsmX86_64_Windows; | |
| else version (Posix) | |
| version = AsmX86_64_Posix; | |
| } | |
| } | |
| else version (PPC) | |
| { | |
| version (Posix) | |
| { | |
| version = AsmPPC_Posix; | |
| version = AsmExternal; | |
| } | |
| } | |
| else version (PPC64) | |
| { | |
| version (Posix) | |
| { | |
| version = AlignFiberStackTo16Byte; | |
| } | |
| } | |
| else version (MIPS_O32) | |
| { | |
| version (Posix) | |
| { | |
| version = AsmMIPS_O32_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; | |
| } | |
| version (Posix) | |
| { | |
| import core.sys.posix.unistd; // for sysconf | |
| 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; | |
| } | |
| } | |
| static immutable size_t PAGESIZE; | |
| version (Posix) static immutable size_t PTHREAD_STACK_MIN; | |
| } | |
| shared static this() | |
| { | |
| version (Windows) | |
| { | |
| SYSTEM_INFO info; | |
| GetSystemInfo(&info); | |
| PAGESIZE = info.dwPageSize; | |
| assert(PAGESIZE < int.max); | |
| } | |
| else version (Posix) | |
| { | |
| PAGESIZE = cast(size_t)sysconf(_SC_PAGESIZE); | |
| PTHREAD_STACK_MIN = cast(size_t)sysconf(_SC_THREAD_STACK_MIN); | |
| } | |
| else | |
| { | |
| static assert(0, "unimplemented"); | |
| } | |
| } | |
| /////////////////////////////////////////////////////////////////////////////// | |
| // Fiber Entry Point and Context Switch | |
| /////////////////////////////////////////////////////////////////////////////// | |
| private | |
| { | |
| extern (C) void fiber_entryPoint() nothrow | |
| { | |
| Fiber obj = Fiber.getThis(); | |
| assert( obj ); | |
| assert( Thread.getThis().m_curr 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; | |
| 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(); | |
| } | |
| // 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 | |
| * {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. | |
| * | |
| * Example: | |
| * ---------------------------------------------------------------------- | |
| * | |
| * class DerivedFiber : Fiber | |
| * { | |
| * this() | |
| * { | |
| * super( &run ); | |
| * } | |
| * | |
| * private : | |
| * void run() | |
| * { | |
| * printf( "Derived fiber running.\n" ); | |
| * } | |
| * } | |
| * | |
| * void fiberFunc() | |
| * { | |
| * printf( "Composed fiber running.\n" ); | |
| * Fiber.yield(); | |
| * printf( "Composed fiber running.\n" ); | |
| * } | |
| * | |
| * // create instances of each type | |
| * Fiber derived = new DerivedFiber(); | |
| * Fiber composed = new Fiber( &fiberFunc ); | |
| * | |
| * // call both fibers once | |
| * derived.call(); | |
| * composed.call(); | |
| * printf( "Execution returned to calling context.\n" ); | |
| * composed.call(); | |
| * | |
| * // since each fiber has run to completion, each should have state TERM | |
| * assert( derived.state == Fiber.State.TERM ); | |
| * assert( composed.state == Fiber.State.TERM ); | |
| * | |
| * ---------------------------------------------------------------------- | |
| * | |
| * Authors: Based on a design by Mikola Lysenko. | |
| */ | |
| class Fiber | |
| { | |
| /////////////////////////////////////////////////////////////////////////// | |
| // Initialization | |
| /////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * 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 | |
| * | |
| * In: | |
| * fn must not be null. | |
| */ | |
| this( void function() fn, size_t sz = PAGESIZE*4, | |
| size_t guardPageSize = PAGESIZE ) nothrow | |
| in | |
| { | |
| assert( fn ); | |
| } | |
| body | |
| { | |
| 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 | |
| * | |
| * In: | |
| * dg must not be null. | |
| */ | |
| this( void delegate() dg, size_t sz = PAGESIZE*4, | |
| size_t guardPageSize = PAGESIZE ) nothrow | |
| in | |
| { | |
| assert( dg ); | |
| } | |
| body | |
| { | |
| allocStack( sz, guardPageSize); | |
| reset( 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; | |
| } | |
| /// ditto | |
| deprecated("Please pass Fiber.Rethrow.yes or .no instead of a boolean.") | |
| final Throwable call( bool rethrow ) | |
| { | |
| return rethrow ? call!(Rethrow.yes)() : call!(Rethrow.no); | |
| } | |
| private void callImpl() nothrow @nogc | |
| in | |
| { | |
| assert( m_state == State.HOLD ); | |
| } | |
| body | |
| { | |
| Fiber 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 ); | |
| } | |
| body | |
| { | |
| 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_fn = fn; | |
| m_call = Call.FN; | |
| } | |
| /// ditto | |
| final void reset( void delegate() dg ) nothrow @nogc | |
| { | |
| reset(); | |
| m_dg = dg; | |
| m_call = Call.DG; | |
| } | |
| /////////////////////////////////////////////////////////////////////////// | |
| // General Properties | |
| /////////////////////////////////////////////////////////////////////////// | |
| /** | |
| * A fiber may occupy one of three states: HOLD, EXEC, and TERM. The HOLD | |
| * state applies to any fiber that is suspended and ready to be called. | |
| * The EXEC state will be set for any fiber that is currently executing. | |
| * And the TERM state is set when a fiber terminates. Once a fiber | |
| * terminates, it must be reset before it may be called again. | |
| */ | |
| enum State | |
| { | |
| HOLD, /// | |
| EXEC, /// | |
| 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 | |
| { | |
| Fiber 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 ); | |
| } | |
| body | |
| { | |
| Fiber 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. | |
| * | |
| * Returns: | |
| * The fiber object representing the calling fiber or null if no fiber | |
| * is currently active within this thread. The result of deleting this object is undefined. | |
| */ | |
| static Fiber getThis() @safe nothrow @nogc | |
| { | |
| return sm_this; | |
| } | |
| /////////////////////////////////////////////////////////////////////////// | |
| // Static Initialization | |
| /////////////////////////////////////////////////////////////////////////// | |
| version (Posix) | |
| { | |
| static this() | |
| { | |
| static if ( __traits( compiles, ucontext_t ) ) | |
| { | |
| int status = getcontext( &sm_utxt ); | |
| assert( status == 0 ); | |
| } | |
| } | |
| } | |
| private: | |
| // | |
| // Initializes a fiber object which has no associated executable function. | |
| // | |
| this() @safe pure nothrow @nogc | |
| { | |
| 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; | |
| private: | |
| /////////////////////////////////////////////////////////////////////////// | |
| // Stack Management | |
| /////////////////////////////////////////////////////////////////////////// | |
| // | |
| // Allocate a new stack for this fiber. | |
| // | |
| final void allocStack( size_t sz, size_t guardPageSize ) nothrow | |
| in | |
| { | |
| assert( !m_pmem && !m_ctxt ); | |
| } | |
| body | |
| { | |
| // adjust alloc size to a multiple of PAGESIZE | |
| sz += PAGESIZE - 1; | |
| sz -= sz % PAGESIZE; | |
| // NOTE: This instance of Thread.Context is dynamic so Fiber objects | |
| // can be collected by the GC so long as no user level references | |
| // to the object exist. If m_ctxt were not dynamic then its | |
| // presence in the global context list would be enough to keep | |
| // this object alive indefinitely. An alternative to allocating | |
| // 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; | |
| static if ( __traits( compiles, VirtualAlloc ) ) | |
| { | |
| // reserve memory for stack | |
| m_pmem = VirtualAlloc( null, | |
| sz + guardPageSize, | |
| MEM_RESERVE, | |
| PAGE_NOACCESS ); | |
| if ( !m_pmem ) | |
| onOutOfMemoryError(); | |
| version (StackGrowsDown) | |
| { | |
| void* stack = m_pmem + guardPageSize; | |
| void* guard = m_pmem; | |
| void* pbase = stack + sz; | |
| } | |
| else | |
| { | |
| void* stack = m_pmem; | |
| void* guard = m_pmem + sz; | |
| void* pbase = stack; | |
| } | |
| // allocate reserved stack segment | |
| stack = VirtualAlloc( stack, | |
| sz, | |
| MEM_COMMIT, | |
| PAGE_READWRITE ); | |
| if ( !stack ) | |
| onOutOfMemoryError(); | |
| if (guardPageSize) | |
| { | |
| // allocate reserved guard page | |
| guard = VirtualAlloc( guard, | |
| guardPageSize, | |
| MEM_COMMIT, | |
| PAGE_READWRITE | PAGE_GUARD ); | |
| if ( !guard ) | |
| onOutOfMemoryError(); | |
| } | |
| m_ctxt.bstack = pbase; | |
| m_ctxt.tstack = pbase; | |
| m_size = sz; | |
| } | |
| else | |
| { | |
| version (Posix) import core.sys.posix.sys.mman; // mmap | |
| version (FreeBSD) import core.sys.freebsd.sys.mman : MAP_ANON; | |
| version (NetBSD) import core.sys.netbsd.sys.mman : MAP_ANON; | |
| version (OpenBSD) import core.sys.openbsd.sys.mman : MAP_ANON; | |
| version (DragonFlyBSD) import core.sys.dragonflybsd.sys.mman : MAP_ANON; | |
| version (CRuntime_Glibc) import core.sys.linux.sys.mman : MAP_ANON; | |
| version (Darwin) import core.sys.darwin.sys.mman : MAP_ANON; | |
| version (CRuntime_UClibc) import core.sys.linux.sys.mman : MAP_ANON; | |
| static if ( __traits( compiles, mmap ) ) | |
| { | |
| // Allocate more for the memory guard | |
| sz += guardPageSize; | |
| m_pmem = mmap( null, | |
| sz, | |
| PROT_READ | PROT_WRITE, | |
| MAP_PRIVATE | MAP_ANON, | |
| -1, | |
| 0 ); | |
| if ( m_pmem == MAP_FAILED ) | |
| m_pmem = null; | |
| } | |
| else static if ( __traits( compiles, valloc ) ) | |
| { | |
| m_pmem = valloc( sz ); | |
| } | |
| else static if ( __traits( compiles, malloc ) ) | |
| { | |
| m_pmem = malloc( sz ); | |
| } | |
| else | |
| { | |
| m_pmem = null; | |
| } | |
| if ( !m_pmem ) | |
| onOutOfMemoryError(); | |
| version (StackGrowsDown) | |
| { | |
| m_ctxt.bstack = m_pmem + sz; | |
| m_ctxt.tstack = m_pmem + sz; | |
| void* guard = m_pmem; | |
| } | |
| else | |
| { | |
| m_ctxt.bstack = m_pmem; | |
| m_ctxt.tstack = m_pmem; | |
| void* guard = m_pmem + sz - guardPageSize; | |
| } | |
| m_size = sz; | |
| static if ( __traits( compiles, mmap ) ) | |
| { | |
| if (guardPageSize) | |
| { | |
| // protect end of stack | |
| if ( mprotect(guard, guardPageSize, PROT_NONE) == -1 ) | |
| abort(); | |
| } | |
| } | |
| else | |
| { | |
| // Supported only for mmap allocated memory - results are | |
| // undefined if applied to memory not obtained by mmap | |
| } | |
| } | |
| Thread.add( m_ctxt ); | |
| } | |
| // | |
| // Free this fiber's stack. | |
| // | |
| final void freeStack() nothrow @nogc | |
| in | |
| { | |
| assert( m_pmem && m_ctxt ); | |
| } | |
| body | |
| { | |
| // 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 ); | |
| static if ( __traits( compiles, VirtualAlloc ) ) | |
| { | |
| VirtualFree( m_pmem, 0, MEM_RELEASE ); | |
| } | |
| else | |
| { | |
| import core.sys.posix.sys.mman; // munmap | |
| static if ( __traits( compiles, mmap ) ) | |
| { | |
| munmap( m_pmem, m_size ); | |
| } | |
| else static if ( __traits( compiles, valloc ) ) | |
| { | |
| free( m_pmem ); | |
| } | |
| else static if ( __traits( compiles, malloc ) ) | |
| { | |
| free( m_pmem ); | |
| } | |
| } | |
| m_pmem = null; | |
| m_ctxt = null; | |
| } | |
| // | |
| // Initialize the allocated stack. | |
| // Look above the definition of 'class Fiber' for some information about the implementation of this routine | |
| // | |
| final void initStack() nothrow @nogc | |
| in | |
| { | |
| assert( m_ctxt.tstack && m_ctxt.tstack == m_ctxt.bstack ); | |
| assert( cast(size_t) m_ctxt.bstack % (void*).sizeof == 0 ); | |
| } | |
| body | |
| { | |
| void* pstack = m_ctxt.tstack; | |
| scope( exit ) m_ctxt.tstack = pstack; | |
| void push( size_t val ) nothrow | |
| { | |
| version (StackGrowsDown) | |
| { | |
| pstack -= size_t.sizeof; | |
| *(cast(size_t*) pstack) = val; | |
| } | |
| else | |
| { | |
| pstack += size_t.sizeof; | |
| *(cast(size_t*) pstack) = val; | |
| } | |
| } | |
| // NOTE: On OS X the stack must be 16-byte aligned according | |
| // to the IA-32 call spec. For x86_64 the stack also needs to | |
| // be aligned to 16-byte according to SysV AMD64 ABI. | |
| version (AlignFiberStackTo16Byte) | |
| { | |
| version (StackGrowsDown) | |
| { | |
| pstack = cast(void*)(cast(size_t)(pstack) - (cast(size_t)(pstack) & 0x0F)); | |
| } | |
| else | |
| { | |
| pstack = cast(void*)(cast(size_t)(pstack) + (cast(size_t)(pstack) & 0x0F)); | |
| } | |
| } | |
| version (AsmX86_Windows) | |
| { | |
| version (StackGrowsDown) {} else static assert( false ); | |
| // On Windows Server 2008 and 2008 R2, an exploit mitigation | |
| // technique known as SEHOP is activated by default. To avoid | |
| // hijacking of the exception handler chain, the presence of a | |
| // Windows-internal handler (ntdll.dll!FinalExceptionHandler) at | |
| // its end is tested by RaiseException. If it is not present, all | |
| // handlers are disregarded, and the program is thus aborted | |
| // (see http://blogs.technet.com/b/srd/archive/2009/02/02/ | |
| // preventing-the-exploitation-of-seh-overwrites-with-sehop.aspx). | |
| // For new threads, this handler is installed by Windows immediately | |
| // after creation. To make exception handling work in fibers, we | |
| // have to insert it for our new stacks manually as well. | |
| // | |
| // To do this, we first determine the handler by traversing the SEH | |
| // chain of the current thread until its end, and then construct a | |
| // registration block for the last handler on the newly created | |
| // thread. We then continue to push all the initial register values | |
| // for the first context switch as for the other implementations. | |
| // | |
| // Note that this handler is never actually invoked, as we install | |
| // our own one on top of it in the fiber entry point function. | |
| // Thus, it should not have any effects on OSes not implementing | |
| // exception chain verification. | |
| alias fp_t = void function(); // Actual signature not relevant. | |
| static struct EXCEPTION_REGISTRATION | |
| { | |
| EXCEPTION_REGISTRATION* next; // sehChainEnd if last one. | |
| fp_t handler; | |
| } | |
| enum sehChainEnd = cast(EXCEPTION_REGISTRATION*) 0xFFFFFFFF; | |
| __gshared static fp_t finalHandler = null; | |
| if ( finalHandler is null ) | |
| { | |
| static EXCEPTION_REGISTRATION* fs0() nothrow | |
| { | |
| asm pure nothrow @nogc | |
| { | |
| naked; | |
| mov EAX, FS:[0]; | |
| ret; | |
| } | |
| } | |
| auto reg = fs0(); | |
| while ( reg.next != sehChainEnd ) reg = reg.next; | |
| // Benign races are okay here, just to avoid re-lookup on every | |
| // fiber creation. | |
| finalHandler = reg.handler; | |
| } | |
| // When linking with /safeseh (supported by LDC, but not DMD) | |
| // the exception chain must not extend to the very top | |
| // of the stack, otherwise the exception chain is also considered | |
| // invalid. Reserving additional 4 bytes at the top of the stack will | |
| // keep the EXCEPTION_REGISTRATION below that limit | |
| size_t reserve = EXCEPTION_REGISTRATION.sizeof + 4; | |
| pstack -= reserve; | |
| *(cast(EXCEPTION_REGISTRATION*)pstack) = | |
| EXCEPTION_REGISTRATION( sehChainEnd, finalHandler ); | |
| push( cast(size_t) &fiber_entryPoint ); // EIP | |
| push( cast(size_t) m_ctxt.bstack - reserve ); // EBP | |
| push( 0x00000000 ); // EDI | |
| push( 0x00000000 ); // ESI | |
| push( 0x00000000 ); // EBX | |
| push( cast(size_t) m_ctxt.bstack - reserve ); // FS:[0] | |
| push( cast(size_t) m_ctxt.bstack ); // FS:[4] | |
| push( cast(size_t) m_ctxt.bstack - m_size ); // FS:[8] | |
| push( 0x00000000 ); // EAX | |
| } | |
| else version (AsmX86_64_Windows) | |
| { | |
| // Using this trampoline instead of the raw fiber_entryPoint | |
| // ensures that during context switches, source and destination | |
| // stacks have the same alignment. Otherwise, the stack would need | |
| // to be shifted by 8 bytes for the first call, as fiber_entryPoint | |
| // is an actual function expecting a stack which is not aligned | |
| // to 16 bytes. | |
| static void trampoline() | |
| { | |
| asm pure nothrow @nogc | |
| { | |
| naked; | |
| sub RSP, 32; // Shadow space (Win64 calling convention) | |
| call fiber_entryPoint; | |
| xor RCX, RCX; // This should never be reached, as | |
| jmp RCX; // fiber_entryPoint must never return. | |
| } | |
| } | |
| push( cast(size_t) &trampoline ); // RIP | |
| push( 0x00000000_00000000 ); // RBP | |
| push( 0x00000000_00000000 ); // R12 | |
| push( 0x00000000_00000000 ); // R13 | |
| push( 0x00000000_00000000 ); // R14 | |
| push( 0x00000000_00000000 ); // R15 | |
| push( 0x00000000_00000000 ); // RDI | |
| push( 0x00000000_00000000 ); // RSI | |
| push( 0x00000000_00000000 ); // XMM6 (high) | |
| push( 0x00000000_00000000 ); // XMM6 (low) | |
| push( 0x00000000_00000000 ); // XMM7 (high) | |
| push( 0x00000000_00000000 ); // XMM7 (low) | |
| push( 0x00000000_00000000 ); // XMM8 (high) | |
| push( 0x00000000_00000000 ); // XMM8 (low) | |
| push( 0x00000000_00000000 ); // XMM9 (high) | |
| push( 0x00000000_00000000 ); // XMM9 (low) | |
| push( 0x00000000_00000000 ); // XMM10 (high) | |
| push( 0x00000000_00000000 ); // XMM10 (low) | |
| push( 0x00000000_00000000 ); // XMM11 (high) | |
| push( 0x00000000_00000000 ); // XMM11 (low) | |
| push( 0x00000000_00000000 ); // XMM12 (high) | |
| push( 0x00000000_00000000 ); // XMM12 (low) | |
| push( 0x00000000_00000000 ); // XMM13 (high) | |
| push( 0x00000000_00000000 ); // XMM13 (low) | |
| push( 0x00000000_00000000 ); // XMM14 (high) | |
| push( 0x00000000_00000000 ); // XMM14 (low) | |
| push( 0x00000000_00000000 ); // XMM15 (high) | |
| push( 0x00000000_00000000 ); // XMM15 (low) | |
| push( 0x00000000_00000000 ); // RBX | |
| push( 0xFFFFFFFF_FFFFFFFF ); // GS:[0] | |
| version (StackGrowsDown) | |
| { | |
| push( cast(size_t) m_ctxt.bstack ); // GS:[8] | |
| push( cast(size_t) m_ctxt.bstack - m_size ); // GS:[16] | |
| } | |
| else | |
| { | |
| push( cast(size_t) m_ctxt.bstack ); // GS:[8] | |
| push( cast(size_t) m_ctxt.bstack + m_size ); // GS:[16] | |
| } | |
| } | |
| else version (AsmX86_Posix) | |
| { | |
| push( 0x00000000 ); // Return address of fiber_entryPoint call | |
| push( cast(size_t) &fiber_entryPoint ); // EIP | |
| push( cast(size_t) m_ctxt.bstack ); // EBP | |
| push( 0x00000000 ); // EDI | |
| push( 0x00000000 ); // ESI | |
| push( 0x00000000 ); // EBX | |
| push( 0x00000000 ); // EAX | |
| } | |
| else version (AsmX86_64_Posix) | |
| { | |
| push( 0x00000000_00000000 ); // Return address of fiber_entryPoint call | |
| push( cast(size_t) &fiber_entryPoint ); // RIP | |
| push( cast(size_t) m_ctxt.bstack ); // RBP | |
| push( 0x00000000_00000000 ); // RBX | |
| push( 0x00000000_00000000 ); // R12 | |
| push( 0x00000000_00000000 ); // R13 | |
| push( 0x00000000_00000000 ); // R14 | |
| push( 0x00000000_00000000 ); // R15 | |
| } | |
| else version (AsmPPC_Posix) | |
| { | |
| version (StackGrowsDown) | |
| { | |
| pstack -= int.sizeof * 5; | |
| } | |
| else | |
| { | |
| pstack += int.sizeof * 5; | |
| } | |
| push( cast(size_t) &fiber_entryPoint ); // link register | |
| push( 0x00000000 ); // control register | |
| push( 0x00000000 ); // old stack pointer | |
| // GPR values | |
| version (StackGrowsDown) | |
| { | |
| pstack -= int.sizeof * 20; | |
| } | |
| else | |
| { | |
| pstack += int.sizeof * 20; | |
| } | |
| assert( (cast(size_t) pstack & 0x0f) == 0 ); | |
| } | |
| else version (AsmMIPS_O32_Posix) | |
| { | |
| version (StackGrowsDown) {} | |
| else static assert(0); | |
| /* We keep the FP registers and the return address below | |
| * the stack pointer, so they don't get scanned by the | |
| * GC. The last frame before swapping the stack pointer is | |
| * organized like the following. | |
| * | |
| * |-----------|<= frame pointer | |
| * | $gp | | |
| * | $s0-8 | | |
| * |-----------|<= stack pointer | |
| * | $ra | | |
| * | align(8) | | |
| * | $f20-30 | | |
| * |-----------| | |
| * | |
| */ | |
| enum SZ_GP = 10 * size_t.sizeof; // $gp + $s0-8 | |
| enum SZ_RA = size_t.sizeof; // $ra | |
| version (MIPS_HardFloat) | |
| { | |
| enum SZ_FP = 6 * 8; // $f20-30 | |
| enum ALIGN = -(SZ_FP + SZ_RA) & (8 - 1); | |
| } | |
| else | |
| { | |
| enum SZ_FP = 0; | |
| enum ALIGN = 0; | |
| } | |
| enum BELOW = SZ_FP + ALIGN + SZ_RA; | |
| enum ABOVE = SZ_GP; | |
| enum SZ = BELOW + ABOVE; | |
| (cast(ubyte*)pstack - SZ)[0 .. SZ] = 0; | |
| pstack -= ABOVE; | |
| *cast(size_t*)(pstack - SZ_RA) = cast(size_t)&fiber_entryPoint; | |
| } | |
| else version (AsmAArch64_Posix) | |
| { | |
| // Like others, FP registers and return address (lr) are kept | |
| // below the saved stack top (tstack) to hide from GC scanning. | |
| // fiber_switchContext expects newp sp to look like this: | |
| // 19: x19 | |
| // ... | |
| // 9: x29 (fp) <-- newp tstack | |
| // 8: x30 (lr) [&fiber_entryPoint] | |
| // 7: d8 | |
| // ... | |
| // 0: d15 | |
| version (StackGrowsDown) {} | |
| else | |
| static assert(false, "Only full descending stacks supported on AArch64"); | |
| // Only need to set return address (lr). Everything else is fine | |
| // zero initialized. | |
| pstack -= size_t.sizeof * 11; // skip past x19-x29 | |
| push(cast(size_t) &fiber_trampoline); // see threadasm.S for docs | |
| pstack += size_t.sizeof; // adjust sp (newp) above lr | |
| } | |
| else version (AsmARM_Posix) | |
| { | |
| /* We keep the FP registers and the return address below | |
| * the stack pointer, so they don't get scanned by the | |
| * GC. The last frame before swapping the stack pointer is | |
| * organized like the following. | |
| * | |
| * | |-----------|<= 'frame starts here' | |
| * | | fp | (the actual frame pointer, r11 isn't | |
| * | | r10-r4 | updated and still points to the previous frame) | |
| * | |-----------|<= stack pointer | |
| * | | lr | | |
| * | | 4byte pad | | |
| * | | d15-d8 |(if FP supported) | |
| * | |-----------| | |
| * Y | |
| * stack grows down: The pointer value here is smaller than some lines above | |
| */ | |
| // frame pointer can be zero, r10-r4 also zero initialized | |
| version (StackGrowsDown) | |
| pstack -= int.sizeof * 8; | |
| else | |
| static assert(false, "Only full descending stacks supported on ARM"); | |
| // link register | |
| push( cast(size_t) &fiber_entryPoint ); | |
| /* | |
| * We do not push padding and d15-d8 as those are zero initialized anyway | |
| * Position the stack pointer above the lr register | |
| */ | |
| pstack += int.sizeof * 1; | |
| } | |
| else version (GNU_AsmX86_Windows) | |
| { | |
| version (StackGrowsDown) {} else static assert( false ); | |
| // Currently, MinGW doesn't utilize SEH exceptions. | |
| // See DMD AsmX86_Windows If this code ever becomes fails and SEH is used. | |
| push( 0x00000000 ); // Return address of fiber_entryPoint call | |
| push( cast(size_t) &fiber_entryPoint ); // EIP | |
| push( 0x00000000 ); // EBP | |
| push( 0x00000000 ); // EDI | |
| push( 0x00000000 ); // ESI | |
| push( 0x00000000 ); // EBX | |
| push( 0xFFFFFFFF ); // FS:[0] - Current SEH frame | |
| push( cast(size_t) m_ctxt.bstack ); // FS:[4] - Top of stack | |
| push( cast(size_t) m_ctxt.bstack - m_size ); // FS:[8] - Bottom of stack | |
| push( 0x00000000 ); // EAX | |
| } | |
| else version (GNU_AsmX86_64_Windows) | |
| { | |
| push( 0x00000000_00000000 ); // Return address of fiber_entryPoint call | |
| push( cast(size_t) &fiber_entryPoint ); // RIP | |
| push( 0x00000000_00000000 ); // RBP | |
| push( 0x00000000_00000000 ); // RBX | |
| push( 0x00000000_00000000 ); // R12 | |
| push( 0x00000000_00000000 ); // R13 | |
| push( 0x00000000_00000000 ); // R14 | |
| push( 0x00000000_00000000 ); // R15 | |
| push( 0xFFFFFFFF_FFFFFFFF ); // GS:[0] - Current SEH frame | |
| version (StackGrowsDown) | |
| { | |
| push( cast(size_t) m_ctxt.bstack ); // GS:[8] - Top of stack | |
| push( cast(size_t) m_ctxt.bstack - m_size ); // GS:[16] - Bottom of stack | |
| } | |
| else | |
| { | |
| push( cast(size_t) m_ctxt.bstack ); // GS:[8] - Top of stack | |
| push( cast(size_t) m_ctxt.bstack + m_size ); // GS:[16] - Bottom of stack | |
| } | |
| } | |
| else static if ( __traits( compiles, ucontext_t ) ) | |
| { | |
| getcontext( &m_utxt ); | |
| m_utxt.uc_stack.ss_sp = m_pmem; | |
| m_utxt.uc_stack.ss_size = m_size; | |
| makecontext( &m_utxt, &fiber_entryPoint, 0 ); | |
| // NOTE: If ucontext is being used then the top of the stack will | |
| // be a pointer to the ucontext_t struct for that fiber. | |
| push( cast(size_t) &m_utxt ); | |
| } | |
| else | |
| static assert(0, "Not implemented"); | |
| } | |
| Thread.Context* m_ctxt; | |
| size_t m_size; | |
| void* m_pmem; | |
| static if ( __traits( compiles, ucontext_t ) ) | |
| { | |
| // NOTE: The static ucontext instance is used to represent the context | |
| // of the executing thread. | |
| static ucontext_t sm_utxt = void; | |
| ucontext_t m_utxt = void; | |
| ucontext_t* m_ucur = null; | |
| } | |
| private: | |
| /////////////////////////////////////////////////////////////////////////// | |
| // Storage of Active Fiber | |
| /////////////////////////////////////////////////////////////////////////// | |
| // | |
| // Sets a thread-local reference to the current fiber object. | |
| // | |
| static void setThis( Fiber f ) nothrow @nogc | |
| { | |
| sm_this = f; | |
| } | |
| static Fiber sm_this; | |
| private: | |
| /////////////////////////////////////////////////////////////////////////// | |
| // Context Switching | |
| /////////////////////////////////////////////////////////////////////////// | |
| // | |
| // Switches into the stack held by this fiber. | |
| // | |
| final void switchIn() nothrow @nogc | |
| { | |
| Thread tobj = Thread.getThis(); | |
| void** oldp = &tobj.m_curr.tstack; | |
| void* newp = m_ctxt.tstack; | |
| // NOTE: The order of operations here is very important. The current | |
| // stack top must be stored before m_lock is set, and pushContext | |
| // must not be called until after m_lock is set. This process | |
| // is intended to prevent a race condition with the suspend | |
| // mechanism used for garbage collection. If it is not followed, | |
| // a badly timed collection could cause the GC to scan from the | |
| // bottom of one stack to the top of another, or to miss scanning | |
| // a stack that still contains valid data. The old stack pointer | |
| // oldp will be set again before the context switch to guarantee | |
| // that it points to exactly the correct stack location so the | |
| // successive pop operations will succeed. | |
| *oldp = getStackTop(); | |
| atomicStore!(MemoryOrder.raw)(*cast(shared)&tobj.m_lock, true); | |
| tobj.pushContext( m_ctxt ); | |
| fiber_switchContext( oldp, newp ); | |
| // NOTE: As above, these operations must be performed in a strict order | |
| // 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; | |
| } | |
| // | |
| // Switches out of the current stack and into the enclosing stack. | |
| // | |
| final void switchOut() nothrow @nogc | |
| { | |
| Thread tobj = Thread.getThis(); | |
| void** oldp = &m_ctxt.tstack; | |
| void* newp = tobj.m_curr.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 | |
| // must not be called until after m_lock is set. This process | |
| // is intended to prevent a race condition with the suspend | |
| // mechanism used for garbage collection. If it is not followed, | |
| // a badly timed collection could cause the GC to scan from the | |
| // bottom of one stack to the top of another, or to miss scanning | |
| // a stack that still contains valid data. The old stack pointer | |
| // oldp will be set again before the context switch to guarantee | |
| // that it points to exactly the correct stack location so the | |
| // successive pop operations will succeed. | |
| *oldp = getStackTop(); | |
| atomicStore!(MemoryOrder.raw)(*cast(shared)&tobj.m_lock, true); | |
| fiber_switchContext( oldp, newp ); | |
| // NOTE: As above, these operations must be performed in a strict order | |
| // to prevent Bad Things from happening. | |
| // NOTE: If use of this fiber is multiplexed across threads, the thread | |
| // executing here may be different from the one above, so get the | |
| // 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; | |
| } | |
| } | |
| version (unittest) | |
| { | |
| class TestFiber : Fiber | |
| { | |
| this() | |
| { | |
| super(&run); | |
| } | |
| void run() | |
| { | |
| foreach (i; 0 .. 1000) | |
| { | |
| sum += i; | |
| Fiber.yield(); | |
| } | |
| } | |
| enum expSum = 1000 * 999 / 2; | |
| size_t sum; | |
| } | |
| void runTen() | |
| { | |
| TestFiber[10] fibs; | |
| foreach (ref fib; fibs) | |
| fib = new TestFiber(); | |
| bool cont; | |
| do { | |
| cont = false; | |
| foreach (fib; fibs) { | |
| if (fib.state == Fiber.State.HOLD) | |
| { | |
| fib.call(); | |
| cont |= fib.state != Fiber.State.TERM; | |
| } | |
| } | |
| } while (cont); | |
| foreach (fib; fibs) | |
| { | |
| assert(fib.sum == TestFiber.expSum); | |
| } | |
| } | |
| } | |
| // Single thread running separate fibers | |
| unittest | |
| { | |
| runTen(); | |
| } | |
| // Multiple threads running separate fibers | |
| unittest | |
| { | |
| auto group = new ThreadGroup(); | |
| foreach (_; 0 .. 4) | |
| { | |
| group.create(&runTen); | |
| } | |
| group.joinAll(); | |
| } | |
| // Multiple threads running shared fibers | |
| unittest | |
| { | |
| shared bool[10] locks; | |
| TestFiber[10] fibs; | |
| void runShared() | |
| { | |
| bool cont; | |
| do { | |
| cont = false; | |
| foreach (idx; 0 .. 10) | |
| { | |
| if (cas(&locks[idx], false, true)) | |
| { | |
| if (fibs[idx].state == Fiber.State.HOLD) | |
| { | |
| fibs[idx].call(); | |
| cont |= fibs[idx].state != Fiber.State.TERM; | |
| } | |
| locks[idx] = false; | |
| } | |
| else | |
| { | |
| cont = true; | |
| } | |
| } | |
| } while (cont); | |
| } | |
| foreach (ref fib; fibs) | |
| { | |
| fib = new TestFiber(); | |
| } | |
| auto group = new ThreadGroup(); | |
| foreach (_; 0 .. 4) | |
| { | |
| group.create(&runShared); | |
| } | |
| group.joinAll(); | |
| foreach (fib; fibs) | |
| { | |
| assert(fib.sum == TestFiber.expSum); | |
| } | |
| } | |
| // Test exception handling inside fibers. | |
| version (Win32) { | |
| // broken on win32 under windows server 2012: bug 13821 | |
| } else unittest { | |
| enum MSG = "Test message."; | |
| string caughtMsg; | |
| (new Fiber({ | |
| try | |
| { | |
| throw new Exception(MSG); | |
| } | |
| catch (Exception e) | |
| { | |
| caughtMsg = e.msg; | |
| } | |
| })).call(); | |
| assert(caughtMsg == MSG); | |
| } | |
| unittest | |
| { | |
| int x = 0; | |
| (new Fiber({ | |
| x++; | |
| })).call(); | |
| assert( x == 1 ); | |
| } | |
| nothrow unittest | |
| { | |
| new Fiber({}).call!(Fiber.Rethrow.no)(); | |
| } | |
| unittest | |
| { | |
| new Fiber({}).call(Fiber.Rethrow.yes); | |
| new Fiber({}).call(Fiber.Rethrow.no); | |
| } | |
| deprecated unittest | |
| { | |
| new Fiber({}).call(true); | |
| new Fiber({}).call(false); | |
| } | |
| version (Win32) { | |
| // broken on win32 under windows server 2012: bug 13821 | |
| } else unittest { | |
| enum MSG = "Test message."; | |
| try | |
| { | |
| (new Fiber({ | |
| throw new Exception( MSG ); | |
| })).call(); | |
| assert( false, "Expected rethrown exception." ); | |
| } | |
| catch ( Throwable t ) | |
| { | |
| assert( t.msg == MSG ); | |
| } | |
| } | |
| // Test exception chaining when switching contexts in finally blocks. | |
| unittest | |
| { | |
| static void throwAndYield(string msg) { | |
| try { | |
| throw new Exception(msg); | |
| } finally { | |
| Fiber.yield(); | |
| } | |
| } | |
| static void fiber(string name) { | |
| try { | |
| try { | |
| throwAndYield(name ~ ".1"); | |
| } finally { | |
| throwAndYield(name ~ ".2"); | |
| } | |
| } catch (Exception e) { | |
| assert(e.msg == name ~ ".1"); | |
| assert(e.next); | |
| assert(e.next.msg == name ~ ".2"); | |
| assert(!e.next.next); | |
| } | |
| } | |
| auto first = new Fiber(() => fiber("first")); | |
| auto second = new Fiber(() => fiber("second")); | |
| first.call(); | |
| second.call(); | |
| first.call(); | |
| second.call(); | |
| first.call(); | |
| second.call(); | |
| assert(first.state == Fiber.State.TERM); | |
| assert(second.state == Fiber.State.TERM); | |
| } | |
| // Test Fiber resetting | |
| unittest | |
| { | |
| static string method; | |
| static void foo() | |
| { | |
| method = "foo"; | |
| } | |
| void bar() | |
| { | |
| method = "bar"; | |
| } | |
| static void expect(Fiber fib, string s) | |
| { | |
| assert(fib.state == Fiber.State.HOLD); | |
| fib.call(); | |
| assert(fib.state == Fiber.State.TERM); | |
| assert(method == s); method = null; | |
| } | |
| auto fib = new Fiber(&foo); | |
| expect(fib, "foo"); | |
| fib.reset(); | |
| expect(fib, "foo"); | |
| fib.reset(&foo); | |
| expect(fib, "foo"); | |
| fib.reset(&bar); | |
| expect(fib, "bar"); | |
| fib.reset(function void(){method = "function";}); | |
| expect(fib, "function"); | |
| fib.reset(delegate void(){method = "delegate";}); | |
| expect(fib, "delegate"); | |
| } | |
| // Test unsafe reset in hold state | |
| unittest | |
| { | |
| auto fib = new Fiber(function {ubyte[2048] buf = void; Fiber.yield();}, 4096); | |
| foreach (_; 0 .. 10) | |
| { | |
| fib.call(); | |
| assert(fib.state == Fiber.State.HOLD); | |
| fib.reset(); | |
| } | |
| } | |
| // stress testing GC stack scanning | |
| unittest | |
| { | |
| import core.memory; | |
| static void unreferencedThreadObject() | |
| { | |
| static void sleep() { Thread.sleep(dur!"msecs"(100)); } | |
| auto thread = new Thread(&sleep).start(); | |
| } | |
| unreferencedThreadObject(); | |
| GC.collect(); | |
| static class Foo | |
| { | |
| this(int value) | |
| { | |
| _value = value; | |
| } | |
| int bar() | |
| { | |
| return _value; | |
| } | |
| int _value; | |
| } | |
| static void collect() | |
| { | |
| auto foo = new Foo(2); | |
| assert(foo.bar() == 2); | |
| GC.collect(); | |
| Fiber.yield(); | |
| GC.collect(); | |
| assert(foo.bar() == 2); | |
| } | |
| auto fiber = new Fiber(&collect); | |
| fiber.call(); | |
| GC.collect(); | |
| fiber.call(); | |
| // thread reference | |
| auto foo = new Foo(2); | |
| void collect2() | |
| { | |
| assert(foo.bar() == 2); | |
| GC.collect(); | |
| Fiber.yield(); | |
| GC.collect(); | |
| assert(foo.bar() == 2); | |
| } | |
| fiber = new Fiber(&collect2); | |
| fiber.call(); | |
| GC.collect(); | |
| fiber.call(); | |
| static void recurse(size_t cnt) | |
| { | |
| --cnt; | |
| Fiber.yield(); | |
| if (cnt) | |
| { | |
| auto fib = new Fiber(() { recurse(cnt); }); | |
| fib.call(); | |
| GC.collect(); | |
| fib.call(); | |
| } | |
| } | |
| fiber = new Fiber(() { recurse(20); }); | |
| fiber.call(); | |
| } | |
| version (AsmX86_64_Windows) | |
| { | |
| // Test Windows x64 calling convention | |
| unittest | |
| { | |
| void testNonvolatileRegister(alias REG)() | |
| { | |
| auto zeroRegister = new Fiber(() { | |
| mixin("asm pure nothrow @nogc { naked; xor "~REG~", "~REG~"; ret; }"); | |
| }); | |
| long after; | |
| mixin("asm pure nothrow @nogc { mov "~REG~", 0xFFFFFFFFFFFFFFFF; }"); | |
| zeroRegister.call(); | |
| mixin("asm pure nothrow @nogc { mov after, "~REG~"; }"); | |
| assert(after == -1); | |
| } | |
| void testNonvolatileRegisterSSE(alias REG)() | |
| { | |
| auto zeroRegister = new Fiber(() { | |
| mixin("asm pure nothrow @nogc { naked; xorpd "~REG~", "~REG~"; ret; }"); | |
| }); | |
| long[2] before = [0xFFFFFFFF_FFFFFFFF, 0xFFFFFFFF_FFFFFFFF], after; | |
| mixin("asm pure nothrow @nogc { movdqu "~REG~", before; }"); | |
| zeroRegister.call(); | |
| mixin("asm pure nothrow @nogc { movdqu after, "~REG~"; }"); | |
| assert(before == after); | |
| } | |
| testNonvolatileRegister!("R12")(); | |
| testNonvolatileRegister!("R13")(); | |
| testNonvolatileRegister!("R14")(); | |
| testNonvolatileRegister!("R15")(); | |
| testNonvolatileRegister!("RDI")(); | |
| testNonvolatileRegister!("RSI")(); | |
| testNonvolatileRegister!("RBX")(); | |
| testNonvolatileRegisterSSE!("XMM6")(); | |
| testNonvolatileRegisterSSE!("XMM7")(); | |
| testNonvolatileRegisterSSE!("XMM8")(); | |
| testNonvolatileRegisterSSE!("XMM9")(); | |
| testNonvolatileRegisterSSE!("XMM10")(); | |
| testNonvolatileRegisterSSE!("XMM11")(); | |
| testNonvolatileRegisterSSE!("XMM12")(); | |
| testNonvolatileRegisterSSE!("XMM13")(); | |
| testNonvolatileRegisterSSE!("XMM14")(); | |
| testNonvolatileRegisterSSE!("XMM15")(); | |
| } | |
| } | |
| version (D_InlineAsm_X86_64) | |
| { | |
| unittest | |
| { | |
| void testStackAlignment() | |
| { | |
| void* pRSP; | |
| asm pure nothrow @nogc | |
| { | |
| mov pRSP, RSP; | |
| } | |
| assert((cast(size_t)pRSP & 0xF) == 0); | |
| } | |
| auto fib = new Fiber(&testStackAlignment); | |
| fib.call(); | |
| } | |
| } | |
| // regression test for Issue 13416 | |
| version (FreeBSD) unittest | |
| { | |
| static void loop() | |
| { | |
| pthread_attr_t attr; | |
| pthread_attr_init(&attr); | |
| auto thr = pthread_self(); | |
| foreach (i; 0 .. 50) | |
| pthread_attr_get_np(thr, &attr); | |
| pthread_attr_destroy(&attr); | |
| } | |
| auto thr = new Thread(&loop).start(); | |
| foreach (i; 0 .. 50) | |
| { | |
| thread_suspendAll(); | |
| thread_resumeAll(); | |
| } | |
| thr.join(); | |
| } | |
| version (DragonFlyBSD) unittest | |
| { | |
| static void loop() | |
| { | |
| pthread_attr_t attr; | |
| pthread_attr_init(&attr); | |
| auto thr = pthread_self(); | |
| foreach (i; 0 .. 50) | |
| pthread_attr_get_np(thr, &attr); | |
| pthread_attr_destroy(&attr); | |
| } | |
| auto thr = new Thread(&loop).start(); | |
| foreach (i; 0 .. 50) | |
| { | |
| thread_suspendAll(); | |
| thread_resumeAll(); | |
| } | |
| thr.join(); | |
| } | |
| unittest | |
| { | |
| // use >PAGESIZE to avoid stack overflow (e.g. in an syscall) | |
| auto thr = new Thread(function{}, 4096 + 1).start(); | |
| thr.join(); | |
| } | |
| /** | |
| * Represents the ID of a thread, as returned by $(D Thread.)$(LREF id). | |
| * The exact type varies from platform to platform. | |
| */ | |
| version (Windows) | |
| alias ThreadID = uint; | |
| else | |
| version (Posix) | |
| alias ThreadID = pthread_t; |