Rodrigo Mesquita pushed to branch wip/romes/multi-threaded-debugger at Glasgow Haskell Compiler / GHC
Commits:
-
b7aee273
by Rodrigo Mesquita at 2026-09-04T10:39:49+01:00
-
8dafed3e
by Rodrigo Mesquita at 2026-09-04T17:10:33+01:00
-
c43cb731
by Rodrigo Mesquita at 2026-09-04T17:29:40+01:00
-
f7a22268
by Rodrigo Mesquita at 2026-09-04T17:38:20+01:00
9 changed files:
- compiler/GHC/Runtime/Debugger.hs
- compiler/GHC/Runtime/Eval.hs
- compiler/GHC/Runtime/Eval/Types.hs
- compiler/GHC/Runtime/Heap/Inspect.hs
- ghc/GHCi/UI.hs
- ghc/GHCi/UI/Monad.hs
- libraries/ghci/GHCi/Run.hs
- + libraries/ghci/GHCi/Run/Breakpoints.hs
- libraries/ghci/ghci.cabal.in
Changes:
| ... | ... | @@ -121,8 +121,7 @@ pprintClosureCommand bindThings force str = do |
| 121 | 121 | -- Then, we extract a substitution,
|
| 122 | 122 | -- mapping the old tyvars to the reconstructed types.
|
| 123 | 123 | let reconstructed_type = termType term
|
| 124 | - hsc_env <- getSession
|
|
| 125 | - case (improveRTTIType hsc_env id_ty' reconstructed_type) of
|
|
| 124 | + case improveRTTIType id_ty' reconstructed_type of
|
|
| 126 | 125 | Nothing -> return (subst, term')
|
| 127 | 126 | Just subst' -> do { logger <- getLogger
|
| 128 | 127 | ; liftIO $
|
| ... | ... | @@ -131,7 +131,7 @@ import GHC.ByteCode.Breakpoints |
| 131 | 131 | import Control.Monad
|
| 132 | 132 | import Data.Dynamic
|
| 133 | 133 | import Data.IntMap (IntMap)
|
| 134 | -import Data.List (find,intercalate)
|
|
| 134 | +import Data.List (intercalate)
|
|
| 135 | 135 | import Data.List.NonEmpty (NonEmpty)
|
| 136 | 136 | import Unsafe.Coerce ( unsafeCoerce )
|
| 137 | 137 | import qualified GHC.Unit.Home.Graph as HUG
|
| ... | ... | @@ -244,12 +244,12 @@ execStmt' stmt stmt_text ExecOptions{..} = do |
| 244 | 244 | handleRunStatus execSingleStep execIsolateMode stmt_text bindings ids
|
| 245 | 245 | status (emptyHistory size)
|
| 246 | 246 | |
| 247 | -runDecls :: GhcMonad m => String -> m [Name]
|
|
| 247 | +runDecls :: GhcMonad m => String -> m [TyThing]
|
|
| 248 | 248 | runDecls = runDeclsWithLocation "<interactive>" 1
|
| 249 | 249 | |
| 250 | 250 | -- | Run some declarations and return any user-visible names that were brought
|
| 251 | 251 | -- into scope.
|
| 252 | -runDeclsWithLocation :: GhcMonad m => String -> Int -> String -> m [Name]
|
|
| 252 | +runDeclsWithLocation :: GhcMonad m => String -> Int -> String -> m [TyThing]
|
|
| 253 | 253 | runDeclsWithLocation source line_num input = do
|
| 254 | 254 | hsc_env <- getSession
|
| 255 | 255 | decls <- liftIO (hscParseDeclsWithLocation hsc_env source line_num input)
|
| ... | ... | @@ -258,18 +258,17 @@ runDeclsWithLocation source line_num input = do |
| 258 | 258 | -- | Like `runDeclsWithLocation`, but takes parsed declarations as argument.
|
| 259 | 259 | -- Useful when doing preprocessing on the AST before execution, e.g. in GHCi
|
| 260 | 260 | -- (see GHCi.UI.runStmt).
|
| 261 | -runParsedDecls :: GhcMonad m => [LHsDecl GhcPs] -> m [Name]
|
|
| 261 | +runParsedDecls :: GhcMonad m => [LHsDecl GhcPs] -> m [TyThing]
|
|
| 262 | 262 | runParsedDecls decls = do
|
| 263 | - hsc_env <- getSession
|
|
| 264 | - (tyThings, ic) <- liftIO (hscParsedDecls hsc_env decls)
|
|
| 265 | - |
|
| 263 | + hsc_env <- getSession
|
|
| 264 | + (tyThings, ic) <- liftIO $ hscParsedDecls hsc_env decls
|
|
| 266 | 265 | setSession $ hsc_env { hsc_IC = ic }
|
| 267 | 266 | hsc_env <- getSession
|
| 268 | 267 | hsc_env' <- liftIO $ rttiEnvironment hsc_env
|
| 269 | 268 | setSession hsc_env'
|
| 270 | - return $ filter (not . isDerivedOccName . nameOccName)
|
|
| 269 | + return $ filter (not . isDerivedOccName . nameOccName . getName)
|
|
| 271 | 270 | -- For this filter, see Note [What to show to users]
|
| 272 | - $ map getName tyThings
|
|
| 271 | + $ tyThings
|
|
| 273 | 272 | |
| 274 | 273 | {- Note [What to show to users]
|
| 275 | 274 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| ... | ... | @@ -316,7 +315,8 @@ handleRunStatus step isolateMode expr bindings final_ids status history0 = do |
| 316 | 315 | liftIO $ Loader.extendLoadedEnv interp modifyHomePackageBytecodeState (zip final_names hvals)
|
| 317 | 316 | hsc_env' <- liftIO $ rttiEnvironment hsc_env{hsc_IC=final_ic}
|
| 318 | 317 | setSession hsc_env'
|
| 319 | - return (ExecComplete (Right final_names) allocs)
|
|
| 318 | + let improved_final_ids = filter (`elem` final_ids) [ i | AnId i <- ic_tythings $ hsc_IC hsc_env' ] -- just to test
|
|
| 319 | + return (ExecComplete (Right improved_final_ids {- TODO: THESE MUST BE IMPROVED BEFORE RETURNED -}) allocs)
|
|
| 320 | 320 | |
| 321 | 321 | -- Completed with an exception
|
| 322 | 322 | EvalComplete alloc (EvalException e) ->
|
| ... | ... | @@ -327,7 +327,7 @@ handleRunStatus step isolateMode expr bindings final_ids status history0 = do |
| 327 | 327 | resume_ctxt_fhv <- liftIO $ mkFinalizedHValue interp resume_ctxt
|
| 328 | 328 | apStack_fhv <- liftIO $ mkFinalizedHValue interp apStack_ref
|
| 329 | 329 | let span = mkGeneralSrcSpan (fsLit "<unknown>")
|
| 330 | - (hsc_env1, names) <- liftIO $
|
|
| 330 | + (hsc_env1, break_ids) <- liftIO $
|
|
| 331 | 331 | bindLocalsAtBreakpoint hsc_env apStack_fhv span Nothing
|
| 332 | 332 | let
|
| 333 | 333 | resume = Resume
|
| ... | ... | @@ -346,7 +346,7 @@ handleRunStatus step isolateMode expr bindings final_ids status history0 = do |
| 346 | 346 | hsc_env2 = pushResume hsc_env1 resume
|
| 347 | 347 | |
| 348 | 348 | setSession hsc_env2
|
| 349 | - return (ExecBreak names Nothing)
|
|
| 349 | + return (ExecBreak break_ids Nothing resume)
|
|
| 350 | 350 | |
| 351 | 351 | -- EvalBreak (Just ...) case: the interpreter stopped at a breakpoint
|
| 352 | 352 | --
|
| ... | ... | @@ -374,7 +374,7 @@ handleRunStatus step isolateMode expr bindings final_ids status history0 = do |
| 374 | 374 | -- This function only returns control to ghci with 'ExecBreak' when it is really meant to break.
|
| 375 | 375 | -- Specifically, for :steplocal or :stepmodule, don't return control
|
| 376 | 376 | -- and simply resume execution from here until we hit a breakpoint we do want to stop at.
|
| 377 | - (hsc_env1, names) <- liftIO $
|
|
| 377 | + (hsc_env1, final_ids) <- liftIO $
|
|
| 378 | 378 | bindLocalsAtBreakpoint hsc_env apStack_fhv span (Just ibi)
|
| 379 | 379 | let
|
| 380 | 380 | resume = Resume
|
| ... | ... | @@ -392,7 +392,7 @@ handleRunStatus step isolateMode expr bindings final_ids status history0 = do |
| 392 | 392 | }
|
| 393 | 393 | hsc_env2 = pushResume hsc_env1 resume
|
| 394 | 394 | setSession hsc_env2
|
| 395 | - return (ExecBreak names (Just ibi))
|
|
| 395 | + return (ExecBreak final_ids (Just ibi) resume)
|
|
| 396 | 396 | else do
|
| 397 | 397 | -- resume with the same step type
|
| 398 | 398 | let eval_opts = (initEvalOpts dflags (enableGhcStepMode step))
|
| ... | ... | @@ -500,13 +500,13 @@ getBreakArray interp InternalBreakpointId{ibi_info_mod} imbs = do |
| 500 | 500 | ( ld_st'
|
| 501 | 501 | , ba
|
| 502 | 502 | )
|
| 503 | -back :: GhcMonad m => Int -> m ([Name], Int, SrcSpan)
|
|
| 503 | +back :: GhcMonad m => Int -> m ([Id], Int, SrcSpan)
|
|
| 504 | 504 | back n = moveHist (+n)
|
| 505 | 505 | |
| 506 | -forward :: GhcMonad m => Int -> m ([Name], Int, SrcSpan)
|
|
| 506 | +forward :: GhcMonad m => Int -> m ([Id], Int, SrcSpan)
|
|
| 507 | 507 | forward n = moveHist (subtract n)
|
| 508 | 508 | |
| 509 | -moveHist :: GhcMonad m => (Int -> Int) -> m ([Name], Int, SrcSpan)
|
|
| 509 | +moveHist :: GhcMonad m => (Int -> Int) -> m ([Id], Int, SrcSpan)
|
|
| 510 | 510 | moveHist fn = do
|
| 511 | 511 | hsc_env <- getSession
|
| 512 | 512 | case ic_resume (hsc_IC hsc_env) of
|
| ... | ... | @@ -530,7 +530,7 @@ moveHist fn = do |
| 530 | 530 | let hug = hsc_HUG hsc_env
|
| 531 | 531 | brks <- readIModBreaks hug ibi
|
| 532 | 532 | getBreakLoc (readIModModBreaks hug) ibi brks
|
| 533 | - (hsc_env1, names) <-
|
|
| 533 | + (hsc_env1, final_ids) <-
|
|
| 534 | 534 | liftIO $ bindLocalsAtBreakpoint hsc_env apStack span mb_info
|
| 535 | 535 | let ic = hsc_IC hsc_env1
|
| 536 | 536 | r' = r { resumeHistoryIx = new_ix }
|
| ... | ... | @@ -538,7 +538,7 @@ moveHist fn = do |
| 538 | 538 | |
| 539 | 539 | setSession hsc_env1{ hsc_IC = ic' }
|
| 540 | 540 | |
| 541 | - return (names, new_ix, span)
|
|
| 541 | + return (final_ids, new_ix, span)
|
|
| 542 | 542 | |
| 543 | 543 | -- careful: we want apStack to be the AP_STACK itself, not a thunk
|
| 544 | 544 | -- around it, hence the cases are carefully constructed below to
|
| ... | ... | @@ -564,7 +564,7 @@ bindLocalsAtBreakpoint |
| 564 | 564 | -> ForeignHValue
|
| 565 | 565 | -> SrcSpan
|
| 566 | 566 | -> Maybe InternalBreakpointId
|
| 567 | - -> IO (HscEnv, [Name])
|
|
| 567 | + -> IO (HscEnv, [Id])
|
|
| 568 | 568 | |
| 569 | 569 | -- Nothing case: we stopped when an exception was raised, not at a
|
| 570 | 570 | -- breakpoint. We have no location information or local variables to
|
| ... | ... | @@ -584,7 +584,7 @@ bindLocalsAtBreakpoint hsc_env apStack span Nothing = do |
| 584 | 584 | interp = hscInterp hsc_env
|
| 585 | 585 | --
|
| 586 | 586 | Loader.extendLoadedEnv interp modifyHomePackageBytecodeState [(exn_name, apStack)]
|
| 587 | - return (hsc_env{ hsc_IC = ictxt1 }, [exn_name])
|
|
| 587 | + return (hsc_env{ hsc_IC = ictxt1 }, [exn_id])
|
|
| 588 | 588 | |
| 589 | 589 | -- Just case: we stopped at a breakpoint, we have information about the location
|
| 590 | 590 | -- of the breakpoint and the free variables of the expression.
|
| ... | ... | @@ -645,7 +645,8 @@ bindLocalsAtBreakpoint hsc_env apStack_fhv span (Just ibi) = do |
| 645 | 645 | Loader.extendLoadedEnv interp modifyHomePackageBytecodeState (zip names fhvs)
|
| 646 | 646 | when result_ok $ Loader.extendLoadedEnv interp modifyHomePackageBytecodeState [(result_name, apStack_fhv)]
|
| 647 | 647 | hsc_env1 <- rttiEnvironment hsc_env{ hsc_IC = ictxt1 }
|
| 648 | - return (hsc_env1, if result_ok then result_name:names else names)
|
|
| 648 | + let improved_final_ids = filter (`elem` final_ids) [ i | AnId i <- ic_tythings $ hsc_IC hsc_env1 ] -- just to test
|
|
| 649 | + return (hsc_env1, improved_final_ids {-TODO: IMPROVE RTTI -})
|
|
| 649 | 650 | where
|
| 650 | 651 | -- We need a fresh Unique for each Id we bind, because the linker
|
| 651 | 652 | -- state is single-threaded and otherwise we'd spam old bindings
|
| ... | ... | @@ -692,12 +693,10 @@ rttiEnvironment hsc_env@HscEnv{hsc_IC=ic} = do |
| 692 | 693 | [id | id <- tmp_ids
|
| 693 | 694 | , not $ noSkolems id
|
| 694 | 695 | , (occNameFS.nameOccName.idName) id /= result_fs]
|
| 695 | - foldM improveTypes hsc_env (map idName incompletelyTypedIds)
|
|
| 696 | + foldM improveTypes hsc_env incompletelyTypedIds
|
|
| 696 | 697 | where
|
| 697 | 698 | noSkolems = noFreeVarsOfType . idType
|
| 698 | - improveTypes hsc_env@HscEnv{hsc_IC=ic} name = do
|
|
| 699 | - let tmp_ids = [id | AnId id <- ic_tythings ic]
|
|
| 700 | - id = expectJust $ find (\i -> idName i == name) tmp_ids
|
|
| 699 | + improveTypes hsc_env@HscEnv{hsc_IC=ic} id = do
|
|
| 701 | 700 | if noSkolems id
|
| 702 | 701 | then return hsc_env
|
| 703 | 702 | else do
|
| ... | ... | @@ -706,7 +705,7 @@ rttiEnvironment hsc_env@HscEnv{hsc_IC=ic} = do |
| 706 | 705 | case mb_new_ty of
|
| 707 | 706 | Nothing -> return hsc_env
|
| 708 | 707 | Just new_ty -> do
|
| 709 | - case improveRTTIType hsc_env old_ty new_ty of
|
|
| 708 | + case improveRTTIType old_ty new_ty of
|
|
| 710 | 709 | Nothing -> warnPprTrace True (":print failed to calculate the "
|
| 711 | 710 | ++ "improvement for a type")
|
| 712 | 711 | (vcat [ text "id" <+> ppr id
|
| ... | ... | @@ -21,7 +21,6 @@ import GHCi.Message (EvalExpr) |
| 21 | 21 | import GHC.ByteCode.Types (InternalBreakpointId(..))
|
| 22 | 22 | import GHC.Driver.Config (EvalStep(..))
|
| 23 | 23 | import GHC.Types.Id
|
| 24 | -import GHC.Types.Name
|
|
| 25 | 24 | import GHC.Types.TyThing
|
| 26 | 25 | import GHC.Types.Name.Reader
|
| 27 | 26 | import GHC.Types.SrcLoc
|
| ... | ... | @@ -160,7 +159,7 @@ data ExecResult |
| 160 | 159 | -- | Execution is complete with either an exception or the list of
|
| 161 | 160 | -- user-visible names that were brought into scope.
|
| 162 | 161 | = ExecComplete
|
| 163 | - { execResult :: Either SomeException [Name]
|
|
| 162 | + { execResult :: Either SomeException [Id]
|
|
| 164 | 163 | , execAllocation :: Word64
|
| 165 | 164 | }
|
| 166 | 165 | |
| ... | ... | @@ -171,8 +170,10 @@ data ExecResult |
| 171 | 170 | -- subsequently deciding whether to really stop here.
|
| 172 | 171 | -- `ExecBreak` always means GHCi breaks.
|
| 173 | 172 | | ExecBreak
|
| 174 | - { breakNames :: [Name]
|
|
| 173 | + { breakNames :: [Id]
|
|
| 175 | 174 | , breakPointId :: Maybe InternalBreakpointId
|
| 175 | + , breakResume :: Resume
|
|
| 176 | + -- ^ to resume this thread blocked on this break
|
|
| 176 | 177 | }
|
| 177 | 178 | |
| 178 | 179 | -- | Essentially a GlobalRdrEnv, but with additional cached values to allow
|
| ... | ... | @@ -1160,8 +1160,8 @@ findPtrTyss i tys = foldM step (i, []) tys |
| 1160 | 1160 | -- improveType <base_type> <rtti_type>
|
| 1161 | 1161 | -- The types can contain skolem type variables, which need to be treated as normal vars.
|
| 1162 | 1162 | -- In particular, we want them to unify with things.
|
| 1163 | -improveRTTIType :: HscEnv -> RttiType -> RttiType -> Maybe Subst
|
|
| 1164 | -improveRTTIType _ base_ty new_ty = U.tcUnifyDebugger base_ty new_ty
|
|
| 1163 | +improveRTTIType :: RttiType -> RttiType -> Maybe Subst
|
|
| 1164 | +improveRTTIType base_ty new_ty = U.tcUnifyDebugger base_ty new_ty
|
|
| 1165 | 1165 | |
| 1166 | 1166 | getDataConArgTys :: DataCon -> Type -> TR [Type]
|
| 1167 | 1167 | -- Given the result type ty of a constructor application (D a b c :: ty)
|
| ... | ... | @@ -1473,7 +1473,7 @@ runOneCommand eh gCmd = do |
| 1473 | 1473 | |
| 1474 | 1474 | -- runStmt wrapper for temporarily overridden line-number
|
| 1475 | 1475 | runStmtWithLineNum :: Int -> String -> SingleStep
|
| 1476 | - -> GHCi (Maybe GHC.ExecResult)
|
|
| 1476 | + -> GHCi (Maybe RunResult)
|
|
| 1477 | 1477 | runStmtWithLineNum lnum stmt step = do
|
| 1478 | 1478 | st0 <- getGHCiState
|
| 1479 | 1479 | setGHCiState st0 { line_number = lnum }
|
| ... | ... | @@ -1538,7 +1538,7 @@ enqueueCommands cmds = do |
| 1538 | 1538 | |
| 1539 | 1539 | -- | Entry point to execute some haskell code from user.
|
| 1540 | 1540 | -- The return value True indicates success, as in `runOneCommand`.
|
| 1541 | -runStmt :: GhciMonad m => String -> SingleStep -> m (Maybe GHC.ExecResult)
|
|
| 1541 | +runStmt :: GhciMonad m => String -> SingleStep -> m (Maybe RunResult)
|
|
| 1542 | 1542 | runStmt input step = do
|
| 1543 | 1543 | dflags <- GHC.getInteractiveDynFlags
|
| 1544 | 1544 | let pflags = initParserOpts dflags
|
| ... | ... | @@ -1560,7 +1560,7 @@ runStmt input step = do |
| 1560 | 1560 | case mb_stmt of
|
| 1561 | 1561 | Nothing ->
|
| 1562 | 1562 | -- empty statement / comment
|
| 1563 | - return (Just exec_complete)
|
|
| 1563 | + return (Just (RunDeclsResult []))
|
|
| 1564 | 1564 | Just stmt ->
|
| 1565 | 1565 | run_stmt stmt
|
| 1566 | 1566 | |
| ... | ... | @@ -1583,8 +1583,6 @@ runStmt input step = do |
| 1583 | 1583 | run_imports imports
|
| 1584 | 1584 | run_decls decls
|
| 1585 | 1585 | where
|
| 1586 | - exec_complete = GHC.ExecComplete (Right []) 0
|
|
| 1587 | - |
|
| 1588 | 1586 | run_imports imports = mapM_ (addImportToContext . unLoc) imports
|
| 1589 | 1587 | |
| 1590 | 1588 | set_pragmas pflags sec supported =
|
| ... | ... | @@ -1593,12 +1591,12 @@ runStmt input step = do |
| 1593 | 1591 | opts = unLoc <$> loc_opts
|
| 1594 | 1592 | in setOptions opts
|
| 1595 | 1593 | |
| 1596 | - run_stmt :: GhciMonad m => GhciLStmt GhcPs -> m (Maybe GHC.ExecResult)
|
|
| 1594 | + run_stmt :: GhciMonad m => GhciLStmt GhcPs -> m (Maybe RunResult)
|
|
| 1597 | 1595 | run_stmt stmt = do
|
| 1598 | 1596 | m_result <- GhciMonad.runStmt stmt input step
|
| 1599 | 1597 | case m_result of
|
| 1600 | 1598 | Nothing -> return Nothing
|
| 1601 | - Just result -> Just <$> afterRunStmt step result
|
|
| 1599 | + Just result -> Just <$> afterRunStmt (RunStmtResult result)
|
|
| 1602 | 1600 | |
| 1603 | 1601 | -- `x = y` (a declaration) should be treated as `let x = y` (a statement).
|
| 1604 | 1602 | -- The reason is because GHCi wasn't designed to support `x = y`, but then
|
| ... | ... | @@ -1614,7 +1612,7 @@ runStmt input step = do |
| 1614 | 1612 | --
|
| 1615 | 1613 | -- Instead of dealing with all these problems individually here we fix this
|
| 1616 | 1614 | -- mess by just treating `x = y` as `let x = y`.
|
| 1617 | - run_decls :: GhciMonad m => [LHsDecl GhcPs] -> m (Maybe GHC.ExecResult)
|
|
| 1615 | + run_decls :: GhciMonad m => [LHsDecl GhcPs] -> m (Maybe RunResult)
|
|
| 1618 | 1616 | -- Only turn `FunBind` and `VarBind` into statements, other bindings
|
| 1619 | 1617 | -- (e.g. `PatBind`) need to stay as decls.
|
| 1620 | 1618 | run_decls [L l (ValD _ bind@FunBind{})] = run_stmt (mk_stmt (locA l) bind)
|
| ... | ... | @@ -1629,8 +1627,8 @@ runStmt input step = do |
| 1629 | 1627 | -- a file, otherwise the read buffer can't be flushed).
|
| 1630 | 1628 | _ <- liftIO $ tryIO $ hFlushAll stdin
|
| 1631 | 1629 | m_result <- GhciMonad.runDecls' decls
|
| 1632 | - forM m_result $ \result ->
|
|
| 1633 | - afterRunStmt step (GHC.ExecComplete (Right result) 0)
|
|
| 1630 | + forM m_result $ \result -> do
|
|
| 1631 | + afterRunStmt (RunDeclsResult result)
|
|
| 1634 | 1632 | |
| 1635 | 1633 | mk_stmt :: SrcSpan -> HsBind GhcPs -> GhciLStmt GhcPs
|
| 1636 | 1634 | mk_stmt loc bind =
|
| ... | ... | @@ -1646,32 +1644,33 @@ runStmt input step = do |
| 1646 | 1644 | where
|
| 1647 | 1645 | modStr = moduleNameString $ moduleName $ icInteractiveModule $ ic
|
| 1648 | 1646 | |
| 1647 | +data RunResult
|
|
| 1648 | + = RunStmtResult GHC.ExecResult
|
|
| 1649 | + | RunDeclsResult [TyThing]
|
|
| 1650 | + |
|
| 1649 | 1651 | -- | Clean up the GHCi environment after a statement has run
|
| 1650 | -afterRunStmt :: GhciMonad m => SingleStep {-^ Type of step we took just before -}
|
|
| 1651 | - -> GHC.ExecResult -> m GHC.ExecResult
|
|
| 1652 | -afterRunStmt step run_result = do
|
|
| 1653 | - resumes <- GHC.getResumeContext
|
|
| 1652 | +afterRunStmt :: GhciMonad m
|
|
| 1653 | + => RunResult -> m RunResult
|
|
| 1654 | +afterRunStmt run_result = do
|
|
| 1654 | 1655 | case run_result of
|
| 1655 | - GHC.ExecComplete{..} ->
|
|
| 1656 | - case execResult of
|
|
| 1657 | - Left ex -> liftIO $ Exception.throwIO ex
|
|
| 1658 | - Right names -> do
|
|
| 1659 | - show_types <- isOptionSet ShowType
|
|
| 1660 | - when show_types $ printTypeOfNames names
|
|
| 1661 | - GHC.ExecBreak names mb_info
|
|
| 1662 | - | first_resume : _ <- resumes
|
|
| 1663 | - -> do mb_id_loc <- toBreakIdAndLocation mb_info
|
|
| 1664 | - let bCmd = maybe "" ( \(_,l) -> onBreakCmd l ) mb_id_loc
|
|
| 1665 | - if (null bCmd)
|
|
| 1666 | - then printStoppedAtBreakInfo first_resume names
|
|
| 1667 | - else enqueueCommands [bCmd]
|
|
| 1668 | - -- run the command set with ":set stop <cmd>"
|
|
| 1669 | - st <- getGHCiState
|
|
| 1670 | - enqueueCommands [stop st]
|
|
| 1671 | - return ()
|
|
| 1672 | - |
|
| 1673 | - | otherwise -> resume step Nothing >>=
|
|
| 1674 | - afterRunStmt step >> return ()
|
|
| 1656 | + RunDeclsResult things -> do
|
|
| 1657 | + show_types <- isOptionSet ShowType
|
|
| 1658 | + when show_types $ printTypeOfThings things
|
|
| 1659 | + RunStmtResult GHC.ExecComplete{..} ->
|
|
| 1660 | + case execResult of
|
|
| 1661 | + Left ex -> liftIO $ Exception.throwIO ex
|
|
| 1662 | + Right ids -> do
|
|
| 1663 | + show_types <- isOptionSet ShowType
|
|
| 1664 | + when show_types $ printTypeOfIds ids
|
|
| 1665 | + RunStmtResult (GHC.ExecBreak ids mb_info resume) -> do
|
|
| 1666 | + mb_id_loc <- toBreakIdAndLocation mb_info
|
|
| 1667 | + let bCmd = maybe "" ( \(_,l) -> onBreakCmd l ) mb_id_loc
|
|
| 1668 | + if (null bCmd)
|
|
| 1669 | + then printStoppedAtBreakInfo resume ids
|
|
| 1670 | + else enqueueCommands [bCmd]
|
|
| 1671 | + -- run the command set with ":set stop <cmd>"
|
|
| 1672 | + st <- getGHCiState
|
|
| 1673 | + enqueueCommands [stop st]
|
|
| 1675 | 1674 | |
| 1676 | 1675 | flushInterpBuffers
|
| 1677 | 1676 | withSignalHandlers $ do
|
| ... | ... | @@ -1680,16 +1679,18 @@ afterRunStmt step run_result = do |
| 1680 | 1679 | |
| 1681 | 1680 | return run_result
|
| 1682 | 1681 | |
| 1683 | -runSuccess :: Maybe GHC.ExecResult -> Bool
|
|
| 1682 | +runSuccess :: Maybe RunResult -> Bool
|
|
| 1684 | 1683 | runSuccess run_result
|
| 1685 | - | Just (GHC.ExecComplete { execResult = Right _ }) <- run_result = True
|
|
| 1684 | + | Just (RunDeclsResult _) <- run_result = True
|
|
| 1685 | + | Just (RunStmtResult GHC.ExecComplete { execResult = Right _ })
|
|
| 1686 | + <- run_result = True
|
|
| 1686 | 1687 | | otherwise = False
|
| 1687 | 1688 | |
| 1688 | -runAllocs :: Maybe GHC.ExecResult -> Maybe Integer
|
|
| 1689 | +runAllocs :: Maybe RunResult -> Maybe Integer
|
|
| 1689 | 1690 | runAllocs m = do
|
| 1690 | 1691 | res <- m
|
| 1691 | 1692 | case res of
|
| 1692 | - GHC.ExecComplete{..} -> Just (fromIntegral execAllocation)
|
|
| 1693 | + RunStmtResult GHC.ExecComplete{..} -> Just (fromIntegral execAllocation)
|
|
| 1693 | 1694 | _ -> Nothing
|
| 1694 | 1695 | |
| 1695 | 1696 | toBreakIdAndLocation :: GhciMonad m
|
| ... | ... | @@ -1703,30 +1704,23 @@ toBreakIdAndLocation (Just inf) = do |
| 1703 | 1704 | return $ listToMaybe [ id_loc | id_loc@(_,loc) <- IntMap.assocs (breaks st),
|
| 1704 | 1705 | breakId loc == bi ]
|
| 1705 | 1706 | |
| 1706 | -printStoppedAtBreakInfo :: GHC.GhcMonad m => Resume -> [Name] -> m ()
|
|
| 1707 | -printStoppedAtBreakInfo res names = do
|
|
| 1707 | +printStoppedAtBreakInfo :: GHC.GhcMonad m => Resume -> [GHC.Id] -> m ()
|
|
| 1708 | +printStoppedAtBreakInfo res ids = do
|
|
| 1708 | 1709 | printForUser =<< pprStopped res
|
| 1709 | - -- printTypeOfNames session names
|
|
| 1710 | - let namesSorted = sortBy compareNames names
|
|
| 1711 | - tythings <- catMaybes `liftM` mapM GHC.lookupName namesSorted
|
|
| 1712 | - docs <- mapM pprTypeAndContents [i | AnId i <- tythings]
|
|
| 1710 | + let idsSorted = sortBy (compareNames `on` getName) ids
|
|
| 1711 | + docs <- mapM pprTypeAndContents idsSorted
|
|
| 1713 | 1712 | printForUserPartWay $ vcat docs
|
| 1714 | 1713 | |
| 1715 | -printTypeOfNames :: GHC.GhcMonad m => [Name] -> m ()
|
|
| 1716 | -printTypeOfNames names
|
|
| 1717 | - = mapM_ (printTypeOfName ) $ sortBy compareNames names
|
|
| 1714 | +printTypeOfIds :: GHC.GhcMonad m => [GHC.Id] -> m ()
|
|
| 1715 | +printTypeOfIds = printTypeOfThings . map AnId
|
|
| 1716 | + |
|
| 1717 | +printTypeOfThings :: GHC.GhcMonad m => [GHC.TyThing] -> m ()
|
|
| 1718 | +printTypeOfThings things
|
|
| 1719 | + = mapM_ printTyThing $ sortBy (compareNames `on` getName) things
|
|
| 1718 | 1720 | |
| 1719 | 1721 | compareNames :: Name -> Name -> Ordering
|
| 1720 | 1722 | compareNames = on compare getOccString S.<> on SrcLoc.leftmost_smallest getSrcSpan
|
| 1721 | 1723 | |
| 1722 | -printTypeOfName :: GHC.GhcMonad m => Name -> m ()
|
|
| 1723 | -printTypeOfName n
|
|
| 1724 | - = do maybe_tything <- GHC.lookupName n
|
|
| 1725 | - case maybe_tything of
|
|
| 1726 | - Nothing -> return ()
|
|
| 1727 | - Just thing -> printTyThing thing
|
|
| 1728 | - |
|
| 1729 | - |
|
| 1730 | 1724 | data MaybeCommand = GotCommand Command | BadCommand | NoLastCommand
|
| 1731 | 1725 | |
| 1732 | 1726 | -- | Entry point for execution a ':<command>' input from user
|
| ... | ... | @@ -4375,7 +4369,7 @@ doContinue step = doContinue' step Nothing |
| 4375 | 4369 | doContinue' :: GhciMonad m => SingleStep -> Maybe Int -> m ()
|
| 4376 | 4370 | doContinue' step mbCnt= do
|
| 4377 | 4371 | runResult <- resume step mbCnt
|
| 4378 | - _ <- afterRunStmt step runResult
|
|
| 4372 | + _ <- afterRunStmt (RunStmtResult runResult)
|
|
| 4379 | 4373 | return ()
|
| 4380 | 4374 | |
| 4381 | 4375 | abandonCmd :: GhciMonad m => String -> m ()
|
| ... | ... | @@ -4526,9 +4520,9 @@ backCmd arg |
| 4526 | 4520 | | otherwise = liftIO $ putStrLn "Syntax: :back [num]"
|
| 4527 | 4521 | where
|
| 4528 | 4522 | back num = withSandboxOnly ":back" $ do
|
| 4529 | - (names, _, pan) <- GHC.back num
|
|
| 4523 | + (ids, _, pan) <- GHC.back num
|
|
| 4530 | 4524 | printForUser $ text "Logged breakpoint at" <+> ppr pan
|
| 4531 | - printTypeOfNames names
|
|
| 4525 | + printTypeOfIds ids
|
|
| 4532 | 4526 | -- run the command set with ":set stop <cmd>"
|
| 4533 | 4527 | st <- getGHCiState
|
| 4534 | 4528 | enqueueCommands [stop st]
|
| ... | ... | @@ -4540,11 +4534,11 @@ forwardCmd arg |
| 4540 | 4534 | | otherwise = liftIO $ putStrLn "Syntax: :forward [num]"
|
| 4541 | 4535 | where
|
| 4542 | 4536 | forward num = withSandboxOnly ":forward" $ do
|
| 4543 | - (names, ix, pan) <- GHC.forward num
|
|
| 4537 | + (ids, ix, pan) <- GHC.forward num
|
|
| 4544 | 4538 | printForUser $ (if (ix == 0)
|
| 4545 | 4539 | then text "Stopped at"
|
| 4546 | 4540 | else text "Logged breakpoint at") <+> ppr pan
|
| 4547 | - printTypeOfNames names
|
|
| 4541 | + printTypeOfIds ids
|
|
| 4548 | 4542 | -- run the command set with ":set stop <cmd>"
|
| 4549 | 4543 | st <- getGHCiState
|
| 4550 | 4544 | enqueueCommands [stop st]
|
| ... | ... | @@ -381,7 +381,7 @@ runStmt stmt stmt_text step = do |
| 381 | 381 | (EvalThis fhv) }
|
| 382 | 382 | Just <$> GHC.execStmt' stmt stmt_text opts
|
| 383 | 383 | |
| 384 | -runDecls :: GhciMonad m => String -> m (Maybe [GHC.Name])
|
|
| 384 | +runDecls :: GhciMonad m => String -> m (Maybe [GHC.TyThing])
|
|
| 385 | 385 | runDecls decls = do
|
| 386 | 386 | st <- getGHCiState
|
| 387 | 387 | reifyGHCi $ \x ->
|
| ... | ... | @@ -393,7 +393,7 @@ runDecls decls = do |
| 393 | 393 | r <- GHC.runDeclsWithLocation (progname st) (line_number st) decls
|
| 394 | 394 | return (Just r)
|
| 395 | 395 | |
| 396 | -runDecls' :: GhciMonad m => [LHsDecl GhcPs] -> m (Maybe [GHC.Name])
|
|
| 396 | +runDecls' :: GhciMonad m => [LHsDecl GhcPs] -> m (Maybe [GHC.TyThing])
|
|
| 397 | 397 | runDecls' decls = do
|
| 398 | 398 | st <- getGHCiState
|
| 399 | 399 | reifyGHCi $ \x ->
|
| 1 | 1 | {-# LANGUAGE GADTs, RecordWildCards, MagicHash, ScopedTypeVariables, CPP,
|
| 2 | - UnboxedTuples, LambdaCase, UnliftedFFITypes, NamedFieldPuns #-}
|
|
| 2 | + UnboxedTuples, LambdaCase, UnliftedFFITypes, NamedFieldPuns, BangPatterns #-}
|
|
| 3 | 3 | {-# OPTIONS_GHC -fno-warn-name-shadowing #-}
|
| 4 | -{-# LANGUAGE BangPatterns #-}
|
|
| 5 | 4 | |
| 6 | 5 | -- |
|
| 7 | 6 | -- Execute GHCi messages.
|
| ... | ... | @@ -20,6 +19,7 @@ import GHCi.CreateBCO |
| 20 | 19 | import GHCi.InfoTable
|
| 21 | 20 | #endif
|
| 22 | 21 | |
| 22 | +import GHCi.Run.Breakpoints
|
|
| 23 | 23 | import GHCi.Coverage
|
| 24 | 24 | import qualified GHC.InfoProv as InfoProv
|
| 25 | 25 | import GHCi.Debugger
|
| ... | ... | @@ -31,8 +31,6 @@ import GHCi.TH |
| 31 | 31 | import GHCi.BreakArray
|
| 32 | 32 | import GHCi.StaticPtrTable
|
| 33 | 33 | |
| 34 | -import qualified Data.Map as Map
|
|
| 35 | -import qualified Data.Set as Set
|
|
| 36 | 34 | import Control.Concurrent
|
| 37 | 35 | import Control.DeepSeq
|
| 38 | 36 | import Control.Exception
|
| ... | ... | @@ -290,7 +288,7 @@ abandonStmt hvref = do |
| 290 | 288 | tid <- localRef hvref
|
| 291 | 289 | ResumeContext{..} <- getThreadResumeContext tid
|
| 292 | 290 | killThread tid
|
| 293 | - withShadowThread tid $ do
|
|
| 291 | + withIsolatedThread tid $ do
|
|
| 294 | 292 | putMVar resumeBreakMVar ()
|
| 295 | 293 | _ <- atomically $
|
| 296 | 294 | readThreadEvalStatus tid
|
| ... | ... | @@ -424,13 +422,13 @@ withBreakAction opts tid@(ThreadId tid#) act |
| 424 | 422 | |
| 425 | 423 | runIf# singleStep rts_enableStopNextBreakpoint
|
| 426 | 424 | runIf# stepOut rts_enableStopAfterReturn
|
| 427 | - runIf isolateThreadBreaks setShadowThread
|
|
| 425 | + runIf isolateThreadBreaks setIsolatedThread
|
|
| 428 | 426 | |
| 429 | 427 | resetBreakAction () = do
|
| 430 | 428 | poke exceptionFlag 0
|
| 431 | 429 | rts_disableStopAfterReturn tid#
|
| 432 | 430 | rts_disableStopNextBreakpoint tid#
|
| 433 | - runIf isolateThreadBreaks unsetShadowThread
|
|
| 431 | + runIf isolateThreadBreaks unsetIsolatedThread
|
|
| 434 | 432 | |
| 435 | 433 | -- freeStablePtr stablePtr TODO: we never free the global stablePtr for
|
| 436 | 434 | -- global action. Maybe we should have a de-init for clean up.
|
| ... | ... | @@ -484,186 +482,3 @@ clearEvalStatus = \case |
| 484 | 482 | EvalComplete w (EvalSuccess _) -> EvalComplete w (EvalSuccess ())
|
| 485 | 483 | EvalBreak ap mb rt rccs -> EvalBreak ap mb rt rccs
|
| 486 | 484 | |
| 487 | ---------------------------------------------------------------------------------
|
|
| 488 | --- Global Debugger Per-Thread Context
|
|
| 489 | --- TODO: when to clean MVar? how to figure out when Threads are dead and we'll never need their mvars again? perhaps a finalizer on the thread id?
|
|
| 490 | --- TODO: Move to independent module, and don't expose the global variables at all. move also the global break action maybe.
|
|
| 491 | --- Maybe there could even be something in that module for setting the global
|
|
| 492 | --- break action on start. maybe just an init function
|
|
| 493 | ---
|
|
| 494 | --- TODO: Note which explains when things are evacuated from the map, why
|
|
| 495 | --- EvalSuccess's don't leak, what is the idea, why have MVar and TVar, why have
|
|
| 496 | --- shadowed threads. Must also explain whatever we come up with for Finalizers
|
|
| 497 | --- to make sure threads evacuate themselves out of the map. Explain in the note
|
|
| 498 | --- as well the status of GHCi multi-threaded, explaining it currently uses
|
|
| 499 | --- single-threaded
|
|
| 500 | ---
|
|
| 501 | --- TODO: Write a blog post about multi-threaded debugging (mby with the contents of the note..)
|
|
| 502 | - |
|
| 503 | -globalBreakStablePtr :: StablePtr BreakpointCallback
|
|
| 504 | -globalBreakStablePtr = unsafePerformIO $ newStablePtr globalBreakAction
|
|
| 505 | - |
|
| 506 | -globalBreakAction :: BreakpointCallback
|
|
| 507 | -globalBreakAction info_mod# info_mod_uid# infox# is_exception apStack = do
|
|
| 508 | - tid <- myThreadId
|
|
| 509 | - ResumeContext{..} <- getThreadResumeContext tid
|
|
| 510 | - resume_r <- mkRemoteRef tid
|
|
| 511 | - apStack_r <- mkRemoteRef apStack
|
|
| 512 | - ccs <- toRemotePtr <$> getCCSOf apStack
|
|
| 513 | - breakpoint <-
|
|
| 514 | - if is_exception
|
|
| 515 | - then pure Nothing
|
|
| 516 | - else do
|
|
| 517 | - info_mod <- BS.packCString (Ptr info_mod#)
|
|
| 518 | - info_mod_uid <- BS.packCString (Ptr info_mod_uid#)
|
|
| 519 | - pure (Just (EvalBreakpoint info_mod info_mod_uid (I# infox#)))
|
|
| 520 | - writeThreadEvalStatus tid $ EvalBreak apStack_r breakpoint resume_r ccs
|
|
| 521 | - |
|
| 522 | - -- Block until this thread is resumed (by the thread which took the
|
|
| 523 | - -- `ResumeContext` from the `statusMVar`).
|
|
| 524 | - --
|
|
| 525 | - -- The `onBreak` function must have been called from `rts/Interpreter.c`
|
|
| 526 | - -- when interpreting a `BRK_FUN`. After taking from the MVar, the function
|
|
| 527 | - -- returns to the continuation on the stack which is where the interpreter
|
|
| 528 | - -- was stopped.
|
|
| 529 | - takeMVar resumeBreakMVar
|
|
| 530 | - |
|
| 531 | -getThreadResumeContext :: ThreadId -> IO ResumeContext
|
|
| 532 | -getThreadResumeContext tid = modifyMVar globalBreakActionMap $ \gbm0 -> do
|
|
| 533 | - case Map.lookup tid gbm0 of
|
|
| 534 | - Nothing -> do
|
|
| 535 | - r <- ResumeContext <$> newEmptyMVar
|
|
| 536 | - let !gbm1 = Map.insert tid r gbm0
|
|
| 537 | - pure (gbm1, r)
|
|
| 538 | - Just ctx -> do
|
|
| 539 | - pure (gbm0, ctx)
|
|
| 540 | - |
|
| 541 | --- | A global mapping from thread ids to the MVars on which they block when stopped.
|
|
| 542 | -globalBreakActionMap :: MVar (Map.Map ThreadId ResumeContext)
|
|
| 543 | -globalBreakActionMap = unsafePerformIO $ newMVar Map.empty
|
|
| 544 | -{-# NOINLINE globalBreakActionMap #-}
|
|
| 545 | - |
|
| 546 | -newtype ResumeContext = ResumeContext
|
|
| 547 | - { resumeBreakMVar :: MVar ()
|
|
| 548 | - -- ^ A thread that hits a breakpoint blocks reading its corresponding MVar
|
|
| 549 | - -- (gotten from the 'globalBreakActionMap').
|
|
| 550 | - -- The debugger can unblock that thread by signaling its MVar.
|
|
| 551 | - }
|
|
| 552 | - |
|
| 553 | --- | To evaluate a statement in the interpreter, a thread is forked by
|
|
| 554 | --- `sandboxIO` to run the statement. To synchronize, the statement result will
|
|
| 555 | --- be written by the thread to the global thread dbg status
|
|
| 556 | --- (`writeThreadEvalStatus`) and `sandboxIO` will block waiting for either any
|
|
| 557 | --- breakpoint to be hit ('readAnyThreadEvalBreak') /or/ for the thread
|
|
| 558 | --- specifically to write its final result. To wait for that thread
|
|
| 559 | --- specifically, use this function.
|
|
| 560 | ---
|
|
| 561 | --- Succeeds when the given thread writes to the global debugger map an eval result.
|
|
| 562 | -readThreadEvalStatus :: ThreadId -> STM (EvalStatus [HValueRef])
|
|
| 563 | -readThreadEvalStatus tid = do
|
|
| 564 | - GlobalDbgStatus{..} <- readTVar globalDbgStatusVar
|
|
| 565 | - case Map.lookup tid threadEvalStatuses of
|
|
| 566 | - Just r -> do
|
|
| 567 | - writeTVar globalDbgStatusVar
|
|
| 568 | - GlobalDbgStatus
|
|
| 569 | - { threadEvalStatuses = Map.delete tid threadEvalStatuses -- clear it
|
|
| 570 | - , shadowedThreads
|
|
| 571 | - }
|
|
| 572 | - pure r
|
|
| 573 | - Nothing -> retry -- until someone writes the result for this thread
|
|
| 574 | - |
|
| 575 | --- | All threads running in the interpreter will be debugger-enabled, meaning
|
|
| 576 | --- if they hit an enabled breakpoint, the 'globalBreakAction' will be
|
|
| 577 | --- executed and write a 'EvalBreak' value to the global thread dbg status map.
|
|
| 578 | --- The debugger will be waiting for threads to break using `readAnyThreadEvalBreak`.
|
|
| 579 | ---
|
|
| 580 | --- Succeeds when any thread hits a breakpoint.
|
|
| 581 | -readAnyThreadEvalBreak :: STM EvalBreak
|
|
| 582 | -readAnyThreadEvalBreak = do
|
|
| 583 | - dbgs <- readTVar globalDbgStatusVar
|
|
| 584 | - -- try reading any thread's 'EvalBreak' and retry if none have yielded one yet
|
|
| 585 | - foldr (\x next -> readStatus dbgs x `orElse` next) retry (Map.toList (threadEvalStatuses dbgs))
|
|
| 586 | - where
|
|
| 587 | - readStatus GlobalDbgStatus{..} (tid, _) -- don't look at dbg-shadowed threads
|
|
| 588 | - | Set.member tid shadowedThreads
|
|
| 589 | - = retry
|
|
| 590 | - |
|
| 591 | - readStatus GlobalDbgStatus{..} (tid, status)
|
|
| 592 | - = do
|
|
| 593 | - case status of
|
|
| 594 | - EvalComplete{} -> retry -- successes can be read with `readThreadEvalStatus`
|
|
| 595 | - EvalPaused b -> do
|
|
| 596 | - writeTVar globalDbgStatusVar
|
|
| 597 | - GlobalDbgStatus
|
|
| 598 | - { threadEvalStatuses = Map.delete tid threadEvalStatuses
|
|
| 599 | - , shadowedThreads
|
|
| 600 | - }
|
|
| 601 | - pure b
|
|
| 602 | - |
|
| 603 | -writeThreadEvalStatus :: ThreadId -> EvalStatus [HValueRef] -> IO ()
|
|
| 604 | -writeThreadEvalStatus tid new =
|
|
| 605 | - atomically $ do
|
|
| 606 | - GlobalDbgStatus{..} <- readTVar globalDbgStatusVar
|
|
| 607 | - writeTVar globalDbgStatusVar $
|
|
| 608 | - GlobalDbgStatus
|
|
| 609 | - { threadEvalStatuses =
|
|
| 610 | - Map.insertWith (\_ _ -> error "writeThreadEvalStatus: should be impossible")
|
|
| 611 | - tid new threadEvalStatuses
|
|
| 612 | - , shadowedThreads }
|
|
| 613 | - |
|
| 614 | --- | Brackets 'setShadowThread'
|
|
| 615 | -withShadowThread :: ThreadId -> IO a -> IO a
|
|
| 616 | -withShadowThread tid = bracket_ (setShadowThread tid) (unsetShadowThread tid)
|
|
| 617 | - |
|
| 618 | --- | Make sure that anyone watching for threads hitting breakpoints with
|
|
| 619 | --- 'readAnyThreadEvalBreak' ignore the given thread if it ever hits a breakpoint.
|
|
| 620 | ---
|
|
| 621 | --- This forces this thread's result to be received solely through
|
|
| 622 | --- 'readThreadEvalStatus' for that ThreadId, which is typically desired when an
|
|
| 623 | --- expression to run should not be seen at all by the debugger (e.g. when
|
|
| 624 | --- evaluating an expr to :force a variable)
|
|
| 625 | -setShadowThread :: ThreadId -> IO ()
|
|
| 626 | -setShadowThread tid = atomically $ do
|
|
| 627 | - GlobalDbgStatus{..} <- readTVar globalDbgStatusVar
|
|
| 628 | - writeTVar globalDbgStatusVar
|
|
| 629 | - GlobalDbgStatus
|
|
| 630 | - { threadEvalStatuses
|
|
| 631 | - , shadowedThreads = Set.insert tid shadowedThreads
|
|
| 632 | - }
|
|
| 633 | - |
|
| 634 | --- | Undoes 'setShadowThread'
|
|
| 635 | -unsetShadowThread :: ThreadId -> IO ()
|
|
| 636 | -unsetShadowThread tid = atomically $ do
|
|
| 637 | - GlobalDbgStatus{..} <- readTVar globalDbgStatusVar
|
|
| 638 | - writeTVar globalDbgStatusVar
|
|
| 639 | - GlobalDbgStatus
|
|
| 640 | - { threadEvalStatuses
|
|
| 641 | - , shadowedThreads = Set.delete tid shadowedThreads
|
|
| 642 | - }
|
|
| 643 | - |
|
| 644 | --- | When we launch a new thread using the debugger, we map ... TODO ... all
|
|
| 645 | --- threads launched by that main stmt may further spawn threads, and any of
|
|
| 646 | --- them might hit a breakpoint (break array toggled, or per-thread step in
|
|
| 647 | --- might be used, or pause button, etc...).
|
|
| 648 | ---
|
|
| 649 | --- When we launch that main thread we also write its result to this map. So
|
|
| 650 | --- this map will only contain the results of threads we explicitly care to
|
|
| 651 | --- observe the result of. Not all threads will write their results here,
|
|
| 652 | --- certainly not ones spawned unknowningly by the main stmt.
|
|
| 653 | --- ...
|
|
| 654 | --- todo words
|
|
| 655 | -globalDbgStatusVar :: TVar GlobalDbgStatus
|
|
| 656 | -globalDbgStatusVar = unsafePerformIO $ newTVarIO $ GlobalDbgStatus Map.empty Set.empty
|
|
| 657 | -{-# NOINLINE globalDbgStatusVar #-}
|
|
| 658 | - |
|
| 659 | -data GlobalDbgStatus = GlobalDbgStatus
|
|
| 660 | - { threadEvalStatuses :: !(Map.Map ThreadId (EvalStatus [HValueRef]))
|
|
| 661 | - -- ^ Whenever a thread hits a breakpoint it inserts in this map its 'EvalBreak'.
|
|
| 662 | - -- We can additionally observe a thread's result by calling
|
|
| 663 | - -- `writeThreadEvalStatus tid (EvalComplete ...)` when the main expression
|
|
| 664 | - -- finishes evaluating, see e.g. 'sandboxIO'.
|
|
| 665 | - , shadowedThreads :: !(Set.Set ThreadId)
|
|
| 666 | - -- ^ Threads in this map are meant to be ignored by 'readAnyThreadEvalBreak'.
|
|
| 667 | - -- That is, even if one of the threads in this set hits a breakpoint, we
|
|
| 668 | - -- can only read its 'EvalBreak' with @'readThreadEvalStatus' tid@ directly.
|
|
| 669 | - } |
| 1 | +{-# LANGUAGE GADTs, RecordWildCards, MagicHash, ScopedTypeVariables, CPP,
|
|
| 2 | + UnboxedTuples, LambdaCase, UnliftedFFITypes, NamedFieldPuns, BangPatterns #-}
|
|
| 3 | +module GHCi.Run.Breakpoints
|
|
| 4 | + (
|
|
| 5 | + -- * Global break action
|
|
| 6 | + globalBreakStablePtr, globalBreakAction
|
|
| 7 | + |
|
| 8 | + -- * Per-thread breakpoints
|
|
| 9 | + , readThreadEvalStatus, readAnyThreadEvalBreak
|
|
| 10 | + , writeThreadEvalStatus
|
|
| 11 | + |
|
| 12 | + -- ** Isolating thread breakpoints
|
|
| 13 | + , withIsolatedThread
|
|
| 14 | + , setIsolatedThread, unsetIsolatedThread
|
|
| 15 | + |
|
| 16 | + -- ** Querying the global thread breakpoint map
|
|
| 17 | + , listOtherPausedThreads
|
|
| 18 | + |
|
| 19 | + -- * Per-thread resume-ing
|
|
| 20 | + , getThreadResumeContext, ResumeContext(..)
|
|
| 21 | + |
|
| 22 | + )
|
|
| 23 | + where
|
|
| 24 | + |
|
| 25 | +import Prelude -- See note [Why do we import Prelude here?]
|
|
| 26 | + |
|
| 27 | +import GHCi.Debugger
|
|
| 28 | +import GHCi.Message
|
|
| 29 | +import GHCi.RemoteTypes
|
|
| 30 | + |
|
| 31 | +import qualified Data.Map as Map
|
|
| 32 | +import qualified Data.Set as Set
|
|
| 33 | +import Control.Concurrent
|
|
| 34 | +import Control.Exception
|
|
| 35 | +import qualified Data.ByteString.Short.Internal as BS
|
|
| 36 | +#if defined(PROFILING)
|
|
| 37 | +import GHC.Data.ShortByteString
|
|
| 38 | +#endif
|
|
| 39 | +import GHC.Exts
|
|
| 40 | +import GHC.Stack
|
|
| 41 | +import Foreign hiding (void)
|
|
| 42 | +import GHC.Conc.Sync
|
|
| 43 | +import GHC.IO hiding ( bracket )
|
|
| 44 | + |
|
| 45 | +--------------------------------------------------------------------------------
|
|
| 46 | +-- * Global break action
|
|
| 47 | +--------------------------------------------------------------------------------
|
|
| 48 | +-- Global Debugger Per-Thread Context
|
|
| 49 | +-- TODO: when to clean MVar? how to figure out when Threads are dead and we'll
|
|
| 50 | +-- never need their mvars again? perhaps a finalizer on the thread id?
|
|
| 51 | +--
|
|
| 52 | +-- TODO: Move to independent module, and don't expose the global variables at all. move also the global break action maybe.
|
|
| 53 | +-- Maybe there could even be something in that module for setting the global
|
|
| 54 | +-- break action on start. maybe just an init function
|
|
| 55 | +--
|
|
| 56 | +-- TODO: Note which explains when things are evacuated from the map, why
|
|
| 57 | +-- EvalSuccess's don't leak, what is the idea, why have MVar and TVar, why have
|
|
| 58 | +-- isolated threads. Must also explain whatever we come up with for Finalizers
|
|
| 59 | +-- to make sure threads evacuate themselves out of the map. Explain in the note
|
|
| 60 | +-- as well the status of GHCi multi-threaded, explaining it currently uses
|
|
| 61 | +-- single-threaded
|
|
| 62 | +--
|
|
| 63 | +-- TODO: Write a blog post about multi-threaded debugging (mby with the contents of the note..)
|
|
| 64 | + |
|
| 65 | +-- TODO: Maybe this could be the return value of doing initialization for the
|
|
| 66 | +-- debugger, which allocates this stable pointer and sets it in the global action.
|
|
| 67 | +--
|
|
| 68 | +-- And de-initialization deallocates this stable pointer and then some other things
|
|
| 69 | +globalBreakStablePtr :: StablePtr BreakpointCallback
|
|
| 70 | +globalBreakStablePtr = unsafePerformIO $ newStablePtr globalBreakAction
|
|
| 71 | + |
|
| 72 | +globalBreakAction :: BreakpointCallback
|
|
| 73 | +globalBreakAction info_mod# info_mod_uid# infox# is_exception apStack = do
|
|
| 74 | + tid <- myThreadId
|
|
| 75 | + ResumeContext{..} <- getThreadResumeContext tid
|
|
| 76 | + resume_r <- mkRemoteRef tid
|
|
| 77 | + apStack_r <- mkRemoteRef apStack
|
|
| 78 | + ccs <- toRemotePtr <$> getCCSOf apStack
|
|
| 79 | + mb_breakpoint <-
|
|
| 80 | + if is_exception
|
|
| 81 | + then pure Nothing
|
|
| 82 | + else do
|
|
| 83 | + info_mod <- BS.packCString (Ptr info_mod#)
|
|
| 84 | + info_mod_uid <- BS.packCString (Ptr info_mod_uid#)
|
|
| 85 | + pure (Just (EvalBreakpoint info_mod info_mod_uid (I# infox#)))
|
|
| 86 | + writeThreadEvalStatus tid $ EvalBreak apStack_r mb_breakpoint resume_r ccs
|
|
| 87 | + |
|
| 88 | + -- Block until this thread is resumed (by the thread which took the
|
|
| 89 | + -- `ResumeContext` from the `statusMVar`).
|
|
| 90 | + --
|
|
| 91 | + -- The `onBreak` function must have been called from `rts/Interpreter.c`
|
|
| 92 | + -- when interpreting a `BRK_FUN`. After taking from the MVar, the function
|
|
| 93 | + -- returns to the continuation on the stack which is where the interpreter
|
|
| 94 | + -- was stopped.
|
|
| 95 | + takeMVar resumeBreakMVar
|
|
| 96 | + |
|
| 97 | +--------------------------------------------------------------------------------
|
|
| 98 | +-- * Per-thread breakpoints
|
|
| 99 | +--------------------------------------------------------------------------------
|
|
| 100 | + |
|
| 101 | +-- | To evaluate a statement in the interpreter, a thread is forked by
|
|
| 102 | +-- `sandboxIO` to run the statement. To synchronize, the statement result will
|
|
| 103 | +-- be written by the thread to the global thread dbg status
|
|
| 104 | +-- (`writeThreadEvalStatus`) and `sandboxIO` will block waiting for either any
|
|
| 105 | +-- breakpoint to be hit ('readAnyThreadEvalBreak') /or/ for the thread
|
|
| 106 | +-- specifically to write its final result. To wait for that thread
|
|
| 107 | +-- specifically, use this function.
|
|
| 108 | +--
|
|
| 109 | +-- Succeeds when the given thread writes to the global debugger map an eval result.
|
|
| 110 | +readThreadEvalStatus :: ThreadId -> STM (EvalStatus [HValueRef])
|
|
| 111 | +readThreadEvalStatus tid = do
|
|
| 112 | + GlobalDbgStatus{..} <- readTVar globalDbgStatusVar
|
|
| 113 | + case Map.lookup tid threadEvalStatuses of
|
|
| 114 | + Just r -> do
|
|
| 115 | + writeTVar globalDbgStatusVar
|
|
| 116 | + GlobalDbgStatus
|
|
| 117 | + { threadEvalStatuses = Map.delete tid threadEvalStatuses -- clear it
|
|
| 118 | + , isolatedThreads
|
|
| 119 | + }
|
|
| 120 | + pure r
|
|
| 121 | + Nothing -> retry -- until someone writes the result for this thread
|
|
| 122 | + |
|
| 123 | +-- | All threads running in the interpreter will be debugger-enabled, meaning
|
|
| 124 | +-- if they hit an enabled breakpoint, the 'globalBreakAction' will be
|
|
| 125 | +-- executed and write a 'EvalBreak' value to the global thread dbg status map.
|
|
| 126 | +-- The debugger will be waiting for threads to break using `readAnyThreadEvalBreak`.
|
|
| 127 | +--
|
|
| 128 | +-- Succeeds when any thread hits a breakpoint.
|
|
| 129 | +readAnyThreadEvalBreak :: STM EvalBreak
|
|
| 130 | +readAnyThreadEvalBreak = do
|
|
| 131 | + dbgs <- readTVar globalDbgStatusVar
|
|
| 132 | + -- try reading any thread's 'EvalBreak' and retry if none have yielded one yet
|
|
| 133 | + foldr (\x next -> readStatus dbgs x `orElse` next) retry (Map.toList (threadEvalStatuses dbgs))
|
|
| 134 | + where
|
|
| 135 | + readStatus GlobalDbgStatus{..} (tid, _) -- don't look at isolated threads
|
|
| 136 | + | Set.member tid isolatedThreads
|
|
| 137 | + = retry
|
|
| 138 | + |
|
| 139 | + readStatus GlobalDbgStatus{..} (tid, status)
|
|
| 140 | + = do
|
|
| 141 | + case status of
|
|
| 142 | + EvalComplete{} -> retry -- successes can be read with `readThreadEvalStatus`
|
|
| 143 | + EvalPaused b -> do
|
|
| 144 | + writeTVar globalDbgStatusVar
|
|
| 145 | + GlobalDbgStatus
|
|
| 146 | + { threadEvalStatuses = Map.delete tid threadEvalStatuses
|
|
| 147 | + , isolatedThreads
|
|
| 148 | + }
|
|
| 149 | + pure b
|
|
| 150 | + |
|
| 151 | +writeThreadEvalStatus :: ThreadId -> EvalStatus [HValueRef] -> IO ()
|
|
| 152 | +writeThreadEvalStatus tid status =
|
|
| 153 | + atomically $ do
|
|
| 154 | + GlobalDbgStatus{..} <- readTVar globalDbgStatusVar
|
|
| 155 | + writeTVar globalDbgStatusVar $
|
|
| 156 | + GlobalDbgStatus
|
|
| 157 | + { threadEvalStatuses =
|
|
| 158 | + Map.insertWith (\_ _ -> error "writeThreadEvalStatus: should be impossible")
|
|
| 159 | + tid status threadEvalStatuses
|
|
| 160 | + , isolatedThreads }
|
|
| 161 | + |
|
| 162 | + |
|
| 163 | +-- ** Isolating thread breakpoints ---------------------------------------------
|
|
| 164 | + |
|
| 165 | +-- | Brackets 'setIsolatedThread'
|
|
| 166 | +withIsolatedThread :: ThreadId -> IO a -> IO a
|
|
| 167 | +withIsolatedThread tid = bracket_ (setIsolatedThread tid) (unsetIsolatedThread tid)
|
|
| 168 | + |
|
| 169 | +-- | Make sure that anyone watching for threads hitting breakpoints with
|
|
| 170 | +-- 'readAnyThreadEvalBreak' ignore the given thread if it ever hits a breakpoint.
|
|
| 171 | +--
|
|
| 172 | +-- This forces this thread's result to be received solely through
|
|
| 173 | +-- 'readThreadEvalStatus' for that ThreadId, which is typically desired when an
|
|
| 174 | +-- expression to run should not be seen at all by the debugger (e.g. when
|
|
| 175 | +-- evaluating an expr to :force a variable)
|
|
| 176 | +setIsolatedThread :: ThreadId -> IO ()
|
|
| 177 | +setIsolatedThread tid = atomically $ do
|
|
| 178 | + GlobalDbgStatus{..} <- readTVar globalDbgStatusVar
|
|
| 179 | + writeTVar globalDbgStatusVar
|
|
| 180 | + GlobalDbgStatus
|
|
| 181 | + { threadEvalStatuses
|
|
| 182 | + , isolatedThreads = Set.insert tid isolatedThreads
|
|
| 183 | + }
|
|
| 184 | + |
|
| 185 | +-- | Undoes 'setIsolatedThread'
|
|
| 186 | +unsetIsolatedThread :: ThreadId -> IO ()
|
|
| 187 | +unsetIsolatedThread tid = atomically $ do
|
|
| 188 | + GlobalDbgStatus{..} <- readTVar globalDbgStatusVar
|
|
| 189 | + writeTVar globalDbgStatusVar
|
|
| 190 | + GlobalDbgStatus
|
|
| 191 | + { threadEvalStatuses
|
|
| 192 | + , isolatedThreads = Set.delete tid isolatedThreads
|
|
| 193 | + }
|
|
| 194 | + |
|
| 195 | +-- ** Querying the global thread breakpoint map --------------------------------
|
|
| 196 | + |
|
| 197 | +-- | List all other threads paused on a breakpoint.
|
|
| 198 | +--
|
|
| 199 | +-- We say "other" because this function is meant to be used in the scenario
|
|
| 200 | +-- where you are already paused at a breakpoint (which has been popped off of
|
|
| 201 | +-- the global thread mapping) and are asking about which other threads are
|
|
| 202 | +-- paused to list them.
|
|
| 203 | +listOtherPausedThreads :: IO [ThreadId]
|
|
| 204 | +listOtherPausedThreads = do
|
|
| 205 | + m <- atomically $ readTVar globalDbgStatusVar
|
|
| 206 | + pure $ Map.keys $ Map.filter isBreak (threadEvalStatuses m)
|
|
| 207 | + where
|
|
| 208 | + isBreak EvalBreak{} = True
|
|
| 209 | + isBreak EvalComplete{} = False
|
|
| 210 | + |
|
| 211 | +-- ** Global thread exec status map --------------------------------------------
|
|
| 212 | + |
|
| 213 | +-- | When we launch a new thread using the debugger, we map ... TODO ... all
|
|
| 214 | +-- threads launched by that main stmt may further spawn threads, and any of
|
|
| 215 | +-- them might hit a breakpoint (break array toggled, or per-thread step in
|
|
| 216 | +-- might be used, or pause button, etc...).
|
|
| 217 | +--
|
|
| 218 | +-- When we launch that main thread we also write its result to this map. So
|
|
| 219 | +-- this map will only contain the results of threads we explicitly care to
|
|
| 220 | +-- observe the result of. Not all threads will write their results here,
|
|
| 221 | +-- certainly not ones spawned unknowningly by the main stmt.
|
|
| 222 | +-- ...
|
|
| 223 | +-- todo words
|
|
| 224 | +globalDbgStatusVar :: TVar GlobalDbgStatus
|
|
| 225 | +globalDbgStatusVar = unsafePerformIO $ newTVarIO $ GlobalDbgStatus Map.empty Set.empty
|
|
| 226 | +{-# NOINLINE globalDbgStatusVar #-}
|
|
| 227 | + |
|
| 228 | +data GlobalDbgStatus = GlobalDbgStatus
|
|
| 229 | + { threadEvalStatuses :: !(Map.Map ThreadId (EvalStatus [HValueRef]))
|
|
| 230 | + -- ^ Whenever a thread hits a breakpoint it inserts in this map its 'EvalBreak'.
|
|
| 231 | + -- We can additionally observe a thread's result by calling
|
|
| 232 | + -- `writeThreadEvalStatus tid (EvalComplete ...)` when the main expression
|
|
| 233 | + -- finishes evaluating, see e.g. 'sandboxIO'.
|
|
| 234 | + , isolatedThreads :: !(Set.Set ThreadId)
|
|
| 235 | + -- ^ Threads in this map are meant to be ignored by 'readAnyThreadEvalBreak'.
|
|
| 236 | + -- That is, even if one of the threads in this set hits a breakpoint, we
|
|
| 237 | + -- can only read its 'EvalBreak' with @'readThreadEvalStatus' tid@ directly.
|
|
| 238 | + }
|
|
| 239 | + |
|
| 240 | +--------------------------------------------------------------------------------
|
|
| 241 | +-- * Per-thread resume-ing
|
|
| 242 | +--------------------------------------------------------------------------------
|
|
| 243 | + |
|
| 244 | +getThreadResumeContext :: ThreadId -> IO ResumeContext
|
|
| 245 | +getThreadResumeContext tid = modifyMVar globalBreakActionMap $ \gbm0 -> do
|
|
| 246 | + case Map.lookup tid gbm0 of
|
|
| 247 | + Nothing -> do
|
|
| 248 | + r <- ResumeContext <$> newEmptyMVar
|
|
| 249 | + let !gbm1 = Map.insert tid r gbm0
|
|
| 250 | + pure (gbm1, r)
|
|
| 251 | + Just ctx -> do
|
|
| 252 | + pure (gbm0, ctx)
|
|
| 253 | + |
|
| 254 | +-- | A global mapping from thread ids to the MVars on which they block when stopped.
|
|
| 255 | +globalBreakActionMap :: MVar (Map.Map ThreadId ResumeContext)
|
|
| 256 | +globalBreakActionMap = unsafePerformIO $ newMVar Map.empty
|
|
| 257 | +{-# NOINLINE globalBreakActionMap #-}
|
|
| 258 | + |
|
| 259 | +newtype ResumeContext = ResumeContext
|
|
| 260 | + { resumeBreakMVar :: MVar ()
|
|
| 261 | + -- ^ A thread that hits a breakpoint blocks reading its corresponding MVar
|
|
| 262 | + -- (gotten from the 'globalBreakActionMap').
|
|
| 263 | + -- The debugger can unblock that thread by signaling its MVar.
|
|
| 264 | + } |
| ... | ... | @@ -61,6 +61,7 @@ library |
| 61 | 61 | exposed-modules:
|
| 62 | 62 | GHCi.Coverage
|
| 63 | 63 | GHCi.Run
|
| 64 | + GHCi.Run.Breakpoints
|
|
| 64 | 65 | GHCi.Debugger
|
| 65 | 66 | GHCi.CreateBCO
|
| 66 | 67 | GHCi.ObjLink
|