[Git][ghc/ghc][wip/romes/top-level-bcos-tag] 2 commits: debugger/rts: Allow toggling step-in per thread
Rodrigo Mesquita pushed to branch wip/romes/top-level-bcos-tag at Glasgow Haskell Compiler / GHC Commits: b0249bf0 by Rodrigo Mesquita at 2025-05-23T09:04:51+01:00 debugger/rts: Allow toggling step-in per thread The RTS global flag `rts_stop_next_breakpoint` globally sets the interpreter to stop at the immediate next breakpoint. With this commit, single step mode can additionally be set per thread in the TSO flag (TSO_STOP_NEXT_BREAKPOINT). Being able to toggle "stop at next breakpoint" per thread is an important requirement for implementing "stepping out" of a function in a multi-threaded context. And, more generally, having a per-thread flag for single-stepping paves the way for multi-threaded debugging. That said, when we want to enable "single step" mode for the whole interpreted program we still want to stop at the immediate next breakpoint, whichever thread it belongs to. That's why we also keep the global `rts_stop_next_breakpoint` flag, with `rts_enableStopNextBreakpointAll` and `rts_disableStopNextBreakpointAll` helpers. Preparation for #26042 - - - - - 8c40855b by Rodrigo Mesquita at 2025-05-23T09:16:43+01:00 rts: Case continuation BCOs This commit introduces the `stg_CASE_CONT_BCO` info table, which is identical to `stg_BCO` and shares the same closure type (== BCO). It changes the bytecode generator to always use `stg_CASE_CONT_BCO_info` when constructing case continuation BCOs, and remain using `stg_BCO` otherwise. This allows us to distinguish at runtime case continuation BCOs from other BCOs. In particular, this is relevant because, unlike other BCOs, the code of a case continuation BCO may refer to variables in its parent's stack frame (ie non-local variables), and therefore its frame position on the stack cannot be changed in isolation. The full motivation and details are in Note [Case continuation BCOs]. Towards #26042 - - - - - 25 changed files: - compiler/GHC/Builtin/primops.txt.pp - compiler/GHC/ByteCode/Asm.hs - compiler/GHC/ByteCode/Instr.hs - compiler/GHC/ByteCode/Linker.hs - compiler/GHC/ByteCode/Types.hs - compiler/GHC/StgToByteCode.hs - libraries/base/src/GHC/Exts.hs - libraries/ghc-heap/GHC/Exts/Heap/Closures.hs - libraries/ghc-heap/GHC/Exts/Heap/FFIClosures_ProfilingDisabled.hsc - libraries/ghc-heap/GHC/Exts/Heap/FFIClosures_ProfilingEnabled.hsc - libraries/ghc-heap/tests/parse_tso_flags.hs - libraries/ghc-internal/src/GHC/Internal/Exts.hs - libraries/ghci/GHCi/CreateBCO.hs - + libraries/ghci/GHCi/Debugger.hs - libraries/ghci/GHCi/ResolvedBCO.hs - libraries/ghci/GHCi/Run.hs - libraries/ghci/ghci.cabal.in - rts/Interpreter.c - rts/Interpreter.h - rts/PrimOps.cmm - rts/Printer.c - rts/RtsSymbols.c - rts/StgMiscClosures.cmm - rts/include/rts/Constants.h - rts/include/stg/MiscClosures.h Changes: ===================================== compiler/GHC/Builtin/primops.txt.pp ===================================== @@ -3872,12 +3872,13 @@ primop MkApUpd0_Op "mkApUpd0#" GenPrimOp with out_of_line = True -primop NewBCOOp "newBCO#" GenPrimOp - ByteArray# -> ByteArray# -> Array# a -> Int# -> ByteArray# -> State# s -> (# State# s, BCO #) - { @'newBCO#' instrs lits ptrs arity bitmap@ creates a new bytecode object. The +primop NewBCOOp "newBCO2#" GenPrimOp + Int8# -> ByteArray# -> ByteArray# -> Array# a -> Int# -> ByteArray# -> State# s -> (# State# s, BCO #) + { @'newBCO2#' is_case_cont instrs lits ptrs arity bitmap@ creates a new bytecode object. The resulting object encodes a function of the given arity with the instructions encoded in @instrs@, and a static reference table usage bitmap given by - @bitmap@. } + @bitmap@. The @is_case_cont@ boolean indicates whether the BCO is a case + continuation (see Note [Case continuation BCOs]) } with effect = ReadWriteEffect out_of_line = True ===================================== compiler/GHC/ByteCode/Asm.hs ===================================== @@ -85,7 +85,7 @@ bcoFreeNames :: UnlinkedBCO -> UniqDSet Name bcoFreeNames bco = bco_refs bco `uniqDSetMinusUniqSet` mkNameSet [unlinkedBCOName bco] where - bco_refs (UnlinkedBCO _ _ _ _ nonptrs ptrs) + bco_refs (UnlinkedBCO _ _ _ _ nonptrs ptrs _) = unionManyUniqDSets ( mkUniqDSet [ n | BCOPtrName n <- elemsFlatBag ptrs ] : mkUniqDSet [ n | BCONPtrItbl n <- elemsFlatBag nonptrs ] : @@ -236,7 +236,8 @@ assembleBCO platform , protoBCOInstrs = instrs , protoBCOBitmap = bitmap , protoBCOBitmapSize = bsize - , protoBCOArity = arity }) = do + , protoBCOArity = arity + , protoBCOIsCaseCont = isCC }) = do -- pass 1: collect up the offsets of the local labels. let initial_offset = 0 @@ -266,7 +267,7 @@ assembleBCO platform let !insns_arr = mkBCOByteArray $ final_isn_array !bitmap_arr = mkBCOByteArray $ mkBitmapArray bsize bitmap - ul_bco = UnlinkedBCO nm arity insns_arr bitmap_arr (fromSmallArray final_lit_array) (fromSmallArray final_ptr_array) + ul_bco = UnlinkedBCO nm arity insns_arr bitmap_arr (fromSmallArray final_lit_array) (fromSmallArray final_ptr_array) isCC -- 8 Aug 01: Finalisers aren't safe when attached to non-primitive -- objects, since they might get run too early. Disable this until ===================================== compiler/GHC/ByteCode/Instr.hs ===================================== @@ -53,7 +53,8 @@ data ProtoBCO a -- what the BCO came from, for debugging only protoBCOExpr :: Either [CgStgAlt] CgStgRhs, -- malloc'd pointers - protoBCOFFIs :: [FFIInfo] + protoBCOFFIs :: [FFIInfo], + protoBCOIsCaseCont :: !Bool -- See Note [Case continuation BCOs] } -- | A local block label (e.g. identifying a case alternative). ===================================== compiler/GHC/ByteCode/Linker.hs ===================================== @@ -59,7 +59,7 @@ linkBCO -> UnlinkedBCO -> IO ResolvedBCO linkBCO interp pkgs_loaded le bco_ix - (UnlinkedBCO _ arity insns bitmap lits0 ptrs0) = do + (UnlinkedBCO _ arity insns bitmap lits0 ptrs0 isCC) = do -- fromIntegral Word -> Word64 should be a no op if Word is Word64 -- otherwise it will result in a cast to longlong on 32bit systems. (lits :: [Word]) <- mapM (fmap fromIntegral . lookupLiteral interp pkgs_loaded le) (elemsFlatBag lits0) @@ -69,7 +69,7 @@ linkBCO interp pkgs_loaded le bco_ix insns bitmap (mkBCOByteArray lits') - (addListToSS emptySS ptrs)) + (addListToSS emptySS ptrs) isCC) lookupLiteral :: Interp -> PkgsLoaded -> LinkerEnv -> BCONPtr -> IO Word lookupLiteral interp pkgs_loaded le ptr = case ptr of ===================================== compiler/GHC/ByteCode/Types.hs ===================================== @@ -167,14 +167,108 @@ newtype ItblPtr = ItblPtr (RemotePtr Heap.StgInfoTable) newtype AddrPtr = AddrPtr (RemotePtr ()) deriving (NFData) +{- +-------------------------------------------------------------------------------- +-- * Byte Code Objects (BCOs) +-------------------------------------------------------------------------------- + +Note [Case continuation BCOs] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A stack with a BCO stack frame at the top looks like: + + (an StgBCO) + | ... | +---> +---------[1]--+ + +------------------+ | | info_tbl_ptr | ------+ + | OTHER FRAME | | +--------------+ | + +------------------+ | | StgArrBytes* | <--- the byte code + | ... | | +--------------+ | + +------------------+ | | ... | | + | fvs1 | | | + +------------------+ | | + | ... | | (StgInfoTable) | + +------------------+ | +----------+ <---+ + | args1 | | | ... | + +------------------+ | +----------+ + | some StgBCO* | -----+ | type=BCO | + +------------------+ +----------+ + Sp | stg_apply_interp | -----+ | ... | + +------------------+ | + | + | (StgInfoTable) + +----> +--------------+ + | ... | + +--------------+ + | type=RET_BCO | + +--------------+ + | ... | + + +The byte code for a BCO heap object makes use of arguments and free variables +which can typically be found within the BCO stack frame. In the code, these +variables are referenced via a statically known stack offset (tracked using +`BCEnv` in `StgToByteCode`). + +However, in /case continuation/ BCOs, the code may additionally refer to free +variables that are outside of that BCO's stack frame -- some free variables of a +case continuation BCO may only be found in the stack frame of a parent BCO. + +Yet, references to these out-of-frame variables are also done in terms of stack +offsets. Thus, they rely on the position of /another frame/ to be fixed. (See +Note [PUSH_L underflow] for more information about references to previous +frames and nested BCOs) + +This makes case continuation BCOs special: unlike normal BCOs, case cont BCO +frames cannot be moved on the stack independently from their parent BCOs. + +In order to be able to distinguish them at runtime, the code generator will use +distinct info table pointers for their closures, even though they will have the +same structure on the heap (StgBCO). Specifically: + + - Normal BCOs are always headed by the `stg_BCO_info` pointer. + - Case continuation BCOs are always headed by the `stg_CASE_CONT_BCO_info` pointer. + +A primary reason why we need to distinguish these two cases is to know where we +can insert a debugger step-out frame (`stg_stop_after_ret_frame`). In +particular, because case cont BCOs may refer to the parent frame, we must not +insert step-out frames between a case cont BCO and its parent. + +As an example, consider the following, where `y` is free in the case alternatives: + + f x y = case x of + True -> y - 1 + False -> y + 1 :: Int + +While interpreting f, the args x and y will be on the stack as part of f's frame. +In its body, a case continuation BCO is pushed (PUSH_ALTS) and then `x` is +entered to be evaluated. Upon entering `x`, the stack would look something like: + + <f arg 2> + <f arg 1> + ... + <Case continuation BCO Frame> + +We cannot insert a step out frame in between: + + + <f arg 2> + <f arg 1> + ... + <inserted step-out frame> <--- BAD! Breaks stack offsets in the case cont. + <Case continuation BCO Frame> + +Instead, we must traverse until the parent BCO and insert the step-out frame before it instead. +-} + data UnlinkedBCO = UnlinkedBCO { unlinkedBCOName :: !Name, unlinkedBCOArity :: {-# UNPACK #-} !Int, - unlinkedBCOInstrs :: !(BCOByteArray Word16), -- insns - unlinkedBCOBitmap :: !(BCOByteArray Word), -- bitmap + unlinkedBCOInstrs :: !(BCOByteArray Word16), -- insns + unlinkedBCOBitmap :: !(BCOByteArray Word), -- bitmap unlinkedBCOLits :: !(FlatBag BCONPtr), -- non-ptrs - unlinkedBCOPtrs :: !(FlatBag BCOPtr) -- ptrs + unlinkedBCOPtrs :: !(FlatBag BCOPtr), -- ptrs + unlinkedBCOIsCaseCont :: !Bool -- See Note [Case continuation BCOs] } instance NFData UnlinkedBCO where @@ -227,10 +321,11 @@ seqCgBreakInfo CgBreakInfo{..} = rnf cgb_resty instance Outputable UnlinkedBCO where - ppr (UnlinkedBCO nm _arity _insns _bitmap lits ptrs) + ppr (UnlinkedBCO nm _arity _insns _bitmap lits ptrs pi) = sep [text "BCO", ppr nm, text "with", ppr (sizeFlatBag lits), text "lits", - ppr (sizeFlatBag ptrs), text "ptrs" ] + ppr (sizeFlatBag ptrs), text "ptrs", + ppr pi, text "is_pos_indep"] instance Outputable CgBreakInfo where ppr info = text "CgBreakInfo" <+> ===================================== compiler/GHC/StgToByteCode.hs ===================================== @@ -253,7 +253,11 @@ mkProtoBCO -> Int -- ^ arity -> WordOff -- ^ bitmap size -> [StgWord] -- ^ bitmap - -> Bool -- ^ True <=> is a return point, rather than a function + -> Bool -- ^ True <=> it's a case continuation, rather than a function + -- Used for + -- (A) Stack check collision and + -- (B) Mark the BCO wrt whether it contains non-local stack + -- references. See Note [Case continuation BCOs]. -> [FFIInfo] -> ProtoBCO Name mkProtoBCO platform _add_bco_name nm instrs_ordlist origin arity bitmap_size bitmap is_ret ffis @@ -264,7 +268,8 @@ mkProtoBCO platform _add_bco_name nm instrs_ordlist origin arity bitmap_size bit protoBCOBitmapSize = fromIntegral bitmap_size, protoBCOArity = arity, protoBCOExpr = origin, - protoBCOFFIs = ffis + protoBCOFFIs = ffis, + protoBCOIsCaseCont = is_ret } where #if MIN_VERSION_rts(1,0,3) @@ -353,6 +358,9 @@ schemeTopBind (id, rhs) -- Park the resulting BCO in the monad. Also requires the -- name of the variable to which this value was bound, -- so as to give the resulting BCO a name. +-- +-- The resulting ProtoBCO expects the free variables and the function arguments +-- to be in the stack directly before it. schemeR :: [Id] -- Free vars of the RHS, ordered as they -- will appear in the thunk. Empty for -- top-level things, which have no free vars. @@ -391,6 +399,8 @@ schemeR_wrk fvs nm original_body (args, body) -- them unlike constructor fields. szsb_args = map (wordsToBytes platform . idSizeW platform) all_args sum_szsb_args = sum szsb_args + -- Make a stack offset for each argument or free var -- they should + -- appear contiguous in the stack, in order. p_init = Map.fromList (zip all_args (mkStackOffsets 0 szsb_args)) -- make the arg bitmap @@ -1401,7 +1411,7 @@ Note [unboxed tuple bytecodes and tuple_BCO] tupleBCO :: Platform -> NativeCallInfo -> [(PrimRep, ByteOff)] -> [FFIInfo] -> ProtoBCO Name tupleBCO platform args_info args = mkProtoBCO platform Nothing invented_name body_code (Left []) - 0{-no arity-} bitmap_size bitmap False{-is alts-} + 0{-no arity-} bitmap_size bitmap False{-not alts-} where {- The tuple BCO is never referred to by name, so we can get away @@ -1422,7 +1432,7 @@ tupleBCO platform args_info args = primCallBCO :: Platform -> NativeCallInfo -> [(PrimRep, ByteOff)] -> [FFIInfo] -> ProtoBCO Name primCallBCO platform args_info args = mkProtoBCO platform Nothing invented_name body_code (Left []) - 0{-no arity-} bitmap_size bitmap False{-is alts-} + 0{-no arity-} bitmap_size bitmap False{-not alts-} where {- The primcall BCO is never referred to by name, so we can get away ===================================== libraries/base/src/GHC/Exts.hs ===================================== @@ -26,12 +26,12 @@ module GHC.Exts -- ** Legacy interface for arrays of arrays module GHC.Internal.ArrayArray, -- * Primitive operations - {-# DEPRECATED ["The BCO, mkApUpd0#, and newBCO# re-exports from GHC.Exts have been deprecated and will be removed in 9.14", "These symbols should be imported from ghc-internal instead if needed."] #-} + {-# DEPRECATED ["The BCO, mkApUpd0#, and newBCO# re-exports from GHC.Exts have been deprecated and will be removed in 10.14", "These symbols should be imported from ghc-internal instead if needed."] #-} Prim.BCO, {-# DEPRECATED ["The BCO, mkApUpd0#, and newBCO# re-exports from GHC.Exts have been deprecated and will be removed in 9.14", "These symbols should be imported from ghc-internal instead if needed."] #-} Prim.mkApUpd0#, {-# DEPRECATED ["The BCO, mkApUpd0#, and newBCO# re-exports from GHC.Exts have been deprecated and will be removed in 9.14", "These symbols should be imported from ghc-internal instead if needed."] #-} - Prim.newBCO#, + IExts.newBCO#, module GHC.Prim, module GHC.Prim.Ext, -- ** Running 'RealWorld' state thread @@ -119,7 +119,7 @@ module GHC.Exts maxTupleSize ) where -import GHC.Internal.Exts +import GHC.Internal.Exts hiding ( newBCO# ) import GHC.Internal.ArrayArray import GHC.Prim hiding ( coerce @@ -132,7 +132,7 @@ import GHC.Prim hiding , isByteArrayWeaklyPinned#, isMutableByteArrayWeaklyPinned# -- deprecated - , BCO, mkApUpd0#, newBCO# + , BCO, mkApUpd0# -- Don't re-export vector FMA instructions , fmaddFloatX4# @@ -256,8 +256,10 @@ import GHC.Prim hiding , minWord8X32# , minWord8X64# ) +import qualified GHC.Internal.Exts as IExts + ( newBCO# ) import qualified GHC.Prim as Prim - ( BCO, mkApUpd0#, newBCO# ) + ( BCO, mkApUpd0# ) import GHC.Prim.Ext ===================================== libraries/ghc-heap/GHC/Exts/Heap/Closures.hs ===================================== @@ -624,6 +624,7 @@ data TsoFlags | TsoMarked | TsoSqueezed | TsoAllocLimit + | TsoStopNextBreakpoint | TsoFlagsUnknownValue Word32 -- ^ Please report this as a bug deriving (Eq, Show, Generic, Ord) ===================================== libraries/ghc-heap/GHC/Exts/Heap/FFIClosures_ProfilingDisabled.hsc ===================================== @@ -87,6 +87,9 @@ parseTsoFlags w | isSet (#const TSO_LOCKED) w = TsoLocked : parseTsoFlags (unset | isSet (#const TSO_MARKED) w = TsoMarked : parseTsoFlags (unset (#const TSO_MARKED) w) | isSet (#const TSO_SQUEEZED) w = TsoSqueezed : parseTsoFlags (unset (#const TSO_SQUEEZED) w) | isSet (#const TSO_ALLOC_LIMIT) w = TsoAllocLimit : parseTsoFlags (unset (#const TSO_ALLOC_LIMIT) w) +#if __GLASGOW_HASKELL__ >= 913 + | isSet (#const TSO_STOP_NEXT_BREAKPOINT) w = TsoStopNextBreakpoint : parseTsoFlags (unset (#const TSO_STOP_NEXT_BREAKPOINT) w) +#endif parseTsoFlags 0 = [] parseTsoFlags w = [TsoFlagsUnknownValue w] ===================================== libraries/ghc-heap/GHC/Exts/Heap/FFIClosures_ProfilingEnabled.hsc ===================================== @@ -87,6 +87,9 @@ parseTsoFlags w | isSet (#const TSO_LOCKED) w = TsoLocked : parseTsoFlags (unset | isSet (#const TSO_MARKED) w = TsoMarked : parseTsoFlags (unset (#const TSO_MARKED) w) | isSet (#const TSO_SQUEEZED) w = TsoSqueezed : parseTsoFlags (unset (#const TSO_SQUEEZED) w) | isSet (#const TSO_ALLOC_LIMIT) w = TsoAllocLimit : parseTsoFlags (unset (#const TSO_ALLOC_LIMIT) w) +#if __GLASGOW_HASKELL__ >= 913 + | isSet (#const TSO_STOP_NEXT_BREAKPOINT) w = TsoStopNextBreakpoint : parseTsoFlags (unset (#const TSO_STOP_NEXT_BREAKPOINT) w) +#endif parseTsoFlags 0 = [] parseTsoFlags w = [TsoFlagsUnknownValue w] ===================================== libraries/ghc-heap/tests/parse_tso_flags.hs ===================================== @@ -13,5 +13,6 @@ main = do assertEqual (parseTsoFlags 64) [TsoMarked] assertEqual (parseTsoFlags 128) [TsoSqueezed] assertEqual (parseTsoFlags 256) [TsoAllocLimit] + assertEqual (parseTsoFlags 512) [TsoStopNextBreakpoint] assertEqual (parseTsoFlags 6) [TsoLocked, TsoBlockx] ===================================== libraries/ghc-internal/src/GHC/Internal/Exts.hs ===================================== @@ -163,6 +163,9 @@ module GHC.Internal.Exts -- * The maximum tuple size maxTupleSize, + + -- * Interpreter + newBCO# ) where import GHC.Internal.Prim hiding ( coerce, dataToTagSmall#, dataToTagLarge#, whereFrom# ) @@ -469,3 +472,18 @@ resizeSmallMutableArray# arr0 szNew a s0 = -- accessible\" by word. considerAccessible :: Bool considerAccessible = True + +-------------------------------------------------------------------------------- +-- Interpreter + +{-| +@'newBCO#' instrs lits ptrs arity bitmap@ creates a new bytecode object. The +resulting object encodes a function of the given arity with the instructions +encoded in @instrs@, and a static reference table usage bitmap given by +@bitmap@. + +Note: Case continuation BCOs, with non-local stack references, must be +constructed using @'newBCO2#' 1@ instead. See Note [Case continuation BCOs]. +-} +newBCO# :: ByteArray# -> ByteArray# -> Array# a -> Int# -> ByteArray# -> State# s -> (# State# s, BCO #) +newBCO# b1 b2 a1 i1 b3 s = newBCO2# (intToInt8# 0#) b1 b2 a1 i1 b3 s ===================================== libraries/ghci/GHCi/CreateBCO.hs ===================================== @@ -87,11 +87,11 @@ linkBCO' arr ResolvedBCO{..} = do literals_barr = barr (getBCOByteArray resolvedBCOLits) PtrsArr marr <- mkPtrsArray arr n_ptrs ptrs + let is_case_cont | resolvedBCOIsCaseCont = intToInt8# 1# + | otherwise = intToInt8# 0# IO $ \s -> case unsafeFreezeArray# marr s of { (# s, arr #) -> - case newBCO insns_barr literals_barr arr arity# bitmap_barr of { IO io -> - io s - }} + newBCO2# is_case_cont insns_barr literals_barr arr arity# bitmap_barr s } -- we recursively link any sub-BCOs while making the ptrs array ===================================== libraries/ghci/GHCi/Debugger.hs ===================================== @@ -0,0 +1,67 @@ +{-# LANGUAGE MagicHash #-} +{-# LANGUAGE UnliftedFFITypes #-} +module GHCi.Debugger + ( + -- * Single step mode + rts_enableStopNextBreakpoint + , rts_enableStopNextBreakpointAll + , rts_disableStopNextBreakpoint + , rts_disableStopNextBreakpointAll + + -- * Stop on exception + , exceptionFlag + + -- * Breakpoint Callback + , BreakpointCallback + , breakPointIOAction + ) where + +import Prelude -- See note [Why do we import Prelude here?] + +import GHC.Base (ThreadId#, Addr#, Int#) +import Foreign.C (CInt) +import Foreign (StablePtr, Ptr, Storable (..)) +import GHCi.RemoteTypes (HValue) + +-------------------------------------------------------------------------------- +-- Single step mode + +-- | Enables the single step mode for a specific thread, thus stopping only on +-- breakpoints in that thread. +foreign import ccall unsafe "rts_enableStopNextBreakpoint" + rts_enableStopNextBreakpoint :: ThreadId# -> IO () + +-- | Disables per-thread single-step mode. Note: if global single-step is +-- enabled we stop at all breakpoints regardless of the per-thread flag. +foreign import ccall unsafe "rts_disableStopNextBreakpoint" + rts_disableStopNextBreakpoint :: ThreadId# -> IO () + +-- | Enables the single step mode for all threads, thus stopping at any +-- existing breakpoint. +foreign import ccall unsafe "rts_enableStopNextBreakpointAll" + rts_enableStopNextBreakpointAll :: IO () + +-- | Disables the single step mode for all threads +foreign import ccall unsafe "rts_disableStopNextBreakpointAll" + rts_disableStopNextBreakpointAll :: IO () + +-------------------------------------------------------------------------------- + +foreign import ccall "&rts_stop_on_exception" exceptionFlag :: Ptr CInt + +-------------------------------------------------------------------------------- + +type BreakpointCallback + = Addr# -- pointer to the breakpoint tick module name + -> Addr# -- pointer to the breakpoint tick module unit id + -> Int# -- breakpoint tick index + -> Addr# -- pointer to the breakpoint info module name + -> Addr# -- pointer to the breakpoint info module unit id + -> Int# -- breakpoint info index + -> Bool -- exception? + -> HValue -- the AP_STACK, or exception + -> IO () + +foreign import ccall "&rts_breakpoint_io_action" + breakPointIOAction :: Ptr (StablePtr BreakpointCallback) + ===================================== libraries/ghci/GHCi/ResolvedBCO.hs ===================================== @@ -45,7 +45,8 @@ data ResolvedBCO resolvedBCOBitmap :: BCOByteArray Word, -- ^ bitmap resolvedBCOLits :: BCOByteArray Word, -- ^ non-ptrs - subword sized entries still take up a full (host) word - resolvedBCOPtrs :: (SizedSeq ResolvedBCOPtr) -- ^ ptrs + resolvedBCOPtrs :: (SizedSeq ResolvedBCOPtr), -- ^ ptrs + resolvedBCOIsCaseCont :: !Bool -- ^ See Note [Case continuation BCOs] } deriving (Generic, Show) @@ -86,7 +87,8 @@ instance Binary ResolvedBCO where put resolvedBCOBitmap put resolvedBCOLits put resolvedBCOPtrs - get = ResolvedBCO <$> get <*> get <*> get <*> get <*> get <*> get + put resolvedBCOIsCaseCont + get = ResolvedBCO <$> get <*> get <*> get <*> get <*> get <*> get <*> get -- See Note [BCOByteArray serialization] instance (Binary a, Storable a, IArray UArray a) => Binary (BCOByteArray a) where ===================================== libraries/ghci/GHCi/Run.hs ===================================== @@ -1,5 +1,5 @@ {-# LANGUAGE GADTs, RecordWildCards, MagicHash, ScopedTypeVariables, CPP, - UnboxedTuples, LambdaCase #-} + UnboxedTuples, LambdaCase, UnliftedFFITypes #-} {-# OPTIONS_GHC -fno-warn-name-shadowing #-} -- | @@ -20,6 +20,7 @@ import GHCi.InfoTable #endif import qualified GHC.InfoProv as InfoProv +import GHCi.Debugger import GHCi.FFI import GHCi.Message import GHCi.ObjLink @@ -332,7 +333,7 @@ withBreakAction opts breakMVar statusMVar act stablePtr <- newStablePtr onBreak poke breakPointIOAction stablePtr when (breakOnException opts) $ poke exceptionFlag 1 - when (singleStep opts) $ setStepFlag + when (singleStep opts) rts_enableStopNextBreakpointAll return stablePtr -- Breaking on exceptions is not enabled by default, since it -- might be a bit surprising. The exception flag is turned off @@ -363,7 +364,7 @@ withBreakAction opts breakMVar statusMVar act resetBreakAction stablePtr = do poke breakPointIOAction noBreakStablePtr poke exceptionFlag 0 - resetStepFlag + rts_disableStopNextBreakpointAll freeStablePtr stablePtr resumeStmt @@ -396,28 +397,6 @@ abandonStmt hvref = do _ <- takeMVar resumeStatusMVar return () -foreign import ccall "&rts_stop_next_breakpoint" stepFlag :: Ptr CInt -foreign import ccall "&rts_stop_on_exception" exceptionFlag :: Ptr CInt - -setStepFlag :: IO () -setStepFlag = poke stepFlag 1 -resetStepFlag :: IO () -resetStepFlag = poke stepFlag 0 - -type BreakpointCallback - = Addr# -- pointer to the breakpoint tick module name - -> Addr# -- pointer to the breakpoint tick module unit id - -> Int# -- breakpoint tick index - -> Addr# -- pointer to the breakpoint info module name - -> Addr# -- pointer to the breakpoint info module unit id - -> Int# -- breakpoint info index - -> Bool -- exception? - -> HValue -- the AP_STACK, or exception - -> IO () - -foreign import ccall "&rts_breakpoint_io_action" - breakPointIOAction :: Ptr (StablePtr BreakpointCallback) - noBreakStablePtr :: StablePtr BreakpointCallback noBreakStablePtr = unsafePerformIO $ newStablePtr noBreakAction ===================================== libraries/ghci/ghci.cabal.in ===================================== @@ -60,6 +60,7 @@ library CPP-Options: -DHAVE_INTERNAL_INTERPRETER exposed-modules: GHCi.Run + GHCi.Debugger GHCi.CreateBCO GHCi.ObjLink GHCi.Signals ===================================== rts/Interpreter.c ===================================== @@ -203,14 +203,14 @@ PUSH_L instruction. |---------| | BCO_1 | -<-┐ -|---------| +|---------| | ......... | |---------| | PUSH_L <n> | BCO_N | ->-┘ |---------| Here BCO_N is syntactically nested within the code for BCO_1 and will result -in code that references the prior stack frame of BCO_1 for some of it's local +in code that references the prior stack frame of BCO_1 for some of its local variables. If a stack overflow happens between the creation of the stack frame for BCO_1 and BCO_N the RTS might move BCO_N to a new stack chunk while leaving BCO_1 in place, invalidating a simple offset based reference to the outer stack @@ -243,9 +243,44 @@ allocate_NONUPD (Capability *cap, int n_words) return allocate(cap, stg_max(sizeofW(StgHeader)+MIN_PAYLOAD_SIZE, n_words)); } -int rts_stop_next_breakpoint = 0; int rts_stop_on_exception = 0; +/* --------------------------------------------------------------------------- + * Enabling and disabling global single step mode + * ------------------------------------------------------------------------ */ + +/* A global toggle for single-step mode. + * Unlike `TSO_STOP_NEXT_BREAKPOINT`, which sets single-step mode per-thread, + * `rts_stop_next_breakpoint` globally enables single-step mode. If enabled, we + * will stop at the immediate next breakpoint regardless of what thread it is in. */ +int rts_stop_next_breakpoint = 0; + +void rts_enableStopNextBreakpointAll(void) +{ + rts_stop_next_breakpoint = 1; +} + +void rts_disableStopNextBreakpointAll(void) +{ + rts_stop_next_breakpoint = 0; +} + +/* --------------------------------------------------------------------------- + * Enabling and disabling per-thread single step mode + * ------------------------------------------------------------------------ */ + +void rts_enableStopNextBreakpoint(StgPtr tso) +{ + ((StgTSO *)tso)->flags |= TSO_STOP_NEXT_BREAKPOINT; +} + +void rts_disableStopNextBreakpoint(StgPtr tso) +{ + ((StgTSO *)tso)->flags &= ~TSO_STOP_NEXT_BREAKPOINT; +} + +/* -------------------------------------------------------------------------- */ + #if defined(INTERP_STATS) #define N_CODES 128 @@ -508,14 +543,35 @@ interpretBCO (Capability* cap) // // We have a BCO application to perform. Stack looks like: // - // | .... | - // +---------------+ - // | arg1 | - // +---------------+ - // | BCO | - // +---------------+ - // Sp | RET_BCO | - // +---------------+ + // + // (an StgBCO) + // +---> +---------[1]--+ + // | | stg_BCO_info | ------+ + // | +--------------+ | + // | | StgArrBytes* | <--- the byte code + // | ... | | +--------------+ | + // +------------------+ | | ... | | + // | fvs1 | | | + // +------------------+ | | + // | ... | | (StgInfoTable) | + // +------------------+ | +----------+ <---+ + // | args1 | | | ... | + // +------------------+ | +----------+ + // | some StgBCO* | -----+ | type=BCO | + // +------------------+ +----------+ + // Sp | stg_apply_interp | -----+ | ... | + // +------------------+ | + // | + // | (StgInfoTable) + // +----> +--------------+ + // | ... | + // +--------------+ + // | type=RET_BCO | + // +--------------+ + // | ... | + // + // [1] An StgBCO's info table pointer may also be stg_CASE_CONT_BCO_info. + // See Note [Case continuation BCOs]. // else if (SpW(0) == (W_)&stg_apply_interp_info) { obj = UNTAG_CLOSURE((StgClosure *)ReadSpW(1)); @@ -1250,7 +1306,7 @@ run_BCO: int arg8_cc; #endif StgArrBytes *breakPoints; - int returning_from_break; + int returning_from_break, stop_next_breakpoint; // the io action to run at a breakpoint StgClosure *ioAction; @@ -1280,6 +1336,13 @@ run_BCO: returning_from_break = cap->r.rCurrentTSO->flags & TSO_STOPPED_ON_BREAKPOINT; + // check whether this thread is set to stop at the immediate next + // breakpoint -- either by the global `rts_stop_next_breakpoint` + // flag, or by the local `TSO_STOP_NEXT_BREAKPOINT` + stop_next_breakpoint = + rts_stop_next_breakpoint || + cap->r.rCurrentTSO->flags & TSO_STOP_NEXT_BREAKPOINT; + #if defined(PROFILING) cap->r.rCCCS = pushCostCentre(cap->r.rCCCS, (CostCentre*)BCO_LIT(arg8_cc)); @@ -1291,20 +1354,20 @@ run_BCO: { breakPoints = (StgArrBytes *) BCO_PTR(arg1_brk_array); - // stop the current thread if either the - // "rts_stop_next_breakpoint" flag is true OR if the - // ignore count for this particular breakpoint is zero + // stop the current thread if either `stop_next_breakpoint` is + // true OR if the ignore count for this particular breakpoint is zero StgInt ignore_count = ((StgInt*)breakPoints->payload)[arg6_tick_index]; - if (rts_stop_next_breakpoint == false && ignore_count > 0) + if (stop_next_breakpoint == false && ignore_count > 0) { // decrement and write back ignore count ((StgInt*)breakPoints->payload)[arg6_tick_index] = --ignore_count; } - else if (rts_stop_next_breakpoint == true || ignore_count == 0) + else if (stop_next_breakpoint == true || ignore_count == 0) { // make sure we don't automatically stop at the // next breakpoint - rts_stop_next_breakpoint = false; + rts_stop_next_breakpoint = 0; + cap->r.rCurrentTSO->flags &= ~TSO_STOP_NEXT_BREAKPOINT; // allocate memory for a new AP_STACK, enough to // store the top stack frame plus an @@ -1477,7 +1540,7 @@ run_BCO: // Here we make sure references we push are tagged. // See Note [CBV Functions and the interpreter] in Info.hs - //Safe some memory reads if we already have a tag. + //Save some memory reads if we already have a tag. if(GET_CLOSURE_TAG(tagged_obj) == 0) { StgClosure *obj = UNTAG_CLOSURE(tagged_obj); switch ( get_itbl(obj)->type ) { ===================================== rts/Interpreter.h ===================================== @@ -11,3 +11,8 @@ RTS_PRIVATE Capability *interpretBCO (Capability* cap); void interp_startup ( void ); void interp_shutdown ( void ); + +void rts_enableStopNextBreakpointAll ( void ); +void rts_disableStopNextBreakpointAll ( void ); +void rts_enableStopNextBreakpoint ( StgPtr ); +void rts_disableStopNextBreakpoint ( StgPtr ); ===================================== rts/PrimOps.cmm ===================================== @@ -55,6 +55,7 @@ import CLOSURE stg_AP_STACK_info; import CLOSURE stg_AP_info; import CLOSURE stg_ARR_WORDS_info; import CLOSURE stg_BCO_info; +import CLOSURE stg_CASE_CONT_BCO_info; import CLOSURE stg_C_FINALIZER_LIST_info; import CLOSURE stg_DEAD_WEAK_info; import CLOSURE stg_END_STM_WATCH_QUEUE_closure; @@ -2434,7 +2435,8 @@ stg_deRefStablePtrzh ( P_ sp ) Bytecode object primitives ------------------------------------------------------------------------- */ -stg_newBCOzh ( P_ instrs, +stg_newBCO2zh ( CBool is_case_cont, + P_ instrs, P_ literals, P_ ptrs, W_ arity, @@ -2449,7 +2451,16 @@ stg_newBCOzh ( P_ instrs, bco = Hp - bytes + WDS(1); // No memory barrier necessary as this is a new allocation. - SET_HDR(bco, stg_BCO_info, CCS_MAIN); + if (is_case_cont > 0) { + /* Uses stg_CASE_CONT_BCO_info to construct the BCO frame (rather than stg_BCO_info). + * Case continuations may contain non-local references to parent frames. The distinct info table + * tag allows the RTS to identify such non-local frames. + * See Note [Case continuation BCOs] + */ + SET_HDR(bco, stg_CASE_CONT_BCO_info, CCS_MAIN); + } else { + SET_HDR(bco, stg_BCO_info, CCS_MAIN); + } StgBCO_instrs(bco) = instrs; StgBCO_literals(bco) = literals; ===================================== rts/Printer.c ===================================== @@ -690,6 +690,8 @@ printStackChunk( StgPtr sp, StgPtr spBottom ) debugBelch("stg_ctoi_V_info" ); } else if (c == (StgWord)&stg_BCO_info) { debugBelch("stg_BCO_info" ); + } else if (c == (StgWord)&stg_CASE_CONT_BCO_info) { + debugBelch("stg_CASE_CONT_BCO_info" ); } else if (c == (StgWord)&stg_apply_interp_info) { debugBelch("stg_apply_interp_info" ); } else if (c == (StgWord)&stg_ret_t_info) { ===================================== rts/RtsSymbols.c ===================================== @@ -639,7 +639,7 @@ extern char **environ; SymI_HasDataProto(stg_copySmallMutableArrayzh) \ SymI_HasDataProto(stg_casSmallArrayzh) \ SymI_HasDataProto(stg_copyArray_barrier) \ - SymI_HasDataProto(stg_newBCOzh) \ + SymI_HasDataProto(stg_newBCO2zh) \ SymI_HasDataProto(stg_newByteArrayzh) \ SymI_HasDataProto(stg_casIntArrayzh) \ SymI_HasDataProto(stg_casInt8Arrayzh) \ @@ -906,7 +906,8 @@ extern char **environ; SymI_HasProto(revertCAFs) \ SymI_HasProto(RtsFlags) \ SymI_NeedsDataProto(rts_breakpoint_io_action) \ - SymI_NeedsDataProto(rts_stop_next_breakpoint) \ + SymI_NeedsDataProto(rts_enableStopNextBreakpointAll) \ + SymI_NeedsDataProto(rts_disableStopNextBreakpointAll) \ SymI_NeedsDataProto(rts_stop_on_exception) \ SymI_HasProto(stopTimer) \ SymI_HasProto(n_capabilities) \ ===================================== rts/StgMiscClosures.cmm ===================================== @@ -464,6 +464,12 @@ INFO_TABLE_RET( stg_dead_thread, RET_SMALL, /* ---------------------------------------------------------------------------- Entry code for a BCO + + `stg_BCO` and `stg_CASE_CONT_BCO` distinguish between a BCO that refers to + non-local variables in its code (using a stack offset) and those that do not. + Only case-continuation BCOs should use non-local variables. + Otherwise, `stg_BCO` and `stg_CASE_CONT_BCO` behave the same. + See Note [Case continuation BCOs]. ------------------------------------------------------------------------- */ INFO_TABLE_FUN( stg_BCO, 3, 0, BCO, "BCO", "BCO", 0, ARG_BCO ) @@ -478,6 +484,15 @@ INFO_TABLE_FUN( stg_BCO, 3, 0, BCO, "BCO", "BCO", 0, ARG_BCO ) jump stg_yield_to_interpreter []; } +INFO_TABLE_FUN( stg_CASE_CONT_BCO, 3, 0, BCO, "BCO", "BCO", 0, ARG_BCO ) +{ + /* Exactly as for stg_BCO */ + Sp_adj(-2); + Sp(1) = R1; + Sp(0) = stg_apply_interp_info; + jump stg_yield_to_interpreter []; +} + /* ---------------------------------------------------------------------------- Info tables for indirections. ===================================== rts/include/rts/Constants.h ===================================== @@ -328,6 +328,12 @@ */ #define TSO_ALLOC_LIMIT 256 +/* + * Enables step-in mode for this thread -- it will stop at the immediate next + * breakpoint found in this thread. + */ +#define TSO_STOP_NEXT_BREAKPOINT 512 + /* * The number of times we spin in a spin lock before yielding (see * #3758). To tune this value, use the benchmark in #3758: run the ===================================== rts/include/stg/MiscClosures.h ===================================== @@ -180,6 +180,7 @@ RTS_ENTRY(stg_BLOCKING_QUEUE_CLEAN); RTS_ENTRY(stg_BLOCKING_QUEUE_DIRTY); RTS_FUN(stg_BCO); +RTS_FUN(stg_CASE_CONT_BCO); RTS_ENTRY(stg_EVACUATED); RTS_ENTRY(stg_WEAK); RTS_ENTRY(stg_DEAD_WEAK); @@ -577,7 +578,7 @@ RTS_FUN_DECL(stg_deRefWeakzh); RTS_FUN_DECL(stg_runRWzh); -RTS_FUN_DECL(stg_newBCOzh); +RTS_FUN_DECL(stg_newBCO2zh); RTS_FUN_DECL(stg_mkApUpd0zh); RTS_FUN_DECL(stg_retryzh); View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/aef9228695b5aaeda7e0aa8a3b4418f... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/aef9228695b5aaeda7e0aa8a3b4418f... You're receiving this email because of your account on gitlab.haskell.org.
participants (1)
-
Rodrigo Mesquita (@alt-romes)