Rodrigo Mesquita pushed to branch wip/romes/exec-break-has-resume at Glasgow Haskell Compiler / GHC
Commits:
-
e87649c0
by Rodrigo Mesquita at 2026-11-02T15:03:34+00:00
5 changed files:
- + changelog.d/ghc-api-exec-break-resume
- compiler/GHC/Runtime/Eval.hs
- compiler/GHC/Runtime/Eval/Types.hs
- ghc/GHCi/UI.hs
- ghc/GHCi/UI/Monad.hs
Changes:
| 1 | +section: ghc-lib
|
|
| 2 | +synopsis: An ``ExecBreak`` now stores its ``Resume`` and ``resumeExec`` receives it explicitly
|
|
| 3 | +issues: #28058
|
|
| 4 | +mrs: !16889
|
|
| 5 | + |
|
| 6 | +description: {
|
|
| 7 | +- ``ExecBreak`` has a new field ``breakResume :: Resume`` with the context needed
|
|
| 8 | + to resume the thread stopped at that breakpoint.
|
|
| 9 | +- ``resumeExec`` takes a specific ``Resume`` to continue as an extra argument,
|
|
| 10 | + instead of resuming the most recent breakpoint in ``ic_resume``.
|
|
| 11 | +- ``resumeExec`` no longer pops the ``Resume`` off ``ic_resume``. Callers that
|
|
| 12 | + rely on the ``ic_resume`` stack must now update it themselves, for instance
|
|
| 13 | + if if calling ``resumeExec`` on the first ``Resume`` in the context.
|
|
| 14 | +} |
| ... | ... | @@ -290,7 +290,8 @@ emptyHistory size = nilBL size |
| 290 | 290 | -- breakpoint if we don't care about that breakpoint (e.g. if using :steplocal
|
| 291 | 291 | -- or :stepmodule, rather than :step, we only care about certain breakpoints).
|
| 292 | 292 | handleRunStatus :: GhcMonad m
|
| 293 | - => SingleStep -> String
|
|
| 293 | + => SingleStep
|
|
| 294 | + -> String
|
|
| 294 | 295 | -> ResumeBindings
|
| 295 | 296 | -> [Id]
|
| 296 | 297 | -> EvalStatus_ [ForeignHValue] [HValueRef]
|
| ... | ... | @@ -343,7 +344,7 @@ handleRunStatus step expr bindings final_ids status history0 = do |
| 343 | 344 | hsc_env2 = pushResume hsc_env1 resume
|
| 344 | 345 | |
| 345 | 346 | setSession hsc_env2
|
| 346 | - return (ExecBreak break_ids Nothing)
|
|
| 347 | + return (ExecBreak break_ids Nothing resume)
|
|
| 347 | 348 | |
| 348 | 349 | -- EvalBreak (Just ...) case: the interpreter stopped at a breakpoint
|
| 349 | 350 | --
|
| ... | ... | @@ -389,7 +390,7 @@ handleRunStatus step expr bindings final_ids status history0 = do |
| 389 | 390 | }
|
| 390 | 391 | hsc_env2 = pushResume hsc_env1 resume
|
| 391 | 392 | setSession hsc_env2
|
| 392 | - return (ExecBreak break_ids (Just ibi))
|
|
| 393 | + return (ExecBreak break_ids (Just ibi) resume)
|
|
| 393 | 394 | else do
|
| 394 | 395 | -- resume with the same step type
|
| 395 | 396 | let eval_opts = initEvalOpts dflags (enableGhcStepMode step)
|
| ... | ... | @@ -404,65 +405,67 @@ handleRunStatus step expr bindings final_ids status history0 = do |
| 404 | 405 | tracing | RunAndLogSteps <- step = True
|
| 405 | 406 | | otherwise = False
|
| 406 | 407 | |
| 407 | -resumeExec :: GhcMonad m => SingleStep -> Maybe Int
|
|
| 408 | +-- | Resume a paused thread given its 'Resume' context.
|
|
| 409 | +--
|
|
| 410 | +-- The interactive things in scope are restored to the things available at the
|
|
| 411 | +-- time this paused thread hit the breakpoint.
|
|
| 412 | +--
|
|
| 413 | +-- This 'Resume' is not popped off the interactive `ic_resume` breakpoint
|
|
| 414 | +-- stack, since there's no way to tell which of the breakpoints in the stack is
|
|
| 415 | +-- the one we're resuming.
|
|
| 416 | +resumeExec :: GhcMonad m
|
|
| 417 | + => SingleStep
|
|
| 418 | + -> Maybe Int
|
|
| 419 | + -> Resume
|
|
| 408 | 420 | -> m ExecResult
|
| 409 | -resumeExec step mbCnt
|
|
| 410 | - = do
|
|
| 411 | - hsc_env <- getSession
|
|
| 412 | - let ic = hsc_IC hsc_env
|
|
| 413 | - resume = ic_resume ic
|
|
| 414 | - |
|
| 415 | - case resume of
|
|
| 416 | - [] -> liftIO $
|
|
| 417 | - throwGhcExceptionIO (ProgramError "not stopped at a breakpoint")
|
|
| 418 | - (r:rs) -> do
|
|
| 419 | - -- unbind the temporary locals by restoring the TypeEnv from
|
|
| 420 | - -- before the breakpoint, and drop this Resume from the
|
|
| 421 | - -- InteractiveContext.
|
|
| 422 | - let (resume_tmp_te,resume_gre_cache) = resumeBindings r
|
|
| 423 | - ic' = ic { ic_tythings = resume_tmp_te,
|
|
| 424 | - ic_gre_cache = resume_gre_cache,
|
|
| 425 | - ic_resume = rs }
|
|
| 426 | - setSession hsc_env{ hsc_IC = ic' }
|
|
| 427 | - |
|
| 428 | - -- remove any bindings created since the breakpoint from the
|
|
| 429 | - -- linker's environment
|
|
| 430 | - let old_names = map getName resume_tmp_te
|
|
| 431 | - new_names = [ n | thing <- ic_tythings ic
|
|
| 432 | - , let n = getName thing
|
|
| 433 | - , not (n `elem` old_names) ]
|
|
| 434 | - interp = hscInterp hsc_env
|
|
| 435 | - dflags = hsc_dflags hsc_env
|
|
| 436 | - liftIO $ Loader.deleteFromLoadedHomeEnv interp new_names
|
|
| 437 | - |
|
| 438 | - case r of
|
|
| 439 | - Resume { resumeStmt = expr
|
|
| 440 | - , resumeContext = fhv
|
|
| 441 | - , resumeBindings = bindings
|
|
| 442 | - , resumeFinalIds = final_ids
|
|
| 443 | - , resumeApStack = apStack
|
|
| 444 | - , resumeBreakpointId = mb_brkpt
|
|
| 445 | - , resumeSpan = span
|
|
| 446 | - , resumeHistory = hist } ->
|
|
| 447 | - do
|
|
| 448 | - -- When the user specified a break ignore count, set it
|
|
| 449 | - -- in the interpreter
|
|
| 450 | - case (mb_brkpt, mbCnt) of
|
|
| 451 | - (Just brkpt, Just cnt) -> setupBreakpoint interp brkpt cnt
|
|
| 452 | - _ -> return ()
|
|
| 453 | - |
|
| 454 | - let eval_opts = initEvalOpts dflags (enableGhcStepMode step)
|
|
| 455 | - status <- liftIO $ GHCi.resumeStmt interp eval_opts fhv
|
|
| 456 | - let prevHistoryLst = fromListBL 50 hist
|
|
| 457 | - hug = hsc_HUG hsc_env
|
|
| 458 | - hist' = case mb_brkpt of
|
|
| 459 | - Nothing -> pure prevHistoryLst
|
|
| 460 | - Just bi
|
|
| 461 | - | breakHere False step span -> do
|
|
| 462 | - hist1 <- liftIO (mkHistory hug apStack bi)
|
|
| 463 | - return $ hist1 `consBL` fromListBL 50 hist
|
|
| 464 | - | otherwise -> pure prevHistoryLst
|
|
| 465 | - handleRunStatus step expr bindings final_ids status =<< hist'
|
|
| 421 | +resumeExec step mbCnt r = do
|
|
| 422 | + hsc_env <- getSession
|
|
| 423 | + let ic = hsc_IC hsc_env
|
|
| 424 | + -- unbind the temporary locals by restoring the TypeEnv from
|
|
| 425 | + -- before the breakpoint
|
|
| 426 | + let (resume_tmp_te,resume_gre_cache) = resumeBindings r
|
|
| 427 | + ic' = ic { ic_tythings = resume_tmp_te,
|
|
| 428 | + ic_gre_cache = resume_gre_cache }
|
|
| 429 | + setSession hsc_env{ hsc_IC = ic' }
|
|
| 430 | + |
|
| 431 | + -- remove any bindings created since the breakpoint from the
|
|
| 432 | + -- linker's environment
|
|
| 433 | + let old_names = map getName resume_tmp_te
|
|
| 434 | + new_names = [ n | thing <- ic_tythings ic
|
|
| 435 | + , let n = getName thing
|
|
| 436 | + , not (n `elem` old_names) ]
|
|
| 437 | + interp = hscInterp hsc_env
|
|
| 438 | + dflags = hsc_dflags hsc_env
|
|
| 439 | + liftIO $ Loader.deleteFromLoadedHomeEnv interp new_names
|
|
| 440 | + |
|
| 441 | + case r of
|
|
| 442 | + Resume { resumeStmt = expr
|
|
| 443 | + , resumeContext = fhv
|
|
| 444 | + , resumeBindings = bindings
|
|
| 445 | + , resumeFinalIds = final_ids
|
|
| 446 | + , resumeApStack = apStack
|
|
| 447 | + , resumeBreakpointId = mb_brkpt
|
|
| 448 | + , resumeSpan = span
|
|
| 449 | + , resumeHistory = hist } ->
|
|
| 450 | + do
|
|
| 451 | + -- When the user specified a break ignore count, set it
|
|
| 452 | + -- in the interpreter
|
|
| 453 | + case (mb_brkpt, mbCnt) of
|
|
| 454 | + (Just brkpt, Just cnt) -> setupBreakpoint interp brkpt cnt
|
|
| 455 | + _ -> return ()
|
|
| 456 | + |
|
| 457 | + let eval_opts = initEvalOpts dflags (enableGhcStepMode step)
|
|
| 458 | + status <- liftIO $ GHCi.resumeStmt interp eval_opts fhv
|
|
| 459 | + let prevHistoryLst = fromListBL 50 hist
|
|
| 460 | + hug = hsc_HUG hsc_env
|
|
| 461 | + hist' = case mb_brkpt of
|
|
| 462 | + Nothing -> pure prevHistoryLst
|
|
| 463 | + Just bi
|
|
| 464 | + | breakHere False step span -> do
|
|
| 465 | + hist1 <- liftIO (mkHistory hug apStack bi)
|
|
| 466 | + return $ hist1 `consBL` fromListBL 50 hist
|
|
| 467 | + | otherwise -> pure prevHistoryLst
|
|
| 468 | + handleRunStatus step expr bindings final_ids status =<< hist'
|
|
| 466 | 469 | |
| 467 | 470 | setupBreakpoint :: GhcMonad m => Interp -> InternalBreakpointId -> Int -> m () -- #19157
|
| 468 | 471 | setupBreakpoint interp ibi cnt = do
|
| ... | ... | @@ -162,6 +162,8 @@ data ExecResult |
| 162 | 162 | | ExecBreak
|
| 163 | 163 | { breakNames :: [Id]
|
| 164 | 164 | , breakPointId :: Maybe InternalBreakpointId
|
| 165 | + , breakResume :: Resume
|
|
| 166 | + -- ^ to resume this thread blocked on this break
|
|
| 165 | 167 | }
|
| 166 | 168 | |
| 167 | 169 | -- | Essentially a GlobalRdrEnv, but with additional cached values to allow
|
| ... | ... | @@ -1618,7 +1618,7 @@ runStmt input step = do |
| 1618 | 1618 | m_result <- GhciMonad.runStmt stmt input step
|
| 1619 | 1619 | case m_result of
|
| 1620 | 1620 | Nothing -> return Nothing
|
| 1621 | - Just result -> Just <$> afterRunStmt step (RunStmtResult result)
|
|
| 1621 | + Just result -> Just <$> afterRunStmt (RunStmtResult result)
|
|
| 1622 | 1622 | |
| 1623 | 1623 | -- `x = y` (a declaration) should be treated as `let x = y` (a statement).
|
| 1624 | 1624 | -- The reason is because GHCi wasn't designed to support `x = y`, but then
|
| ... | ... | @@ -1650,7 +1650,7 @@ runStmt input step = do |
| 1650 | 1650 | _ <- liftIO $ tryIO $ hFlushAll stdin
|
| 1651 | 1651 | m_result <- GhciMonad.runDecls' decls
|
| 1652 | 1652 | forM m_result $ \result -> do
|
| 1653 | - afterRunStmt step (RunDeclsResult result)
|
|
| 1653 | + afterRunStmt (RunDeclsResult result)
|
|
| 1654 | 1654 | |
| 1655 | 1655 | mk_stmt :: SrcSpan -> HsBind GhcPs -> GhciLStmt GhcPs
|
| 1656 | 1656 | mk_stmt loc bind =
|
| ... | ... | @@ -1671,10 +1671,9 @@ data RunResult |
| 1671 | 1671 | | RunDeclsResult [TyThing]
|
| 1672 | 1672 | |
| 1673 | 1673 | -- | Clean up the GHCi environment after a statement has run
|
| 1674 | -afterRunStmt :: GhciMonad m => SingleStep {-^ Type of step we took just before -}
|
|
| 1675 | - -> RunResult -> m RunResult
|
|
| 1676 | -afterRunStmt step run_result = do
|
|
| 1677 | - resumes <- GHC.getResumeContext
|
|
| 1674 | +afterRunStmt :: GhciMonad m
|
|
| 1675 | + => RunResult -> m RunResult
|
|
| 1676 | +afterRunStmt run_result = do
|
|
| 1678 | 1677 | case run_result of
|
| 1679 | 1678 | RunDeclsResult things -> do
|
| 1680 | 1679 | show_types <- isOptionSet ShowType
|
| ... | ... | @@ -1685,20 +1684,15 @@ afterRunStmt step run_result = do |
| 1685 | 1684 | Right ids -> do
|
| 1686 | 1685 | show_types <- isOptionSet ShowType
|
| 1687 | 1686 | when show_types $ printTypeOfIds ids
|
| 1688 | - RunStmtResult (GHC.ExecBreak ids mb_info)
|
|
| 1689 | - | first_resume : _ <- resumes
|
|
| 1690 | - -> do mb_id_loc <- toBreakIdAndLocation mb_info
|
|
| 1691 | - let bCmd = maybe "" ( \(_,l) -> onBreakCmd l ) mb_id_loc
|
|
| 1692 | - if (null bCmd)
|
|
| 1693 | - then printStoppedAtBreakInfo first_resume ids
|
|
| 1694 | - else enqueueCommands [bCmd]
|
|
| 1695 | - -- run the command set with ":set stop <cmd>"
|
|
| 1696 | - st <- getGHCiState
|
|
| 1697 | - enqueueCommands [stop st]
|
|
| 1698 | - return ()
|
|
| 1699 | - |
|
| 1700 | - | otherwise -> resume step Nothing >>=
|
|
| 1701 | - afterRunStmt step . RunStmtResult >> return ()
|
|
| 1687 | + RunStmtResult (GHC.ExecBreak ids mb_info resume) -> do
|
|
| 1688 | + mb_id_loc <- toBreakIdAndLocation mb_info
|
|
| 1689 | + let bCmd = maybe "" ( \(_,l) -> onBreakCmd l ) mb_id_loc
|
|
| 1690 | + if (null bCmd)
|
|
| 1691 | + then printStoppedAtBreakInfo resume ids
|
|
| 1692 | + else enqueueCommands [bCmd]
|
|
| 1693 | + -- run the command set with ":set stop <cmd>"
|
|
| 1694 | + st <- getGHCiState
|
|
| 1695 | + enqueueCommands [stop st]
|
|
| 1702 | 1696 | |
| 1703 | 1697 | flushInterpBuffers
|
| 1704 | 1698 | withSignalHandlers $ do
|
| ... | ... | @@ -4401,7 +4395,7 @@ doContinue step = doContinue' step Nothing |
| 4401 | 4395 | doContinue' :: GhciMonad m => SingleStep -> Maybe Int -> m ()
|
| 4402 | 4396 | doContinue' step mbCnt= do
|
| 4403 | 4397 | runResult <- resume step mbCnt
|
| 4404 | - _ <- afterRunStmt step (RunStmtResult runResult)
|
|
| 4398 | + _ <- afterRunStmt (RunStmtResult runResult)
|
|
| 4405 | 4399 | return ()
|
| 4406 | 4400 | |
| 4407 | 4401 | abandonCmd :: GhciMonad m => String -> m ()
|
| ... | ... | @@ -38,6 +38,7 @@ import GHCi.UI.Info (ModInfo) |
| 38 | 38 | import qualified GHC
|
| 39 | 39 | import GHC.Driver.Monad hiding (liftIO)
|
| 40 | 40 | import GHC.Utils.Outputable
|
| 41 | +import GHC.Utils.Panic
|
|
| 41 | 42 | import qualified GHC.Driver.Ppr as Ppr
|
| 42 | 43 | import GHC.Types.Name.Occurrence
|
| 43 | 44 | import GHC.Driver.Session
|
| ... | ... | @@ -412,7 +413,12 @@ resume step mbIgnoreCnt = do |
| 412 | 413 | withProgName (progname st) $
|
| 413 | 414 | withArgs (args st) $
|
| 414 | 415 | reflectGHCi x $ do
|
| 415 | - GHC.resumeExec step mbIgnoreCnt
|
|
| 416 | + GHC.getResumeContext >>= \case
|
|
| 417 | + [] -> liftIO $
|
|
| 418 | + throwGhcExceptionIO (GHC.ProgramError "not stopped at a breakpoint")
|
|
| 419 | + (r:rs) -> do
|
|
| 420 | + modifySession $ \env -> env{hsc_IC = (hsc_IC env){ ic_resume = rs }}
|
|
| 421 | + GHC.resumeExec step mbIgnoreCnt r
|
|
| 416 | 422 | |
| 417 | 423 | -- --------------------------------------------------------------------------
|
| 418 | 424 | -- timing & statistics
|