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