Exception.cmm 16.5 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
/* -----------------------------------------------------------------------------
 *
 * (c) The GHC Team, 1998-2004
 *
 * Exception support
 *
 * This file is written in a subset of C--, extended with various
 * features specific to GHC.  It is compiled by GHC directly.  For the
 * syntax of .cmm files, see the parser in ghc/compiler/cmm/CmmParse.y.
 *
 * ---------------------------------------------------------------------------*/

#include "Cmm.h"
14
#include "RaiseAsync.h"
15

Ian Lynagh's avatar
Ian Lynagh committed
16
import ghczmprim_GHCziBool_True_closure;
17

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/* -----------------------------------------------------------------------------
   Exception Primitives

   A thread can request that asynchronous exceptions not be delivered
   ("blocked") for the duration of an I/O computation.  The primitive
   
	blockAsyncExceptions# :: IO a -> IO a

   is used for this purpose.  During a blocked section, asynchronous
   exceptions may be unblocked again temporarily:

	unblockAsyncExceptions# :: IO a -> IO a

   Furthermore, asynchronous exceptions are blocked automatically during
   the execution of an exception handler.  Both of these primitives
   leave a continuation on the stack which reverts to the previous
   state (blocked or unblocked) on exit.

   A thread which wants to raise an exception in another thread (using
   killThread#) must block until the target thread is ready to receive
   it.  The action of unblocking exceptions in a thread will release all
   the threads waiting to deliver exceptions to that thread.

41
42
43
44
45
46
47
   NB. there's a bug in here.  If a thread is inside an
   unsafePerformIO, and inside blockAsyncExceptions# (there is an
   unblockAsyncExceptions_ret on the stack), and it is blocked in an
   interruptible operation, and it receives an exception, then the
   unsafePerformIO thunk will be updated with a stack object
   containing the unblockAsyncExceptions_ret frame.  Later, when
   someone else evaluates this thunk, the blocked exception state is
48
   not restored.
49

50
51
   -------------------------------------------------------------------------- */

52
INFO_TABLE_RET( stg_unblockAsyncExceptionszh_ret, RET_SMALL )
53
{
54
55
    CInt r;

56
57
58
    StgTSO_flags(CurrentTSO) = StgTSO_flags(CurrentTSO) & 
	~(TSO_BLOCKEX::I32|TSO_INTERRUPTIBLE::I32);

59
60
61
62
63
64
65
    /* Eagerly raise a blocked exception, if there is one */
    if (StgTSO_blocked_exceptions(CurrentTSO) != END_TSO_QUEUE) {
        /* 
         * We have to be very careful here, as in killThread#, since
         * we are about to raise an async exception in the current
         * thread, which might result in the thread being killed.
         */
66
67
68
69
70
71
72
73
74

#ifndef REG_R1
        /*
         * raiseAsync assumes that the stack is in ThreadRunGHC state,
         * i.e. with a return address on the top.  In unreg mode, the
         * return value for IO is on top of the return address, so we
         * need to make a small adjustment here.
         */
        Sp_adj(1);
75
76
77
78
79
80
81
82
#endif
        STK_CHK_GEN( WDS(2), R1_PTR, stg_unblockAsyncExceptionszh_ret_info);
        Sp_adj(-2);
        Sp(1) = R1;
#ifdef REG_R1
        Sp(0) = stg_gc_unpt_r1_info;
#else
        Sp(0) = stg_ut_1_0_unreg_info;
83
#endif
84
        SAVE_THREAD_STATE();
85
        (r) = foreign "C" maybePerformBlockedException (MyCapability() "ptr", 
86
87
88
89
    					              CurrentTSO "ptr") [R1];

        if (r != 0::CInt) {
            if (StgTSO_what_next(CurrentTSO) == ThreadKilled::I16) {
90
                jump stg_threadFinished;
91
92
93
94
95
96
            } else {
                LOAD_THREAD_STATE();
                ASSERT(StgTSO_what_next(CurrentTSO) == ThreadRunGHC::I16);
                jump %ENTRY_CODE(Sp(0));
            }
        }
97
98
99
100
101
102
103
104
105
#ifndef REG_R1
        /* 
         * Readjust stack in unregisterised mode if we didn't raise an
         * exception, see above
         */
        else {
            Sp_adj(-1);
        }
#endif
106
107
    }

108
109
110
111
112
113
114
115
116
117
#ifdef REG_R1
    Sp_adj(1);
    jump %ENTRY_CODE(Sp(0));
#else
    Sp(1) = Sp(0);
    Sp_adj(1);
    jump %ENTRY_CODE(Sp(1));
#endif
}

118
INFO_TABLE_RET( stg_blockAsyncExceptionszh_ret, RET_SMALL )
119
{
120
121
122
    StgTSO_flags(CurrentTSO) = 
	StgTSO_flags(CurrentTSO) | TSO_BLOCKEX::I32 | TSO_INTERRUPTIBLE::I32;

123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#ifdef REG_R1
    Sp_adj(1);
    jump %ENTRY_CODE(Sp(0));
#else
    Sp(1) = Sp(0);
    Sp_adj(1);
    jump %ENTRY_CODE(Sp(1));
#endif
}

blockAsyncExceptionszh_fast
{
    /* Args: R1 :: IO a */
    STK_CHK_GEN( WDS(2)/* worst case */, R1_PTR, blockAsyncExceptionszh_fast);

138
139
140
141
142
143
144
145
146
147
148
149
    if ((TO_W_(StgTSO_flags(CurrentTSO)) & TSO_BLOCKEX) == 0) {
	
	StgTSO_flags(CurrentTSO) = 
	   StgTSO_flags(CurrentTSO) | TSO_BLOCKEX::I32 | TSO_INTERRUPTIBLE::I32;

	/* avoid growing the stack unnecessarily */
	if (Sp(0) == stg_blockAsyncExceptionszh_ret_info) {
	    Sp_adj(1);
	} else {
	    Sp_adj(-1);
	    Sp(0) = stg_unblockAsyncExceptionszh_ret_info;
	}
150
151
152
    }
    TICK_UNKNOWN_CALL();
    TICK_SLOW_CALL_v();
153
    jump stg_ap_v_fast;
154
155
156
157
}

unblockAsyncExceptionszh_fast
{
158
159
    CInt r;

160
161
162
    /* Args: R1 :: IO a */
    STK_CHK_GEN( WDS(2), R1_PTR, unblockAsyncExceptionszh_fast);

163
    if ((TO_W_(StgTSO_flags(CurrentTSO)) & TSO_BLOCKEX) != 0) {
164

165
166
167
	StgTSO_flags(CurrentTSO) = StgTSO_flags(CurrentTSO) & 
	   ~(TSO_BLOCKEX::I32|TSO_INTERRUPTIBLE::I32);

168
169
170
171
172
173
174
175
        /* Eagerly raise a blocked exception, if there is one */
        if (StgTSO_blocked_exceptions(CurrentTSO) != END_TSO_QUEUE) {
            /* 
             * We have to be very careful here, as in killThread#, since
             * we are about to raise an async exception in the current
             * thread, which might result in the thread being killed.
             */
            SAVE_THREAD_STATE();
176
            (r) = foreign "C" maybePerformBlockedException (MyCapability() "ptr", 
177
178
179
180
						      CurrentTSO "ptr") [R1];

            if (r != 0::CInt) {
                if (StgTSO_what_next(CurrentTSO) == ThreadKilled::I16) {
181
                    jump stg_threadFinished;
182
183
184
185
186
187
188
189
	        } else {
	            LOAD_THREAD_STATE();
	            ASSERT(StgTSO_what_next(CurrentTSO) == ThreadRunGHC::I16);
	            jump %ENTRY_CODE(Sp(0));
	        }
            }
        }

190
191
192
193
194
195
196
	/* avoid growing the stack unnecessarily */
	if (Sp(0) == stg_unblockAsyncExceptionszh_ret_info) {
	    Sp_adj(1);
	} else {
	    Sp_adj(-1);
	    Sp(0) = stg_blockAsyncExceptionszh_ret_info;
	}
197
198
199
    }
    TICK_UNKNOWN_CALL();
    TICK_SLOW_CALL_v();
200
    jump stg_ap_v_fast;
201
202
203
204
205
}


killThreadzh_fast
{
206
207
208
209
210
211
212
213
214
    /* args: R1 = TSO to kill, R2 = Exception */

    W_ why_blocked;
    W_ target;
    W_ exception;
    
    target = R1;
    exception = R2;
    
215
    STK_CHK_GEN( WDS(3), R1_PTR & R2_PTR, killThreadzh_fast);
216
217
218
219
220
221
222

    /* 
     * We might have killed ourselves.  In which case, better be *very*
     * careful.  If the exception killed us, then return to the scheduler.
     * If the exception went to a catch frame, we'll just continue from
     * the handler.
     */
223
224
225
226
227
  loop:
    if (StgTSO_what_next(target) == ThreadRelocated::I16) {
        target = StgTSO_link(target);
        goto loop;
    }
228
    if (target == CurrentTSO) {
229
	SAVE_THREAD_STATE();
230
231
232
	/* ToDo: what if the current thread is blocking exceptions? */
	foreign "C" throwToSingleThreaded(MyCapability() "ptr", 
					  target "ptr", exception "ptr")[R1,R2];
233
	if (StgTSO_what_next(CurrentTSO) == ThreadKilled::I16) {
234
            jump stg_threadFinished;
235
	} else {
236
237
238
239
240
241
242
243
244
	    LOAD_THREAD_STATE();
	    ASSERT(StgTSO_what_next(CurrentTSO) == ThreadRunGHC::I16);
	    jump %ENTRY_CODE(Sp(0));
	}
    } else {
	W_ out;
	W_ retcode;
	out = BaseReg + OFFSET_StgRegTable_rmp_tmp_w;
	
245
	(retcode) = foreign "C" throwTo(MyCapability() "ptr",
246
247
248
249
250
251
252
253
254
				      CurrentTSO "ptr",
				      target "ptr",
				      exception "ptr",
				      out "ptr") [R1,R2];
	
	switch [THROWTO_SUCCESS .. THROWTO_BLOCKED] (retcode) {

	case THROWTO_SUCCESS: {
	    jump %ENTRY_CODE(Sp(0));
255
256
	}

257
258
259
260
261
262
263
	case THROWTO_BLOCKED: {
	    R3 = W_[out];
	    // we must block, and call throwToReleaseTarget() before returning
	    jump stg_block_throwto;
	}
	}
    }
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
}

/* -----------------------------------------------------------------------------
   Catch frames
   -------------------------------------------------------------------------- */

#ifdef REG_R1
#define SP_OFF 0
#else
#define SP_OFF 1
#endif

/* Catch frames are very similar to update frames, but when entering
 * one we just pop the frame off the stack and perform the correct
 * kind of return to the activation record underneath us on the stack.
 */

281
282
283
284
285
INFO_TABLE_RET(stg_catch_frame, CATCH_FRAME,
#if defined(PROFILING)
  W_ unused1, W_ unused2,
#endif
  W_ unused3, "ptr" W_ unused4)
Simon Marlow's avatar
Simon Marlow committed
286
287
288
#ifdef REG_R1
   {
      Sp = Sp + SIZEOF_StgCatchFrame;
Simon Marlow's avatar
Simon Marlow committed
289
      jump %ENTRY_CODE(Sp(SP_OFF));
Simon Marlow's avatar
Simon Marlow committed
290
291
292
293
294
295
296
   }
#else
   {
      W_ rval;
      rval = Sp(0);
      Sp = Sp + SIZEOF_StgCatchFrame;
      Sp(0) = rval;
Simon Marlow's avatar
Simon Marlow committed
297
      jump %ENTRY_CODE(Sp(SP_OFF));
Simon Marlow's avatar
Simon Marlow committed
298
299
   }
#endif
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324

/* -----------------------------------------------------------------------------
 * The catch infotable
 *
 * This should be exactly the same as would be generated by this STG code
 *
 * catch = {x,h} \n {} -> catch#{x,h}
 *
 * It is used in deleteThread when reverting blackholes.
 * -------------------------------------------------------------------------- */

INFO_TABLE(stg_catch,2,0,FUN,"catch","catch")
{
  R2 = StgClosure_payload(R1,1); /* h */
  R1 = StgClosure_payload(R1,0); /* x */
  jump catchzh_fast;
}

catchzh_fast
{
    /* args: R1 = m :: IO a, R2 = handler :: Exception -> IO a */
    STK_CHK_GEN(SIZEOF_StgCatchFrame + WDS(1), R1_PTR & R2_PTR, catchzh_fast);
  
    /* Set up the catch frame */
    Sp = Sp - SIZEOF_StgCatchFrame;
325
    SET_HDR(Sp,stg_catch_frame_info,W_[CCCS]);
326
327
    
    StgCatchFrame_handler(Sp) = R2;
328
    StgCatchFrame_exceptions_blocked(Sp) = TO_W_(StgTSO_flags(CurrentTSO)) & TSO_BLOCKEX;
329
330
331
332
333
    TICK_CATCHF_PUSHED();

    /* Apply R1 to the realworld token */
    TICK_UNKNOWN_CALL();
    TICK_SLOW_CALL_v();
334
    jump stg_ap_v_fast;
335
}
336
337
338
339
340
341
342
343
344
345
346

/* -----------------------------------------------------------------------------
 * The raise infotable
 * 
 * This should be exactly the same as would be generated by this STG code
 *
 *   raise = {err} \n {} -> raise#{err}
 *
 * It is used in raisezh_fast to update thunks on the update list
 * -------------------------------------------------------------------------- */

347
INFO_TABLE(stg_raise,1,0,THUNK_1_0,"raise","raise")
348
{
349
  R1 = StgThunk_payload(R1,0);
350
351
352
  jump raisezh_fast;
}

353
354
355
356
section "data" {
  no_break_on_exception: W_[1];
}

357
INFO_TABLE_RET(stg_raise_ret, RET_SMALL, "ptr" W_ arg1)
358
359
360
361
362
363
364
{
  R1 = Sp(1);
  Sp = Sp + WDS(2);
  W_[no_break_on_exception] = 1;  
  jump raisezh_fast;
}

365
366
367
368
raisezh_fast
{
    W_ handler;
    W_ frame_type;
369
    W_ exception;
370
371
    /* args : R1 :: Exception */

372
   exception = R1;
373
374
375
376
377
378
379

#if defined(PROFILING)
    /* Debugging tool: on raising an  exception, show where we are. */

    /* ToDo: currently this is a hack.  Would be much better if
     * the info was only displayed for an *uncaught* exception.
     */
Simon Marlow's avatar
Simon Marlow committed
380
    if (RtsFlags_ProfFlags_showCCSOnException(RtsFlags) != 0::I32) {
381
      foreign "C" fprintCCS_stderr(W_[CCCS] "ptr") [];
382
383
    }
#endif
andy@galois.com's avatar
andy@galois.com committed
384
    
385
retry_pop_stack:
386
    StgTSO_sp(CurrentTSO) = Sp;
387
    (frame_type) = foreign "C" raiseExceptionHelper(BaseReg "ptr", CurrentTSO "ptr", exception "ptr") [];
388
    Sp = StgTSO_sp(CurrentTSO);
389
390
391
    if (frame_type == ATOMICALLY_FRAME) {
      /* The exception has reached the edge of a memory transaction.  Check that 
       * the transaction is valid.  If not then perhaps the exception should
tharris@microsoft.com's avatar
tharris@microsoft.com committed
392
393
394
395
396
397
398
       * not have been thrown: re-run the transaction.  "trec" will either be
       * a top-level transaction running the atomic block, or a nested 
       * transaction running an invariant check.  In the latter case we
       * abort and de-allocate the top-level transaction that encloses it
       * as well (we could just abandon its transaction record, but this makes
       * sure it's marked as aborted and available for re-use). */
      W_ trec, outer;
399
400
      W_ r;
      trec = StgTSO_trec(CurrentTSO);
401
402
      (r) = foreign "C" stmValidateNestOfTransactions(trec "ptr") [];
      ("ptr" outer) = foreign "C" stmGetEnclosingTRec(trec "ptr") [];
403
404
      foreign "C" stmAbortTransaction(MyCapability() "ptr", trec "ptr") [];
      foreign "C" stmFreeAbortedTRec(MyCapability() "ptr", trec "ptr") [];
tharris@microsoft.com's avatar
tharris@microsoft.com committed
405
406

      if (outer != NO_TREC) {
407
408
        foreign "C" stmAbortTransaction(MyCapability() "ptr", outer "ptr") [];
        foreign "C" stmFreeAbortedTRec(MyCapability() "ptr", outer "ptr") [];
tharris@microsoft.com's avatar
tharris@microsoft.com committed
409
410
      }

411
      StgTSO_trec(CurrentTSO) = NO_TREC;
Simon Marlow's avatar
Simon Marlow committed
412
      if (r != 0) {
413
414
415
416
417
418
        // Transaction was valid: continue searching for a catch frame
        Sp = Sp + SIZEOF_StgAtomicallyFrame;
        goto retry_pop_stack;
      } else {
        // Transaction was not valid: we retry the exception (otherwise continue
        // with a further call to raiseExceptionHelper)
419
        ("ptr" trec) = foreign "C" stmStartTransaction(MyCapability() "ptr", NO_TREC "ptr") [];
420
421
        StgTSO_trec(CurrentTSO) = trec;
        R1 = StgAtomicallyFrame_code(Sp);
422
        jump stg_ap_v_fast;
423
424
      }          
    }
425

426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
    // After stripping the stack, see whether we should break here for
    // GHCi (c.f. the -fbreak-on-exception flag).  We do this after
    // stripping the stack for a reason: we'll be inspecting values in
    // GHCi, and it helps if all the thunks under evaluation have
    // already been updated with the exception, rather than being left
    // as blackholes.
    if (W_[no_break_on_exception] != 0) {
        W_[no_break_on_exception] = 0;
    } else {
        if (TO_W_(CInt[rts_stop_on_exception]) != 0) {
            W_ ioAction;
            // we don't want any further exceptions to be caught,
            // until GHCi is ready to handle them.  This prevents
            // deadlock if an exception is raised in InteractiveUI,
            // for exmplae.  Perhaps the stop_on_exception flag should
            // be per-thread.
            W_[rts_stop_on_exception] = 0;
443
            ("ptr" ioAction) = foreign "C" deRefStablePtr (W_[rts_breakpoint_io_action] "ptr") [];
444
445
446
447
            Sp = Sp - WDS(7);
            Sp(6) = exception;
            Sp(5) = stg_raise_ret_info;
            Sp(4) = stg_noforceIO_info;    // required for unregisterised
448
            Sp(3) = exception;             // the AP_STACK
Ian Lynagh's avatar
Ian Lynagh committed
449
450
            Sp(2) = ghczmprim_GHCziBool_True_closure; // dummy breakpoint info
            Sp(1) = ghczmprim_GHCziBool_True_closure; // True <=> a breakpoint
451
            R1 = ioAction;
452
            jump RET_LBL(stg_ap_pppv);
453
454
455
        }
    }

456
    if (frame_type == STOP_FRAME) {
457
458
459
460
461
462
	/*
	 * We've stripped the entire stack, the thread is now dead.
	 * We will leave the stack in a GC'able state, see the stg_stop_thread
	 * entry code in StgStartup.cmm.
	 */
	Sp = CurrentTSO + TSO_OFFSET_StgTSO_stack 
463
		+ WDS(TO_W_(StgTSO_stack_size(CurrentTSO))) - WDS(2);
464
	Sp(1) = exception;	/* save the exception */
465
	Sp(0) = stg_enter_info; /* so that GC can traverse this stack */
466
467
	StgTSO_what_next(CurrentTSO) = ThreadKilled::I16;
	SAVE_THREAD_STATE();	/* inline! */
468

469
        jump stg_threadFinished;
470
471
    }

472
473
    /* Ok, Sp points to the enclosing CATCH_FRAME or CATCH_STM_FRAME.  Pop everything
     * down to and including this frame, update Su, push R1, and enter the handler.
474
     */
475
476
477
478
479
    if (frame_type == CATCH_FRAME) {
      handler = StgCatchFrame_handler(Sp);
    } else {
      handler = StgCatchSTMFrame_handler(Sp);
    }
480
481
482
483
484
485
486

    /* Restore the blocked/unblocked state for asynchronous exceptions
     * at the CATCH_FRAME.  
     *
     * If exceptions were unblocked, arrange that they are unblocked
     * again after executing the handler by pushing an
     * unblockAsyncExceptions_ret stack frame.
tharris@microsoft.com's avatar
tharris@microsoft.com committed
487
488
489
     *
     * If we've reached an STM catch frame then roll back the nested
     * transaction we were using.
490
491
492
     */
    W_ frame;
    frame = Sp;
493
494
495
496
497
498
499
    if (frame_type == CATCH_FRAME) {
      Sp = Sp + SIZEOF_StgCatchFrame;
      if (StgCatchFrame_exceptions_blocked(frame) == 0) {
        Sp_adj(-1);
        Sp(0) = stg_unblockAsyncExceptionszh_ret_info;
      }
    } else {
tharris@microsoft.com's avatar
tharris@microsoft.com committed
500
501
      W_ trec, outer;
      trec = StgTSO_trec(CurrentTSO);
502
      ("ptr" outer) = foreign "C" stmGetEnclosingTRec(trec "ptr") [];
tharris@microsoft.com's avatar
tharris@microsoft.com committed
503
504
505
      foreign "C" stmAbortTransaction(MyCapability() "ptr", trec "ptr") [];
      foreign "C" stmFreeAbortedTRec(MyCapability() "ptr", trec "ptr") [];
      StgTSO_trec(CurrentTSO) = outer;
506
      Sp = Sp + SIZEOF_StgCatchSTMFrame;
507
508
509
510
    }

    /* Ensure that async excpetions are blocked when running the handler.
    */
511
512
    StgTSO_flags(CurrentTSO) = 
	StgTSO_flags(CurrentTSO) | TSO_BLOCKEX::I32 | TSO_INTERRUPTIBLE::I32;
513
514
515
516
517

    /* Call the handler, passing the exception value and a realworld
     * token as arguments.
     */
    Sp_adj(-1);
518
    Sp(0) = exception;
519
520
521
522
523
524
525
526
527
528
529
530
    R1 = handler;
    Sp_adj(-1);
    TICK_UNKNOWN_CALL();
    TICK_SLOW_CALL_pv();
    jump RET_LBL(stg_ap_pv);
}

raiseIOzh_fast
{
  /* Args :: R1 :: Exception */
  jump raisezh_fast;
}