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

Commits:

4 changed files:

Changes:

  • compiler/GHC/Runtime/Eval/Types.hs
    ... ... @@ -16,7 +16,7 @@ module GHC.Runtime.Eval.Types (
    16 16
     import GHC.Prelude
    
    17 17
     
    
    18 18
     import GHCi.RemoteTypes
    
    19
    -import GHCi.Message (EvalExpr, ResumeContext)
    
    19
    +import GHCi.Message (EvalExpr)
    
    20 20
     import GHC.ByteCode.Types (InternalBreakpointId(..))
    
    21 21
     import GHC.Driver.Config (EvalStep(..))
    
    22 22
     import GHC.Types.Id
    
    ... ... @@ -28,6 +28,7 @@ import GHC.Utils.Exception
    28 28
     
    
    29 29
     import Data.Word
    
    30 30
     import GHC.Stack.CCS
    
    31
    +import Control.Concurrent (ThreadId)
    
    31 32
     
    
    32 33
     data ExecOptions
    
    33 34
      = ExecOptions
    
    ... ... @@ -170,7 +171,7 @@ data IcGlobalRdrEnv = IcGlobalRdrEnv
    170 171
     
    
    171 172
     data Resume = Resume
    
    172 173
            { resumeStmt      :: String       -- the original statement
    
    173
    -       , resumeContext   :: ForeignRef (ResumeContext [HValueRef])
    
    174
    +       , resumeContext   :: ForeignRef ThreadId
    
    174 175
            , resumeBindings  :: ResumeBindings
    
    175 176
            , resumeFinalIds  :: [Id]         -- [Id] to bind on completion
    
    176 177
            , resumeApStack   :: ForeignHValue -- The object from which we can get
    

  • compiler/GHC/Runtime/Interpreter.hs
    ... ... @@ -303,16 +303,16 @@ evalStmt interp opts foreign_expr = do
    303 303
     resumeStmt
    
    304 304
       :: Interp
    
    305 305
       -> EvalOpts
    
    306
    -  -> ForeignRef (ResumeContext [HValueRef])
    
    306
    +  -> ForeignRef ThreadId
    
    307 307
       -> IO (EvalStatus_ [ForeignHValue] [HValueRef])
    
    308
    -resumeStmt interp opts resume_ctxt = do
    
    309
    -  status <- withForeignRef resume_ctxt $ \rhv ->
    
    308
    +resumeStmt interp opts resume_tid = do
    
    309
    +  status <- withForeignRef resume_tid $ \rhv ->
    
    310 310
         interpCmd interp (ResumeStmt opts rhv)
    
    311 311
       handleEvalStatus interp status
    
    312 312
     
    
    313
    -abandonStmt :: Interp -> ForeignRef (ResumeContext [HValueRef]) -> IO ()
    
    314
    -abandonStmt interp resume_ctxt =
    
    315
    -  withForeignRef resume_ctxt $ \rhv ->
    
    313
    +abandonStmt :: Interp -> ForeignRef ThreadId -> IO ()
    
    314
    +abandonStmt interp resume_tid =
    
    315
    +  withForeignRef resume_tid $ \rhv ->
    
    316 316
         interpCmd interp (AbandonStmt rhv)
    
    317 317
     
    
    318 318
     handleEvalStatus
    

  • libraries/ghci/GHCi/Message.hs
    ... ... @@ -19,7 +19,6 @@ module GHCi.Message
    19 19
       , SerializableException(..)
    
    20 20
       , toSerializableException, fromSerializableException
    
    21 21
       , THResult(..), THResultType(..)
    
    22
    -  , ResumeContext(..)
    
    23 22
       , QState(..)
    
    24 23
       , getMessage, putMessage, getTHMessage, putTHMessage
    
    25 24
       , Pipe, mkPipeFromHandles, mkPipeFromContinuations, remoteCall, remoteTHCall, readPipe, writePipe
    
    ... ... @@ -138,12 +137,12 @@ data Message a where
    138 137
       -- | Resume evaluation of a statement after a breakpoint
    
    139 138
       ResumeStmt
    
    140 139
        :: EvalOpts
    
    141
    -   -> RemoteRef (ResumeContext [HValueRef])
    
    140
    +   -> RemoteRef ThreadId
    
    142 141
        -> Message (EvalStatus [HValueRef])
    
    143 142
     
    
    144 143
       -- | Abandon evaluation of a statement after a breakpoint
    
    145 144
       AbandonStmt
    
    146
    -   :: RemoteRef (ResumeContext [HValueRef])
    
    145
    +   :: RemoteRef ThreadId
    
    147 146
        -> Message ()
    
    148 147
     
    
    149 148
       -- | Evaluate something of type @IO String@
    
    ... ... @@ -245,7 +244,7 @@ data Message a where
    245 244
     
    
    246 245
       -- | Resume forcing a free variable in a breakpoint (#2950)
    
    247 246
       ResumeSeq
    
    248
    -    :: RemoteRef (ResumeContext ())
    
    247
    +    :: RemoteRef ThreadId
    
    249 248
         -> Message (EvalStatus ())
    
    250 249
     
    
    251 250
       -- | User-defined request encoded as a tag/payload pair.  This is left
    
    ... ... @@ -400,12 +399,6 @@ data EvalOpts = EvalOpts
    400 399
     
    
    401 400
     instance Binary EvalOpts
    
    402 401
     
    
    403
    -data ResumeContext a = ResumeContext
    
    404
    -  { resumeBreakMVar :: MVar ()
    
    405
    -  , resumeStatusMVar :: MVar (EvalStatus a)
    
    406
    -  , resumeThreadId :: ThreadId
    
    407
    -  }
    
    408
    -
    
    409 402
     -- | We can pass simple expressions to EvalStmt, consisting of values
    
    410 403
     -- and application.  This allows us to wrap the statement to be
    
    411 404
     -- executed in another function, which is used by GHCi to implement
    
    ... ... @@ -425,7 +418,7 @@ data EvalStatus_ a b
    425 418
       | EvalBreak
    
    426 419
            HValueRef{- AP_STACK -}
    
    427 420
            (Maybe EvalBreakpoint)
    
    428
    -       (RemoteRef (ResumeContext b))
    
    421
    +       (RemoteRef ThreadId)
    
    429 422
            (RemotePtr CostCentreStack) -- Cost centre stack
    
    430 423
       deriving (Generic, Show)
    
    431 424
     
    

  • libraries/ghci/GHCi/Run.hs
    1 1
     {-# LANGUAGE GADTs, RecordWildCards, MagicHash, ScopedTypeVariables, CPP,
    
    2 2
         UnboxedTuples, LambdaCase, UnliftedFFITypes #-}
    
    3 3
     {-# OPTIONS_GHC -fno-warn-name-shadowing #-}
    
    4
    +{-# LANGUAGE BangPatterns #-}
    
    4 5
     
    
    5 6
     -- |
    
    6 7
     -- Execute GHCi messages.
    
    ... ... @@ -30,6 +31,7 @@ import GHCi.TH
    30 31
     import GHCi.BreakArray
    
    31 32
     import GHCi.StaticPtrTable
    
    32 33
     
    
    34
    +import qualified Data.Map as Map
    
    33 35
     import Control.Concurrent
    
    34 36
     import Control.DeepSeq
    
    35 37
     import Control.Exception
    
    ... ... @@ -194,20 +196,22 @@ evalStringToString r str = do
    194 196
     -- information about the breakpoint than the current iserv
    
    195 197
     -- process.
    
    196 198
     doSeq :: RemoteRef a -> IO (EvalStatus ())
    
    197
    -doSeq ref = do
    
    199
    +doSeq ref = clearEvalStatus <$> do
    
    198 200
         sandboxIO evalOptsSeq $ do
    
    199 201
           _ <- (void $ evaluate =<< localRef ref)
    
    200
    -      return ()
    
    202
    +      return []
    
    201 203
     
    
    202 204
     -- | Process a ResumeSeq message. Continue the :force processing     #2950
    
    203 205
     -- after a breakpoint.
    
    204
    -resumeSeq :: RemoteRef (ResumeContext ()) -> IO (EvalStatus ())
    
    205
    -resumeSeq hvref = do
    
    206
    -    ResumeContext{..} <- localRef hvref
    
    207
    -    withBreakAction evalOptsSeq resumeBreakMVar resumeStatusMVar (Just resumeThreadId) $
    
    206
    +resumeSeq :: RemoteRef ThreadId -> IO (EvalStatus ())
    
    207
    +resumeSeq hvref = clearEvalStatus <$> do
    
    208
    +    resumeThreadId <- localRef hvref
    
    209
    +    withBreakAction evalOptsSeq (Just resumeThreadId) $ do
    
    210
    +      ResumeContext{..} <- getThreadResumeContext resumeThreadId
    
    208 211
           mask_ $ do
    
    209 212
             putMVar resumeBreakMVar () -- this awakens the stopped thread...
    
    210
    -        redirectInterrupts resumeThreadId $ takeMVar resumeStatusMVar
    
    213
    +        redirectInterrupts resumeThreadId $ do
    
    214
    +          takeMVar resumeStatusMVar
    
    211 215
     
    
    212 216
     evalOptsSeq :: EvalOpts
    
    213 217
     evalOptsSeq = EvalOpts
    
    ... ... @@ -228,21 +232,21 @@ evalOptsSeq = EvalOpts
    228 232
     -- only while we execute the user's code.  We can't afford to lose the final
    
    229 233
     -- putMVar, otherwise deadlock ensues. (#1583, #1922, #1946)
    
    230 234
     
    
    231
    -sandboxIO :: EvalOpts -> IO a -> IO (EvalStatus a)
    
    235
    +sandboxIO :: EvalOpts -> IO [HValueRef] -> IO (EvalStatus [HValueRef])
    
    232 236
     sandboxIO opts io = do
    
    233 237
       -- We are running in uninterruptibleMask
    
    234
    -  breakMVar <- newEmptyMVar
    
    235
    -  statusMVar <- newEmptyMVar
    
    236
    -  withBreakAction opts breakMVar statusMVar Nothing $ do
    
    238
    +  withBreakAction opts Nothing $ do
    
    237 239
         let runIt = measureAlloc $ tryEval $ rethrow opts $ clearCCS io
    
    238 240
         if useSandboxThread opts
    
    239 241
            then do
    
    240 242
              tid <- forkIO $ do
    
    241 243
                tid <- myThreadId
    
    244
    +           ResumeContext{..} <- getThreadResumeContext tid
    
    242 245
                labelThread tid "GHCi sandbox"
    
    243
    -           unsafeUnmask runIt >>= putMVar statusMVar
    
    246
    +           unsafeUnmask runIt >>= putMVar resumeStatusMVar
    
    244 247
                                     -- empty: can't block
    
    245
    -         redirectInterrupts tid $ unsafeUnmask $ takeMVar statusMVar
    
    248
    +         ResumeContext{..} <- getThreadResumeContext tid
    
    249
    +         redirectInterrupts tid $ unsafeUnmask $ takeMVar resumeStatusMVar
    
    246 250
            else
    
    247 251
               -- GLUT on OS X needs to run on the main thread. If you
    
    248 252
               -- try to use it from another thread then you just get a
    
    ... ... @@ -328,71 +332,47 @@ tryEval io = do
    328 332
     -- resets everything when the computation has stopped running.  This
    
    329 333
     -- is a not-very-good way to ensure that only the interactive
    
    330 334
     -- evaluation should generate breakpoints.
    
    331
    -withBreakAction :: EvalOpts -> MVar ()
    
    332
    -                -> MVar (EvalStatus b)
    
    335
    +withBreakAction :: EvalOpts
    
    333 336
                     -> Maybe ThreadId -- ^ If resuming, the current threadId
    
    334 337
                     -> IO a -> IO a
    
    335
    -withBreakAction opts breakMVar statusMVar mtid act
    
    338
    +withBreakAction opts mtid act
    
    336 339
      = bracket setBreakAction resetBreakAction (\_ -> act)
    
    337 340
      where
    
    338 341
        setBreakAction = do
    
    339
    -     stablePtr <- newStablePtr onBreak
    
    340
    -     poke breakPointIOAction stablePtr
    
    342
    +     poke breakPointIOAction globalBreakStablePtr -- TODO: This is thread unsafe, as one thread might be accessing this global variable while this thread tries to overwrite it.
    
    341 343
          when (breakOnException opts) $ poke exceptionFlag 1
    
    342
    -     when (singleStep opts) rts_enableStopNextBreakpointAll
    
    344
    +     when (singleStep opts) $ do
    
    345
    +      case mtid of
    
    346
    +        Nothing -> rts_enableStopNextBreakpointAll
    
    347
    +        Just (ThreadId tid) -> do
    
    348
    +          rts_enableStopNextBreakpoint tid
    
    343 349
          when (stepOut opts) $ do
    
    344 350
           case mtid of
    
    345 351
             Nothing -> rts_enableStopNextBreakpointAll -- just enable single-step when no thread is stopped
    
    346 352
             Just (ThreadId tid) -> do
    
    347 353
               rts_enableStopAfterReturn tid
    
    348
    -     return stablePtr
    
    354
    +     return ()
    
    349 355
             -- Breaking on exceptions is not enabled by default, since it
    
    350 356
             -- might be a bit surprising.  The exception flag is turned off
    
    351 357
             -- as soon as it is hit, or in resetBreakAction below.
    
    352 358
     
    
    353
    -   onBreak :: BreakpointCallback
    
    354
    -   onBreak info_mod# info_mod_uid# infox# is_exception apStack = do
    
    355
    -     tid <- myThreadId
    
    356
    -     let resume = ResumeContext
    
    357
    -           { resumeBreakMVar = breakMVar
    
    358
    -           , resumeStatusMVar = statusMVar
    
    359
    -           , resumeThreadId = tid }
    
    360
    -     resume_r <- mkRemoteRef resume
    
    361
    -     apStack_r <- mkRemoteRef apStack
    
    362
    -     ccs <- toRemotePtr <$> getCCSOf apStack
    
    363
    -     breakpoint <-
    
    364
    -       if is_exception
    
    365
    -       then pure Nothing
    
    366
    -       else do
    
    367
    -         info_mod <- BS.packCString (Ptr info_mod#)
    
    368
    -         info_mod_uid <- BS.packCString (Ptr info_mod_uid#)
    
    369
    -         pure (Just (EvalBreakpoint info_mod info_mod_uid (I# infox#)))
    
    370
    -     putMVar statusMVar $ EvalBreak apStack_r breakpoint resume_r ccs
    
    371
    -
    
    372
    -     -- Block until this thread is resumed (by the thread which took the
    
    373
    -     -- `ResumeContext` from the `statusMVar`).
    
    374
    -     --
    
    375
    -     -- The `onBreak` function must have been called from `rts/Interpreter.c`
    
    376
    -     -- when interpreting a `BRK_FUN`. After taking from the MVar, the function
    
    377
    -     -- returns to the continuation on the stack which is where the interpreter
    
    378
    -     -- was stopped.
    
    379
    -     takeMVar breakMVar
    
    380
    -
    
    381
    -   resetBreakAction stablePtr = do
    
    382
    -     poke breakPointIOAction noBreakStablePtr
    
    359
    +   resetBreakAction {-stablePtr-}() = do
    
    360
    +     -- poke breakPointIOAction noBreakStablePtr -- Never unset it.
    
    361
    +     -- TODO: What about evaluating a thunk with breakpoints? when did we get "ignoring breakpoint..." before?
    
    383 362
          poke exceptionFlag 0
    
    384 363
          rts_disableStopNextBreakpointAll
    
    385 364
          case mtid of
    
    386 365
           Just (ThreadId tid) -> rts_disableStopAfterReturn tid
    
    387 366
           _                   -> pure ()
    
    388
    -     freeStablePtr stablePtr
    
    367
    +     -- freeStablePtr stablePtr
    
    389 368
     
    
    390 369
     resumeStmt
    
    391
    -  :: EvalOpts -> RemoteRef (ResumeContext [HValueRef])
    
    370
    +  :: EvalOpts -> RemoteRef ThreadId
    
    392 371
       -> IO (EvalStatus [HValueRef])
    
    393
    -resumeStmt opts hvref = do
    
    394
    -  ResumeContext{..} <- localRef hvref
    
    395
    -  withBreakAction opts resumeBreakMVar resumeStatusMVar (Just resumeThreadId) $
    
    372
    +resumeStmt opts rtid = do
    
    373
    +  resumeThreadId <- localRef rtid
    
    374
    +  ResumeContext{..} <- getThreadResumeContext resumeThreadId
    
    375
    +  withBreakAction opts (Just resumeThreadId) $
    
    396 376
         mask_ $ do
    
    397 377
           putMVar resumeBreakMVar () -- this awakens the stopped thread...
    
    398 378
           redirectInterrupts resumeThreadId $ takeMVar resumeStatusMVar
    
    ... ... @@ -409,20 +389,42 @@ resumeStmt opts hvref = do
    409 389
     --          step is necessary to prevent race conditions with
    
    410 390
     --          -fbreak-on-exception (see #5975).
    
    411 391
     --  See test break010.
    
    412
    -abandonStmt :: RemoteRef (ResumeContext [HValueRef]) -> IO ()
    
    392
    +abandonStmt :: RemoteRef ThreadId -> IO ()
    
    413 393
     abandonStmt hvref = do
    
    414
    -  ResumeContext{..} <- localRef hvref
    
    415
    -  killThread resumeThreadId
    
    394
    +  tid <- localRef hvref
    
    395
    +  ResumeContext{..} <- getThreadResumeContext tid
    
    396
    +  killThread tid
    
    416 397
       putMVar resumeBreakMVar ()
    
    417 398
       _ <- takeMVar resumeStatusMVar
    
    418 399
       return ()
    
    419 400
     
    
    420
    -noBreakStablePtr :: StablePtr BreakpointCallback
    
    421
    -noBreakStablePtr = unsafePerformIO $ newStablePtr noBreakAction
    
    422
    -
    
    423
    -noBreakAction :: BreakpointCallback
    
    424
    -noBreakAction _ _ _ False _ = putStrLn "*** Ignoring breakpoint"
    
    425
    -noBreakAction _ _ _ True  _ = return () -- exception: just continue
    
    401
    +globalBreakStablePtr :: StablePtr BreakpointCallback
    
    402
    +globalBreakStablePtr = unsafePerformIO $ newStablePtr globalBreakAction
    
    403
    +
    
    404
    +globalBreakAction :: BreakpointCallback
    
    405
    +globalBreakAction info_mod# info_mod_uid# infox# is_exception apStack = do
    
    406
    +  tid <- myThreadId
    
    407
    +  ResumeContext{..} <- getThreadResumeContext tid
    
    408
    +  resume_r <- mkRemoteRef tid
    
    409
    +  apStack_r <- mkRemoteRef apStack
    
    410
    +  ccs <- toRemotePtr <$> getCCSOf apStack
    
    411
    +  breakpoint <-
    
    412
    +    if is_exception
    
    413
    +    then pure Nothing
    
    414
    +    else do
    
    415
    +      info_mod <- BS.packCString (Ptr info_mod#)
    
    416
    +      info_mod_uid <- BS.packCString (Ptr info_mod_uid#)
    
    417
    +      pure (Just (EvalBreakpoint info_mod info_mod_uid (I# infox#)))
    
    418
    +  putMVar resumeStatusMVar $ EvalBreak apStack_r breakpoint resume_r ccs
    
    419
    +
    
    420
    +  -- Block until this thread is resumed (by the thread which took the
    
    421
    +  -- `ResumeContext` from the `statusMVar`).
    
    422
    +  --
    
    423
    +  -- The `onBreak` function must have been called from `rts/Interpreter.c`
    
    424
    +  -- when interpreting a `BRK_FUN`. After taking from the MVar, the function
    
    425
    +  -- returns to the continuation on the stack which is where the interpreter
    
    426
    +  -- was stopped.
    
    427
    +  takeMVar resumeBreakMVar
    
    426 428
     
    
    427 429
     -- Malloc and copy the bytes.  We don't have any way to monitor the
    
    428 430
     -- lifetime of this memory, so it just leaks.
    
    ... ... @@ -463,3 +465,32 @@ getIdValFromApStack apStack (I# stackDepth) = do
    463 465
                 case ok of
    
    464 466
                   0# -> return Nothing -- AP_STACK not found
    
    465 467
                   _  -> return (Just (unsafeCoerce# result))
    
    468
    +
    
    469
    +clearEvalStatus :: EvalStatus a -> EvalStatus ()
    
    470
    +clearEvalStatus = \case
    
    471
    +  EvalComplete w (EvalException se) -> EvalComplete w (EvalException se)
    
    472
    +  EvalComplete w (EvalSuccess _)    -> EvalComplete w (EvalSuccess ())
    
    473
    +  EvalBreak ap mb rt rccs           -> EvalBreak ap mb rt rccs
    
    474
    +
    
    475
    +--------------------------------------------------------------------------------
    
    476
    +-- Global Debugger Per-Thread Context
    
    477
    +-- TODO: when to clean?
    
    478
    +
    
    479
    +getThreadResumeContext :: ThreadId -> IO (ResumeContext [HValueRef])
    
    480
    +getThreadResumeContext tid = modifyMVar globalBreakActionMap $ \gbm0 -> do
    
    481
    +  case Map.lookup tid gbm0 of
    
    482
    +    Nothing  -> do
    
    483
    +      r <- ResumeContext <$> newEmptyMVar <*> newEmptyMVar
    
    484
    +      let !gbm1 = Map.insert tid r gbm0
    
    485
    +      pure (gbm1, r)
    
    486
    +    Just ctx -> do
    
    487
    +      pure (gbm0, ctx)
    
    488
    +
    
    489
    +globalBreakActionMap :: MVar (Map.Map ThreadId (ResumeContext [HValueRef]))
    
    490
    +globalBreakActionMap = unsafePerformIO $ newMVar Map.empty
    
    491
    +{-# NOINLINE globalBreakActionMap #-}
    
    492
    +
    
    493
    +data ResumeContext a = ResumeContext
    
    494
    +  { resumeBreakMVar :: MVar ()
    
    495
    +  , resumeStatusMVar :: MVar (EvalStatus a)
    
    496
    +  }