Rodrigo Mesquita pushed to branch wip/romes/step-out-11 at Glasgow Haskell Compiler / GHC
Commits:
e4726887 by Rodrigo Mesquita at 2025-08-01T14:11:19+01:00
debugger: Allow BRK_FUNs to head case continuation BCOs
When we start executing a BCO, we may want to yield to the scheduler:
this may be triggered by a heap/stack check, context switch, or a
breakpoint. To yield, we need to put the stack in a state such that
when execution is resumed we are back to where we yielded from.
Previously, a BKR_FUN could only head a function BCO because we only
knew how to construct a valid stack for yielding from one -- simply add
`apply_interp_info` + the BCO to resume executing. This is valid because
the stack at the start of run_BCO is headed by that BCO's arguments.
However, in case continuation BCOs (as per Note [Case continuation BCOs]),
we couldn't easily reconstruct a valid stack that could be resumed
because we dropped too soon the stack frames regarding the value
returned (stg_ret) and received (stg_ctoi) by that continuation.
This is especially tricky because of the variable type and size return
frames (e.g. pointer ret_p/ctoi_R1p vs a tuple ret_t/ctoi_t2).
The trick to being able to yield from a BRK_FUN at the start of a case
cont BCO is to stop removing the ret frame headers eagerly and instead
keep them until the BCO starts executing. The new layout at the start of
a case cont. BCO is described by the new Note [Stack layout when entering run_BCO].
Now, we keep the ret_* and ctoi_* frames when entering run_BCO.
A BRK_FUN is then executed if found, and the stack is yielded as-is with
the preserved ret and ctoi frames.
Then, a case cont BCO's instructions always SLIDE off the headers of the
ret and ctoi frames, in StgToByteCode.doCase, turning a stack like
| .... |
+---------------+
| fv2 |
+---------------+
| fv1 |
+---------------+
| BCO |
+---------------+
| stg_ctoi_ret_ |
+---------------+
| retval |
+---------------+
| stg_ret_..... |
+---------------+
into
| .... |
+---------------+
| fv2 |
+---------------+
| fv1 |
+---------------+
| retval |
+---------------+
for the remainder of the BCO.
Moreover, this more uniform approach of keeping the ret and ctoi frames
means we need less ad-hoc logic concerning the variable size of
ret_tuple vs ret_p/np frames in the code generator and interpreter:
Always keep the return to cont. stack intact at the start of run_BCO,
and the statically generated instructions will take care of adjusting
it.
Unlocks BRK_FUNs at the start of case cont. BCOs which will enable a
better user-facing step-out (#26042) which is free of the bugs the
current BRK_ALTS implementation suffers from (namely, using BRK_FUN
rather than BRK_ALTS in a case cont. means we'll never accidentally end
up in a breakpoint "deeper" than the continuation, because we stop at
the case cont itself rather than on the first breakpoint we evaluate
after it).
- - - - -
efcdfc7c by Rodrigo Mesquita at 2025-08-01T14:11:20+01:00
BRK_FUN with InternalBreakLocs for code-generation time breakpoints
At the start of a case continuation BCO, place a BRK_FUN.
This BRK_FUN uses the new "internal breakpoint location" -- allowing us
to come up with a valid source location for this breakpoint that is not associated with a source-level tick.
For case continuation BCOs, we use the last tick seen before it as the
source location. The reasoning is described in Note [Debugger: Stepout internal break locs].
Note how T26042c, which was broken because it displayed the incorrect
behavior of the previous step out when we'd end up at a deeper level
than the one from which we initiated step-out, is now fixed.
As of this commit, BRK_ALTS is now dead code and is thus dropped.
Note [Debugger: Stepout internal break locs]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Step-out tells the interpreter to run until the current function
returns to where it was called from, and stop there.
This is achieved by enabling the BRK_FUN found on the first RET_BCO
frame on the stack (See [Note Debugger: Step-out]).
Case continuation BCOs (which select an alternative branch) must
therefore be headed by a BRK_FUN. An example:
f x = case g x of <--- end up here
1 -> ...
2 -> ...
g y = ... <--- step out from here
- `g` will return a value to the case continuation BCO in `f`
- The case continuation BCO will receive the value returned from g
- Match on it and push the alternative continuation for that branch
- And then enter that alternative.
If we step-out of `g`, the first RET_BCO on the stack is the case
continuation of `f` -- execution should stop at its start, before
selecting an alternative. (One might ask, "why not enable the breakpoint
in the alternative instead?", because the alternative continuation is
only pushed to the stack *after* it is selected by the case cont. BCO)
However, the case cont. BCO is not associated with any source-level
tick, it is merely the glue code which selects alternatives which do
have source level ticks. Therefore, we have to come up at code
generation time with a breakpoint location ('InternalBreakLoc') to
display to the user when it is stopped there.
Our solution is to use the last tick seen just before reaching the case
continuation. This is robust because a case continuation will thus
always have a relevant breakpoint location:
- The source location will be the last source-relevant expression
executed before the continuation is pushed
- So the source location will point to the thing you've just stepped
out of
- Doing :step-local from there will put you on the selected
alternative (which at the source level may also be the e.g. next
line in a do-block)
Examples, using angle brackets (<<...>>) to denote the breakpoint span:
f x = case <<g x>> {- step in here -} of
1 -> ...
2 -> ...>
g y = <<...>> <--- step out from here
...
f x = < ...
2 -> ...>>
doing :step-local ...
f x = case g x of
1 -> <<...>> <--- stop in the alternative
2 -> ...
A second example based on T26042d2, where the source is a do-block IO
action, optimised to a chain of `case expressions`.
main = do
putStrLn "hello1"
<<f>> <--- step-in here
putStrLn "hello3"
putStrLn "hello4"
f = do
<> <--- step-out from here
putStrLn "hello2.2"
...
main = do
putStrLn "hello1"
<<f>> <--- end up here again, the previously executed expression
putStrLn "hello3"
putStrLn "hello4"
doing step/step-local ...
main = do
putStrLn "hello1"
f
<> <--- straight to the next line
putStrLn "hello4"
Finishes #26042
- - - - -
23 changed files:
- compiler/GHC/ByteCode/Asm.hs
- compiler/GHC/ByteCode/Breakpoints.hs
- compiler/GHC/ByteCode/Instr.hs
- compiler/GHC/Linker/Loader.hs
- compiler/GHC/Runtime/Debugger/Breakpoints.hs
- compiler/GHC/Runtime/Eval.hs
- compiler/GHC/StgToByteCode.hs
- ghc/GHCi/UI.hs
- libraries/ghci/GHCi/Run.hs
- rts/Disassembler.c
- rts/Interpreter.c
- rts/Profiling.c
- rts/include/rts/Bytecodes.h
- testsuite/tests/ghci.debugger/scripts/T26042b.stdout
- testsuite/tests/ghci.debugger/scripts/T26042c.script
- testsuite/tests/ghci.debugger/scripts/T26042c.stdout
- + testsuite/tests/ghci.debugger/scripts/T26042d2.hs
- + testsuite/tests/ghci.debugger/scripts/T26042d2.script
- + testsuite/tests/ghci.debugger/scripts/T26042d2.stdout
- testsuite/tests/ghci.debugger/scripts/T26042e.stdout
- testsuite/tests/ghci.debugger/scripts/T26042f2.stdout
- testsuite/tests/ghci.debugger/scripts/T26042g.stdout
- testsuite/tests/ghci.debugger/scripts/all.T
Changes:
=====================================
compiler/GHC/ByteCode/Asm.hs
=====================================
@@ -854,8 +854,6 @@ assembleI platform i = case i of
emit_ bci_BRK_FUN [ Op p1, Op info_addr, Op info_unitid_addr
, SmallOp (toW16 infox), Op np ]
- BRK_ALTS active -> emit_ bci_BRK_ALTS [SmallOp (if active then 1 else 0)]
-
#if MIN_VERSION_rts(1,0,3)
BCO_NAME name -> do np <- lit1 (BCONPtrStr name)
emit_ bci_BCO_NAME [Op np]
=====================================
compiler/GHC/ByteCode/Breakpoints.hs
=====================================
@@ -1,4 +1,5 @@
{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE DerivingStrategies #-}
-- | Breakpoint information constructed during ByteCode generation.
--
@@ -15,6 +16,7 @@ module GHC.ByteCode.Breakpoints
-- ** Internal breakpoint identifier
, InternalBreakpointId(..), BreakInfoIndex
+ , InternalBreakLoc(..)
-- * Operations
@@ -23,7 +25,7 @@ module GHC.ByteCode.Breakpoints
-- ** Source-level information operations
, getBreakLoc, getBreakVars, getBreakDecls, getBreakCCS
- , getBreakSourceId
+ , getBreakSourceId, getBreakSourceMod
-- * Utils
, seqInternalModBreaks
@@ -165,7 +167,7 @@ data CgBreakInfo
{ cgb_tyvars :: ![IfaceTvBndr] -- ^ Type variables in scope at the breakpoint
, cgb_vars :: ![Maybe (IfaceIdBndr, Word)]
, cgb_resty :: !IfaceType
- , cgb_tick_id :: !BreakpointId
+ , cgb_tick_id :: !(Either InternalBreakLoc BreakpointId)
-- ^ This field records the original breakpoint tick identifier for this
-- internal breakpoint info. It is used to convert a breakpoint
-- *occurrence* index ('InternalBreakpointId') into a *definition* index
@@ -173,9 +175,18 @@ data CgBreakInfo
--
-- The modules of breakpoint occurrence and breakpoint definition are not
-- necessarily the same: See Note [Breakpoint identifiers].
+ --
+ -- If there is no original tick identifier (that is, the breakpoint was
+ -- created during code generation), instead refer directly to the SrcSpan
+ -- we want to use for it.
}
-- See Note [Syncing breakpoint info] in GHC.Runtime.Eval
+-- | Breakpoints created during code generation don't have a source-level tick
+-- location. Instead, we come up with one ourselves.
+newtype InternalBreakLoc = InternalBreakLoc SrcSpan
+ deriving newtype (Eq, Show, NFData, Outputable)
+
-- | Get an internal breakpoint info by 'InternalBreakpointId'
getInternalBreak :: InternalBreakpointId -> InternalModBreaks -> CgBreakInfo
getInternalBreak (InternalBreakpointId mod ix) imbs =
@@ -196,27 +207,36 @@ assert_modules_match ibi_mod imbs_mod =
-- | Get the source module and tick index for this breakpoint
-- (as opposed to the module where this breakpoint occurs, which is in 'InternalBreakpointId')
-getBreakSourceId :: InternalBreakpointId -> InternalModBreaks -> BreakpointId
+getBreakSourceId :: InternalBreakpointId -> InternalModBreaks -> Either InternalBreakLoc BreakpointId
getBreakSourceId (InternalBreakpointId ibi_mod ibi_ix) imbs =
assert_modules_match ibi_mod (imodBreaks_module imbs) $
let cgb = imodBreaks_breakInfo imbs IM.! ibi_ix
in cgb_tick_id cgb
+-- | Get the source module for this breakpoint (where the breakpoint is defined)
+getBreakSourceMod :: InternalBreakpointId -> InternalModBreaks -> Module
+getBreakSourceMod (InternalBreakpointId ibi_mod ibi_ix) imbs =
+ assert_modules_match ibi_mod (imodBreaks_module imbs) $
+ let cgb = imodBreaks_breakInfo imbs IM.! ibi_ix
+ in case cgb_tick_id cgb of
+ Left InternalBreakLoc{} -> imodBreaks_module imbs
+ Right BreakpointId{bi_tick_mod} -> bi_tick_mod
+
-- | Get the source span for this breakpoint
getBreakLoc :: (Module -> IO ModBreaks) -> InternalBreakpointId -> InternalModBreaks -> IO SrcSpan
-getBreakLoc = getBreakXXX modBreaks_locs
+getBreakLoc = getBreakXXX modBreaks_locs (\(InternalBreakLoc x) -> x)
-- | Get the vars for this breakpoint
getBreakVars :: (Module -> IO ModBreaks) -> InternalBreakpointId -> InternalModBreaks -> IO [OccName]
-getBreakVars = getBreakXXX modBreaks_vars
+getBreakVars = getBreakXXX modBreaks_vars (const [])
-- | Get the decls for this breakpoint
getBreakDecls :: (Module -> IO ModBreaks) -> InternalBreakpointId -> InternalModBreaks -> IO [String]
-getBreakDecls = getBreakXXX modBreaks_decls
+getBreakDecls = getBreakXXX modBreaks_decls (const [])
-- | Get the decls for this breakpoint
-getBreakCCS :: (Module -> IO ModBreaks) -> InternalBreakpointId -> InternalModBreaks -> IO (String, String)
-getBreakCCS = getBreakXXX modBreaks_ccs
+getBreakCCS :: (Module -> IO ModBreaks) -> InternalBreakpointId -> InternalModBreaks -> IO (Maybe (String, String))
+getBreakCCS = getBreakXXX (fmap Just . modBreaks_ccs) (const Nothing)
-- | Internal utility to access a ModBreaks field at a particular breakpoint index
--
@@ -228,14 +248,17 @@ getBreakCCS = getBreakXXX modBreaks_ccs
-- 'ModBreaks'. When the tick module is different, we need to look up the
-- 'ModBreaks' in the HUG for that other module.
--
+-- When there is no tick module (the breakpoint was generated at codegen), use
+-- the function on internal mod breaks.
+--
-- To avoid cyclic dependencies, we instead receive a function that looks up
-- the 'ModBreaks' given a 'Module'
-getBreakXXX :: (ModBreaks -> Array BreakTickIndex a) -> (Module -> IO ModBreaks) -> InternalBreakpointId -> InternalModBreaks -> IO a
-getBreakXXX view lookupModule (InternalBreakpointId ibi_mod ibi_ix) imbs =
+getBreakXXX :: (ModBreaks -> Array BreakTickIndex a) -> (InternalBreakLoc -> a) -> (Module -> IO ModBreaks) -> InternalBreakpointId -> InternalModBreaks -> IO a
+getBreakXXX view viewInternal lookupModule (InternalBreakpointId ibi_mod ibi_ix) imbs =
assert_modules_match ibi_mod (imodBreaks_module imbs) $ do
let cgb = imodBreaks_breakInfo imbs IM.! ibi_ix
case cgb_tick_id cgb of
- BreakpointId{bi_tick_mod, bi_tick_index}
+ Right BreakpointId{bi_tick_mod, bi_tick_index}
| bi_tick_mod == ibi_mod
-> do
let these_mbs = imodBreaks_modBreaks imbs
@@ -244,6 +267,8 @@ getBreakXXX view lookupModule (InternalBreakpointId ibi_mod ibi_ix) imbs =
-> do
other_mbs <- lookupModule bi_tick_mod
return $ view other_mbs ! bi_tick_index
+ Left l ->
+ return $ viewInternal l
--------------------------------------------------------------------------------
-- Instances
=====================================
compiler/GHC/ByteCode/Instr.hs
=====================================
@@ -260,10 +260,6 @@ data BCInstr
-- Breakpoints
| BRK_FUN !InternalBreakpointId
- -- An internal breakpoint for triggering a break on any case alternative
- -- See Note [Debugger: BRK_ALTS]
- | BRK_ALTS !Bool {- enabled? -}
-
#if MIN_VERSION_rts(1,0,3)
-- | A "meta"-instruction for recording the name of a BCO for debugging purposes.
-- These are ignored by the interpreter but helpfully printed by the disassmbler.
@@ -458,7 +454,6 @@ instance Outputable BCInstr where
= text "BRK_FUN" <+> text "<breakarray>"
<+> ppr info_mod <+> ppr infox
<+> text "<cc>"
- ppr (BRK_ALTS active) = text "BRK_ALTS" <+> ppr active
#if MIN_VERSION_rts(1,0,3)
ppr (BCO_NAME nm) = text "BCO_NAME" <+> text (show nm)
#endif
@@ -584,7 +579,6 @@ bciStackUse OP_INDEX_ADDR{} = 0
bciStackUse SWIZZLE{} = 0
bciStackUse BRK_FUN{} = 0
-bciStackUse BRK_ALTS{} = 0
-- These insns actually reduce stack use, but we need the high-tide level,
-- so can't use this info. Not that it matters much.
=====================================
compiler/GHC/Linker/Loader.hs
=====================================
@@ -59,6 +59,7 @@ import GHCi.RemoteTypes
import GHC.Iface.Load
import GHCi.Message (ConInfoTable(..), LoadedDLL)
+import GHC.ByteCode.Breakpoints
import GHC.ByteCode.Linker
import GHC.ByteCode.Asm
import GHC.ByteCode.Types
@@ -1711,8 +1712,10 @@ allocateCCS interp ce mbss
let count = maybe 0 ((+1) . fst) $ IM.lookupMax imodBreaks_breakInfo
let ccs = IM.map
(\info ->
- fromMaybe (toRemotePtr nullPtr)
- (M.lookup (cgb_tick_id info) ccss)
+ case cgb_tick_id info of
+ Right bi -> fromMaybe (toRemotePtr nullPtr)
+ (M.lookup bi ccss)
+ Left InternalBreakLoc{} -> toRemotePtr nullPtr
)
imodBreaks_breakInfo
assertPpr (count == length ccs)
=====================================
compiler/GHC/Runtime/Debugger/Breakpoints.hs
=====================================
@@ -253,8 +253,11 @@ mkBreakpointOccurrences = do
let imod = modBreaks_module $ imodBreaks_modBreaks ibrks
IntMap.foldrWithKey (\info_ix cgi bmp -> do
let ibi = InternalBreakpointId imod info_ix
- let BreakpointId tick_mod tick_ix = cgb_tick_id cgi
- extendModuleEnvWith (IntMap.unionWith (S.<>)) bmp tick_mod (IntMap.singleton tick_ix [ibi])
+ case cgb_tick_id cgi of
+ Right (BreakpointId tick_mod tick_ix)
+ -> extendModuleEnvWith (IntMap.unionWith (S.<>)) bmp tick_mod (IntMap.singleton tick_ix [ibi])
+ Left _
+ -> bmp
) bmp0 (imodBreaks_breakInfo ibrks)
--------------------------------------------------------------------------------
@@ -287,7 +290,7 @@ getCurrentBreakModule = do
Nothing -> pure Nothing
Just ibi -> do
brks <- readIModBreaks hug ibi
- return $ Just $ bi_tick_mod $ getBreakSourceId ibi brks
+ return $ Just $ getBreakSourceMod ibi brks
ix ->
Just <$> getHistoryModule hug (resumeHistory r !! (ix-1))
=====================================
compiler/GHC/Runtime/Eval.hs
=====================================
@@ -151,7 +151,7 @@ getHistoryModule :: HUG.HomeUnitGraph -> History -> IO Module
getHistoryModule hug hist = do
let ibi = historyBreakpointId hist
brks <- readIModBreaks hug ibi
- return $ bi_tick_mod $ getBreakSourceId ibi brks
+ return $ getBreakSourceMod ibi brks
getHistorySpan :: HUG.HomeUnitGraph -> History -> IO SrcSpan
getHistorySpan hug hist = do
=====================================
compiler/GHC/StgToByteCode.hs
=====================================
@@ -63,7 +63,7 @@ import GHC.StgToCmm.Closure ( NonVoid(..), fromNonVoid, idPrimRepU,
assertNonVoidIds, assertNonVoidStgArgs )
import GHC.StgToCmm.Layout
import GHC.Runtime.Heap.Layout hiding (WordOff, ByteOff, wordsToBytes)
-import GHC.Runtime.Interpreter ( interpreterProfiled )
+import GHC.Runtime.Interpreter ( interpreterProfiled, readIModModBreaks )
import GHC.Data.Bitmap
import GHC.Data.FlatBag as FlatBag
import GHC.Data.OrdList
@@ -99,6 +99,7 @@ import GHC.CoreToIface
import Control.Monad.IO.Class
import Control.Monad.Trans.Reader (ReaderT(..))
import Control.Monad.Trans.State (StateT(..))
+import Data.Array ((!))
-- -----------------------------------------------------------------------------
-- Generating byte code for a complete module
@@ -393,26 +394,26 @@ schemeR_wrk fvs nm original_body (args, body)
-- | Introduce break instructions for ticked expressions.
-- If no breakpoint information is available, the instruction is omitted.
schemeER_wrk :: StackDepth -> BCEnv -> CgStgExpr -> BcM BCInstrList
-schemeER_wrk d p (StgTick (Breakpoint tick_ty tick_id fvs) rhs) = do
- code <- schemeE d 0 p rhs
- mb_current_mod_breaks <- getCurrentModBreaks
- case mb_current_mod_breaks of
- -- if we're not generating ModBreaks for this module for some reason, we
- -- can't store breakpoint occurrence information.
- Nothing -> pure code
- Just current_mod_breaks -> do
- platform <- profilePlatform <$> getProfile
- let idOffSets = getVarOffSets platform d p fvs
- ty_vars = tyCoVarsOfTypesWellScoped (tick_ty:map idType fvs)
- toWord :: Maybe (Id, WordOff) -> Maybe (Id, Word)
- toWord = fmap (\(i, wo) -> (i, fromIntegral wo))
- breakInfo = dehydrateCgBreakInfo ty_vars (map toWord idOffSets) tick_ty tick_id
+schemeER_wrk d p (StgTick bp@(Breakpoint tick_ty tick_id fvs) rhs) = do
+ platform <- profilePlatform <$> getProfile
+
+ -- When we find a tick we update the "last breakpoint location".
+ -- We use it when constructing step-out BRK_FUNs in doCase
+ -- See Note [Debugger: Stepout internal break locs]
+ code <- withBreakTick bp $ schemeE d 0 p rhs
+
+ let idOffSets = getVarOffSets platform d p fvs
+ ty_vars = tyCoVarsOfTypesWellScoped (tick_ty:map idType fvs)
+ toWord :: Maybe (Id, WordOff) -> Maybe (Id, Word)
+ toWord = fmap (\(i, wo) -> (i, fromIntegral wo))
+ breakInfo = dehydrateCgBreakInfo ty_vars (map toWord idOffSets) tick_ty (Right tick_id)
- let info_mod = modBreaks_module current_mod_breaks
- infox <- newBreakInfo breakInfo
+ mibi <- newBreakInfo breakInfo
+
+ return $ case mibi of
+ Nothing -> code
+ Just ibi -> BRK_FUN ibi `consOL` code
- let breakInstr = BRK_FUN (InternalBreakpointId info_mod infox)
- return $ breakInstr `consOL` code
schemeER_wrk d p rhs = schemeE d 0 p rhs
getVarOffSets :: Platform -> StackDepth -> BCEnv -> [Id] -> [Maybe (Id, WordOff)]
@@ -1140,43 +1141,34 @@ doCase d s p scrut bndr alts
-- When an alt is entered, it assumes the returned value is
-- on top of the itbl; see Note [Return convention for non-tuple values]
-- for details.
- ret_frame_size_b :: StackDepth
- ret_frame_size_b | ubx_tuple_frame =
- (if profiling then 5 else 4) * wordSize platform
- | otherwise = 2 * wordSize platform
+ ret_frame_size_w :: WordOff
+ ret_frame_size_w | ubx_tuple_frame =
+ if profiling then 5 else 4
+ | otherwise = 2
-- The stack space used to save/restore the CCCS when profiling
save_ccs_size_b | profiling &&
not ubx_tuple_frame = 2 * wordSize platform
| otherwise = 0
- -- The size of the return frame info table pointer if one exists
- unlifted_itbl_size_b :: StackDepth
- unlifted_itbl_size_b | ubx_tuple_frame = wordSize platform
- | otherwise = 0
-
(bndr_size, call_info, args_offsets)
| ubx_tuple_frame =
let bndr_reps = typePrimRep (idType bndr)
(call_info, args_offsets) =
layoutNativeCall profile NativeTupleReturn 0 id bndr_reps
- in ( wordsToBytes platform (nativeCallSize call_info)
+ in ( nativeCallSize call_info
, call_info
, args_offsets
)
- | otherwise = ( wordsToBytes platform (idSizeW platform bndr)
+ | otherwise = ( idSizeW platform bndr
, voidTupleReturnInfo
, []
)
- -- depth of stack after the return value has been pushed
+ -- Depth of stack after the return value has been pushed
+ -- This is the stack depth at the continuation.
d_bndr =
- d + ret_frame_size_b + bndr_size
-
- -- depth of stack after the extra info table for an unlifted return
- -- has been pushed, if any. This is the stack depth at the
- -- continuation.
- d_alts = d + ret_frame_size_b + bndr_size + unlifted_itbl_size_b
+ d + wordsToBytes platform bndr_size
-- Env in which to compile the alts, not including
-- any vars bound by the alts themselves
@@ -1188,13 +1180,13 @@ doCase d s p scrut bndr alts
-- given an alt, return a discr and code for it.
codeAlt :: CgStgAlt -> BcM (Discr, BCInstrList)
codeAlt GenStgAlt{alt_con=DEFAULT,alt_bndrs=_,alt_rhs=rhs}
- = do rhs_code <- schemeE d_alts s p_alts rhs
+ = do rhs_code <- schemeE d_bndr s p_alts rhs
return (NoDiscr, rhs_code)
codeAlt alt@GenStgAlt{alt_con=_, alt_bndrs=bndrs, alt_rhs=rhs}
-- primitive or nullary constructor alt: no need to UNPACK
| null real_bndrs = do
- rhs_code <- schemeE d_alts s p_alts rhs
+ rhs_code <- schemeE d_bndr s p_alts rhs
return (my_discr alt, rhs_code)
| isUnboxedTupleType bndr_ty || isUnboxedSumType bndr_ty =
let bndr_ty = idPrimRepU . fromNonVoid
@@ -1206,7 +1198,7 @@ doCase d s p scrut bndr alts
bndr_ty
(assertNonVoidIds bndrs)
- stack_bot = d_alts
+ stack_bot = d_bndr
p' = Map.insertList
[ (arg, tuple_start -
@@ -1224,7 +1216,7 @@ doCase d s p scrut bndr alts
(addIdReps (assertNonVoidIds real_bndrs))
size = WordOff tot_wds
- stack_bot = d_alts + wordsToBytes platform size
+ stack_bot = d_bndr + wordsToBytes platform size
-- convert offsets from Sp into offsets into the virtual stack
p' = Map.insertList
@@ -1324,22 +1316,53 @@ doCase d s p scrut bndr alts
alt_stuff <- mapM codeAlt alts
alt_final0 <- mkMultiBranch maybe_ncons alt_stuff
- let alt_final1
- | ubx_tuple_frame = SLIDE 0 2 `consOL` alt_final0
- | otherwise = alt_final0
- alt_final
- | gopt Opt_InsertBreakpoints (hsc_dflags hsc_env)
- -- See Note [Debugger: BRK_ALTS]
- = BRK_ALTS False `consOL` alt_final1
- | otherwise = alt_final1
+ let
+
+ -- drop the stg_ctoi_*_info header...
+ alt_final1 = SLIDE bndr_size ret_frame_size_w `consOL` alt_final0
+
+ -- after dropping the stg_ret_*_info header
+ alt_final2
+ | ubx_tuple_frame = SLIDE 0 3 `consOL` alt_final1
+ | otherwise = SLIDE 0 1 `consOL` alt_final1
+
+ -- When entering a case continuation BCO, the stack is always headed
+ -- by the stg_ret frame and the stg_ctoi frame that returned to it.
+ -- See Note [Stack layout when entering run_BCO]
+ --
+ -- Right after the breakpoint instruction, a case continuation BCO
+ -- drops the stg_ret and stg_ctoi frame headers (see alt_final1,
+ -- alt_final2), leaving the stack with the scrutinee followed by the
+ -- free variables (with depth==d_bndr)
+ alt_final <- getLastBreakTick >>= \case
+ Just (Breakpoint tick_ty tick_id fvs)
+ | gopt Opt_InsertBreakpoints (hsc_dflags hsc_env)
+ -- Construct an internal breakpoint to put at the start of this case
+ -- continuation BCO, for step-out.
+ -- See Note [Debugger: Stepout internal break locs]
+ -> do
+ internal_tick_loc <- makeCaseInternalBreakLoc tick_id
+
+ -- same fvs available in the case expression are available in the case continuation
+ let idOffSets = getVarOffSets platform d p fvs
+ ty_vars = tyCoVarsOfTypesWellScoped (tick_ty:map idType fvs)
+ toWord :: Maybe (Id, WordOff) -> Maybe (Id, Word)
+ toWord = fmap (\(i, wo) -> (i, fromIntegral wo))
+ breakInfo = dehydrateCgBreakInfo ty_vars (map toWord idOffSets) tick_ty (Left internal_tick_loc)
+
+ mibi <- newBreakInfo breakInfo
+ return $ case mibi of
+ Nothing -> alt_final2
+ Just ibi -> BRK_FUN ibi `consOL` alt_final2
+ _ -> pure alt_final2
add_bco_name <- shouldAddBcoName
let
alt_bco_name = getName bndr
alt_bco = mkProtoBCO platform add_bco_name alt_bco_name alt_final (Left alts)
0{-no arity-} bitmap_size bitmap True{-is alts-}
- scrut_code <- schemeE (d + ret_frame_size_b + save_ccs_size_b)
- (d + ret_frame_size_b + save_ccs_size_b)
+ scrut_code <- schemeE (d + wordsToBytes platform ret_frame_size_w + save_ccs_size_b)
+ (d + wordsToBytes platform ret_frame_size_w + save_ccs_size_b)
p scrut
if ubx_tuple_frame
then do let tuple_bco = tupleBCO platform call_info args_offsets
@@ -1351,72 +1374,122 @@ doCase d s p scrut bndr alts
_ -> panic "schemeE(StgCase).push_alts"
in return (PUSH_ALTS alt_bco scrut_rep `consOL` scrut_code)
+-- | Come up with an 'InternalBreakLoc' from the location of the given 'BreakpointId'.
+-- See also Note [Debugger: Stepout internal break locs]
+makeCaseInternalBreakLoc :: BreakpointId -> BcM InternalBreakLoc
+makeCaseInternalBreakLoc bid = do
+ hug <- hsc_HUG <$> getHscEnv
+ curr_mod <- getCurrentModule
+ mb_mod_brks <- getCurrentModBreaks
+
+ InternalBreakLoc <$> case bid of
+ BreakpointId{bi_tick_mod, bi_tick_index}
+ | bi_tick_mod == curr_mod
+ , Just these_mbs <- mb_mod_brks
+ -> do
+ return $ modBreaks_locs these_mbs ! bi_tick_index
+ | otherwise
+ -> do
+ other_mbs <- liftIO $ readIModModBreaks hug bi_tick_mod
+ return $ modBreaks_locs other_mbs ! bi_tick_index
+
{-
-Note [Debugger: BRK_ALTS]
-~~~~~~~~~~~~~~~~~~~~~~~~~
-As described in Note [Debugger: Step-out] in rts/Interpreter.c, to implement
-the stepping-out debugger feature we traverse the stack at runtime, identify
-the first continuation BCO, and explicitly enable that BCO's breakpoint thus
-ensuring that we stop exactly when we return to the continuation.
-
-However, case continuation BCOs (produced by PUSH_ALTS and which merely compute
-which case alternative BCO to enter next) contain no user-facing breakpoint
-ticks (BRK_FUN). While we could in principle add breakpoints in case continuation
-BCOs, there are a few reasons why this is not an attractive option:
-
- 1) It's not useful to a user stepping through the program to always have a
- breakpoint after the scrutinee is evaluated but before the case alternative
- is selected. The source span associated with such a breakpoint would also be
- slightly awkward to choose.
-
- 2) It's not easy to add a breakpoint tick before the case alternatives because in
- essentially all internal representations they are given as a list of Alts
- rather than an expression.
-
-To provide the debugger a way to break in a case continuation
-despite the BCOs' lack of BRK_FUNs, we introduce an alternative
-type of breakpoint, represented by the BRK_ALTS instruction,
-at the start of every case continuation BCO. For instance,
-
- case x of
- 0# -> ...
- _ -> ...
-
-will produce a continuation of the form (N.B. the below bytecode
-is simplified):
-
- PUSH_ALTS P
- BRK_ALTS 0
- TESTEQ_I 0 lblA
- PUSH_BCO
- BRK_FUN 0
- -- body of 0# alternative
- ENTER
-
- lblA:
- PUSH_BCO
- BRK_FUN 1
- -- body of wildcard alternative
- ENTER
-
-When enabled (by its single boolean operand), the BRK_ALTS instruction causes
-the program to break at the next encountered breakpoint (implemented
-by setting the TSO's TSO_STOP_NEXT_BREAKPOINT flag). Since the case
-continuation BCO will ultimately jump to one of the alternatives (each of
-which having its own BRK_FUN) we are guaranteed to stop in the taken alternative.
-
-It's important that BRK_ALTS (just like BRK_FUN) is the first instruction of
-the BCO, since that's where the debugger will look to enable it at runtime.
-
-KNOWN ISSUES:
--------------
-This implementation of BRK_ALTS that modifies the first argument of the
-bytecode to enable it does not allow multi-threaded debugging because the BCO
-object is shared across threads and enabling the breakpoint in one will enable
-it in all other threads too. This will have to change to support multi-threads
-debugging.
-
-The progress towards multi-threaded debugging is tracked by #26064
+Note [Debugger: Stepout internal break locs]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Step-out tells the interpreter to run until the current function
+returns to where it was called from, and stop there.
+
+This is achieved by enabling the BRK_FUN found on the first RET_BCO
+frame on the stack (See [Note Debugger: Step-out]).
+
+Case continuation BCOs (which select an alternative branch) must
+therefore be headed by a BRK_FUN. An example:
+
+ f x = case g x of <--- end up here
+ 1 -> ...
+ 2 -> ...
+
+ g y = ... <--- step out from here
+
+- `g` will return a value to the case continuation BCO in `f`
+- The case continuation BCO will receive the value returned from g
+- Match on it and push the alternative continuation for that branch
+- And then enter that alternative.
+
+If we step-out of `g`, the first RET_BCO on the stack is the case
+continuation of `f` -- execution should stop at its start, before
+selecting an alternative. (One might ask, "why not enable the breakpoint
+in the alternative instead?", because the alternative continuation is
+only pushed to the stack *after* it is selected by the case cont. BCO)
+
+However, the case cont. BCO is not associated with any source-level
+tick, it is merely the glue code which selects alternatives which do
+have source level ticks. Therefore, we have to come up at code
+generation time with a breakpoint location ('InternalBreakLoc') to
+display to the user when it is stopped there.
+
+Our solution is to use the last tick seen just before reaching the case
+continuation. This is robust because a case continuation will thus
+always have a relevant breakpoint location:
+
+ - The source location will be the last source-relevant expression
+ executed before the continuation is pushed
+
+ - So the source location will point to the thing you've just stepped
+ out of
+
+ - Doing :step-local from there will put you on the selected
+ alternative (which at the source level may also be the e.g. next
+ line in a do-block)
+
+Examples, using angle brackets (<<...>>) to denote the breakpoint span:
+
+ f x = case <<g x>> {- step in here -} of
+ 1 -> ...
+ 2 -> ...>
+
+ g y = <<...>> <--- step out from here
+
+ ...
+
+ f x = < ...
+ 2 -> ...>>
+
+ doing :step-local ...
+
+ f x = case g x of
+ 1 -> <<...>> <--- stop in the alternative
+ 2 -> ...
+
+A second example based on T26042d2, where the source is a do-block IO
+action, optimised to a chain of `case expressions`.
+
+ main = do
+ putStrLn "hello1"
+ <<f>> <--- step-in here
+ putStrLn "hello3"
+ putStrLn "hello4"
+
+ f = do
+ <> <--- step-out from here
+ putStrLn "hello2.2"
+
+ ...
+
+ main = do
+ putStrLn "hello1"
+ <<f>> <--- end up here again, the previously executed expression
+ putStrLn "hello3"
+ putStrLn "hello4"
+
+ doing step/step-local ...
+
+ main = do
+ putStrLn "hello1"
+ f
+ <> <--- straight to the next line
+ putStrLn "hello4"
-}
-- -----------------------------------------------------------------------------
@@ -2619,6 +2692,7 @@ data BcM_Env
{ bcm_hsc_env :: !HscEnv
, bcm_module :: !Module -- current module (for breakpoints)
, modBreaks :: !(Maybe ModBreaks)
+ , last_bp_tick :: !(Maybe StgTickish)
}
data BcM_State
@@ -2637,7 +2711,7 @@ newtype BcM r = BcM (BcM_Env -> BcM_State -> IO (r, BcM_State))
runBc :: HscEnv -> Module -> Maybe ModBreaks -> BcM r -> IO (r, BcM_State)
runBc hsc_env this_mod mbs (BcM m)
- = m (BcM_Env hsc_env this_mod mbs) (BcM_State 0 0 IntMap.empty)
+ = m (BcM_Env hsc_env this_mod mbs Nothing) (BcM_State 0 0 IntMap.empty)
instance HasDynFlags BcM where
getDynFlags = hsc_dflags <$> getHscEnv
@@ -2667,14 +2741,19 @@ getLabelsBc n = BcM $ \_ st ->
let ctr = nextlabel st
in return (coerce [ctr .. ctr+n-1], st{nextlabel = ctr+n})
-newBreakInfo :: CgBreakInfo -> BcM Int
-newBreakInfo info = BcM $ \_ st ->
- let ix = breakInfoIdx st
- st' = st
- { breakInfo = IntMap.insert ix info (breakInfo st)
- , breakInfoIdx = ix + 1
- }
- in return (ix, st')
+newBreakInfo :: CgBreakInfo -> BcM (Maybe InternalBreakpointId)
+newBreakInfo info = BcM $ \env st -> do
+ -- if we're not generating ModBreaks for this module for some reason, we
+ -- can't store breakpoint occurrence information.
+ case modBreaks env of
+ Nothing -> pure (Nothing, st)
+ Just modBreaks -> do
+ let ix = breakInfoIdx st
+ st' = st
+ { breakInfo = IntMap.insert ix info (breakInfo st)
+ , breakInfoIdx = ix + 1
+ }
+ return (Just $ InternalBreakpointId (modBreaks_module modBreaks) ix, st')
getCurrentModule :: BcM Module
getCurrentModule = BcM $ \env st -> return (bcm_module env, st)
@@ -2682,12 +2761,20 @@ getCurrentModule = BcM $ \env st -> return (bcm_module env, st)
getCurrentModBreaks :: BcM (Maybe ModBreaks)
getCurrentModBreaks = BcM $ \env st -> return (modBreaks env, st)
+withBreakTick :: StgTickish -> BcM a -> BcM a
+withBreakTick bp (BcM act) = BcM $ \env st ->
+ act env{last_bp_tick=Just bp} st
+
+getLastBreakTick :: BcM (Maybe StgTickish)
+getLastBreakTick = BcM $ \env st ->
+ pure (last_bp_tick env, st)
+
tickFS :: FastString
tickFS = fsLit "ticked"
-- Dehydrating CgBreakInfo
-dehydrateCgBreakInfo :: [TyVar] -> [Maybe (Id, Word)] -> Type -> BreakpointId -> CgBreakInfo
+dehydrateCgBreakInfo :: [TyVar] -> [Maybe (Id, Word)] -> Type -> Either InternalBreakLoc BreakpointId -> CgBreakInfo
dehydrateCgBreakInfo ty_vars idOffSets tick_ty bid =
CgBreakInfo
{ cgb_tyvars = map toIfaceTvBndr ty_vars
=====================================
ghc/GHCi/UI.hs
=====================================
@@ -45,7 +45,7 @@ import GHC.Runtime.Eval (mkTopLevEnv)
import GHC.Runtime.Eval.Utils
-- The GHC interface
-import GHC.ByteCode.Breakpoints (imodBreaks_modBreaks, InternalBreakpointId(..), getBreakSourceId)
+import GHC.ByteCode.Breakpoints (imodBreaks_modBreaks, InternalBreakpointId(..), getBreakSourceId, getBreakSourceMod)
import GHC.Runtime.Interpreter
import GHCi.RemoteTypes
import GHCi.BreakArray( breakOn, breakOff )
@@ -1621,7 +1621,7 @@ toBreakIdAndLocation (Just inf) = do
brks <- liftIO $ readIModBreaks hug inf
let bi = getBreakSourceId inf brks
return $ listToMaybe [ id_loc | id_loc@(_,loc) <- IntMap.assocs (breaks st),
- breakId loc == bi ]
+ Right (breakId loc) == bi ]
printStoppedAtBreakInfo :: GHC.GhcMonad m => Resume -> [Name] -> m ()
printStoppedAtBreakInfo res names = do
@@ -3825,7 +3825,7 @@ pprStopped res = do
hug <- hsc_HUG <$> GHC.getSession
brks <- liftIO $ readIModBreaks hug ibi
return $ Just $ moduleName $
- bi_tick_mod $ getBreakSourceId ibi brks
+ getBreakSourceMod ibi brks
return $
text "Stopped in"
<+> ((case mb_mod_name of
=====================================
libraries/ghci/GHCi/Run.hs
=====================================
@@ -362,6 +362,14 @@ withBreakAction opts breakMVar statusMVar mtid act
info_mod_uid <- BS.packCString (Ptr info_mod_uid#)
pure (Just (EvalBreakpoint info_mod info_mod_uid (I# infox#)))
putMVar statusMVar $ EvalBreak apStack_r breakpoint resume_r ccs
+
+ -- Block until this thread is resumed (by the thread which took the
+ -- `ResumeContext` from the `statusMVar`).
+ --
+ -- The `onBreak` function must have been called from `rts/Interpreter.c`
+ -- when interpreting a `BRK_FUN`. After taking from the MVar, the function
+ -- returns to the continuation on the stack which is where the interpreter
+ -- was stopped.
takeMVar breakMVar
resetBreakAction stablePtr = do
=====================================
rts/Disassembler.c
=====================================
@@ -101,9 +101,6 @@ disInstr ( StgBCO *bco, int pc )
}
debugBelch("\n");
break; }
- case bci_BRK_ALTS:
- debugBelch ("BRK_ALTS %d\n", BCO_NEXT);
- break;
case bci_SWIZZLE: {
W_ stkoff = BCO_GET_LARGE_ARG;
StgInt by = BCO_GET_LARGE_ARG;
=====================================
rts/Interpreter.c
=====================================
@@ -284,6 +284,18 @@ allocate_NONUPD (Capability *cap, int n_words)
return allocate(cap, stg_max(sizeofW(StgHeader)+MIN_PAYLOAD_SIZE, n_words));
}
+STATIC_INLINE int
+is_ret_bco_frame(const StgPtr frame_head) {
+ return ( (W_)frame_head == (W_)&stg_ret_t_info
+ || (W_)frame_head == (W_)&stg_ret_v_info
+ || (W_)frame_head == (W_)&stg_ret_p_info
+ || (W_)frame_head == (W_)&stg_ret_n_info
+ || (W_)frame_head == (W_)&stg_ret_f_info
+ || (W_)frame_head == (W_)&stg_ret_d_info
+ || (W_)frame_head == (W_)&stg_ret_l_info
+ );
+}
+
int rts_stop_on_exception = 0;
/* ---------------------------------------------------------------------------
@@ -346,16 +358,11 @@ to the continuation.
To achieve this, when the flag is set as the interpreter is re-entered:
(1) Traverse the stack until a RET_BCO frame is found or we otherwise hit the
bottom (STOP_FRAME).
- (2) Look for a breakpoint instruction heading the BCO instructions (a
+ (2) Look for a BRK_FUN instruction heading the BCO instructions (a
breakpoint, when present, is always the first instruction in a BCO)
- (2a) For PUSH_ALT BCOs, the breakpoint instruction will be BRK_ALTS
- (as explained in Note [Debugger: BRK_ALTS]) and it can be enabled by
- setting its first operand to 1.
-
- (2b) Otherwise, the instruction will be BRK_FUN and the breakpoint can be
- enabled by setting the associated BreakArray at the associated tick
- index to 0.
+ The breakpoint can be enabled by setting the associated BreakArray at the
+ associated internal breakpoint index to 0.
By simply enabling the breakpoint heading the continuation we can ensure that
when it is returned to we will stop there without additional work -- it
@@ -692,8 +699,13 @@ interpretBCO (Capability* cap)
StgPtr restoreStackPointer = Sp;
/* The first BCO on the stack is the one we are already stopped at.
- * Skip it. */
- Sp = SafeSpWP(stack_frame_sizeW((StgClosure *)Sp));
+ * Skip it. In the case of returning to a case cont. BCO, there are two
+ * frames to skip before we reach the first continuation frame.
+ * */
+ int to_skip = is_ret_bco_frame((StgPtr)SpW(0)) ? 2 : 1;
+ for (int i = 0; i < to_skip; i++) {
+ Sp = SafeSpWP(stack_frame_sizeW((StgClosure *)Sp));
+ }
/* Traverse upwards until continuation BCO, or the end */
while ((type = get_itbl((StgClosure*)Sp)->type) != RET_BCO
@@ -711,8 +723,8 @@ interpretBCO (Capability* cap)
int bciPtr = 0;
StgWord16 bci = BCO_NEXT;
- /* A breakpoint instruction (BRK_FUN or BRK_ALTS) is always the first
- * instruction in a BCO */
+ /* A breakpoint instruction (BRK_FUN) can only be the first instruction
+ * in a BCO */
if ((bci & 0xFF) == bci_BRK_FUN) {
W_ arg1_brk_array, arg4_info_index;
@@ -727,10 +739,6 @@ interpretBCO (Capability* cap)
// ACTIVATE the breakpoint by tick index
((StgInt*)breakPoints->payload)[arg4_info_index] = 0;
}
- else if ((bci & 0xFF) == bci_BRK_ALTS) {
- // ACTIVATE BRK_ALTS by setting its only argument to ON
- instrs[1] = 1;
- }
// else: if there is no BRK instruction perhaps we should keep
// traversing; that said, the continuation should always have a BRK
}
@@ -844,7 +852,6 @@ eval_obj:
debugBelch("\n\n");
);
-// IF_DEBUG(sanity,checkStackChunk(Sp, cap->r.rCurrentTSO->stack+cap->r.rCurrentTSO->stack_size));
IF_DEBUG(sanity,checkStackFrame(Sp));
switch ( get_itbl(obj)->type ) {
@@ -1086,11 +1093,33 @@ do_return_pointer:
// Returning to an interpreted continuation: put the object on
// the stack, and start executing the BCO.
INTERP_TICK(it_retto_BCO);
- Sp_subW(1);
- SpW(0) = (W_)tagged_obj;
- obj = (StgClosure*)ReadSpW(2);
+ obj = (StgClosure*)ReadSpW(1);
ASSERT(get_itbl(obj)->type == BCO);
- goto run_BCO_return_pointer;
+
+ // Heap check
+ if (doYouWantToGC(cap)) {
+ Sp_subW(2);
+ SpW(1) = (W_)tagged_obj;
+ SpW(0) = (W_)&stg_ret_p_info;
+ RETURN_TO_SCHEDULER(ThreadInterpret, HeapOverflow);
+ }
+ else {
+
+ // Stack checks aren't necessary at return points, the stack use
+ // is aggregated into the enclosing function entry point.
+
+ // Make sure stack is headed by a ctoi R1p frame when returning a pointer
+ ASSERT(ReadSpW(0) == (W_)&stg_ctoi_R1p_info);
+
+ // Add the return frame on top of the args
+ Sp_subW(2);
+ SpW(1) = (W_)tagged_obj;
+ SpW(0) = (W_)&stg_ret_p_info;
+ }
+
+ /* Keep the ret frame and the ctoi frame for run_BCO.
+ * See Note [Stack layout when entering run_BCO] */
+ goto run_BCO;
default:
do_return_unrecognised:
@@ -1159,8 +1188,9 @@ do_return_nonpointer:
// get the offset of the header of the next stack frame
offset = stack_frame_sizeW((StgClosure *)Sp);
+ StgClosure* next_frame = (StgClosure*)(SafeSpWP(offset));
- switch (get_itbl((StgClosure*)(SafeSpWP(offset)))->type) {
+ switch (get_itbl(next_frame)->type) {
case RET_BCO:
// Returning to an interpreted continuation: pop the return frame
@@ -1168,8 +1198,58 @@ do_return_nonpointer:
// executing the BCO.
INTERP_TICK(it_retto_BCO);
obj = (StgClosure*)ReadSpW(offset+1);
+
ASSERT(get_itbl(obj)->type == BCO);
- goto run_BCO_return_nonpointer;
+
+ // Heap check
+ if (doYouWantToGC(cap)) {
+ RETURN_TO_SCHEDULER(ThreadInterpret, HeapOverflow);
+ }
+ else {
+ // Stack checks aren't necessary at return points, the stack use
+ // is aggregated into the enclosing function entry point.
+
+#if defined(PROFILING)
+ /*
+ Restore the current cost centre stack if a tuple is being returned.
+
+ When a "simple" unlifted value is returned, the cccs is restored with
+ an stg_restore_cccs frame on the stack, for example:
+
+ ...
+ stg_ctoi_D1
+ <CCCS>
+ stg_restore_cccs
+
+ But stg_restore_cccs cannot deal with tuples, which may have more
+ things on the stack. Therefore we store the CCCS inside the
+ stg_ctoi_t frame.
+
+ If we have a tuple being returned, the stack looks like this:
+
+ ...
+ <CCCS> <- to restore, Sp offset
+ tuple_BCO
+ tuple_info
+ cont_BCO
+ stg_ctoi_t <- next frame
+ tuple_data_1
+ ...
+ tuple_data_n
+ tuple_info
+ tuple_BCO
+ stg_ret_t <- Sp
+ */
+
+ if(SpW(0) == (W_)&stg_ret_t_info) {
+ cap->r.rCCCS = (CostCentreStack*)ReadSpW(offset + 4);
+ }
+#endif
+
+ /* Keep the ret frame and the ctoi frame for run_BCO.
+ * See Note [Stack layout when entering run_BCO] */
+ goto run_BCO;
+ }
default:
{
@@ -1332,111 +1412,90 @@ do_apply:
RETURN_TO_SCHEDULER_NO_PAUSE(ThreadRunGHC, ThreadYielding);
}
- // ------------------------------------------------------------------------
- // Ok, we now have a bco (obj), and its arguments are all on the
- // stack. We can start executing the byte codes.
- //
- // The stack is in one of two states. First, if this BCO is a
- // function:
- //
- // | .... |
- // +---------------+
- // | arg2 |
- // +---------------+
- // | arg1 |
- // +---------------+
- //
- // Second, if this BCO is a continuation:
- //
- // | .... |
- // +---------------+
- // | fv2 |
- // +---------------+
- // | fv1 |
- // +---------------+
- // | BCO |
- // +---------------+
- // | stg_ctoi_ret_ |
- // +---------------+
- // | retval |
- // +---------------+
- //
- // where retval is the value being returned to this continuation.
- // In the event of a stack check, heap check, or context switch,
- // we need to leave the stack in a sane state so the garbage
- // collector can find all the pointers.
- //
- // (1) BCO is a function: the BCO's bitmap describes the
- // pointerhood of the arguments.
- //
- // (2) BCO is a continuation: BCO's bitmap describes the
- // pointerhood of the free variables.
- //
- // Sadly we have three different kinds of stack/heap/cswitch check
- // to do:
-
-
-run_BCO_return_pointer:
- // Heap check
- if (doYouWantToGC(cap)) {
- Sp_subW(1); SpW(0) = (W_)&stg_ret_p_info;
- RETURN_TO_SCHEDULER(ThreadInterpret, HeapOverflow);
- }
- // Stack checks aren't necessary at return points, the stack use
- // is aggregated into the enclosing function entry point.
-
- goto run_BCO;
-
-run_BCO_return_nonpointer:
- // Heap check
- if (doYouWantToGC(cap)) {
- RETURN_TO_SCHEDULER(ThreadInterpret, HeapOverflow);
- }
- // Stack checks aren't necessary at return points, the stack use
- // is aggregated into the enclosing function entry point.
-
-#if defined(PROFILING)
- /*
- Restore the current cost centre stack if a tuple is being returned.
-
- When a "simple" unlifted value is returned, the cccs is restored with
- an stg_restore_cccs frame on the stack, for example:
-
- ...
- stg_ctoi_D1
- <CCCS>
- stg_restore_cccs
-
- But stg_restore_cccs cannot deal with tuples, which may have more
- things on the stack. Therefore we store the CCCS inside the
- stg_ctoi_t frame.
-
- If we have a tuple being returned, the stack looks like this:
-
- ...
- <CCCS> <- to restore, Sp offset
- tuple_BCO
- tuple_info
- cont_BCO
- stg_ctoi_t <- next frame
- tuple_data_1
- ...
- tuple_data_n
- tuple_info
- tuple_BCO
- stg_ret_t <- Sp
- */
-
- if(SpW(0) == (W_)&stg_ret_t_info) {
- cap->r.rCCCS = (CostCentreStack*)ReadSpW(stack_frame_sizeW((StgClosure *)Sp) + 4);
- }
-#endif
-
- if (SpW(0) != (W_)&stg_ret_t_info) {
- Sp_addW(1);
- }
- goto run_BCO;
+/*
+Note [Stack layout when entering run_BCO]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+We have a bco (obj), and its arguments are all on the stack. We can start
+executing the byte codes.
+
+The stack is in one of two states. First, if this BCO is a
+function (in run_BCO_fun or run_BCO)
+
+ | .... |
+ +---------------+
+ | arg2 |
+ +---------------+
+ | arg1 |
+ +---------------+
+
+Second, if this BCO is a case cont., as per Note [Case continuation BCOs] (only
+in run_BCO):
+
+ | .... |
+ +---------------+
+ | fv2 |
+ +---------------+
+ | fv1 |
+ +---------------+
+ | BCO |
+ +---------------+
+ | stg_ctoi_ret_ |
+ +---------------+
+ | retval |
+ +---------------+
+ | stg_ret_..... |
+ +---------------+
+
+where retval is the value being returned to this continuation.
+In the event of a stack check, heap check, context switch,
+or breakpoint, we need to leave the stack in a sane state so
+the garbage collector can find all the pointers.
+
+ (1) BCO is a function: the BCO's bitmap describes the
+ pointerhood of the arguments.
+
+ (2) BCO is a continuation: BCO's bitmap describes the
+ pointerhood of the free variables.
+
+To reconstruct a valid stack state for yielding (such that when we return to
+the interpreter we end up in the same place from where we yielded), we need to
+differentiate the two cases again:
+
+ (1) For function BCOs, the arguments are directly on top of the stack, so it
+ suffices to add a `stg_apply_interp_info` frame header using the BCO that is
+ being applied to these arguments (i.e. the `obj` being run)
+
+ (2) For continuation BCOs, the stack is already consistent -- that's why we
+ keep the ret and ctoi frame on top of the stack when we start executing it.
+
+ We couldn't reconstruct a valid stack that resumes the case continuation
+ execution just from the return and free vars values alone because we wouldn't
+ know what kind of result it was (are we returning a pointer, non pointer int,
+ a tuple? etc.); especially considering some frames have different sizes,
+ notably unboxed tuple return frames (see Note [unboxed tuple bytecodes and tuple_BCO]).
+
+ For consistency, the first instructions in a case continuation BCO, right
+ after a possible BRK_FUN heading it, are two SLIDEs to remove the stg_ret_
+ and stg_ctoi_ frame headers, leaving only the return value followed by the
+ free vars. Theses slides use statically known offsets computed in StgToByteCode.hs.
+ Following the continuation BCO diagram above, SLIDING would result in:
+
+ | .... |
+ +---------------+
+ | fv2 |
+ +---------------+
+ | fv1 |
+ +---------------+
+ | retval |
+ +---------------+
+*/
+// Ok, we now have a bco (obj), and its arguments are all on the stack as
+// described by Note [Stack layout when entering run_BCO].
+// We can start executing the byte codes.
+//
+// Sadly we have three different kinds of stack/heap/cswitch check
+// to do:
run_BCO_fun:
IF_DEBUG(sanity,
Sp_subW(2);
@@ -1466,6 +1525,7 @@ run_BCO_fun:
// Now, actually interpret the BCO... (no returning to the
// scheduler again until the stack is in an orderly state).
+ // See also Note [Stack layout when entering run_BCO]
run_BCO:
INTERP_TICK(it_BCO_entries);
{
@@ -1519,7 +1579,7 @@ run_BCO:
switch (bci & 0xFF) {
- /* check for a breakpoint on the beginning of a let binding */
+ /* check for a breakpoint on the beginning of a BCO */
case bci_BRK_FUN:
{
W_ arg1_brk_array, arg2_info_mod_name, arg3_info_mod_id, arg4_info_index;
@@ -1572,6 +1632,13 @@ run_BCO:
{
breakPoints = (StgArrBytes *) BCO_PTR(arg1_brk_array);
+ StgPtr stack_head = (StgPtr)SpW(0);
+
+ // When the BRK_FUN is at the start of a case continuation BCO,
+ // the stack is headed by the frame returning the value at the start.
+ // See Note [Stack layout when entering run_BCO]
+ int is_case_cont_BCO = is_ret_bco_frame(stack_head);
+
// 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)[arg4_info_index];
@@ -1580,36 +1647,80 @@ run_BCO:
// decrement and write back ignore count
((StgInt*)breakPoints->payload)[arg4_info_index] = --ignore_count;
}
- else if (stop_next_breakpoint == true || ignore_count == 0)
+ else if (
+ /* Doing step-in (but don't stop at case continuation BCOs,
+ * those are only useful when stepping out) */
+ (stop_next_breakpoint == true && !is_case_cont_BCO)
+ /* Or breakpoint is explicitly enabled */
+ || ignore_count == 0)
{
// make sure we don't automatically stop at the
// next breakpoint
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
- // stg_apply_interp_info pointer and a pointer to
- // the BCO
- size_words = BCO_BITMAP_SIZE(obj) + 2;
- new_aps = (StgAP_STACK *) allocate(cap, AP_STACK_sizeW(size_words));
- new_aps->size = size_words;
- new_aps->fun = &stg_dummy_ret_closure;
-
- // fill in the payload of the AP_STACK
- new_aps->payload[0] = (StgClosure *)&stg_apply_interp_info;
- new_aps->payload[1] = (StgClosure *)obj;
-
- // copy the contents of the top stack frame into the AP_STACK
- for (i = 2; i < size_words; i++)
- {
- new_aps->payload[i] = (StgClosure *)ReadSpW(i-2);
+ /* To yield execution we need to come up with a consistent AP_STACK
+ * to store in the :history data structure.
+ */
+ if (is_case_cont_BCO) {
+
+ // If the BCO is a case cont. then the stack is headed by the
+ // stg_ret and a stg_ctoi frames which caused this same BCO
+ // to be run. This stack is already well-formed, so it
+ // needs only to be copied to the AP_STACK.
+ // See Note [Stack layout when entering run_BCO]
+
+ // stg_ret_*
+ int size_returned_frame = stack_frame_sizeW((StgClosure *)Sp);
+
+ ASSERT(obj == UNTAG_CLOSURE((StgClosure*)ReadSpW(size_returned_frame+1)));
+
+ // stg_ctoi_*
+ int size_cont_frame_head = stack_frame_sizeW((StgClosure*)SafeSpWP(size_returned_frame));
+
+ // Continuation stack is already well formed,
+ // so just copy it whole to the AP_STACK
+ size_words = size_returned_frame
+ + size_cont_frame_head;
+ new_aps = (StgAP_STACK *) allocate(cap, AP_STACK_sizeW(size_words));
+ new_aps->size = size_words;
+ new_aps->fun = &stg_dummy_ret_closure;
+
+ // (1) Fill in the payload of the AP_STACK:
+ for (i = 0; i < size_words; i++) {
+ new_aps->payload[i] = (StgClosure *)ReadSpW(i);
+ }
+ }
+ else {
+
+ // The BCO is a function, therefore the arguments are
+ // directly on top of the stack.
+ // To construct a valid stack chunk simply add an
+ // stg_apply_interp and the current BCO to the stack.
+ // See also Note [Stack layout when entering run_BCO]
+
+ // (1) Allocate memory for a new AP_STACK, enough to store
+ // the top stack frame plus an stg_apply_interp_info pointer
+ // and a pointer to the BCO
+ size_words = BCO_BITMAP_SIZE(obj) + 2;
+ new_aps = (StgAP_STACK *) allocate(cap, AP_STACK_sizeW(size_words));
+ new_aps->size = size_words;
+ new_aps->fun = &stg_dummy_ret_closure;
+
+ // (1.1) the continuation frame
+ new_aps->payload[0] = (StgClosure *)&stg_apply_interp_info;
+ new_aps->payload[1] = (StgClosure *)obj;
+
+ // (1.2.1) copy the args/free vars of the top stack frame into the AP_STACK
+ for (i = 2; i < size_words; i++) {
+ new_aps->payload[i] = (StgClosure *)ReadSpW(i-2);
+ }
}
// No write barrier is needed here as this is a new allocation
SET_HDR(new_aps,&stg_AP_STACK_info,cap->r.rCCCS);
- // Arrange the stack to call the breakpoint IO action, and
+ // (2) Arrange the stack to call the breakpoint IO action, and
// continue execution of this BCO when the IO action returns.
//
// ioAction :: Addr# -- the breakpoint info module
@@ -1622,12 +1733,27 @@ run_BCO:
ioAction = (StgClosure *) deRefStablePtr (
rts_breakpoint_io_action);
- Sp_subW(13);
- SpW(12) = (W_)obj;
- SpW(11) = (W_)&stg_apply_interp_info;
+ // (2.1) Construct the continuation to which we'll return in
+ // this thread after the `rts_breakpoint_io_action` returns.
+ //
+ // For case cont. BCOs, the continuation to re-run this BCO
+ // is already first on the stack. For function BCOs we need
+ // to add an `stg_apply_interp` apply to the current BCO.
+ // See Note [Stack layout when entering run_BCO]
+ if (!is_case_cont_BCO) {
+ Sp_subW(2); // stg_apply_interp_info + StgBCO*
+
+ // (2.1.2) Write the continuation frame (above the stg_ret
+ // frame if one exists)
+ SpW(1) = (W_)obj;
+ SpW(0) = (W_)&stg_apply_interp_info;
+ }
+
+ // (2.2) The `rts_breakpoint_io_action` call
+ Sp_subW(11);
SpW(10) = (W_)new_aps;
- SpW(9) = (W_)False_closure; // True <=> an exception
- SpW(8) = (W_)&stg_ap_ppv_info;
+ SpW(9) = (W_)False_closure; // True <=> an exception
+ SpW(8) = (W_)&stg_ap_ppv_info;
SpW(7) = (W_)arg4_info_index;
SpW(6) = (W_)&stg_ap_n_info;
SpW(5) = (W_)BCO_LIT(arg3_info_mod_id);
@@ -1656,17 +1782,6 @@ run_BCO:
goto nextInsn;
}
- /* See Note [Debugger: BRK_ALTS] */
- case bci_BRK_ALTS:
- {
- StgWord16 active = BCO_NEXT;
- if (active) {
- cap->r.rCurrentTSO->flags |= TSO_STOP_NEXT_BREAKPOINT;
- }
-
- goto nextInsn;
- }
-
case bci_STKCHECK: {
// Explicit stack check at the beginning of a function
// *only* (stack checks in case alternatives are
=====================================
rts/Profiling.c
=====================================
@@ -411,7 +411,7 @@ void enterFunCCS (StgRegTable *reg, CostCentreStack *ccsfn)
}
// common case 2: the function stack is empty, or just CAF
- if (ccsfn->cc->is_caf) {
+ if (ccsfn->cc == NULL || ccsfn->cc->is_caf) {
return;
}
=====================================
rts/include/rts/Bytecodes.h
=====================================
@@ -214,8 +214,6 @@
#define bci_OP_INDEX_ADDR_32 242
#define bci_OP_INDEX_ADDR_64 243
-#define bci_BRK_ALTS 244
-
/* If you need to go past 255 then you will run into the flags */
=====================================
testsuite/tests/ghci.debugger/scripts/T26042b.stdout
=====================================
@@ -8,35 +8,32 @@ _result ::
10 foo True i = return i
^^^^^^^^
11 foo False _ = do
-Stopped in Main.bar, T26042b.hs:21:3-10
+Stopped in Main., T26042b.hs:20:3-17
_result ::
GHC.Internal.Prim.State# GHC.Internal.Prim.RealWorld
-> (# GHC.Internal.Prim.State# GHC.Internal.Prim.RealWorld,
Int #) = _
-y :: Int = _
+19 let t = z * 2
20 y <- foo True t
+ ^^^^^^^^^^^^^^^
21 return y
- ^^^^^^^^
-22
-Stopped in Main.foo, T26042b.hs:15:3-10
+Stopped in Main., T26042b.hs:14:3-18
_result ::
GHC.Internal.Prim.State# GHC.Internal.Prim.RealWorld
-> (# GHC.Internal.Prim.State# GHC.Internal.Prim.RealWorld,
Int #) = _
-n :: Int = _
+13 y = 4
14 n <- bar (x + y)
+ ^^^^^^^^^^^^^^^^
15 return n
- ^^^^^^^^
-16
-Stopped in Main.main, T26042b.hs:6:3-9
+Stopped in Main., T26042b.hs:5:3-26
_result ::
GHC.Internal.Prim.State# GHC.Internal.Prim.RealWorld
-> (# GHC.Internal.Prim.State# GHC.Internal.Prim.RealWorld,
() #) = _
-a :: Int = _
+4 main = do
5 a <- foo False undefined
+ ^^^^^^^^^^^^^^^^^^^^^^^^
6 print a
- ^^^^^^^
-7 print a
14
14
=====================================
testsuite/tests/ghci.debugger/scripts/T26042c.script
=====================================
@@ -14,15 +14,7 @@ main
-- we go straight to `main`.
:stepout
:list
--- stepping out from here will stop in the thunk (TODO: WHY?)
-:stepout
-:list
-
--- bring us back to main from the thunk (why were we stopped there?...)
-:stepout
-:list
-
--- and finally out
+-- stepping out from here will exit main
:stepout
-- this test is also run with optimisation to make sure the IO bindings inline and we can stop at them
=====================================
testsuite/tests/ghci.debugger/scripts/T26042c.stdout
=====================================
@@ -8,17 +8,14 @@ _result ::
10 foo True i = return i
^^^^^^^^
11 foo False _ = do
-Stopped in Main.main, T26042c.hs:6:3-9
+Stopped in Main., T26042c.hs:5:3-26
_result ::
GHC.Internal.Prim.State# GHC.Internal.Prim.RealWorld
-> (# GHC.Internal.Prim.State# GHC.Internal.Prim.RealWorld,
() #) = _
-a :: Int = _
+4 main = do
5 a <- foo False undefined
+ ^^^^^^^^^^^^^^^^^^^^^^^^
6 print a
- ^^^^^^^
-7 print a
14
14
-not stopped at a breakpoint
-not stopped at a breakpoint
=====================================
testsuite/tests/ghci.debugger/scripts/T26042d2.hs
=====================================
@@ -0,0 +1,13 @@
+
+module Main where
+
+main = do
+ putStrLn "hello1"
+ f
+ putStrLn "hello3"
+ putStrLn "hello4"
+
+f = do
+ putStrLn "hello2.1"
+ putStrLn "hello2.2"
+{-# NOINLINE f #-}
=====================================
testsuite/tests/ghci.debugger/scripts/T26042d2.script
=====================================
@@ -0,0 +1,12 @@
+:load T26042d2.hs
+
+:break 11
+main
+:list
+:stepout
+:list
+:stepout
+
+-- should exit! we compile this test case with -O1 to make sure the monad >> are inlined
+-- and thus the test relies on the filtering behavior based on SrcSpans for stepout
+
=====================================
testsuite/tests/ghci.debugger/scripts/T26042d2.stdout
=====================================
@@ -0,0 +1,24 @@
+Breakpoint 0 activated at T26042d2.hs:11:3-21
+hello1
+Stopped in Main.f, T26042d2.hs:11:3-21
+_result ::
+ GHC.Internal.Prim.State# GHC.Internal.Prim.RealWorld
+ -> (# GHC.Internal.Prim.State# GHC.Internal.Prim.RealWorld,
+ () #) = _
+10 f = do
+11 putStrLn "hello2.1"
+ ^^^^^^^^^^^^^^^^^^^
+12 putStrLn "hello2.2"
+hello2.1
+hello2.2
+Stopped in Main., T26042d2.hs:6:3
+_result ::
+ GHC.Internal.Prim.State# GHC.Internal.Prim.RealWorld
+ -> (# GHC.Internal.Prim.State# GHC.Internal.Prim.RealWorld,
+ () #) = _
+5 putStrLn "hello1"
+6 f
+ ^
+7 putStrLn "hello3"
+hello3
+hello4
=====================================
testsuite/tests/ghci.debugger/scripts/T26042e.stdout
=====================================
@@ -7,14 +7,12 @@ y :: [a1] -> Int = _
11 let !z = y x
^^^^^^^^^^^^
12 let !t = y ['b']
-Stopped in T7.main, T26042e.hs:19:3-11
+Stopped in T7., T26042e.hs:18:3-17
_result :: IO () = _
-x :: Int = _
-y :: Int = _
+17 main = do
18 let !(x, y) = a
+ ^^^^^^^^^^^^^^^
19 print '1'
- ^^^^^^^^^
-20 print '2'
'1'
'2'
'3'
=====================================
testsuite/tests/ghci.debugger/scripts/T26042f2.stdout
=====================================
@@ -8,18 +8,16 @@ x :: Int = 450
21 pure (x + 3)
^^
22 {-# OPAQUE t #-}
-Stopped in T8.g, T26042f.hs:15:3-17
+Stopped in T8., T26042f.hs:14:3-14
_result :: Identity Int = _
-a :: Int = 453
+13 g x = do
14 a <- t (x*2)
+ ^^^^^^^^^^^^
15 n <- pure (a+a)
- ^^^^^^^^^^^^^^^
-16 return (n+n)
-Stopped in T8.f, T26042f.hs:9:3-17
+Stopped in T8., T26042f.hs:8:3-14
_result :: Identity Int = _
-b :: Int = 1812
+7 f x = do
8 b <- g (x*x)
+ ^^^^^^^^^^^^
9 y <- pure (b+b)
- ^^^^^^^^^^^^^^^
-10 return (y+y)
7248
=====================================
testsuite/tests/ghci.debugger/scripts/T26042g.stdout
=====================================
@@ -6,10 +6,13 @@ x :: Int = 14
11 succ x = (-) (x - 2) (x + 1)
^^^^^^^^^^^^^^^^^^^
12
-Stopped in T9.top, T26042g.hs:8:10-21
+Stopped in T9., T26042g.hs:(6,3)-(8,21)
_result :: Int = _
+5 top = do
+ vv
+6 case succ 14 of
7 5 -> 5
8 _ -> 6 + other 55
- ^^^^^^^^^^^^
+ ^^
9
171
=====================================
testsuite/tests/ghci.debugger/scripts/all.T
=====================================
@@ -147,8 +147,9 @@ test('T25932', extra_files(['T25932.hs']), ghci_script, ['T25932.script'])
# Step out tests
test('T26042b', [extra_hc_opts('-O -fno-unoptimized-core-for-interpreter'), extra_files(['T26042b.hs'])], ghci_script, ['T26042b.script'])
-test('T26042c', [expect_broken(26042),extra_hc_opts('-O -fno-unoptimized-core-for-interpreter'), extra_files(['T26042c.hs'])], ghci_script, ['T26042c.script'])
+test('T26042c', [extra_hc_opts('-O -fno-unoptimized-core-for-interpreter'), extra_files(['T26042c.hs'])], ghci_script, ['T26042c.script'])
test('T26042d', [extra_hc_opts('-O -fno-unoptimized-core-for-interpreter'), extra_files(['T26042d.hs'])], ghci_script, ['T26042d.script'])
+test('T26042d2', [extra_hc_opts('-O -fno-unoptimized-core-for-interpreter'), extra_files(['T26042d2.hs'])], ghci_script, ['T26042d2.script'])
test('T26042e', extra_files(['T26042e.hs']), ghci_script, ['T26042e.script'])
test('T26042f1', extra_files(['T26042f.hs', 'T26042f.script']), ghci_script, ['T26042f.script']) # >> is not inlined, so stepout has nowhere to stop
test('T26042f2', [extra_hc_opts('-O -fno-unoptimized-core-for-interpreter'), extra_files(['T26042f.hs', 'T26042f.script'])], ghci_script, ['T26042f.script'])
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/75599f78cc3f1b3f61485b8e72ee6be...
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/75599f78cc3f1b3f61485b8e72ee6be...
You're receiving this email because of your account on gitlab.haskell.org.