| 473 | | The last tricky part is how we resume execution of a thread after a breakpoint. This |
| | 473 | The last tricky part is how we resume execution of a thread after a breakpoint. This is the purpose of the fourth argument of `Runbreak`: |
| | 474 | {{{ |
| | 475 | resume :: IO RunResult |
| | 476 | }}} |
| | 477 | As you can see it is an I/O action that will eventually yield a `RunResult`. This accords with our intuition that, at least for terminating computations, we will get another `RunResult` if we execute this thing. It could be another breakpoint, or it may be a final value. `resume` is defined like so: |
| | 478 | {{{ |
| | 479 | do stablePtr <- newStablePtr onBreakAction |
| | 480 | poke breakPointIOAction stablePtr |
| | 481 | putMVar breakMVar () |
| | 482 | status <- takeMVar statusMVar |
| | 483 | switchOnStatus ref new_hsc_env names status |
| | 484 | }}} |
| | 485 | The first thing it does is set up the `onBreakAction` global variable. Then it writes to the `breakMVar` which wakes up the blocked expression thread. Then it waits for `statusMVar` to be filled in again. Eventually when we get a status value, we call the `switchInStatus` function to decide what to do (either we hit another breakpoint, or we completed). |
| | 486 | |
| | 487 | I've been a bit crafty in my implementation, and you will notice that `resume` and `onBreakAction` are mutually recursive. So in `main/GHC.runStmt` you will see them defined like this: |
| | 488 | {{{ |
| | 489 | let (resume, onBreakAction) |
| | 490 | = ( ..., ...) |
| | 491 | }}} |
| | 492 | The reason I did it this way is because they need to share their own versions of `breakMVar` and `statusMVar`. This must be understood in the context that we can have nested breakpoints. By writing them in this mutually recursive fashion, we can have multiple (`resume`, `onBreaAction`) pairs, and that they don't get their MVars mixed up. |
| | 493 | |