[project @ 2003-09-21 22:20:51 by wolfgang]
[ghc-hetmet.git] / ghc / rts / Capability.c
1 /* ---------------------------------------------------------------------------
2  *
3  * (c) The GHC Team, 2002
4  *
5  * Capabilities
6  *
7  * A Capability represent the token required to execute STG code,
8  * and all the state an OS thread/task needs to run Haskell code:
9  * its STG registers, a pointer to its TSO, a nursery etc. During
10  * STG execution, a pointer to the capabilitity is kept in a
11  * register (BaseReg).
12  *
13  * Only in an SMP build will there be multiple capabilities, for
14  * the threaded RTS and other non-threaded builds, there is only
15  * one global capability, namely MainCapability.
16  * 
17  * --------------------------------------------------------------------------*/
18 #include "PosixSource.h"
19 #include "Rts.h"
20 #include "RtsUtils.h"
21 #include "OSThreads.h"
22 #include "Capability.h"
23 #include "Schedule.h"  /* to get at EMPTY_RUN_QUEUE() */
24 #include "Signals.h" /* to get at handleSignalsInThisThread() */
25
26 #if !defined(SMP)
27 Capability MainCapability;     /* for non-SMP, we have one global capability */
28 #endif
29
30 nat rts_n_free_capabilities;
31
32 #if defined(RTS_SUPPORTS_THREADS)
33 /* returning_worker_cond: when a worker thread returns from executing an
34  * external call, it needs to wait for an RTS Capability before passing
35  * on the result of the call to the Haskell thread that made it.
36  * 
37  * returning_worker_cond is signalled in Capability.releaseCapability().
38  *
39  */
40 Condition returning_worker_cond = INIT_COND_VAR;
41
42 /*
43  * To avoid starvation of threads blocked on worker_thread_cond,
44  * the task(s) that enter the Scheduler will check to see whether
45  * there are one or more worker threads blocked waiting on
46  * returning_worker_cond.
47  */
48 nat rts_n_waiting_workers = 0;
49
50 /* thread_ready_cond: when signalled, a thread has become runnable for a
51  * task to execute.
52  *
53  * In the non-SMP case, it also implies that the thread that is woken up has
54  * exclusive access to the RTS and all its data structures (that are not
55  * locked by the Scheduler's mutex).
56  *
57  * thread_ready_cond is signalled whenever noCapabilities doesn't hold.
58  *
59  */
60 Condition thread_ready_cond = INIT_COND_VAR;
61
62 /*
63  * To be able to make an informed decision about whether or not 
64  * to create a new task when making an external call, keep track of
65  * the number of tasks currently blocked waiting on thread_ready_cond.
66  * (if > 0 => no need for a new task, just unblock an existing one).
67  *
68  * waitForWorkCapability() takes care of keeping it up-to-date;
69  * Task.startTask() uses its current value.
70  */
71 nat rts_n_waiting_tasks = 0;
72 #endif
73
74 /* -----------------------------------------------------------------------------
75    Initialisation
76    -------------------------------------------------------------------------- */
77 static
78 void
79 initCapability( Capability *cap )
80 {
81     cap->f.stgGCEnter1     = (F_)__stg_gc_enter_1;
82     cap->f.stgGCFun        = (F_)__stg_gc_fun;
83 }
84
85 #if defined(SMP)
86 static void initCapabilities_(nat n);
87 #endif
88
89 /* 
90  * Function:  initCapabilities()
91  *
92  * Purpose:   set up the Capability handling. For the SMP build,
93  *            we keep a table of them, the size of which is
94  *            controlled by the user via the RTS flag RtsFlags.ParFlags.nNodes
95  *
96  * Pre-conditions: no locks assumed held.
97  */
98 void
99 initCapabilities()
100 {
101 #if defined(RTS_SUPPORTS_THREADS)
102   initCondition(&returning_worker_cond);
103   initCondition(&thread_ready_cond);
104 #endif
105
106 #if defined(SMP)
107   initCapabilities_(RtsFlags.ParFlags.nNodes);
108 #else
109   initCapability(&MainCapability);
110   rts_n_free_capabilities = 1;
111 #endif
112
113   return;
114 }
115
116 #if defined(SMP)
117 /* Free capability list. */
118 static Capability *free_capabilities; /* Available capabilities for running threads */
119 static Capability *returning_capabilities; 
120         /* Capabilities being passed to returning worker threads */
121 #endif
122
123 /* -----------------------------------------------------------------------------
124    Acquiring capabilities
125    -------------------------------------------------------------------------- */
126
127 /*
128  * Function:  grabCapability(Capability**)
129  * 
130  * Purpose:   the act of grabbing a capability is easy; just 
131  *            remove one from the free capabilities list (which
132  *            may just have one entry). In threaded builds, worker
133  *            threads are prevented from doing so willy-nilly
134  *            via the condition variables thread_ready_cond and
135  *            returning_worker_cond.
136  *
137  */ 
138 void grabCapability(Capability** cap)
139 {
140 #ifdef RTS_SUPPORTS_THREADS
141   ASSERT(rts_n_free_capabilities > 0);
142 #endif
143 #if !defined(SMP)
144   rts_n_free_capabilities = 0;
145   *cap = &MainCapability;
146   handleSignalsInThisThread();
147 #else
148   *cap = free_capabilities;
149   free_capabilities = (*cap)->link;
150   rts_n_free_capabilities--;
151 #endif
152     IF_DEBUG(scheduler,
153              fprintf(stderr,"worker thread (%p): got capability\n",
154                 osThreadId()));
155 }
156
157 /*
158  * Function:  releaseCapability(Capability*)
159  *
160  * Purpose:   Letting go of a capability. Causes a
161  *            'returning worker' thread or a 'waiting worker'
162  *            to wake up, in that order.
163  *
164  */
165 void releaseCapability(Capability* cap
166 #if !defined(SMP)
167                        STG_UNUSED
168 #endif
169 )
170 {       // Precondition: sched_mutex must be held
171 #if defined(RTS_SUPPORTS_THREADS)
172 #ifndef SMP
173   ASSERT(rts_n_free_capabilities == 0);
174 #endif
175   /* Check to see whether a worker thread can be given
176      the go-ahead to return the result of an external call..*/
177   if (rts_n_waiting_workers > 0) {
178     /* Decrement the counter here to avoid livelock where the
179      * thread that is yielding its capability will repeatedly
180      * signal returning_worker_cond.
181      */
182 #if defined(SMP)
183         // SMP variant untested
184     cap->link = returning_capabilities;
185     returning_capabilities = cap;
186 #else
187 #endif
188     rts_n_waiting_workers--;
189     signalCondition(&returning_worker_cond);
190   } else /*if ( !EMPTY_RUN_QUEUE() )*/ {
191 #if defined(SMP)
192     cap->link = free_capabilities;
193     free_capabilities = cap;
194     rts_n_free_capabilities++;
195 #else
196     rts_n_free_capabilities = 1;
197 #endif
198     /* Signal that a capability is available */
199     signalCondition(&thread_ready_cond);
200   }
201 #endif
202     IF_DEBUG(scheduler,
203              fprintf(stderr,"worker thread (%p): released capability\n",
204                 osThreadId()));
205  return;
206 }
207
208 #if defined(RTS_SUPPORTS_THREADS)
209 /*
210  * When a native thread has completed the execution of an external
211  * call, it needs to communicate the result back. This is done
212  * as follows:
213  *
214  *  - in resumeThread(), the thread calls grabReturnCapability().
215  *  - If no capabilities are readily available, grabReturnCapability()
216  *    increments a counter rts_n_waiting_workers, and blocks
217  *    waiting for the condition returning_worker_cond to become
218  *    signalled.
219  *  - upon entry to the Scheduler, a worker thread checks the
220  *    value of rts_n_waiting_workers. If > 0, the worker thread
221  *    will yield its capability to let a returning worker thread
222  *    proceed with returning its result -- this is done via
223  *    yieldToReturningWorker().
224  *  - the worker thread that yielded its capability then tries
225  *    to re-grab a capability and re-enter the Scheduler.
226  */
227
228 /*
229  * Function: grabReturnCapability(Capability**)
230  *
231  * Purpose:  when an OS thread returns from an external call,
232  * it calls grabReturnCapability() (via Schedule.resumeThread())
233  * to wait for permissions to enter the RTS & communicate the
234  * result of the external call back to the Haskell thread that
235  * made it.
236  *
237  * Pre-condition:  pMutex is held.
238  * Post-condition: pMutex is still held and a capability has
239  *                 been assigned to the worker thread.
240  */
241 void
242 grabReturnCapability(Mutex* pMutex, Capability** pCap)
243 {
244   IF_DEBUG(scheduler,
245            fprintf(stderr,"worker (%p): returning, waiting for lock.\n", osThreadId()));
246   IF_DEBUG(scheduler,
247            fprintf(stderr,"worker (%p): returning; workers waiting: %d\n",
248                    osThreadId(), rts_n_waiting_workers));
249   if ( noCapabilities() ) {
250     rts_n_waiting_workers++;
251     wakeBlockedWorkerThread();
252     context_switch = 1; // make sure it's our turn soon
253     waitCondition(&returning_worker_cond, pMutex);
254 #if defined(SMP)
255     *pCap = returning_capabilities;
256     returning_capabilities = (*pCap)->link;
257 #else
258     *pCap = &MainCapability;
259     ASSERT(rts_n_free_capabilities == 0);
260     handleSignalsInThisThread();
261 #endif
262   } else {
263     grabCapability(pCap);
264   }
265   return;
266 }
267
268
269 /* -----------------------------------------------------------------------------
270    Yielding/waiting for capabilities
271    -------------------------------------------------------------------------- */
272
273 /*
274  * Function: yieldToReturningWorker(Mutex*,Capability*,Condition*)
275  *
276  * Purpose:  when, upon entry to the Scheduler, an OS worker thread
277  *           spots that one or more threads are blocked waiting for
278  *           permission to return back their result, it gives up
279  *           its Capability.
280  *           Immediately afterwards, it tries to reaquire the Capabilty
281  *           using waitForWorkCapability.
282  *
283  *
284  * Pre-condition:  pMutex is assumed held and the thread possesses
285  *                 a Capability.
286  * Post-condition: pMutex is held and the thread possesses
287  *                 a Capability.
288  */
289 void
290 yieldToReturningWorker(Mutex* pMutex, Capability** pCap, Condition* pThreadCond)
291 {
292   if ( rts_n_waiting_workers > 0 ) {
293     IF_DEBUG(scheduler,
294              fprintf(stderr,"worker thread (%p): giving up RTS token\n", osThreadId()));
295     releaseCapability(*pCap);
296         /* And wait for work */
297     waitForWorkCapability(pMutex, pCap, pThreadCond);
298     IF_DEBUG(scheduler,
299              fprintf(stderr,"worker thread (%p): got back RTS token (after yieldToReturningWorker)\n",
300                 osThreadId()));
301   }
302   return;
303 }
304
305
306 /*
307  * Function: waitForWorkCapability(Mutex*, Capability**, Condition*)
308  *
309  * Purpose:  wait for a Capability to become available. In
310  *           the process of doing so, updates the number
311  *           of tasks currently blocked waiting for a capability/more
312  *           work. That counter is used when deciding whether or
313  *           not to create a new worker thread when an external
314  *           call is made.
315  *           If pThreadCond is not NULL, a capability can be specifically
316  *           passed to this thread using passCapability.
317  *
318  * Pre-condition: pMutex is held.
319  * Post-condition: pMutex is held and *pCap is held by the current thread
320  */
321  
322 static Condition *passTarget = NULL;
323  
324 void 
325 waitForWorkCapability(Mutex* pMutex, Capability** pCap, Condition* pThreadCond)
326 {
327 #ifdef SMP
328   #error SMP version not implemented
329 #endif
330   IF_DEBUG(scheduler,
331            fprintf(stderr,"worker thread (%p): wait for cap (cond: %p)\n",
332               osThreadId(),pThreadCond));
333   while ( noCapabilities() || (pThreadCond && passTarget != pThreadCond)
334       || (!pThreadCond && passTarget)) {
335     if(pThreadCond)
336     {
337       waitCondition(pThreadCond, pMutex);
338       IF_DEBUG(scheduler,
339                fprintf(stderr,"worker thread (%p): get passed capability\n",
340                   osThreadId()));
341     }
342     else
343     {
344       rts_n_waiting_tasks++;
345       waitCondition(&thread_ready_cond, pMutex);
346       rts_n_waiting_tasks--;
347       IF_DEBUG(scheduler,
348                fprintf(stderr,"worker thread (%p): get normal capability\n",
349                   osThreadId()));
350     }
351   }
352   passTarget = NULL;
353   grabCapability(pCap);
354   return;
355 }
356
357 /*
358  * Function: passCapability(Mutex*, Capability*, Condition*)
359  *
360  * Purpose:  Let go of the capability and make sure the thread associated
361  *           with the Condition pTargetThreadCond gets it next.
362  *
363  * Pre-condition: pMutex is held and cap is held by the current thread
364  * Post-condition: pMutex is held; cap will be grabbed by the "target"
365  *                 thread when pMutex is released.
366  */
367
368 void
369 passCapability(Mutex* pMutex, Capability* cap, Condition *pTargetThreadCond)
370 {
371 #ifdef SMP
372   #error SMP version not implemented
373 #endif
374     rts_n_free_capabilities = 1;
375     signalCondition(pTargetThreadCond);
376     passTarget = pTargetThreadCond;
377     IF_DEBUG(scheduler,
378              fprintf(stderr,"worker thread (%p): passCapability\n",
379                 osThreadId()));
380 }
381
382
383 #endif /* RTS_SUPPORTS_THREADS */
384
385 #if defined(SMP)
386 /*
387  * Function: initCapabilities_(nat)
388  *
389  * Purpose:  upon startup, allocate and fill in table
390  *           holding 'n' Capabilities. Only for SMP, since
391  *           it is the only build that supports multiple
392  *           capabilities within the RTS.
393  */
394 static void
395 initCapabilities_(nat n)
396 {
397   nat i;
398   Capability *cap, *prev;
399   cap  = NULL;
400   prev = NULL;
401   for (i = 0; i < n; i++) {
402     cap = stgMallocBytes(sizeof(Capability), "initCapabilities");
403     initCapability(cap);
404     cap->link = prev;
405     prev = cap;
406   }
407   free_capabilities = cap;
408   rts_n_free_capabilities = n;
409   returning_capabilities = NULL;
410   IF_DEBUG(scheduler,fprintf(stderr,"scheduler: Allocated %d capabilities\n", n_free_capabilities););
411 }
412 #endif /* SMP */
413