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