Rodrigo Mesquita pushed to branch wip/romes/multi-threaded-debugger at Glasgow Haskell Compiler / GHC

Commits:

9 changed files:

Changes:

  • compiler/GHC/Runtime/Debugger.hs
    ... ... @@ -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 $
    

  • compiler/GHC/Runtime/Eval.hs
    ... ... @@ -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
    

  • compiler/GHC/Runtime/Eval/Types.hs
    ... ... @@ -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
    

  • compiler/GHC/Runtime/Heap/Inspect.hs
    ... ... @@ -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)
    

  • ghc/GHCi/UI.hs
    ... ... @@ -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]
    

  • ghc/GHCi/UI/Monad.hs
    ... ... @@ -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 ->
    

  • libraries/ghci/GHCi/Run.hs
    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
    -  }

  • libraries/ghci/GHCi/Run/Breakpoints.hs
    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
    +  }

  • libraries/ghci/ghci.cabal.in
    ... ... @@ -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