553df3dfdd082842fba88bd1127c06ac4f91acb8
[ghc.git] / rts / Exception.cmm
1 /* -----------------------------------------------------------------------------
2  *
3  * (c) The GHC Team, 1998-2004
4  *
5  * Exception support
6  *
7  * This file is written in a subset of C--, extended with various
8  * features specific to GHC.  It is compiled by GHC directly.  For the
9  * syntax of .cmm files, see the parser in ghc/compiler/cmm/CmmParse.y.
10  *
11  * ---------------------------------------------------------------------------*/
12
13 #include "Cmm.h"
14 #include "RaiseAsync.h"
15
16 import ghczmprim_GHCziTypes_True_closure;
17
18 /* -----------------------------------------------------------------------------
19    Exception Primitives
20
21    A thread can request that asynchronous exceptions not be delivered
22    ("blocked") for the duration of an I/O computation.  The primitive
23    
24         maskAsyncExceptions# :: IO a -> IO a
25
26    is used for this purpose.  During a blocked section, asynchronous
27    exceptions may be unblocked again temporarily:
28
29         unmaskAsyncExceptions# :: IO a -> IO a
30
31    Furthermore, asynchronous exceptions are blocked automatically during
32    the execution of an exception handler.  Both of these primitives
33    leave a continuation on the stack which reverts to the previous
34    state (blocked or unblocked) on exit.
35
36    A thread which wants to raise an exception in another thread (using
37    killThread#) must block until the target thread is ready to receive
38    it.  The action of unblocking exceptions in a thread will release all
39    the threads waiting to deliver exceptions to that thread.
40
41    NB. there's a bug in here.  If a thread is inside an
42    unsafePerformIO, and inside maskAsyncExceptions# (there is an
43    unmaskAsyncExceptions_ret on the stack), and it is blocked in an
44    interruptible operation, and it receives an exception, then the
45    unsafePerformIO thunk will be updated with a stack object
46    containing the unmaskAsyncExceptions_ret frame.  Later, when
47    someone else evaluates this thunk, the blocked exception state is
48    not restored.
49
50    -------------------------------------------------------------------------- */
51
52
53 INFO_TABLE_RET(stg_unmaskAsyncExceptionszh_ret, RET_SMALL, W_ info_ptr)
54     /* explicit stack */
55 {
56     CInt r;
57
58     P_ ret;
59     ret = R1;
60
61     StgTSO_flags(CurrentTSO) = %lobits32(
62       TO_W_(StgTSO_flags(CurrentTSO)) & ~(TSO_BLOCKEX|TSO_INTERRUPTIBLE));
63
64     /* Eagerly raise a blocked exception, if there is one */
65     if (StgTSO_blocked_exceptions(CurrentTSO) != END_TSO_QUEUE) {
66
67         STK_CHK_P_LL (WDS(2), stg_unmaskAsyncExceptionszh_ret_info, R1);
68         /* 
69          * We have to be very careful here, as in killThread#, since
70          * we are about to raise an async exception in the current
71          * thread, which might result in the thread being killed.
72          */
73         Sp_adj(-2);
74         Sp(1) = ret;
75         Sp(0) = stg_ret_p_info;
76         SAVE_THREAD_STATE();
77         (r) = ccall maybePerformBlockedException (MyCapability() "ptr",
78                                                       CurrentTSO "ptr");
79         if (r != 0::CInt) {
80             if (StgTSO_what_next(CurrentTSO) == ThreadKilled::I16) {
81                 jump stg_threadFinished [];
82             } else {
83                 LOAD_THREAD_STATE();
84                 ASSERT(StgTSO_what_next(CurrentTSO) == ThreadRunGHC::I16);
85                 R1 = ret;
86                 jump %ENTRY_CODE(Sp(0)) [R1];
87             }
88         }
89         else {
90             /*
91                the thread might have been removed from the
92                blocked_exception list by someone else in the meantime.
93                Just restore the stack pointer and continue.  
94             */   
95             Sp_adj(2);
96         }
97     }
98
99     Sp_adj(1);
100     R1 = ret;
101     jump %ENTRY_CODE(Sp(0)) [R1];
102 }
103
104 INFO_TABLE_RET(stg_maskAsyncExceptionszh_ret, RET_SMALL, W_ info_ptr)
105     return (P_ ret)
106 {
107     StgTSO_flags(CurrentTSO) = 
108        %lobits32(
109          TO_W_(StgTSO_flags(CurrentTSO))
110           | TSO_BLOCKEX | TSO_INTERRUPTIBLE
111       );
112
113     return (ret);
114 }
115
116 INFO_TABLE_RET(stg_maskUninterruptiblezh_ret, RET_SMALL, W_ info_ptr)
117     return (P_ ret)
118 {
119     StgTSO_flags(CurrentTSO) = 
120        %lobits32(
121         (TO_W_(StgTSO_flags(CurrentTSO))
122           | TSO_BLOCKEX)
123           & ~TSO_INTERRUPTIBLE
124        );
125
126     return (ret);
127 }
128
129 stg_maskAsyncExceptionszh /* explicit stack */
130 {
131     /* Args: R1 :: IO a */
132     STK_CHK_P_LL (WDS(1)/* worst case */, stg_maskAsyncExceptionszh, R1);
133
134     if ((TO_W_(StgTSO_flags(CurrentTSO)) & TSO_BLOCKEX) == 0) {
135         /* avoid growing the stack unnecessarily */
136         if (Sp(0) == stg_maskAsyncExceptionszh_ret_info) {
137             Sp_adj(1);
138         } else {
139             Sp_adj(-1);
140             Sp(0) = stg_unmaskAsyncExceptionszh_ret_info;
141         }
142     } else {
143         if ((TO_W_(StgTSO_flags(CurrentTSO)) & TSO_INTERRUPTIBLE) == 0) {
144             Sp_adj(-1);
145             Sp(0) = stg_maskUninterruptiblezh_ret_info;
146         }
147     }
148
149     StgTSO_flags(CurrentTSO) = %lobits32(
150         TO_W_(StgTSO_flags(CurrentTSO)) | TSO_BLOCKEX | TSO_INTERRUPTIBLE);
151
152     TICK_UNKNOWN_CALL();
153     TICK_SLOW_CALL_fast_v();
154     jump stg_ap_v_fast [R1];
155 }
156
157 stg_maskUninterruptiblezh /* explicit stack */
158 {
159     /* Args: R1 :: IO a */
160     STK_CHK_P_LL (WDS(1)/* worst case */, stg_maskUninterruptiblezh, R1);
161
162     if ((TO_W_(StgTSO_flags(CurrentTSO)) & TSO_BLOCKEX) == 0) {
163         /* avoid growing the stack unnecessarily */
164         if (Sp(0) == stg_maskUninterruptiblezh_ret_info) {
165             Sp_adj(1);
166         } else {
167             Sp_adj(-1);
168             Sp(0) = stg_unmaskAsyncExceptionszh_ret_info;
169         }
170     } else {
171         if ((TO_W_(StgTSO_flags(CurrentTSO)) & TSO_INTERRUPTIBLE) != 0) {
172             Sp_adj(-1);
173             Sp(0) = stg_maskAsyncExceptionszh_ret_info;
174         }
175     }
176
177     StgTSO_flags(CurrentTSO) = %lobits32(
178         (TO_W_(StgTSO_flags(CurrentTSO)) | TSO_BLOCKEX) & ~TSO_INTERRUPTIBLE);
179
180     TICK_UNKNOWN_CALL();
181     TICK_SLOW_CALL_fast_v();
182     jump stg_ap_v_fast [R1];
183 }
184
185 stg_unmaskAsyncExceptionszh /* explicit stack */
186 {
187     CInt r;
188     W_ level;
189
190     /* Args: R1 :: IO a */
191     P_ io;
192     io = R1;
193
194     STK_CHK_P_LL (WDS(4), stg_unmaskAsyncExceptionszh, io);
195     /* 4 words: one for the unblock frame, 3 for setting up the
196      * stack to call maybePerformBlockedException() below.
197      */
198
199     /* If exceptions are already unblocked, there's nothing to do */
200     if ((TO_W_(StgTSO_flags(CurrentTSO)) & TSO_BLOCKEX) != 0) {
201
202         /* avoid growing the stack unnecessarily */
203         if (Sp(0) == stg_unmaskAsyncExceptionszh_ret_info) {
204             Sp_adj(1);
205         } else {
206             Sp_adj(-1);
207             if ((TO_W_(StgTSO_flags(CurrentTSO)) & TSO_INTERRUPTIBLE) != 0) {
208                 Sp(0) = stg_maskAsyncExceptionszh_ret_info;
209             } else {
210                 Sp(0) = stg_maskUninterruptiblezh_ret_info;
211             }
212         }
213
214         StgTSO_flags(CurrentTSO) = %lobits32(
215             TO_W_(StgTSO_flags(CurrentTSO)) & ~(TSO_BLOCKEX|TSO_INTERRUPTIBLE));
216
217         /* Eagerly raise a blocked exception, if there is one */
218         if (StgTSO_blocked_exceptions(CurrentTSO) != END_TSO_QUEUE) {
219             /* 
220              * We have to be very careful here, as in killThread#, since
221              * we are about to raise an async exception in the current
222              * thread, which might result in the thread being killed.
223              *
224              * Now, if we are to raise an exception in the current
225              * thread, there might be an update frame above us on the
226              * stack due to unsafePerformIO.  Hence, the stack must
227              * make sense, because it is about to be snapshotted into
228              * an AP_STACK.
229              */
230             Sp_adj(-3);
231             Sp(2) = stg_ap_v_info;
232             Sp(1) = io;
233             Sp(0) = stg_enter_info;
234
235             SAVE_THREAD_STATE();
236             (r) = ccall maybePerformBlockedException (MyCapability() "ptr",
237                                                       CurrentTSO "ptr");
238
239             if (r != 0::CInt) {
240                 if (StgTSO_what_next(CurrentTSO) == ThreadKilled::I16) {
241                     jump stg_threadFinished [];
242                 } else {
243                     LOAD_THREAD_STATE();
244                     ASSERT(StgTSO_what_next(CurrentTSO) == ThreadRunGHC::I16);
245                     R1 = io;
246                     jump %ENTRY_CODE(Sp(0)) [R1];
247                 }
248             } else {
249                 /* we'll just call R1 directly, below */
250                 Sp_adj(3);
251             }
252         }
253
254     }
255     TICK_UNKNOWN_CALL();
256     TICK_SLOW_CALL_fast_v();
257     R1 = io;
258     jump stg_ap_v_fast [R1];
259 }
260
261
262 stg_getMaskingStatezh ()
263 {
264     /* args: none */
265     /* 
266        returns: 0 == unmasked,
267                 1 == masked, non-interruptible,
268                 2 == masked, interruptible
269     */
270     return (((TO_W_(StgTSO_flags(CurrentTSO)) & TSO_BLOCKEX) != 0) +
271             ((TO_W_(StgTSO_flags(CurrentTSO)) & TSO_INTERRUPTIBLE) != 0));
272 }
273
274 stg_killThreadzh (P_ target, P_ exception)
275 {
276     W_ why_blocked;
277
278     /* Needs 3 words because throwToSingleThreaded uses some stack */
279     STK_CHK_PP (WDS(3), stg_killThreadzh, target, exception);
280     /* We call allocate in throwTo(), so better check for GC */
281     MAYBE_GC_PP (stg_killThreadzh, target, exception);
282
283     /* 
284      * We might have killed ourselves.  In which case, better be *very*
285      * careful.  If the exception killed us, then return to the scheduler.
286      * If the exception went to a catch frame, we'll just continue from
287      * the handler.
288      */
289     if (target == CurrentTSO) {
290         /*
291          * So what should happen if a thread calls "throwTo self" inside
292          * unsafePerformIO, and later the closure is evaluated by another
293          * thread?  Presumably it should behave as if throwTo just returned,
294          * and then continue from there.  See #3279, #3288.  This is what
295          * happens: on resumption, we will just jump to the next frame on
296          * the stack, which is the return point for stg_killThreadzh.
297          */
298         R1 = target;
299         R2 = exception;
300         jump stg_killMyself [R1,R2];
301     } else {
302         W_ msg;
303
304         (msg) = ccall throwTo(MyCapability() "ptr",
305                                     CurrentTSO "ptr",
306                                     target "ptr",
307                                     exception "ptr");
308         
309         if (msg == NULL) {
310             return ();
311         } else {
312             StgTSO_why_blocked(CurrentTSO) = BlockedOnMsgThrowTo;
313             StgTSO_block_info(CurrentTSO) = msg;
314             // we must block, and unlock the message before returning
315             jump stg_block_throwto (target, exception);
316         }
317     }
318 }
319
320 /*
321  * We must switch into low-level Cmm in order to raise an exception in
322  * the current thread, hence this is in a separate proc with arguments
323  * passed explicitly in R1 and R2.
324  */
325 stg_killMyself
326 {
327     P_ target, exception;
328     target = R1;
329     exception = R2;
330
331     SAVE_THREAD_STATE();
332     /* ToDo: what if the current thread is blocking exceptions? */
333     ccall throwToSingleThreaded(MyCapability() "ptr", 
334                                 target "ptr", exception "ptr");
335     if (StgTSO_what_next(CurrentTSO) == ThreadKilled::I16) {
336         jump stg_threadFinished [];
337     } else {
338         LOAD_THREAD_STATE();
339         ASSERT(StgTSO_what_next(CurrentTSO) == ThreadRunGHC::I16);
340         jump %ENTRY_CODE(Sp(0)) [];
341     }
342 }
343
344 /* -----------------------------------------------------------------------------
345    Catch frames
346    -------------------------------------------------------------------------- */
347
348 /* Catch frames are very similar to update frames, but when entering
349  * one we just pop the frame off the stack and perform the correct
350  * kind of return to the activation record underneath us on the stack.
351  */
352
353 #define CATCH_FRAME_FIELDS(w_,p_,info_ptr,p1,p2,exceptions_blocked,handler)   \
354   w_ info_ptr,                                                          \
355   PROF_HDR_FIELDS(w_,p1,p2)                                             \
356   w_ exceptions_blocked,                                                \
357   p_ handler
358
359
360 INFO_TABLE_RET(stg_catch_frame, CATCH_FRAME,
361                CATCH_FRAME_FIELDS(W_,P_,info_ptr, p1, p2,
362                                   exceptions_blocked,handler))
363     return (P_ ret)
364 {
365     return (ret);
366 }
367
368 /* -----------------------------------------------------------------------------
369  * The catch infotable
370  *
371  * This should be exactly the same as would be generated by this STG code
372  *
373  * catch = {x,h} \n {} -> catch#{x,h}
374  *
375  * It is used in deleteThread when reverting blackholes.
376  * -------------------------------------------------------------------------- */
377
378 INFO_TABLE(stg_catch,2,0,FUN,"catch","catch")
379     (P_ node)
380 {
381     jump stg_catchzh(StgClosure_payload(node,0),StgClosure_payload(node,1));
382 }
383
384 stg_catchzh ( P_ io,      /* :: IO a */
385               P_ handler  /* :: Exception -> IO a */ )
386 {
387     W_ exceptions_blocked;
388
389     STK_CHK_GEN();
390   
391     exceptions_blocked =
392         TO_W_(StgTSO_flags(CurrentTSO)) & (TSO_BLOCKEX | TSO_INTERRUPTIBLE);
393     TICK_CATCHF_PUSHED();
394
395     /* Apply R1 to the realworld token */
396     TICK_UNKNOWN_CALL();
397     TICK_SLOW_CALL_fast_v();
398
399     jump stg_ap_v_fast
400         (CATCH_FRAME_FIELDS(,,stg_catch_frame_info, CCCS, 0,
401                             exceptions_blocked, handler))
402         (io);
403 }
404
405 /* -----------------------------------------------------------------------------
406  * The raise infotable
407  * 
408  * This should be exactly the same as would be generated by this STG code
409  *
410  *   raise = {err} \n {} -> raise#{err}
411  *
412  * It is used in stg_raisezh to update thunks on the update list
413  * -------------------------------------------------------------------------- */
414
415 INFO_TABLE(stg_raise,1,0,THUNK_1_0,"raise","raise")
416 {
417     jump stg_raisezh(StgThunk_payload(R1,0));
418 }
419
420 section "data" {
421   no_break_on_exception: W_[1];
422 }
423
424 INFO_TABLE_RET(stg_raise_ret, RET_SMALL, W_ info_ptr, P_ exception)
425     return (P_ ret)
426 {
427     W_[no_break_on_exception] = 1;
428     jump stg_raisezh (exception);
429 }
430
431 stg_raisezh /* explicit stack */
432 /*
433  * args : R1 :: Exception
434  *
435  * Here we assume that the NativeNodeCall convention always puts the
436  * first argument in R1 (which it does).  We cannot use high-level cmm
437  * due to all the LOAD_THREAD_STATE()/SAVE_THREAD_STATE() and stack
438  * walking that happens in here.
439  */
440 {
441     W_ handler;
442     W_ frame_type;
443     W_ exception;
444
445    exception = R1;
446
447 #if defined(PROFILING)
448     /* Debugging tool: on raising an  exception, show where we are. */
449
450     /* ToDo: currently this is a hack.  Would be much better if
451      * the info was only displayed for an *uncaught* exception.
452      */
453     if (RtsFlags_ProfFlags_showCCSOnException(RtsFlags) != 0::I32) {
454         SAVE_THREAD_STATE();
455         ccall fprintCCS_stderr(CCCS "ptr",
456                                      exception "ptr",
457                                      CurrentTSO "ptr");
458         LOAD_THREAD_STATE();
459     }
460 #endif
461     
462 retry_pop_stack:
463     SAVE_THREAD_STATE();
464     (frame_type) = ccall raiseExceptionHelper(BaseReg "ptr", CurrentTSO "ptr", exception "ptr");
465     LOAD_THREAD_STATE();
466     if (frame_type == ATOMICALLY_FRAME) {
467       /* The exception has reached the edge of a memory transaction.  Check that 
468        * the transaction is valid.  If not then perhaps the exception should
469        * not have been thrown: re-run the transaction.  "trec" will either be
470        * a top-level transaction running the atomic block, or a nested 
471        * transaction running an invariant check.  In the latter case we
472        * abort and de-allocate the top-level transaction that encloses it
473        * as well (we could just abandon its transaction record, but this makes
474        * sure it's marked as aborted and available for re-use). */
475       W_ trec, outer;
476       W_ r;
477       trec = StgTSO_trec(CurrentTSO);
478       (r) = ccall stmValidateNestOfTransactions(MyCapability() "ptr", trec "ptr");
479       outer  = StgTRecHeader_enclosing_trec(trec);
480       ccall stmAbortTransaction(MyCapability() "ptr", trec "ptr");
481       ccall stmFreeAbortedTRec(MyCapability() "ptr", trec "ptr");
482
483       if (outer != NO_TREC) {
484         ccall stmAbortTransaction(MyCapability() "ptr", outer "ptr");
485         ccall stmFreeAbortedTRec(MyCapability() "ptr", outer "ptr");
486       }
487
488       StgTSO_trec(CurrentTSO) = NO_TREC;
489       if (r != 0) {
490         // Transaction was valid: continue searching for a catch frame
491         Sp = Sp + SIZEOF_StgAtomicallyFrame;
492         goto retry_pop_stack;
493       } else {
494         // Transaction was not valid: we retry the exception (otherwise continue
495         // with a further call to raiseExceptionHelper)
496         ("ptr" trec) = ccall stmStartTransaction(MyCapability() "ptr", NO_TREC "ptr");
497         StgTSO_trec(CurrentTSO) = trec;
498         R1 = StgAtomicallyFrame_code(Sp);
499         jump stg_ap_v_fast [R1];
500       }          
501     }
502
503     // After stripping the stack, see whether we should break here for
504     // GHCi (c.f. the -fbreak-on-exception flag).  We do this after
505     // stripping the stack for a reason: we'll be inspecting values in
506     // GHCi, and it helps if all the thunks under evaluation have
507     // already been updated with the exception, rather than being left
508     // as blackholes.
509     if (W_[no_break_on_exception] != 0) {
510         W_[no_break_on_exception] = 0;
511     } else {
512         if (TO_W_(CInt[rts_stop_on_exception]) != 0) {
513             W_ ioAction;
514             // we don't want any further exceptions to be caught,
515             // until GHCi is ready to handle them.  This prevents
516             // deadlock if an exception is raised in InteractiveUI,
517             // for exmplae.  Perhaps the stop_on_exception flag should
518             // be per-thread.
519             CInt[rts_stop_on_exception] = 0;
520             ("ptr" ioAction) = ccall deRefStablePtr (W_[rts_breakpoint_io_action] "ptr");
521             Sp = Sp - WDS(6);
522             Sp(5) = exception;
523             Sp(4) = stg_raise_ret_info;
524             Sp(3) = exception;             // the AP_STACK
525             Sp(2) = ghczmprim_GHCziTypes_True_closure; // dummy breakpoint info
526             Sp(1) = ghczmprim_GHCziTypes_True_closure; // True <=> a breakpoint
527             R1 = ioAction;
528             jump RET_LBL(stg_ap_pppv) [R1];
529         }
530     }
531
532     if (frame_type == STOP_FRAME) {
533         /*
534          * We've stripped the entire stack, the thread is now dead.
535          * We will leave the stack in a GC'able state, see the stg_stop_thread
536          * entry code in StgStartup.cmm.
537          */
538         W_ stack;
539         stack = StgTSO_stackobj(CurrentTSO);
540         Sp = stack + OFFSET_StgStack_stack
541                 + WDS(TO_W_(StgStack_stack_size(stack))) - WDS(2);
542         Sp(1) = exception;      /* save the exception */
543         Sp(0) = stg_enter_info; /* so that GC can traverse this stack */
544         StgTSO_what_next(CurrentTSO) = ThreadKilled::I16;
545         SAVE_THREAD_STATE();    /* inline! */
546
547         jump stg_threadFinished [];
548     }
549
550     /* Ok, Sp points to the enclosing CATCH_FRAME or CATCH_STM_FRAME.
551      * Pop everything down to and including this frame, update Su,
552      * push R1, and enter the handler.
553      */
554     if (frame_type == CATCH_FRAME) {
555       handler = StgCatchFrame_handler(Sp);
556     } else {
557       handler = StgCatchSTMFrame_handler(Sp);
558     }
559
560     /* Restore the blocked/unblocked state for asynchronous exceptions
561      * at the CATCH_FRAME.  
562      *
563      * If exceptions were unblocked, arrange that they are unblocked
564      * again after executing the handler by pushing an
565      * unmaskAsyncExceptions_ret stack frame.
566      *
567      * If we've reached an STM catch frame then roll back the nested
568      * transaction we were using.
569      */
570     W_ frame;
571     frame = Sp;
572     if (frame_type == CATCH_FRAME)
573     {
574       Sp = Sp + SIZEOF_StgCatchFrame;
575       if ((StgCatchFrame_exceptions_blocked(frame) & TSO_BLOCKEX) == 0) {
576           Sp_adj(-1);
577           Sp(0) = stg_unmaskAsyncExceptionszh_ret_info;
578       }
579
580       /* Ensure that async exceptions are blocked when running the handler.
581       */
582       StgTSO_flags(CurrentTSO) = %lobits32(
583           TO_W_(StgTSO_flags(CurrentTSO)) | TSO_BLOCKEX | TSO_INTERRUPTIBLE);
584
585       /* The interruptible state is inherited from the context of the
586        * catch frame, but note that TSO_INTERRUPTIBLE is only meaningful
587        * if TSO_BLOCKEX is set.  (we got this wrong earlier, and #4988
588        * was a symptom of the bug).
589        */
590       if ((StgCatchFrame_exceptions_blocked(frame) &
591            (TSO_BLOCKEX | TSO_INTERRUPTIBLE)) == TSO_BLOCKEX) {
592           StgTSO_flags(CurrentTSO) = %lobits32(
593               TO_W_(StgTSO_flags(CurrentTSO)) & ~TSO_INTERRUPTIBLE);
594       }
595     }
596     else /* CATCH_STM_FRAME */
597     {
598       W_ trec, outer;
599       trec = StgTSO_trec(CurrentTSO);
600       outer  = StgTRecHeader_enclosing_trec(trec);
601       ccall stmAbortTransaction(MyCapability() "ptr", trec "ptr");
602       ccall stmFreeAbortedTRec(MyCapability() "ptr", trec "ptr");
603       StgTSO_trec(CurrentTSO) = outer;
604       Sp = Sp + SIZEOF_StgCatchSTMFrame;
605     }
606
607     /* Call the handler, passing the exception value and a realworld
608      * token as arguments.
609      */
610     Sp_adj(-1);
611     Sp(0) = exception;
612     R1 = handler;
613     Sp_adj(-1);
614     TICK_UNKNOWN_CALL();
615     TICK_SLOW_CALL_fast_pv();
616     jump RET_LBL(stg_ap_pv) [R1];
617 }
618
619 stg_raiseIOzh (P_ exception)
620 {
621     jump stg_raisezh (exception);
622 }