X-Git-Url: http://git.megacz.com/?a=blobdiff_plain;f=ghc%2Frts%2FCapability.c;h=d96b724b4ebe553be3cc322926f6109e94ce80e0;hb=d139a7ae37a61da8f641f0cae33d0ceb7479f398;hp=77096a96d3ea5fa646d835e83894c72cd5c30543;hpb=efa41d9d5eada7aa3230a2bd03b97a8b7025ef2e;p=ghc-hetmet.git diff --git a/ghc/rts/Capability.c b/ghc/rts/Capability.c index 77096a9..d96b724 100644 --- a/ghc/rts/Capability.c +++ b/ghc/rts/Capability.c @@ -6,20 +6,22 @@ * * A Capability represent the token required to execute STG code, * and all the state an OS thread/task needs to run Haskell code: - * its STG registers, a pointer to its TSO, a nursery etc. During + * its STG registers, a pointer to its TSO, a nursery etc. During * STG execution, a pointer to the capabilitity is kept in a * register (BaseReg). * - * Only in an SMP build will there be multiple capabilities, the threaded - * RTS and other non-threaded builds, there is one global capability, - * namely MainRegTable. + * Only in an SMP build will there be multiple capabilities, for + * the threaded RTS and other non-threaded builds, there is only + * one global capability, namely MainCapability. * * --------------------------------------------------------------------------*/ #include "PosixSource.h" #include "Rts.h" -#include "Schedule.h" #include "RtsUtils.h" +#include "OSThreads.h" #include "Capability.h" +#include "Schedule.h" /* to get at EMPTY_RUN_QUEUE() */ +#include "Signals.h" /* to get at handleSignalsInThisThread() */ #if !defined(SMP) Capability MainCapability; /* for non-SMP, we have one global capability */ @@ -42,33 +44,63 @@ Condition returning_worker_cond = INIT_COND_VAR; * the task(s) that enter the Scheduler will check to see whether * there are one or more worker threads blocked waiting on * returning_worker_cond. - * - * Locks needed: sched_mutex */ nat rts_n_waiting_workers = 0; + +/* thread_ready_cond: when signalled, a thread has become runnable for a + * task to execute. + * + * In the non-SMP case, it also implies that the thread that is woken up has + * exclusive access to the RTS and all its data structures (that are not + * locked by the Scheduler's mutex). + * + * thread_ready_cond is signalled whenever noCapabilities doesn't hold. + * + */ +Condition thread_ready_cond = INIT_COND_VAR; + +/* + * To be able to make an informed decision about whether or not + * to create a new task when making an external call, keep track of + * the number of tasks currently blocked waiting on thread_ready_cond. + * (if > 0 => no need for a new task, just unblock an existing one). + * + * waitForWorkCapability() takes care of keeping it up-to-date; + * Task.startTask() uses its current value. + */ +nat rts_n_waiting_tasks = 0; #endif +/* ----------------------------------------------------------------------------- + Initialisation + -------------------------------------------------------------------------- */ static void initCapability( Capability *cap ) { - cap->f.stgChk0 = (F_)__stg_chk_0; - cap->f.stgChk1 = (F_)__stg_chk_1; cap->f.stgGCEnter1 = (F_)__stg_gc_enter_1; - cap->f.stgUpdatePAP = (F_)__stg_update_PAP; + cap->f.stgGCFun = (F_)__stg_gc_fun; } -#ifdef SMP +#if defined(SMP) static void initCapabilities_(nat n); #endif /* + * Function: initCapabilities() + * + * Purpose: set up the Capability handling. For the SMP build, + * we keep a table of them, the size of which is + * controlled by the user via the RTS flag RtsFlags.ParFlags.nNodes + * + * Pre-conditions: no locks assumed held. */ void initCapabilities() { #if defined(RTS_SUPPORTS_THREADS) - initCondition(returning_worker_cond); + initCondition(&returning_worker_cond); + initCondition(&thread_ready_cond); #endif #if defined(SMP) @@ -81,18 +113,37 @@ initCapabilities() return; } -/* Free capability list. - * Locks required: sched_mutex. - */ #if defined(SMP) +/* Free capability list. */ static Capability *free_capabilities; /* Available capabilities for running threads */ +static Capability *returning_capabilities; + /* Capabilities being passed to returning worker threads */ #endif +/* ----------------------------------------------------------------------------- + Acquiring capabilities + -------------------------------------------------------------------------- */ + +/* + * Function: grabCapability(Capability**) + * + * Purpose: the act of grabbing a capability is easy; just + * remove one from the free capabilities list (which + * may just have one entry). In threaded builds, worker + * threads are prevented from doing so willy-nilly + * via the condition variables thread_ready_cond and + * returning_worker_cond. + * + */ void grabCapability(Capability** cap) { +#ifdef RTS_SUPPORTS_THREADS + ASSERT(rts_n_free_capabilities > 0); +#endif #if !defined(SMP) rts_n_free_capabilities = 0; *cap = &MainCapability; + handleSignalsInThisThread(); #else *cap = free_capabilities; free_capabilities = (*cap)->link; @@ -103,26 +154,21 @@ void grabCapability(Capability** cap) /* * Function: releaseCapability(Capability*) * - * Purpose: Letting go of a capability. + * Purpose: Letting go of a capability. Causes a + * 'returning worker' thread or a 'waiting worker' + * to wake up, in that order. * - * Pre-condition: sched_mutex is assumed held by current thread. - * Post-condition: */ void releaseCapability(Capability* cap #if !defined(SMP) STG_UNUSED #endif ) -{ -#if defined(SMP) - cap->link = free_capabilities; - free_capabilities = cap; - rts_n_free_capabilities++; -#else - rts_n_free_capabilities = 1; -#endif - +{ // Precondition: sched_mutex must be held #if defined(RTS_SUPPORTS_THREADS) +#ifndef SMP + ASSERT(rts_n_free_capabilities == 0); +#endif /* Check to see whether a worker thread can be given the go-ahead to return the result of an external call..*/ if (rts_n_waiting_workers > 0) { @@ -130,14 +176,27 @@ void releaseCapability(Capability* cap * thread that is yielding its capability will repeatedly * signal returning_worker_cond. */ +#if defined(SMP) + // SMP variant untested + cap->link = returning_capabilities; + returning_capabilities = cap; +#else +#endif rts_n_waiting_workers--; signalCondition(&returning_worker_cond); - } else if ( !EMPTY_RUN_QUEUE() ) { - /* Signal that work is available */ + } else /*if ( !EMPTY_RUN_QUEUE() )*/ { +#if defined(SMP) + cap->link = free_capabilities; + free_capabilities = cap; + rts_n_free_capabilities++; +#else + rts_n_free_capabilities = 1; +#endif + /* Signal that a capability is available */ signalCondition(&thread_ready_cond); } #endif - return; + return; } #if defined(RTS_SUPPORTS_THREADS) @@ -155,7 +214,7 @@ void releaseCapability(Capability* cap * value of rts_n_waiting_workers. If > 0, the worker thread * will yield its capability to let a returning worker thread * proceed with returning its result -- this is done via - * yieldCapability(). + * yieldToReturningWorker(). * - the worker thread that yielded its capability then tries * to re-grab a capability and re-enter the Scheduler. */ @@ -164,60 +223,100 @@ void releaseCapability(Capability* cap * Function: grabReturnCapability(Capability**) * * Purpose: when an OS thread returns from an external call, - * it calls grabReturningCapability() (via Schedule.resumeThread()) + * it calls grabReturnCapability() (via Schedule.resumeThread()) * to wait for permissions to enter the RTS & communicate the - * result of the ext. call back to the Haskell thread that + * result of the external call back to the Haskell thread that * made it. * - * Pre-condition: sched_mutex isn't held. - * Post-condition: sched_mutex is held and a capability has + * Pre-condition: pMutex is held. + * Post-condition: pMutex is still held and a capability has * been assigned to the worker thread. */ void -grabReturnCapability(Capability** pCap) +grabReturnCapability(Mutex* pMutex, Capability** pCap) { IF_DEBUG(scheduler, - sched_belch("thread %d: returning, waiting for sched. lock.\n", osThreadId())); - ACQUIRE_LOCK(&sched_mutex); - rts_n_waiting_workers++; + fprintf(stderr,"worker (%ld): returning, waiting for lock.\n", osThreadId())); IF_DEBUG(scheduler, - sched_belch("worker (%d,%d): returning; workers waiting: %d\n", - tok, osThreadId(), rts_n_waiting_workers)); - while ( noCapabilities() ) { - waitCondition(&returning_worker_cond, &sched_mutex); + fprintf(stderr,"worker (%ld): returning; workers waiting: %d\n", + osThreadId(), rts_n_waiting_workers)); + if ( noCapabilities() ) { + rts_n_waiting_workers++; + wakeBlockedWorkerThread(); + context_switch = 1; // make sure it's our turn soon + waitCondition(&returning_worker_cond, pMutex); +#if defined(SMP) + *pCap = returning_capabilities; + returning_capabilities = (*pCap)->link; +#else + *pCap = &MainCapability; + ASSERT(rts_n_free_capabilities == 0); + handleSignalsInThisThread(); +#endif + } else { + grabCapability(pCap); } - - grabCapability(pCap); return; } + +/* ----------------------------------------------------------------------------- + Yielding/waiting for capabilities + -------------------------------------------------------------------------- */ + /* - * Function: yieldCapability(Capability**) + * Function: yieldToReturningWorker(Mutex*,Capability*) * * Purpose: when, upon entry to the Scheduler, an OS worker thread * spots that one or more threads are blocked waiting for * permission to return back their result, it gives up * its Capability. * - * Pre-condition: sched_mutex is held and the thread possesses + * Pre-condition: pMutex is assumed held and the thread possesses * a Capability. - * Post-condition: sched_mutex isn't held and the Capability has + * Post-condition: pMutex is held and the Capability has * been given back. */ void -yieldCapability(Capability* cap) +yieldToReturningWorker(Mutex* pMutex, Capability** pCap) { + if ( rts_n_waiting_workers > 0 ) { IF_DEBUG(scheduler, - sched_belch("worker thread (%d): giving up RTS token\n", osThreadId())); - releaseCapability(cap); - RELEASE_LOCK(&sched_mutex); - yieldThread(); - /* At this point, sched_mutex has been given up & we've - * forced a thread context switch. Guaranteed to be - * enough for the signalled worker thread to race - * ahead? - */ - return; + fprintf(stderr,"worker thread (%p): giving up RTS token\n", osThreadId())); + releaseCapability(*pCap); + /* And wait for work */ + waitForWorkCapability(pMutex, pCap, rtsFalse); + IF_DEBUG(scheduler, + fprintf(stderr,"worker thread (%p): got back RTS token (after yieldToReturningWorker)\n", + osThreadId())); + } + return; +} + + +/* + * Function: waitForWorkCapability(Mutex*, Capability**, rtsBool) + * + * Purpose: wait for a Capability to become available. In + * the process of doing so, updates the number + * of tasks currently blocked waiting for a capability/more + * work. That counter is used when deciding whether or + * not to create a new worker thread when an external + * call is made. + * + * Pre-condition: pMutex is held. + * Post-condition: pMutex is held and *pCap is held by the current thread + */ +void +waitForWorkCapability(Mutex* pMutex, Capability** pCap, rtsBool runnable) +{ + while ( noCapabilities() || (runnable && EMPTY_RUN_QUEUE()) ) { + rts_n_waiting_tasks++; + waitCondition(&thread_ready_cond, pMutex); + rts_n_waiting_tasks--; + } + grabCapability(pCap); + return; } #endif /* RTS_SUPPORTS_THREADS */ @@ -230,9 +329,6 @@ yieldCapability(Capability* cap) * holding 'n' Capabilities. Only for SMP, since * it is the only build that supports multiple * capabilities within the RTS. - * - * Pre-condition: sched_mutex is held. - * */ static void initCapabilities_(nat n) @@ -249,6 +345,7 @@ initCapabilities_(nat n) } free_capabilities = cap; rts_n_free_capabilities = n; + returning_capabilities = NULL; IF_DEBUG(scheduler,fprintf(stderr,"scheduler: Allocated %d capabilities\n", n_free_capabilities);); } #endif /* SMP */