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