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

Commits:

2 changed files:

Changes:

  • libraries/ghci/GHCi/Message.hs
    1 1
     {-# LANGUAGE GADTs, DeriveGeneric, StandaloneDeriving, ScopedTypeVariables,
    
    2 2
         GeneralizedNewtypeDeriving, ExistentialQuantification, RecordWildCards,
    
    3
    -    CPP, NamedFieldPuns #-}
    
    3
    +    CPP, NamedFieldPuns, PatternSynonyms #-}
    
    4 4
     {-# OPTIONS_GHC -fno-warn-name-shadowing -fno-warn-orphans #-}
    
    5 5
     
    
    6 6
     -- |
    
    ... ... @@ -14,8 +14,8 @@ module GHCi.Message
    14 14
       , ConInfoTable(..)
    
    15 15
       , THMessage(..), THMsg(..)
    
    16 16
       , QResult(..)
    
    17
    -  , EvalStatus_(..), EvalStatus, EvalResult(..), EvalOpts(..), EvalExpr(..)
    
    18
    -  , EvalBreakpoint (..)
    
    17
    +  , EvalStatus_(..,EvalBreak), EvalStatus, EvalResult(..), EvalOpts(..), EvalExpr(..)
    
    18
    +  , EvalBreak(..), EvalBreakpoint (..)
    
    19 19
       , SerializableException(..)
    
    20 20
       , toSerializableException, fromSerializableException
    
    21 21
       , THResult(..), THResultType(..)
    
    ... ... @@ -415,13 +415,22 @@ type EvalStatus a = EvalStatus_ a a
    415 415
     
    
    416 416
     data EvalStatus_ a b
    
    417 417
       = EvalComplete Word64 (EvalResult a)
    
    418
    -  | EvalBreak
    
    418
    +  | EvalPaused EvalBreak
    
    419
    +  deriving (Generic, Show)
    
    420
    +
    
    421
    +pattern EvalBreak :: HValueRef -> (Maybe EvalBreakpoint) -> (RemoteRef ThreadId) -> (RemotePtr CostCentreStack) -> EvalStatus_ a b
    
    422
    +pattern EvalBreak a b c d = EvalPaused (EvalBreak_ a b c d)
    
    423
    +{-# COMPLETE EvalComplete, EvalBreak #-}
    
    424
    +
    
    425
    +data EvalBreak
    
    426
    +  = EvalBreak_
    
    419 427
            HValueRef{- AP_STACK -}
    
    420 428
            (Maybe EvalBreakpoint)
    
    421 429
            (RemoteRef ThreadId)
    
    422 430
            (RemotePtr CostCentreStack) -- Cost centre stack
    
    423 431
       deriving (Generic, Show)
    
    424 432
     
    
    433
    +instance Binary EvalBreak
    
    425 434
     instance Binary a => Binary (EvalStatus_ a b)
    
    426 435
     
    
    427 436
     data EvalBreakpoint = EvalBreakpoint
    

  • libraries/ghci/GHCi/Run.hs
    1 1
     {-# LANGUAGE GADTs, RecordWildCards, MagicHash, ScopedTypeVariables, CPP,
    
    2
    -    UnboxedTuples, LambdaCase, UnliftedFFITypes #-}
    
    2
    +    UnboxedTuples, LambdaCase, UnliftedFFITypes, NamedFieldPuns #-}
    
    3 3
     {-# OPTIONS_GHC -fno-warn-name-shadowing #-}
    
    4 4
     {-# LANGUAGE BangPatterns #-}
    
    5 5
     
    
    ... ... @@ -32,6 +32,7 @@ import GHCi.BreakArray
    32 32
     import GHCi.StaticPtrTable
    
    33 33
     
    
    34 34
     import qualified Data.Map as Map
    
    35
    +import qualified Data.Set as Set
    
    35 36
     import Control.Concurrent
    
    36 37
     import Control.DeepSeq
    
    37 38
     import Control.Exception
    
    ... ... @@ -211,7 +212,9 @@ resumeSeq hvref = clearEvalStatus <$> do
    211 212
           mask_ $ do
    
    212 213
             putMVar resumeBreakMVar () -- this awakens the stopped thread...
    
    213 214
             redirectInterrupts resumeThreadId $
    
    214
    -          readThreadEvalStatus resumeThreadId
    
    215
    +          withShadowThread resumeThreadId $
    
    216
    +            atomically $
    
    217
    +            readThreadEvalStatus resumeThreadId
    
    215 218
     
    
    216 219
     evalOptsSeq :: EvalOpts
    
    217 220
     evalOptsSeq = EvalOpts
    
    ... ... @@ -244,8 +247,9 @@ sandboxIO opts io = do
    244 247
                labelThread tid "GHCi sandbox"
    
    245 248
                unsafeUnmask runIt >>= writeThreadEvalStatus tid
    
    246 249
     
    
    247
    -         redirectInterrupts tid $ unsafeUnmask $
    
    248
    -           readThreadEvalStatus tid
    
    250
    +         redirectInterrupts tid $ unsafeUnmask $ atomically $
    
    251
    +           (EvalPaused <$> readAnyThreadEvalBreak)
    
    252
    +            `orElse` readThreadEvalStatus tid
    
    249 253
            else
    
    250 254
               -- GLUT on OS X needs to run on the main thread. If you
    
    251 255
               -- try to use it from another thread then you just get a
    
    ... ... @@ -379,7 +383,8 @@ resumeStmt opts rtid = do
    379 383
         mask_ $ do
    
    380 384
           putMVar resumeBreakMVar () -- this awakens the stopped thread...
    
    381 385
           redirectInterrupts resumeThreadId $
    
    382
    -        readThreadEvalStatus resumeThreadId
    
    386
    +        withShadowThread resumeThreadId $
    
    387
    +          atomically $ readThreadEvalStatus resumeThreadId
    
    383 388
     
    
    384 389
     -- when abandoning a computation we have to
    
    385 390
     --      (a) kill the thread with an async exception, so that the
    
    ... ... @@ -399,7 +404,8 @@ abandonStmt hvref = do
    399 404
       ResumeContext{..} <- getThreadResumeContext tid
    
    400 405
       killThread tid
    
    401 406
       putMVar resumeBreakMVar ()
    
    402
    -  _ <- readThreadEvalStatus tid
    
    407
    +  _ <- withShadowThread tid $ atomically $
    
    408
    +    readThreadEvalStatus tid
    
    403 409
       return ()
    
    404 410
     
    
    405 411
     -- Malloc and copy the bytes.  We don't have any way to monitor the
    
    ... ... @@ -505,42 +511,114 @@ newtype ResumeContext = ResumeContext
    505 511
       -- The debugger can unblock that thread by signaling its MVar.
    
    506 512
       }
    
    507 513
     
    
    508
    --- brilliant STM
    
    509
    --- * only retries when the map is written again
    
    510
    --- * essentially blocks until the result for this thread is there
    
    511
    -readThreadEvalStatus :: ThreadId -> IO (EvalStatus [HValueRef])
    
    514
    +-- | To evaluate a statement in the interpreter, a thread is forked by
    
    515
    +-- `sandboxIO` to run the statement. To synchronize, the statement result will
    
    516
    +-- be written by the thread to the global thread dbg status
    
    517
    +-- (`writeThreadEvalStatus`) and `sandboxIO` will block waiting for either any
    
    518
    +-- breakpoint to be hit ('readAnyThreadEvalBreak') /or/ for the thread
    
    519
    +-- specifically to write its final result. To wait for that thread
    
    520
    +-- specifically, use this function.
    
    521
    +--
    
    522
    +-- Succeeds when the given thread writes to the global debugger map an eval result.
    
    523
    +readThreadEvalStatus :: ThreadId -> STM (EvalStatus [HValueRef])
    
    512 524
     readThreadEvalStatus tid = do
    
    513
    -  atomically $ do
    
    514
    -    dbgmap <- readTVar globalDbgStatusVar
    
    515
    -    case Map.lookup tid dbgmap of
    
    516
    -      Just r  -> do
    
    517
    -        writeTVar globalDbgStatusVar $!
    
    518
    -          Map.delete tid dbgmap -- clear it
    
    519
    -        pure r
    
    520
    -      Nothing -> retry -- until someone writes the result for this thread
    
    525
    +  GlobalDbgStatus{..} <- readTVar globalDbgStatusVar
    
    526
    +  case Map.lookup tid threadEvalStatuses of
    
    527
    +    Just r  -> do
    
    528
    +      writeTVar globalDbgStatusVar
    
    529
    +        GlobalDbgStatus
    
    530
    +          { threadEvalStatuses = Map.delete tid threadEvalStatuses -- clear it
    
    531
    +          , shadowedThreads
    
    532
    +          }
    
    533
    +      pure r
    
    534
    +    Nothing -> retry -- until someone writes the result for this thread
    
    535
    +
    
    536
    +-- | All threads running in the interpreter will be debugger-enabled, meaning
    
    537
    +-- if they hit an enabled breakpoint, the 'globalBreakAction' will be
    
    538
    +-- executed and write a 'EvalBreak' value to the global thread dbg status map.
    
    539
    +-- The debugger will be waiting for threads to break using `readAnyThreadEvalBreak`.
    
    540
    +--
    
    541
    +-- Succeeds when any thread hits a breakpoint.
    
    542
    +readAnyThreadEvalBreak :: STM EvalBreak
    
    543
    +readAnyThreadEvalBreak = do
    
    544
    +  dbgs <- readTVar globalDbgStatusVar
    
    545
    +  -- try reading all mvars and retry if none yield a result
    
    546
    +  foldr (\x next -> readStatus dbgs x `orElse` next) retry (Map.toList (threadEvalStatuses dbgs))
    
    547
    +  where
    
    548
    +    readStatus GlobalDbgStatus{..} (tid, _) -- don't look at dbg-shadowed threads
    
    549
    +      | Set.member tid shadowedThreads
    
    550
    +      = retry
    
    551
    +
    
    552
    +    readStatus GlobalDbgStatus{..} (tid, status)
    
    553
    +      = do
    
    554
    +      case status of
    
    555
    +        EvalComplete{} -> retry -- successes can be read with `readThreadEvalStatus`
    
    556
    +        EvalPaused b   -> do
    
    557
    +          writeTVar globalDbgStatusVar
    
    558
    +            GlobalDbgStatus
    
    559
    +              { threadEvalStatuses = Map.delete tid threadEvalStatuses
    
    560
    +              , shadowedThreads
    
    561
    +              }
    
    562
    +          pure b
    
    521 563
     
    
    522 564
     writeThreadEvalStatus :: ThreadId -> EvalStatus [HValueRef] -> IO ()
    
    523
    -writeThreadEvalStatus tid new = do
    
    565
    +writeThreadEvalStatus tid new =
    
    524 566
       atomically $ do
    
    525
    -    dbgmap <- readTVar globalDbgStatusVar
    
    526
    -    writeTVar globalDbgStatusVar $!
    
    527
    -      Map.insertWith (\_ _ -> error "writeThreadEvalStatus: should be impossible") tid new dbgmap
    
    528
    -
    
    529
    --- | A global synchronization variable which a thread writes an 'EvalBreak' to
    
    530
    --- when it hits a breakpoint. This variable is shared across all threads, and
    
    531
    --- the debugger can read the threads which hit breakpoints by reading from this
    
    532
    --- variable. It will also be written with 'EvalSuccess' when a `sandboxIO`
    
    533
    --- action finishes (TODO: wait what? that is a bit weird. so what if we run two
    
    534
    --- evalStmt? they will both race to write a result? but then how do we tell
    
    535
    --- which result we got from where? this won't do in general, but let's see if
    
    536
    --- it can solve some problems first)
    
    567
    +    GlobalDbgStatus{..} <- readTVar globalDbgStatusVar
    
    568
    +    writeTVar globalDbgStatusVar $
    
    569
    +      GlobalDbgStatus
    
    570
    +        { threadEvalStatuses =
    
    571
    +            Map.insertWith (\_ _ -> error "writeThreadEvalStatus: should be impossible")
    
    572
    +                tid new threadEvalStatuses
    
    573
    +        , shadowedThreads }
    
    574
    +
    
    575
    +-- | Make sure that anyone watching for threads hitting breakpoints with
    
    576
    +-- 'readAnyThreadEvalBreak' ignore the given thread if it ever hits a breakpoint.
    
    537 577
     --
    
    538
    --- IDEA: maybe `sandboxIO` and friends receive as an argument the var on which
    
    539
    --- to expect the result specifically? And distinguish that from the global break var?
    
    540
    --- Then we split the EvalSuccess and EvalBreak into two datatypes.
    
    578
    +-- This forces this thread's result to be received solely through
    
    579
    +-- 'readThreadEvalStatus' for that ThreadId, which is typically desired when an
    
    580
    +-- expression to run should not be seen at all by the debugger (e.g. when
    
    581
    +-- evaluating an expr to :force a variable)
    
    582
    +withShadowThread :: ThreadId -> IO a -> IO a
    
    583
    +withShadowThread tid = bracket_ setShadowThread unsetShadowThread
    
    584
    +  where
    
    585
    +    setShadowThread = atomically $ do
    
    586
    +      GlobalDbgStatus{..} <- readTVar globalDbgStatusVar
    
    587
    +      writeTVar globalDbgStatusVar
    
    588
    +        GlobalDbgStatus
    
    589
    +          { threadEvalStatuses
    
    590
    +          , shadowedThreads = Set.insert tid shadowedThreads
    
    591
    +          }
    
    592
    +    unsetShadowThread = atomically $ do
    
    593
    +      GlobalDbgStatus{..} <- readTVar globalDbgStatusVar
    
    594
    +      writeTVar globalDbgStatusVar
    
    595
    +        GlobalDbgStatus
    
    596
    +          { threadEvalStatuses
    
    597
    +          , shadowedThreads = Set.delete tid shadowedThreads
    
    598
    +          }
    
    599
    +
    
    600
    +-- | When we launch a new thread using the debugger, we map ... TODO ... all
    
    601
    +-- threads launched by that main stmt may further spawn threads, and any of
    
    602
    +-- them might hit a breakpoint (break array toggled, or per-thread step in
    
    603
    +-- might be used, or pause button, etc...).
    
    541 604
     --
    
    542
    --- Instead, let's just assume we may wait for a single thread's result and this
    
    543
    --- kind of falls out nicely. See my impl. notes.
    
    544
    -globalDbgStatusVar :: TVar (Map.Map ThreadId (EvalStatus [HValueRef]))
    
    545
    -globalDbgStatusVar = unsafePerformIO $ newTVarIO Map.empty
    
    605
    +-- When we launch that main thread we also write its result to this map. So
    
    606
    +-- this map will only contain the results of threads we explicitly care to
    
    607
    +-- observe the result of. Not all threads will write their results here,
    
    608
    +-- certainly not ones spawned unknowningly by the main stmt.
    
    609
    +-- ...
    
    610
    +-- todo words
    
    611
    +globalDbgStatusVar :: TVar GlobalDbgStatus
    
    612
    +globalDbgStatusVar = unsafePerformIO $ newTVarIO $ GlobalDbgStatus Map.empty Set.empty
    
    546 613
     {-# NOINLINE globalDbgStatusVar #-}
    
    614
    +
    
    615
    +data GlobalDbgStatus = GlobalDbgStatus
    
    616
    +  { threadEvalStatuses :: !(Map.Map ThreadId (EvalStatus [HValueRef]))
    
    617
    +  -- ^ Whenever a thread hits a breakpoint it inserts in this map its 'EvalBreak'.
    
    618
    +  -- We can additionally observe the result of a thread by calling
    
    619
    +  -- `writeThreadEvalStatus` at the end with its result, e.g. in 'sandboxIO'
    
    620
    +  , shadowedThreads    :: !(Set.Set ThreadId)
    
    621
    +  -- ^ Threads in this map are meant to be ignored by 'readAnyThreadEvalBreak'.
    
    622
    +  -- That is, even if one of the threads in this set hits a breakpoint, we
    
    623
    +  -- can only read its 'EvalBreak' with @'readThreadEvalStatus' tid@ directly.
    
    624
    +  }