[Git][ghc/ghc][wip/romes/exec-break-has-resume] interp: Make resumeExec and ExecBreak use `Resume` directly
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 interp: Make resumeExec and ExecBreak use `Resume` directly Previously, `Resume` contexts were just put directly in the global interactive environment state, so they were only accessible through it. However, in the context of multi-threaded debugging, in that many threads may hit breakpoints, it no longer suffices to have a global linear list of breakpoints hit. We need a per-breakpoint Resume context and need to be able to wield it directly rather than going through a global context. The driving changes of this commit are: - Put the Resume data in ExecBreak - Make resumeExec receive a Resume as an argument This also simplifies the interpreter in that we no longer necessarily rely on the global interactive state to resume a thread. We can do it directly from the ExecBreak. The interactive context still maintains a stack of resumes, but `resumeExec` can no longer pop the resume it resumes off of the breakpoints stack, since it is no longer guaranteed to be resuming the first in that list. Hence, callers of `resumeExec` must update ic_resume on their own. Fixes #28058 - - - - - 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: ===================================== changelog.d/ghc-api-exec-break-resume ===================================== @@ -0,0 +1,14 @@ +section: ghc-lib +synopsis: An ``ExecBreak`` now stores its ``Resume`` and ``resumeExec`` receives it explicitly +issues: #28058 +mrs: !16889 + +description: { +- ``ExecBreak`` has a new field ``breakResume :: Resume`` with the context needed + to resume the thread stopped at that breakpoint. +- ``resumeExec`` takes a specific ``Resume`` to continue as an extra argument, + instead of resuming the most recent breakpoint in ``ic_resume``. +- ``resumeExec`` no longer pops the ``Resume`` off ``ic_resume``. Callers that + rely on the ``ic_resume`` stack must now update it themselves, for instance + if if calling ``resumeExec`` on the first ``Resume`` in the context. +} ===================================== compiler/GHC/Runtime/Eval.hs ===================================== @@ -290,7 +290,8 @@ emptyHistory size = nilBL size -- breakpoint if we don't care about that breakpoint (e.g. if using :steplocal -- or :stepmodule, rather than :step, we only care about certain breakpoints). handleRunStatus :: GhcMonad m - => SingleStep -> String + => SingleStep + -> String -> ResumeBindings -> [Id] -> EvalStatus_ [ForeignHValue] [HValueRef] @@ -343,7 +344,7 @@ handleRunStatus step expr bindings final_ids status history0 = do hsc_env2 = pushResume hsc_env1 resume setSession hsc_env2 - return (ExecBreak break_ids Nothing) + return (ExecBreak break_ids Nothing resume) -- EvalBreak (Just ...) case: the interpreter stopped at a breakpoint -- @@ -389,7 +390,7 @@ handleRunStatus step expr bindings final_ids status history0 = do } hsc_env2 = pushResume hsc_env1 resume setSession hsc_env2 - return (ExecBreak break_ids (Just ibi)) + return (ExecBreak break_ids (Just ibi) resume) else do -- resume with the same step type let eval_opts = initEvalOpts dflags (enableGhcStepMode step) @@ -404,65 +405,67 @@ handleRunStatus step expr bindings final_ids status history0 = do tracing | RunAndLogSteps <- step = True | otherwise = False -resumeExec :: GhcMonad m => SingleStep -> Maybe Int +-- | Resume a paused thread given its 'Resume' context. +-- +-- The interactive things in scope are restored to the things available at the +-- time this paused thread hit the breakpoint. +-- +-- This 'Resume' is not popped off the interactive `ic_resume` breakpoint +-- stack, since there's no way to tell which of the breakpoints in the stack is +-- the one we're resuming. +resumeExec :: GhcMonad m + => SingleStep + -> Maybe Int + -> Resume -> m ExecResult -resumeExec step mbCnt - = do - hsc_env <- getSession - let ic = hsc_IC hsc_env - resume = ic_resume ic - - case resume of - [] -> liftIO $ - throwGhcExceptionIO (ProgramError "not stopped at a breakpoint") - (r:rs) -> do - -- unbind the temporary locals by restoring the TypeEnv from - -- before the breakpoint, and drop this Resume from the - -- InteractiveContext. - let (resume_tmp_te,resume_gre_cache) = resumeBindings r - ic' = ic { ic_tythings = resume_tmp_te, - ic_gre_cache = resume_gre_cache, - ic_resume = rs } - setSession hsc_env{ hsc_IC = ic' } - - -- remove any bindings created since the breakpoint from the - -- linker's environment - let old_names = map getName resume_tmp_te - new_names = [ n | thing <- ic_tythings ic - , let n = getName thing - , not (n `elem` old_names) ] - interp = hscInterp hsc_env - dflags = hsc_dflags hsc_env - liftIO $ Loader.deleteFromLoadedHomeEnv interp new_names - - case r of - Resume { resumeStmt = expr - , resumeContext = fhv - , resumeBindings = bindings - , resumeFinalIds = final_ids - , resumeApStack = apStack - , resumeBreakpointId = mb_brkpt - , resumeSpan = span - , resumeHistory = hist } -> - do - -- When the user specified a break ignore count, set it - -- in the interpreter - case (mb_brkpt, mbCnt) of - (Just brkpt, Just cnt) -> setupBreakpoint interp brkpt cnt - _ -> return () - - let eval_opts = initEvalOpts dflags (enableGhcStepMode step) - status <- liftIO $ GHCi.resumeStmt interp eval_opts fhv - let prevHistoryLst = fromListBL 50 hist - hug = hsc_HUG hsc_env - hist' = case mb_brkpt of - Nothing -> pure prevHistoryLst - Just bi - | breakHere False step span -> do - hist1 <- liftIO (mkHistory hug apStack bi) - return $ hist1 `consBL` fromListBL 50 hist - | otherwise -> pure prevHistoryLst - handleRunStatus step expr bindings final_ids status =<< hist' +resumeExec step mbCnt r = do + hsc_env <- getSession + let ic = hsc_IC hsc_env + -- unbind the temporary locals by restoring the TypeEnv from + -- before the breakpoint + let (resume_tmp_te,resume_gre_cache) = resumeBindings r + ic' = ic { ic_tythings = resume_tmp_te, + ic_gre_cache = resume_gre_cache } + setSession hsc_env{ hsc_IC = ic' } + + -- remove any bindings created since the breakpoint from the + -- linker's environment + let old_names = map getName resume_tmp_te + new_names = [ n | thing <- ic_tythings ic + , let n = getName thing + , not (n `elem` old_names) ] + interp = hscInterp hsc_env + dflags = hsc_dflags hsc_env + liftIO $ Loader.deleteFromLoadedHomeEnv interp new_names + + case r of + Resume { resumeStmt = expr + , resumeContext = fhv + , resumeBindings = bindings + , resumeFinalIds = final_ids + , resumeApStack = apStack + , resumeBreakpointId = mb_brkpt + , resumeSpan = span + , resumeHistory = hist } -> + do + -- When the user specified a break ignore count, set it + -- in the interpreter + case (mb_brkpt, mbCnt) of + (Just brkpt, Just cnt) -> setupBreakpoint interp brkpt cnt + _ -> return () + + let eval_opts = initEvalOpts dflags (enableGhcStepMode step) + status <- liftIO $ GHCi.resumeStmt interp eval_opts fhv + let prevHistoryLst = fromListBL 50 hist + hug = hsc_HUG hsc_env + hist' = case mb_brkpt of + Nothing -> pure prevHistoryLst + Just bi + | breakHere False step span -> do + hist1 <- liftIO (mkHistory hug apStack bi) + return $ hist1 `consBL` fromListBL 50 hist + | otherwise -> pure prevHistoryLst + handleRunStatus step expr bindings final_ids status =<< hist' setupBreakpoint :: GhcMonad m => Interp -> InternalBreakpointId -> Int -> m () -- #19157 setupBreakpoint interp ibi cnt = do ===================================== compiler/GHC/Runtime/Eval/Types.hs ===================================== @@ -162,6 +162,8 @@ data ExecResult | ExecBreak { breakNames :: [Id] , breakPointId :: Maybe InternalBreakpointId + , breakResume :: Resume + -- ^ to resume this thread blocked on this break } -- | Essentially a GlobalRdrEnv, but with additional cached values to allow ===================================== ghc/GHCi/UI.hs ===================================== @@ -1618,7 +1618,7 @@ runStmt input step = do m_result <- GhciMonad.runStmt stmt input step case m_result of Nothing -> return Nothing - Just result -> Just <$> afterRunStmt step (RunStmtResult result) + Just result -> Just <$> afterRunStmt (RunStmtResult result) -- `x = y` (a declaration) should be treated as `let x = y` (a statement). -- The reason is because GHCi wasn't designed to support `x = y`, but then @@ -1650,7 +1650,7 @@ runStmt input step = do _ <- liftIO $ tryIO $ hFlushAll stdin m_result <- GhciMonad.runDecls' decls forM m_result $ \result -> do - afterRunStmt step (RunDeclsResult result) + afterRunStmt (RunDeclsResult result) mk_stmt :: SrcSpan -> HsBind GhcPs -> GhciLStmt GhcPs mk_stmt loc bind = @@ -1671,10 +1671,9 @@ data RunResult | RunDeclsResult [TyThing] -- | Clean up the GHCi environment after a statement has run -afterRunStmt :: GhciMonad m => SingleStep {-^ Type of step we took just before -} - -> RunResult -> m RunResult -afterRunStmt step run_result = do - resumes <- GHC.getResumeContext +afterRunStmt :: GhciMonad m + => RunResult -> m RunResult +afterRunStmt run_result = do case run_result of RunDeclsResult things -> do show_types <- isOptionSet ShowType @@ -1685,20 +1684,15 @@ afterRunStmt step run_result = do Right ids -> do show_types <- isOptionSet ShowType when show_types $ printTypeOfIds ids - RunStmtResult (GHC.ExecBreak ids mb_info) - | first_resume : _ <- resumes - -> do mb_id_loc <- toBreakIdAndLocation mb_info - let bCmd = maybe "" ( \(_,l) -> onBreakCmd l ) mb_id_loc - if (null bCmd) - then printStoppedAtBreakInfo first_resume ids - else enqueueCommands [bCmd] - -- run the command set with ":set stop <cmd>" - st <- getGHCiState - enqueueCommands [stop st] - return () - - | otherwise -> resume step Nothing >>= - afterRunStmt step . RunStmtResult >> return () + RunStmtResult (GHC.ExecBreak ids mb_info resume) -> do + mb_id_loc <- toBreakIdAndLocation mb_info + let bCmd = maybe "" ( \(_,l) -> onBreakCmd l ) mb_id_loc + if (null bCmd) + then printStoppedAtBreakInfo resume ids + else enqueueCommands [bCmd] + -- run the command set with ":set stop <cmd>" + st <- getGHCiState + enqueueCommands [stop st] flushInterpBuffers withSignalHandlers $ do @@ -4401,7 +4395,7 @@ doContinue step = doContinue' step Nothing doContinue' :: GhciMonad m => SingleStep -> Maybe Int -> m () doContinue' step mbCnt= do runResult <- resume step mbCnt - _ <- afterRunStmt step (RunStmtResult runResult) + _ <- afterRunStmt (RunStmtResult runResult) return () abandonCmd :: GhciMonad m => String -> m () ===================================== ghc/GHCi/UI/Monad.hs ===================================== @@ -38,6 +38,7 @@ import GHCi.UI.Info (ModInfo) import qualified GHC import GHC.Driver.Monad hiding (liftIO) import GHC.Utils.Outputable +import GHC.Utils.Panic import qualified GHC.Driver.Ppr as Ppr import GHC.Types.Name.Occurrence import GHC.Driver.Session @@ -412,7 +413,12 @@ resume step mbIgnoreCnt = do withProgName (progname st) $ withArgs (args st) $ reflectGHCi x $ do - GHC.resumeExec step mbIgnoreCnt + GHC.getResumeContext >>= \case + [] -> liftIO $ + throwGhcExceptionIO (GHC.ProgramError "not stopped at a breakpoint") + (r:rs) -> do + modifySession $ \env -> env{hsc_IC = (hsc_IC env){ ic_resume = rs }} + GHC.resumeExec step mbIgnoreCnt r -- -------------------------------------------------------------------------- -- timing & statistics View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/e87649c0c4416c68e885d37165d8200e... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/e87649c0c4416c68e885d37165d8200e... You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
participants (1)
-
Rodrigo Mesquita (@alt-romes)