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