rts: Set unwind information for catch_frame
[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     unwind Sp = Sp + SIZEOF_StgCatchFrame;
374     return (ret);
375 }
376
377 /* -----------------------------------------------------------------------------
378  * The catch infotable
379  *
380  * This should be exactly the same as would be generated by this STG code
381  *
382  * catch = {x,h} \n {} -> catch#{x,h}
383  *
384  * It is used in deleteThread when reverting blackholes.
385  * -------------------------------------------------------------------------- */
386
387 INFO_TABLE(stg_catch,2,0,FUN,"catch","catch")
388     (P_ node)
389 {
390     jump stg_catchzh(StgClosure_payload(node,0),StgClosure_payload(node,1));
391 }
392
393 stg_catchzh ( P_ io,      /* :: IO a */
394               P_ handler  /* :: Exception -> IO a */ )
395 {
396     W_ exceptions_blocked;
397
398     STK_CHK_GEN();
399
400     exceptions_blocked =
401         TO_W_(StgTSO_flags(CurrentTSO)) & (TSO_BLOCKEX | TSO_INTERRUPTIBLE);
402     TICK_CATCHF_PUSHED();
403
404     /* Apply R1 to the realworld token */
405     TICK_UNKNOWN_CALL();
406     TICK_SLOW_CALL_fast_v();
407
408     jump stg_ap_v_fast
409         (CATCH_FRAME_FIELDS(,,stg_catch_frame_info, CCCS, 0,
410                             exceptions_blocked, handler))
411         (io);
412 }
413
414 /* -----------------------------------------------------------------------------
415  * The raise infotable
416  *
417  * This should be exactly the same as would be generated by this STG code
418  *
419  *   raise = {err} \n {} -> raise#{err}
420  *
421  * It is used in stg_raisezh to update thunks on the update list
422  * -------------------------------------------------------------------------- */
423
424 INFO_TABLE(stg_raise,1,0,THUNK_1_0,"raise","raise")
425 {
426     jump stg_raisezh(StgThunk_payload(R1,0));
427 }
428
429 section "data" {
430   no_break_on_exception: W_[1];
431 }
432
433 INFO_TABLE_RET(stg_raise_ret, RET_SMALL, W_ info_ptr, P_ exception)
434     return (P_ ret)
435 {
436     unwind Sp = Sp + WDS(2);
437     W_[no_break_on_exception] = 1;
438     jump stg_raisezh (exception);
439 }
440
441 stg_raisezh /* explicit stack */
442 /*
443  * args : R1 :: Exception
444  *
445  * Here we assume that the NativeNodeCall convention always puts the
446  * first argument in R1 (which it does).  We cannot use high-level cmm
447  * due to all the LOAD_THREAD_STATE()/SAVE_THREAD_STATE() and stack
448  * walking that happens in here.
449  */
450 {
451     W_ handler;
452     W_ frame_type;
453     W_ exception;
454
455    exception = R1;
456
457 #if defined(PROFILING)
458     /* Debugging tool: on raising an  exception, show where we are. */
459
460     /* ToDo: currently this is a hack.  Would be much better if
461      * the info was only displayed for an *uncaught* exception.
462      */
463     if (RtsFlags_ProfFlags_showCCSOnException(RtsFlags) != 0::CBool) {
464         SAVE_THREAD_STATE();
465         ccall fprintCCS_stderr(CCCS "ptr",
466                                      exception "ptr",
467                                      CurrentTSO "ptr");
468         LOAD_THREAD_STATE();
469     }
470 #endif
471
472 retry_pop_stack:
473     SAVE_THREAD_STATE();
474     (frame_type) = ccall raiseExceptionHelper(BaseReg "ptr", CurrentTSO "ptr", exception "ptr");
475     LOAD_THREAD_STATE();
476     if (frame_type == ATOMICALLY_FRAME) {
477       /* The exception has reached the edge of a memory transaction.  Check that
478        * the transaction is valid.  If not then perhaps the exception should
479        * not have been thrown: re-run the transaction.  "trec" will either be
480        * a top-level transaction running the atomic block, or a nested
481        * transaction running an invariant check.  In the latter case we
482        * abort and de-allocate the top-level transaction that encloses it
483        * as well (we could just abandon its transaction record, but this makes
484        * sure it's marked as aborted and available for re-use). */
485       W_ trec, outer;
486       W_ r;
487       trec = StgTSO_trec(CurrentTSO);
488       (r) = ccall stmValidateNestOfTransactions(MyCapability() "ptr", trec "ptr");
489       outer  = StgTRecHeader_enclosing_trec(trec);
490       ccall stmAbortTransaction(MyCapability() "ptr", trec "ptr");
491       ccall stmFreeAbortedTRec(MyCapability() "ptr", trec "ptr");
492
493       if (outer != NO_TREC) {
494         ccall stmAbortTransaction(MyCapability() "ptr", outer "ptr");
495         ccall stmFreeAbortedTRec(MyCapability() "ptr", outer "ptr");
496       }
497
498       StgTSO_trec(CurrentTSO) = NO_TREC;
499       if (r != 0) {
500         // Transaction was valid: continue searching for a catch frame
501         Sp = Sp + SIZEOF_StgAtomicallyFrame;
502         goto retry_pop_stack;
503       } else {
504         // Transaction was not valid: we retry the exception (otherwise continue
505         // with a further call to raiseExceptionHelper)
506         ("ptr" trec) = ccall stmStartTransaction(MyCapability() "ptr", NO_TREC "ptr");
507         StgTSO_trec(CurrentTSO) = trec;
508         R1 = StgAtomicallyFrame_code(Sp);
509         jump stg_ap_v_fast [R1];
510       }
511     }
512
513     // After stripping the stack, see whether we should break here for
514     // GHCi (c.f. the -fbreak-on-exception flag).  We do this after
515     // stripping the stack for a reason: we'll be inspecting values in
516     // GHCi, and it helps if all the thunks under evaluation have
517     // already been updated with the exception, rather than being left
518     // as blackholes.
519     if (W_[no_break_on_exception] != 0) {
520         W_[no_break_on_exception] = 0;
521     } else {
522         if (TO_W_(CInt[rts_stop_on_exception]) != 0) {
523             W_ ioAction;
524             // we don't want any further exceptions to be caught,
525             // until GHCi is ready to handle them.  This prevents
526             // deadlock if an exception is raised in InteractiveUI,
527             // for exmplae.  Perhaps the stop_on_exception flag should
528             // be per-thread.
529             CInt[rts_stop_on_exception] = 0;
530             ("ptr" ioAction) = ccall deRefStablePtr (W_[rts_breakpoint_io_action] "ptr");
531             Sp = Sp - WDS(9);
532             Sp(8) = exception;
533             Sp(7) = stg_raise_ret_info;
534             Sp(6) = exception;
535             Sp(5) = ghczmprim_GHCziTypes_True_closure; // True <=> a breakpoint
536             Sp(4) = stg_ap_ppv_info;
537             Sp(3) = 0;
538             Sp(2) = stg_ap_n_info;
539             Sp(1) = 0;
540             R1 = ioAction;
541             jump RET_LBL(stg_ap_n) [R1];
542         }
543     }
544
545     if (frame_type == STOP_FRAME) {
546         /*
547          * We've stripped the entire stack, the thread is now dead.
548          * We will leave the stack in a GC'able state, see the stg_stop_thread
549          * entry code in StgStartup.cmm.
550          */
551         W_ stack;
552         stack = StgTSO_stackobj(CurrentTSO);
553         Sp = stack + OFFSET_StgStack_stack
554                 + WDS(TO_W_(StgStack_stack_size(stack))) - WDS(2);
555         Sp(1) = exception;      /* save the exception */
556         Sp(0) = stg_enter_info; /* so that GC can traverse this stack */
557         StgTSO_what_next(CurrentTSO) = ThreadKilled::I16;
558         SAVE_THREAD_STATE();    /* inline! */
559
560         jump stg_threadFinished [];
561     }
562
563     /* Ok, Sp points to the enclosing CATCH_FRAME or CATCH_STM_FRAME.
564      * Pop everything down to and including this frame, update Su,
565      * push R1, and enter the handler.
566      */
567     if (frame_type == CATCH_FRAME) {
568       handler = StgCatchFrame_handler(Sp);
569     } else {
570       handler = StgCatchSTMFrame_handler(Sp);
571     }
572
573     /* Restore the masked/unmasked state for asynchronous exceptions
574      * at the CATCH_FRAME.
575      *
576      * If exceptions were unmasked, arrange that they are unmasked
577      * again after executing the handler by pushing an
578      * unmaskAsyncExceptions_ret stack frame.
579      *
580      * If we've reached an STM catch frame then roll back the nested
581      * transaction we were using.
582      */
583     W_ frame;
584     frame = Sp;
585     if (frame_type == CATCH_FRAME)
586     {
587       Sp = Sp + SIZEOF_StgCatchFrame;
588       if ((StgCatchFrame_exceptions_blocked(frame) & TSO_BLOCKEX) == 0) {
589           Sp_adj(-1);
590           Sp(0) = stg_unmaskAsyncExceptionszh_ret_info;
591       }
592
593       /* Ensure that async exceptions are masked when running the handler.
594       */
595       StgTSO_flags(CurrentTSO) = %lobits32(
596           TO_W_(StgTSO_flags(CurrentTSO)) | TSO_BLOCKEX | TSO_INTERRUPTIBLE);
597
598       /* The interruptible state is inherited from the context of the
599        * catch frame, but note that TSO_INTERRUPTIBLE is only meaningful
600        * if TSO_BLOCKEX is set.  (we got this wrong earlier, and #4988
601        * was a symptom of the bug).
602        */
603       if ((StgCatchFrame_exceptions_blocked(frame) &
604            (TSO_BLOCKEX | TSO_INTERRUPTIBLE)) == TSO_BLOCKEX) {
605           StgTSO_flags(CurrentTSO) = %lobits32(
606               TO_W_(StgTSO_flags(CurrentTSO)) & ~TSO_INTERRUPTIBLE);
607       }
608     }
609     else /* CATCH_STM_FRAME */
610     {
611       W_ trec, outer;
612       trec = StgTSO_trec(CurrentTSO);
613       outer  = StgTRecHeader_enclosing_trec(trec);
614       ccall stmAbortTransaction(MyCapability() "ptr", trec "ptr");
615       ccall stmFreeAbortedTRec(MyCapability() "ptr", trec "ptr");
616       StgTSO_trec(CurrentTSO) = outer;
617       Sp = Sp + SIZEOF_StgCatchSTMFrame;
618     }
619
620     /* Call the handler, passing the exception value and a realworld
621      * token as arguments.
622      */
623     Sp_adj(-1);
624     Sp(0) = exception;
625     R1 = handler;
626     Sp_adj(-1);
627     TICK_UNKNOWN_CALL();
628     TICK_SLOW_CALL_fast_pv();
629     jump RET_LBL(stg_ap_pv) [R1];
630 }
631
632 stg_raiseIOzh (P_ exception)
633 {
634     jump stg_raisezh (exception);
635 }