[project @ 2000-03-17 13:30:23 by simonmar]
[ghc-hetmet.git] / ghc / rts / Exception.hc
1 /* -----------------------------------------------------------------------------
2  * $Id: Exception.hc,v 1.10 2000/03/17 13:30:23 simonmar Exp $
3  *
4  * (c) The GHC Team, 1998-1999
5  *
6  * Exception support
7  *
8  * ---------------------------------------------------------------------------*/
9
10 #include "Rts.h"
11 #include "Exception.h"
12 #include "Schedule.h"
13 #include "StgRun.h"
14 #include "Storage.h"
15 #include "RtsUtils.h"
16 #include "RtsFlags.h"
17 #if defined(PAR)
18 # include "FetchMe.h"
19 #endif
20
21 /* -----------------------------------------------------------------------------
22    Exception Primitives
23
24    A thread can request that asynchronous exceptions not be delivered
25    ("blocked") for the duration of an I/O computation.  The primitive
26    
27         blockAsyncExceptions# :: IO a -> IO a
28
29    is used for this purpose.  During a blocked section, asynchronous
30    exceptions may be unblocked again temporarily:
31
32         unblockAsyncExceptions# :: IO a -> IO a
33
34    Furthermore, asynchronous exceptions are blocked automatically during
35    the execution of an exception handler.  Both of these primitives
36    leave a continuation on the stack which reverts to the previous
37    state (blocked or unblocked) on exit.
38
39    A thread which wants to raise an exception in another thread (using
40    killThread#) must block until the target thread is ready to receive
41    it.  The action of unblocking exceptions in a thread will release all
42    the threads waiting to deliver exceptions to that thread.
43
44    -------------------------------------------------------------------------- */
45
46 FN_(blockAsyncExceptionszh_fast)
47 {
48   FB_
49     /* Args: R1 :: IO a */
50     STK_CHK_GEN( 2/* worst case */, R1_PTR, blockAsyncExceptionszh_fast, );
51
52     if (CurrentTSO->blocked_exceptions == NULL) {
53       CurrentTSO->blocked_exceptions = END_TSO_QUEUE;
54       /* avoid growing the stack unnecessarily */
55       if (Sp[0] == (W_)&blockAsyncExceptionszh_ret_info) {
56         Sp++;
57       } else {
58         Sp--;
59         Sp[0] = (W_)&unblockAsyncExceptionszh_ret_info;
60       }
61     }
62     Sp--;
63     Sp[0] = ARG_TAG(0);
64     JMP_(GET_ENTRY(R1.cl));
65   FE_
66 }
67
68 INFO_TABLE_SRT_BITMAP(unblockAsyncExceptionszh_ret_info, unblockAsyncExceptionszh_ret_entry, 0, 0, 0, 0, RET_SMALL, , EF_, 0, 0);
69 FN_(unblockAsyncExceptionszh_ret_entry)
70 {
71   FB_
72     ASSERT(CurrentTSO->blocked_exceptions != NULL);
73 #if defined(GRAN)
74       awakenBlockedQueue(CurrentTSO->blocked_exceptions, 
75                          CurrentTSO->block_info.closure);
76 #elif defined(PAR)
77       // is CurrentTSO->block_info.closure always set to the node
78       // holding the blocking queue !? -- HWL
79       awakenBlockedQueue(CurrentTSO->blocked_exceptions, 
80                          CurrentTSO->block_info.closure);
81 #else
82     awakenBlockedQueue(CurrentTSO->blocked_exceptions);
83 #endif
84     CurrentTSO->blocked_exceptions = NULL;
85 #ifdef REG_R1
86     Sp++;
87     JMP_(ENTRY_CODE(Sp[0]));
88 #else
89     Sp[1] = Sp[0];
90     Sp++;
91     JMP_(ENTRY_CODE(Sp[1]));
92 #endif
93   FE_
94 }
95
96 FN_(unblockAsyncExceptionszh_fast)
97 {
98   FB_
99     /* Args: R1 :: IO a */
100     STK_CHK_GEN(2, R1_PTR, unblockAsyncExceptionszh_fast, );
101
102     if (CurrentTSO->blocked_exceptions != NULL) {
103 #if defined(GRAN)
104       awakenBlockedQueue(CurrentTSO->blocked_exceptions, 
105                          CurrentTSO->block_info.closure);
106 #elif defined(PAR)
107       // is CurrentTSO->block_info.closure always set to the node
108       // holding the blocking queue !? -- HWL
109       awakenBlockedQueue(CurrentTSO->blocked_exceptions, 
110                          CurrentTSO->block_info.closure);
111 #else
112       awakenBlockedQueue(CurrentTSO->blocked_exceptions);
113 #endif
114       CurrentTSO->blocked_exceptions = NULL;
115
116       /* avoid growing the stack unnecessarily */
117       if (Sp[0] == (W_)&unblockAsyncExceptionszh_ret_info) {
118         Sp++;
119       } else {
120         Sp--;   
121         Sp[0] = (W_)&blockAsyncExceptionszh_ret_info;
122       }
123     }
124     Sp--;
125     Sp[0] = ARG_TAG(0);
126     JMP_(GET_ENTRY(R1.cl));
127   FE_
128 }
129
130 INFO_TABLE_SRT_BITMAP(blockAsyncExceptionszh_ret_info, blockAsyncExceptionszh_ret_entry, 0, 0, 0, 0, RET_SMALL, , EF_, 0, 0);
131 FN_(blockAsyncExceptionszh_ret_entry)
132 {
133   FB_
134     ASSERT(CurrentTSO->blocked_exceptions == NULL);
135     CurrentTSO->blocked_exceptions = END_TSO_QUEUE;
136 #ifdef REG_R1
137     Sp++;
138     JMP_(ENTRY_CODE(Sp[0]));
139 #else
140     Sp[1] = Sp[0];
141     Sp++;
142     JMP_(ENTRY_CODE(Sp[1]));
143 #endif
144   FE_
145 }
146
147 FN_(killThreadzh_fast)
148 {
149   FB_
150   /* args: R1.p = TSO to kill, R2.p = Exception */
151
152   /* This thread may have been relocated.
153    * (see Schedule.c:threadStackOverflow)
154    */
155   while (R1.t->what_next == ThreadRelocated) {
156     R1.t = R1.t->link;
157   }
158
159   /* If the target thread is currently blocking async exceptions,
160    * we'll have to block until it's ready to accept them.  The
161    * exception is interruptible threads - ie. those that are blocked
162    * on some resource.
163    */
164   if (R1.t->blocked_exceptions != NULL && !interruptible(R1.t) ) {
165     
166     /* ToDo (SMP): locking if destination thread is currently
167      * running...
168      */
169     CurrentTSO->link = R1.t->blocked_exceptions;
170     R1.t->blocked_exceptions = CurrentTSO;
171
172     CurrentTSO->why_blocked = BlockedOnException;
173     CurrentTSO->block_info.tso = R1.t;
174     
175     BLOCK( R1_PTR | R2_PTR, killThreadzh_fast );
176   }
177
178   /* Killed threads turn into zombies, which might be garbage
179    * collected at a later date.  That's why we don't have to
180    * explicitly remove them from any queues they might be on.
181    */
182
183   /* We might have killed ourselves.  In which case, better be *very*
184    * careful.  If the exception killed us, then return to the scheduler.
185    * If the exception went to a catch frame, we'll just continue from
186    * the handler.
187    */
188   if (R1.t == CurrentTSO) {
189         SaveThreadState();      /* inline! */
190         STGCALL2(raiseAsync, R1.t, R2.cl);
191         if (CurrentTSO->what_next == ThreadKilled) {
192                 R1.w = ThreadYielding;
193                 JMP_(StgReturn);
194         }
195         LoadThreadState();
196         if (CurrentTSO->what_next == ThreadEnterGHC) {
197                 R1.w = Sp[0];
198                 Sp++;
199                 JMP_(GET_ENTRY(R1.cl));
200         } else {
201                 barf("killThreadzh_fast");
202         }
203   } else {
204         STGCALL2(raiseAsync, R1.t, R2.cl);
205   }
206
207   JMP_(ENTRY_CODE(Sp[0]));
208   FE_
209 }
210
211 /* -----------------------------------------------------------------------------
212    Catch frames
213    -------------------------------------------------------------------------- */
214
215 #ifdef REG_R1
216 #define CATCH_FRAME_ENTRY_TEMPLATE(label,ret)   \
217    FN_(label);                                  \
218    FN_(label)                                   \
219    {                                            \
220       FB_                                       \
221       Su = ((StgCatchFrame *)Sp)->link;         \
222       Sp += sizeofW(StgCatchFrame);             \
223       JMP_(ret);                                \
224       FE_                                       \
225    }
226 #else
227 #define CATCH_FRAME_ENTRY_TEMPLATE(label,ret)   \
228    FN_(label);                                  \
229    FN_(label)                                   \
230    {                                            \
231       StgWord rval;                             \
232       FB_                                       \
233       rval = Sp[0];                             \
234       Sp++;                                     \
235       Su = ((StgCatchFrame *)Sp)->link;         \
236       Sp += sizeofW(StgCatchFrame) - 1;         \
237       Sp[0] = rval;                             \
238       JMP_(ret);                                \
239       FE_                                       \
240    }
241 #endif
242
243 #ifdef REG_R1
244 #define SP_OFF 0
245 #else
246 #define SP_OFF 1
247 #endif
248
249 CATCH_FRAME_ENTRY_TEMPLATE(catch_frame_entry,ENTRY_CODE(Sp[SP_OFF]));
250 CATCH_FRAME_ENTRY_TEMPLATE(catch_frame_0_entry,RET_VEC(Sp[SP_OFF],0));
251 CATCH_FRAME_ENTRY_TEMPLATE(catch_frame_1_entry,RET_VEC(Sp[SP_OFF],1));
252 CATCH_FRAME_ENTRY_TEMPLATE(catch_frame_2_entry,RET_VEC(Sp[SP_OFF],2));
253 CATCH_FRAME_ENTRY_TEMPLATE(catch_frame_3_entry,RET_VEC(Sp[SP_OFF],3));
254 CATCH_FRAME_ENTRY_TEMPLATE(catch_frame_4_entry,RET_VEC(Sp[SP_OFF],4));
255 CATCH_FRAME_ENTRY_TEMPLATE(catch_frame_5_entry,RET_VEC(Sp[SP_OFF],5));
256 CATCH_FRAME_ENTRY_TEMPLATE(catch_frame_6_entry,RET_VEC(Sp[SP_OFF],6));
257 CATCH_FRAME_ENTRY_TEMPLATE(catch_frame_7_entry,RET_VEC(Sp[SP_OFF],7));
258
259 #ifdef PROFILING
260 #define CATCH_FRAME_BITMAP 7
261 #else
262 #define CATCH_FRAME_BITMAP 3
263 #endif
264
265 /* Catch frames are very similar to update frames, but when entering
266  * one we just pop the frame off the stack and perform the correct
267  * kind of return to the activation record underneath us on the stack.
268  */
269
270 VEC_POLY_INFO_TABLE(catch_frame, CATCH_FRAME_BITMAP, NULL/*srt*/, 0/*srt_off*/, 0/*srt_len*/, CATCH_FRAME,, EF_);
271
272 /* -----------------------------------------------------------------------------
273  * The catch infotable
274  *
275  * This should be exactly the same as would be generated by this STG code
276  *
277  * catch = {x,h} \n {} -> catch#{x,h}
278  *
279  * It is used in deleteThread when reverting blackholes.
280  * -------------------------------------------------------------------------- */
281
282 INFO_TABLE(catch_info,catch_entry,2,0,FUN,,EF_,0,0);
283 STGFUN(catch_entry)
284 {
285   FB_
286   R2.cl = payloadCPtr(R1.cl,1); /* h */
287   R1.cl = payloadCPtr(R1.cl,0); /* x */
288   JMP_(catchzh_fast);
289   FE_
290 }
291
292 FN_(catchzh_fast)
293 {
294   StgCatchFrame *fp;
295   FB_
296
297     /* args: R1 = m :: IO a, R2 = handler :: Exception -> IO a */
298     STK_CHK_GEN(sizeofW(StgCatchFrame) + 1, R1_PTR | R2_PTR, catchzh_fast, );
299   
300     /* Set up the catch frame */
301     Sp -= sizeofW(StgCatchFrame);
302     fp = (StgCatchFrame *)Sp;
303     SET_HDR(fp,(StgInfoTable *)&catch_frame_info,CCCS);
304     fp -> handler = R2.cl;
305     fp -> exceptions_blocked = (CurrentTSO->blocked_exceptions != NULL);
306     fp -> link = Su;
307     Su = (StgUpdateFrame *)fp;
308     TICK_CATCHF_PUSHED();
309
310     /* Push realworld token and enter R1. */
311     Sp--;
312     Sp[0] = ARG_TAG(0);
313     TICK_ENT_VIA_NODE();
314     JMP_(GET_ENTRY(R1.cl));
315     
316   FE_
317 }      
318
319 /* -----------------------------------------------------------------------------
320  * The raise infotable
321  * 
322  * This should be exactly the same as would be generated by this STG code
323  *
324  *   raise = {err} \n {} -> raise#{err}
325  *
326  * It is used in raisezh_fast to update thunks on the update list
327  * -------------------------------------------------------------------------- */
328
329 INFO_TABLE(raise_info,raise_entry,1,0,FUN,,EF_,0,0);
330 STGFUN(raise_entry)
331 {
332   FB_
333   R1.cl = R1.cl->payload[0];
334   JMP_(raisezh_fast);
335   FE_
336 }
337
338 FN_(raisezh_fast)
339 {
340   StgClosure *handler;
341   StgUpdateFrame *p;
342   StgClosure *raise_closure;
343   FB_
344     /* args : R1 = error */
345
346
347 #if defined(PROFILING)
348
349     /* Debugging tool: on raising an  exception, show where we are. */
350
351     /* ToDo: currently this is a hack.  Would be much better if
352      * the info was only displayed for an *uncaught* exception.
353      */
354     if (RtsFlags.ProfFlags.showCCSOnException) {
355       STGCALL2(print_ccs,stderr,CCCS);
356     }
357
358 #endif
359
360     p = Su;
361
362     /* This closure represents the expression 'raise# E' where E
363      * is the exception raise.  It is used to overwrite all the
364      * thunks which are currently under evaluataion.
365      */
366     raise_closure = (StgClosure *)RET_STGCALL1(P_,allocate,
367                                                sizeofW(StgClosure)+1);
368     raise_closure->header.info = &raise_info;
369     raise_closure->payload[0] = R1.cl;
370
371     while (1) {
372
373       switch (get_itbl(p)->type) {
374
375       case UPDATE_FRAME:
376         UPD_IND(p->updatee,raise_closure);
377         p = p->link;
378         continue;
379
380       case SEQ_FRAME:
381         p = ((StgSeqFrame *)p)->link;
382         continue;
383
384       case CATCH_FRAME:
385         /* found it! */
386         break;
387
388       case STOP_FRAME:
389         barf("raisezh_fast: STOP_FRAME");
390
391       default:
392         barf("raisezh_fast: weird activation record");
393       }
394       
395       break;
396
397     }
398     
399     /* Ok, p points to the enclosing CATCH_FRAME.  Pop everything down to
400      * and including this frame, update Su, push R1, and enter the handler.
401      */
402     Su = ((StgCatchFrame *)p)->link; 
403     handler = ((StgCatchFrame *)p)->handler;
404     
405     Sp = (P_)p + sizeofW(StgCatchFrame);
406
407     /* Restore the blocked/unblocked state for asynchronous exceptions
408      * at the CATCH_FRAME.  
409      *
410      * If exceptions were unblocked, arrange that they are unblocked
411      * again after executing the handler by pushing an
412      * unblockAsyncExceptions_ret stack frame.
413      */
414     if (! ((StgCatchFrame *)p)->exceptions_blocked) {
415       *(--Sp) = (W_)&unblockAsyncExceptionszh_ret_info;
416     }
417
418     /* Ensure that async excpetions are blocked when running the handler.
419     */
420     if (CurrentTSO->blocked_exceptions == NULL) {
421       CurrentTSO->blocked_exceptions = END_TSO_QUEUE;
422     }
423
424     /* Enter the handler, passing the exception value and a realworld
425      * token as arguments.
426      */
427     Sp -= 2;
428     Sp[0] = R1.w;
429     Sp[1] = ARG_TAG(0);
430     TICK_ENT_VIA_NODE();
431     R1.cl = handler;
432     JMP_(GET_ENTRY(R1.cl));
433
434   FE_
435 }