ghci debugger: improve break/resume control flow
After interpreting bytecode (`evalStmt`), we may want to hand off
control to "GHCi.UI" in order to display an interactive break prompt:
1. When an /active/ breakpoint (one set with :break ...) is hit
2. At any breakpoint, when using :step from a breakpoint
3. At any breakpoint in the same function f, when :steplocal is called
from a breakpoint in f
4. At any breakpoint in the same module, when :stepmodule is used
Whether to pass control to the UI is now fully determined by
`handleRunStatus` which transforms an `EvalStatus_` into an
`ExecResult`. When `ExecBreak` is returned from `handleRunStatus` to
GHCi, it always means GHCi breaks.
`handleRunStatus` determines whether to loop and resume evaluation right away, or
when to return to GHCi (by returning `ExecBreak` or `ExecComplete`).
- (1) is queried using the `BreakpointStatus` message (the
`breakpointStatus` call)
- (2,3,4) are determined by the predicate `breakHere step span`, which
inspects the improved `SingleStep` type to determine whether we care
about this breakpoint even if it is not active.
This refactor solves two big performance problems with the previous control flow:
- We no longer call `withArgs/withProgram` repeatedly in the
break/resume loop, but rather just once "at the top".
- We now avoid computing the expensive `bindLocalsAtBreakpoint` for
breakpoints we'd never inspect.
In the test added, calling `:steplocal` after breaking on `main = fib 25`
now takes 12 seconds rather than 49 seconds on my machine.
Fixes #25779
Edited by Rodrigo Mesquita