[Git][ghc/ghc][wip/ipe-return-prefer-current-mod] compiler/ipe: Prefer source ticks in current module for return frames
by Ben Gamari (@bgamari) 21 Jun '26
by Ben Gamari (@bgamari) 21 Jun '26
21 Jun '26
Ben Gamari pushed to branch wip/ipe-return-prefer-current-mod at Glasgow Haskell Compiler / GHC
Commits:
8fbf9c06 by Ben Gamari at 2026-06-21T08:23:11-04:00
compiler/ipe: Prefer source ticks in current module for return frames
Previously we would simply attribute return frames to the nearest
enclosing tick in the calling frame. However, this will very frequently
produce unhelpful results (e.g. pointing to `(>>)` rather than the
calling function).
- - - - -
5 changed files:
- compiler/GHC/Driver/GenerateCgIPEStub.hs
- compiler/GHC/Driver/Main/Compile.hs
- + testsuite/tests/rts/ipe/T27408.hs
- + testsuite/tests/rts/ipe/T27408.stdout
- testsuite/tests/rts/ipe/all.T
Changes:
=====================================
compiler/GHC/Driver/GenerateCgIPEStub.hs
=====================================
@@ -1,7 +1,9 @@
module GHC.Driver.GenerateCgIPEStub (generateCgIPEStub, lookupEstimatedTicks) where
+import Control.Applicative ((<|>))
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
+import Data.Maybe (listToMaybe)
import Data.Semigroup ((<>))
import GHC.Cmm
import GHC.Cmm.CLabel (CLabel, mkAsmTempLabel)
@@ -10,6 +12,7 @@ import GHC.Cmm.Dataflow.Block (blockSplit, blockToList)
import GHC.Cmm.Dataflow.Label
import GHC.Cmm.Info.Build (emptySRT)
import GHC.Cmm.Pipeline (cmmPipeline)
+import GHC.Data.FastString (FastString, mkFastString)
import GHC.Data.Stream (liftIO, liftEff)
import qualified GHC.Data.Stream as Stream
import GHC.Driver.Env (hsc_dflags, hsc_logger)
@@ -28,9 +31,10 @@ import GHC.StgToCmm.Utils
import GHC.StgToCmm.CgUtils (CgStream)
import GHC.Types.IPE (InfoTableProvMap (provInfoTables), IpeSourceLocation)
import GHC.Types.Name.Set (NonCaffySet)
+import GHC.Types.SrcLoc (srcSpanFile)
import GHC.Types.Tickish (GenTickish (SourceNote))
import GHC.Unit.Types (Module, moduleName)
-import GHC.Unit.Module (moduleNameString)
+import GHC.Unit.Module (moduleNameString, ModLocation, ml_hs_file)
import qualified GHC.Utils.Logger as Logger
import GHC.Utils.Outputable (ppr)
import GHC.Types.Unique.DSM
@@ -257,11 +261,12 @@ generateCgIPEStub hsc_env this_mod denv (nonCaffySet, moduleLFInfos, infoTablesW
-- performance suffered considerably as a result (see #23103).
lookupEstimatedTicks
:: HscEnv
+ -> ModLocation -- ^ location of the module being compiled, for IPE provenance
-> Map CmmInfoTable (Maybe IpeSourceLocation)
-> IPEStats
-> CmmGroupSRTs
-> IO (Map CmmInfoTable (Maybe IpeSourceLocation), IPEStats)
-lookupEstimatedTicks hsc_env ipes stats cmm_group_srts =
+lookupEstimatedTicks hsc_env mod_location ipes stats cmm_group_srts =
-- Pass 2: Create an entry in the IPE map for every info table listed in
-- this CmmGroupSRTs. If the info table is a stack info table and
-- -finfo-table-map-with-stack is enabled, look up its estimated source
@@ -276,19 +281,24 @@ lookupEstimatedTicks hsc_env ipes stats cmm_group_srts =
dflags = hsc_dflags hsc_env
platform = targetPlatform dflags
- -- Pass 1: Map every label meeting the conditions described in Note
- -- [Stacktraces from Info Table Provenance Entries (IPE based stack
- -- unwinding)] to the estimated source location (also as described in the
- -- aformentioned note)
+ -- Source file of the module being compiled, used to prefer current-module
+ -- source ticks for return frames. See Note [Prefer current-module source
+ -- ticks for return frames].
+ mb_src_file = mkFastString <$> ml_hs_file mod_location
+
+ -- Pass 1: Map every label meeting the conditions described in
+ -- Note [Stacktraces from Info Table Provenance Entries (IPE based stack unwinding)]
+ -- to the estimated source location (also as described in the aformentioned
+ -- note).
--
-- Note: It's important that this remains a thunk so we do not compute this
-- map if -fno-info-table-with-stack is given
labelsToSources :: Map CLabel IpeSourceLocation
labelsToSources =
if platformTablesNextToCode platform then
- foldl' labelsToSourcesWithTNTC Map.empty cmm_group_srts
+ foldl' (labelsToSourcesWithTNTC mb_src_file) Map.empty cmm_group_srts
else
- foldl' labelsToSourcesSansTNTC Map.empty cmm_group_srts
+ foldl' (labelsToSourcesSansTNTC mb_src_file) Map.empty cmm_group_srts
collectInfoTables
:: (Map CmmInfoTable (Maybe IpeSourceLocation), IPEStats)
@@ -331,15 +341,16 @@ lookupEstimatedTicks hsc_env ipes stats cmm_group_srts =
-- | See Note [Stacktraces from Info Table Provenance Entries (IPE based stack unwinding)]
labelsToSourcesWithTNTC
- :: Map CLabel IpeSourceLocation
+ :: Maybe FastString -- ^ source file of the module being compiled
+ -> Map CLabel IpeSourceLocation
-> GenCmmDecl RawCmmStatics CmmTopInfo CmmGraph
-> Map CLabel IpeSourceLocation
-labelsToSourcesWithTNTC acc (CmmProc _ _ _ cmm_graph) =
+labelsToSourcesWithTNTC mb_src_file acc (CmmProc _ _ _ cmm_graph) =
foldl' go acc (toBlockList cmm_graph)
where
go :: Map CLabel IpeSourceLocation -> CmmBlock -> Map CLabel IpeSourceLocation
go acc block =
- case (,) <$> returnFrameLabel <*> lastTickInBlock of
+ case (,) <$> returnFrameLabel <*> bestTickInBlock of
Just (clabel, src_loc) -> Map.insert clabel src_loc acc
Nothing -> acc
where
@@ -351,36 +362,135 @@ labelsToSourcesWithTNTC acc (CmmProc _ _ _ cmm_graph) =
(CmmCall _ (Just l) _ _ _ _) -> Just $ mkAsmTempLabel l
_ -> Nothing
- lastTickInBlock = foldr maybeTick Nothing (blockToList middleBlock)
+ -- All SourceNotes in the block, in block order.
+ -- See Note [Prefer current-module source ticks for return frames].
+ bestTickInBlock = preferThisFile mb_src_file procFallback (blockToList middleBlock)
+
+ -- Enclosing current-module note for the whole proc (its function's own
+ -- span), used when a return frame's own block has no current-module tick.
+ procFallback = enclosingThisFileTick mb_src_file (toBlockList cmm_graph)
+labelsToSourcesWithTNTC _ acc _ = acc
+
+{-
+Note [Prefer current-module source ticks for return frames]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+A return frame's source location is taken from the `SourceNote`s of the block
+that *ends* in the frame's call (see
+Note [Stacktraces from Info Table Provenance Entries (IPE based stack unwinding]
+above and `labelsToSourcesWithTNTC`). At `-O`, inlining means such a
+block frequently carries `SourceNote`s for inlined library glue (`>>`, `>>=`,
+`threadDelay`, ...) and the *nearest* note — the one historically chosen — is
+often a library note rather than the user's code. The resulting IPE entry then
+points at the library (its file and label; see `toCgIPE` in
+GHC.StgToCmm.InfoTableProv, which takes the file from the note's own span), so a
+backtrace of a thread blocked in such a primop shows no user frames.
+
+For a concrete example, compile (at -O1)
+
+ -- Scan.hs
+ f3 :: IO ()
+ f3 = threadDelay 1000000 >> putStrLn "done"
+
+The body of `f3` reaches the inlined `threadDelay`'s internal `delay#` in a
+block whose notes are *all* from `Conc.IO`/`Base` — the user's `Scan.hs` tick
+for `f3` sits only in the proc's entry block:
+
+ entry: -- the proc's entry block
+ //tick src<.../Base.hs:2306:5-18>
+ //tick src<Scan.hs:13:1-43> -- the enclosing `f3` span
+ //tick src<.../Conc/IO.hs:(223,1)-(235,10)>
+ ...
+ delayBlk: -- no Scan.hs note here:
+ //tick src<.../Conc/IO.hs:232:5-13>
+ //tick src<.../Base.hs:2268:1-9>
+ //tick src<.../Conc/IO.hs:(232,25)-(235,10)> -- nearest note
+ call stg_delay#(R1) returns to delayCont, args: 8, res: 8, upd: 8;
+
+Naively taking the nearest note attributes `delayCont` to `Conc/IO.hs:232`,
+i.e. an internal of `threadDelay`, rather than `f3`.
+
+To fix this we attribute a return frame's source location in the following
+preference order:
+
+ 1. the nearest tick in the frame's block whose file is that of the module
+ being compiled - the precise user call site. (When the user makes a
+ blocking call directly, e.g. `f v = takeMVar v`, such a note is present in
+ the call's block and this rule suffices; the `delayBlk` above has none.)
+ 2. failing that, the proc's *enclosing* current-module note (the outermost
+ current-module `SourceNote` in the proc, i.e. its function's own span).
+ For `f3` this is `src<Scan.hs:13:1-43>`, so `delayCont` is attributed to
+ `f3` rather than to `threadDelay`'s internals.
+ 3. failing that, the nearest note of any module (the historical behaviour).
+
+This mirrors the same-file preference the DWARF path uses in
+`GHC.Cmm.DebugBlock.bestSrcTick` and that `GHC.Stg.Debug.quickSourcePos` uses
+for closures.
+-}
- maybeTick :: CmmNode O O -> Maybe IpeSourceLocation -> Maybe IpeSourceLocation
- maybeTick _ s@(Just _) = s
- maybeTick (CmmTick (SourceNote span name)) Nothing = Just (span, name)
- maybeTick _ _ = Nothing
-labelsToSourcesWithTNTC acc _ = acc
+-- | Pick the 'IpeSourceLocation' to attribute to a return frame from the
+-- source-note-bearing nodes of its block (in block order).
+--
+-- See Note [Prefer current-module source ticks for return frames].
+preferThisFile :: Maybe FastString -> Maybe IpeSourceLocation -> [CmmNode O O] -> Maybe IpeSourceLocation
+preferThisFile mb_src_file procFallback nodes =
+ nearest fromThisFile <|> procFallback <|> nearest sourceNotes
+ where
+ sourceNotes = [ (span, name) | CmmTick (SourceNote span name) <- nodes ]
+ fromThisFile = case mb_src_file of
+ Just f -> filter ((== f) . srcSpanFile . fst) sourceNotes
+ Nothing -> []
+ nearest = listToMaybe . reverse
+
+-- | The outermost 'SourceNote' from the module being compiled across a proc's
+-- blocks (in 'toBlockList' order, so the entry block's note — the function's own
+-- span — comes first). Used as a fallback so inlined cross-module code is still
+-- labelled with the enclosing user function. 'Nothing' when the proc has no
+-- current-module note (e.g. when compiling the library itself).
+enclosingThisFileTick :: Maybe FastString -> [CmmBlock] -> Maybe IpeSourceLocation
+enclosingThisFileTick mb_src_file blocks =
+ listToMaybe
+ [ (span, name)
+ | b <- blocks
+ , let (_, mid, _) = blockSplit b
+ , CmmTick (SourceNote span name) <- blockToList mid
+ , Just (srcSpanFile span) == mb_src_file ]
-- | See Note [Stacktraces from Info Table Provenance Entries (IPE based stack unwinding)]
labelsToSourcesSansTNTC
- :: Map CLabel IpeSourceLocation
+ :: Maybe FastString -- ^ source file of the module being compiled
+ -> Map CLabel IpeSourceLocation
-> GenCmmDecl RawCmmStatics CmmTopInfo CmmGraph
-> Map CLabel IpeSourceLocation
-labelsToSourcesSansTNTC acc (CmmProc _ _ _ cmm_graph) =
+labelsToSourcesSansTNTC mb_src_file acc (CmmProc _ _ _ cmm_graph) =
foldl' go acc (toBlockList cmm_graph)
where
+ -- See 'enclosingThisFileTick'.
+ procFallback = enclosingThisFileTick mb_src_file (toBlockList cmm_graph)
+
go :: Map CLabel IpeSourceLocation -> CmmBlock -> Map CLabel IpeSourceLocation
- go acc block = fst $ foldl' collectLabels (acc, Nothing) (blockToList middleBlock)
+ go acc block = fst $ foldl' collectLabels (acc, (Nothing, Nothing)) (blockToList middleBlock)
where
(_, middleBlock, _) = blockSplit block
+ -- We track the nearest preceding SourceNote from the module being
+ -- compiled and the nearest of any module, and prefer the former (then
+ -- the proc's enclosing current-module note) when attributing a return
+ -- frame. See Note [Prefer current-module source ticks for return frames].
collectLabels
- :: (Map CLabel IpeSourceLocation, Maybe IpeSourceLocation)
+ :: (Map CLabel IpeSourceLocation, (Maybe IpeSourceLocation, Maybe IpeSourceLocation))
-> CmmNode O O
- -> (Map CLabel IpeSourceLocation, Maybe IpeSourceLocation)
- collectLabels (!acc, lastTick) b =
- case (b, lastTick) of
- (CmmStore _ (CmmLit (CmmLabel l)) _, Just src_loc) ->
- (Map.insert l src_loc acc, Nothing)
- (CmmTick (SourceNote span name), _) ->
- (acc, Just (span, name))
- _ -> (acc, lastTick)
-labelsToSourcesSansTNTC acc _ = acc
+ -> (Map CLabel IpeSourceLocation, (Maybe IpeSourceLocation, Maybe IpeSourceLocation))
+ collectLabels (!acc, st@(lastThis, lastAny)) b =
+ case b of
+ CmmStore _ (CmmLit (CmmLabel l)) _ ->
+ case lastThis <|> procFallback <|> lastAny of
+ Just src_loc -> (Map.insert l src_loc acc, (Nothing, Nothing))
+ Nothing -> (acc, st)
+ CmmTick (SourceNote span name) ->
+ let tick = (span, name)
+ lastThis'
+ | Just (srcSpanFile span) == mb_src_file = Just tick
+ | otherwise = lastThis
+ in (acc, (lastThis', Just tick))
+ _ -> (acc, st)
+labelsToSourcesSansTNTC _ acc _ = acc
=====================================
compiler/GHC/Driver/Main/Compile.hs
=====================================
@@ -699,7 +699,7 @@ hscGenHardCode hsc_env cgguts mod_loc output_filename = do
_ ->
do
cmms <- {-# SCC "StgToCmm" #-}
- doCodeGen hsc_env this_mod denv tycons
+ doCodeGen hsc_env this_mod mod_loc denv tycons
cost_centre_info
stg_binds
@@ -956,14 +956,17 @@ This reduces residency towards the end of the CodeGen phase significantly
(5-10%).
-}
-doCodeGen :: HscEnv -> Module -> InfoTableProvMap -> [TyCon]
+doCodeGen :: HscEnv -> Module
+ -> ModLocation -- ^ location of the module being compiled, used to
+ -- prefer current-module IPE source locations
+ -> InfoTableProvMap -> [TyCon]
-> CollectedCCs
-> [CgStgTopBinding] -- ^ Bindings come already annotated with fvs
-> IO (CgStream CmmGroupSRTs CmmCgInfos)
-- Note we produce a 'Stream' of CmmGroups, so that the
-- backend can be run incrementally. Otherwise it generates all
-- the C-- up front, which has a significant space cost.
-doCodeGen hsc_env this_mod denv tycons
+doCodeGen hsc_env this_mod mod_location denv tycons
cost_centre_info stg_binds_w_fvs = do
let dflags = hsc_dflags hsc_env
logger = hsc_logger hsc_env
@@ -1032,7 +1035,7 @@ doCodeGen hsc_env this_mod denv tycons
-- Positions] in GHC.Stg.Debug.
(ipes', stats') <-
if (gopt Opt_InfoTableMap dflags) then
- liftIO $ lookupEstimatedTicks hsc_env ipes stats cmm_srts
+ liftIO $ lookupEstimatedTicks hsc_env mod_location ipes stats cmm_srts
else
return (ipes, stats)
=====================================
testsuite/tests/rts/ipe/T27408.hs
=====================================
@@ -0,0 +1,18 @@
+module Main where
+
+import GHC.Stack.CloneStack (StackEntry(..), cloneMyStack, decode)
+
+userFunction :: IO [StackEntry]
+userFunction = do
+ putStr ""
+ stk <- cloneMyStack
+ putStr ""
+ es <- decode stk
+ putStr ""
+ return es
+
+main :: IO ()
+main = do
+ entries <- userFunction
+ let ours = filter ((== "Main") . moduleName) entries
+ mapM_ (\e -> putStrLn (moduleName e ++ "\t" ++ functionName e)) ours
=====================================
testsuite/tests/rts/ipe/T27408.stdout
=====================================
@@ -0,0 +1,3 @@
+Main
+Main main
+Main userFunction
=====================================
testsuite/tests/rts/ipe/all.T
=====================================
@@ -4,6 +4,16 @@ def noCapabilityOutputFilter(s):
test('ipeMap', [extra_files(['ipe_lib.c', 'ipe_lib.h']), c_src, omit_ghci], compile_and_run, ['ipe_lib.c'])
+# Return frames whose Cmm block is dominated by inlined library ticks (e.g. the
+# (>>)/(>>=) of a user do-block at -O) should still be attributed to the user's
+# module. See Note [Prefer current-module source ticks for return frames] in
+# GHC.Driver.GenerateCgIPEStub.
+test('T27408',
+ [ omit_ghci # cloneMyStack# is not available in ghci
+ , js_broken(22261) # cloneMyStack# not yet implemented in the JS backend
+ ],
+ compile_and_run, ['-O1 -finfo-table-map -g3'])
+
# Manually create IPE entries and dump them to event log (stderr).
test('ipeEventLog',
[ c_src,
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/8fbf9c06a3e27491e9b783450cc7eba…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/8fbf9c06a3e27491e9b783450cc7eba…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc][wip/stm-mvar-deadlock-backtrace] 5 commits: compiler/ipe: Prefer source ticks in current module for return frames
by Ben Gamari (@bgamari) 21 Jun '26
by Ben Gamari (@bgamari) 21 Jun '26
21 Jun '26
Ben Gamari pushed to branch wip/stm-mvar-deadlock-backtrace at Glasgow Haskell Compiler / GHC
Commits:
8fbf9c06 by Ben Gamari at 2026-06-21T08:23:11-04:00
compiler/ipe: Prefer source ticks in current module for return frames
Previously we would simply attribute return frames to the nearest
enclosing tick in the calling frame. However, this will very frequently
produce unhelpful results (e.g. pointing to `(>>)` rather than the
calling function).
- - - - -
abd9dc0e by Ben Gamari at 2026-06-21T11:20:56-04:00
testsuite: Add LoopBacktrace test
- - - - -
4010d192 by Ben Gamari at 2026-06-21T11:20:56-04:00
Throw nontermination exceptions via `throw`
This ensures that the exception that results gets the usual backtrace
annotations.
- - - - -
f8b70be2 by Ben Gamari at 2026-06-21T11:38:17-04:00
compiler: Report backtraces in MVar and STM dealocks
Apply the same treatment previously given to Nontermination exceptions
to MVar and STM deadlock exceptions, using `throw` instead of ad-hoc
throwing with `throwToSingleThread` to ensure that the usual backtrace
machinery is involved.
- - - - -
fadf9d48 by Ben Gamari at 2026-06-21T11:38:17-04:00
testsuite: Add tests for deadlock backtraces
- - - - -
24 changed files:
- compiler/GHC/Driver/GenerateCgIPEStub.hs
- compiler/GHC/Driver/Main/Compile.hs
- libraries/base/src/Control/Exception/Base.hs
- libraries/base/src/GHC/IO/Exception.hs
- libraries/ghc-internal/include/RtsIfaceSymbols.h
- libraries/ghc-internal/src/GHC/Internal/Control/Exception/Base.hs
- libraries/ghc-internal/src/GHC/Internal/IO/Exception.hs
- rts/Prelude.h
- rts/RaiseAsync.c
- rts/RaiseAsync.h
- rts/RtsStartup.c
- rts/Schedule.c
- rts/include/rts/RtsToHsIface.h
- + testsuite/tests/rts/LoopBacktrace.hs
- + testsuite/tests/rts/LoopBacktrace.stderr
- + testsuite/tests/rts/LoopBacktrace.stdout
- + testsuite/tests/rts/MVarDeadlockBacktrace.hs
- + testsuite/tests/rts/MVarDeadlockBacktrace.stderr
- + testsuite/tests/rts/STMDeadlockBacktrace.hs
- + testsuite/tests/rts/STMDeadlockBacktrace.stderr
- testsuite/tests/rts/all.T
- + testsuite/tests/rts/ipe/T27408.hs
- + testsuite/tests/rts/ipe/T27408.stdout
- testsuite/tests/rts/ipe/all.T
Changes:
=====================================
compiler/GHC/Driver/GenerateCgIPEStub.hs
=====================================
@@ -1,7 +1,9 @@
module GHC.Driver.GenerateCgIPEStub (generateCgIPEStub, lookupEstimatedTicks) where
+import Control.Applicative ((<|>))
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
+import Data.Maybe (listToMaybe)
import Data.Semigroup ((<>))
import GHC.Cmm
import GHC.Cmm.CLabel (CLabel, mkAsmTempLabel)
@@ -10,6 +12,7 @@ import GHC.Cmm.Dataflow.Block (blockSplit, blockToList)
import GHC.Cmm.Dataflow.Label
import GHC.Cmm.Info.Build (emptySRT)
import GHC.Cmm.Pipeline (cmmPipeline)
+import GHC.Data.FastString (FastString, mkFastString)
import GHC.Data.Stream (liftIO, liftEff)
import qualified GHC.Data.Stream as Stream
import GHC.Driver.Env (hsc_dflags, hsc_logger)
@@ -28,9 +31,10 @@ import GHC.StgToCmm.Utils
import GHC.StgToCmm.CgUtils (CgStream)
import GHC.Types.IPE (InfoTableProvMap (provInfoTables), IpeSourceLocation)
import GHC.Types.Name.Set (NonCaffySet)
+import GHC.Types.SrcLoc (srcSpanFile)
import GHC.Types.Tickish (GenTickish (SourceNote))
import GHC.Unit.Types (Module, moduleName)
-import GHC.Unit.Module (moduleNameString)
+import GHC.Unit.Module (moduleNameString, ModLocation, ml_hs_file)
import qualified GHC.Utils.Logger as Logger
import GHC.Utils.Outputable (ppr)
import GHC.Types.Unique.DSM
@@ -257,11 +261,12 @@ generateCgIPEStub hsc_env this_mod denv (nonCaffySet, moduleLFInfos, infoTablesW
-- performance suffered considerably as a result (see #23103).
lookupEstimatedTicks
:: HscEnv
+ -> ModLocation -- ^ location of the module being compiled, for IPE provenance
-> Map CmmInfoTable (Maybe IpeSourceLocation)
-> IPEStats
-> CmmGroupSRTs
-> IO (Map CmmInfoTable (Maybe IpeSourceLocation), IPEStats)
-lookupEstimatedTicks hsc_env ipes stats cmm_group_srts =
+lookupEstimatedTicks hsc_env mod_location ipes stats cmm_group_srts =
-- Pass 2: Create an entry in the IPE map for every info table listed in
-- this CmmGroupSRTs. If the info table is a stack info table and
-- -finfo-table-map-with-stack is enabled, look up its estimated source
@@ -276,19 +281,24 @@ lookupEstimatedTicks hsc_env ipes stats cmm_group_srts =
dflags = hsc_dflags hsc_env
platform = targetPlatform dflags
- -- Pass 1: Map every label meeting the conditions described in Note
- -- [Stacktraces from Info Table Provenance Entries (IPE based stack
- -- unwinding)] to the estimated source location (also as described in the
- -- aformentioned note)
+ -- Source file of the module being compiled, used to prefer current-module
+ -- source ticks for return frames. See Note [Prefer current-module source
+ -- ticks for return frames].
+ mb_src_file = mkFastString <$> ml_hs_file mod_location
+
+ -- Pass 1: Map every label meeting the conditions described in
+ -- Note [Stacktraces from Info Table Provenance Entries (IPE based stack unwinding)]
+ -- to the estimated source location (also as described in the aformentioned
+ -- note).
--
-- Note: It's important that this remains a thunk so we do not compute this
-- map if -fno-info-table-with-stack is given
labelsToSources :: Map CLabel IpeSourceLocation
labelsToSources =
if platformTablesNextToCode platform then
- foldl' labelsToSourcesWithTNTC Map.empty cmm_group_srts
+ foldl' (labelsToSourcesWithTNTC mb_src_file) Map.empty cmm_group_srts
else
- foldl' labelsToSourcesSansTNTC Map.empty cmm_group_srts
+ foldl' (labelsToSourcesSansTNTC mb_src_file) Map.empty cmm_group_srts
collectInfoTables
:: (Map CmmInfoTable (Maybe IpeSourceLocation), IPEStats)
@@ -331,15 +341,16 @@ lookupEstimatedTicks hsc_env ipes stats cmm_group_srts =
-- | See Note [Stacktraces from Info Table Provenance Entries (IPE based stack unwinding)]
labelsToSourcesWithTNTC
- :: Map CLabel IpeSourceLocation
+ :: Maybe FastString -- ^ source file of the module being compiled
+ -> Map CLabel IpeSourceLocation
-> GenCmmDecl RawCmmStatics CmmTopInfo CmmGraph
-> Map CLabel IpeSourceLocation
-labelsToSourcesWithTNTC acc (CmmProc _ _ _ cmm_graph) =
+labelsToSourcesWithTNTC mb_src_file acc (CmmProc _ _ _ cmm_graph) =
foldl' go acc (toBlockList cmm_graph)
where
go :: Map CLabel IpeSourceLocation -> CmmBlock -> Map CLabel IpeSourceLocation
go acc block =
- case (,) <$> returnFrameLabel <*> lastTickInBlock of
+ case (,) <$> returnFrameLabel <*> bestTickInBlock of
Just (clabel, src_loc) -> Map.insert clabel src_loc acc
Nothing -> acc
where
@@ -351,36 +362,135 @@ labelsToSourcesWithTNTC acc (CmmProc _ _ _ cmm_graph) =
(CmmCall _ (Just l) _ _ _ _) -> Just $ mkAsmTempLabel l
_ -> Nothing
- lastTickInBlock = foldr maybeTick Nothing (blockToList middleBlock)
+ -- All SourceNotes in the block, in block order.
+ -- See Note [Prefer current-module source ticks for return frames].
+ bestTickInBlock = preferThisFile mb_src_file procFallback (blockToList middleBlock)
+
+ -- Enclosing current-module note for the whole proc (its function's own
+ -- span), used when a return frame's own block has no current-module tick.
+ procFallback = enclosingThisFileTick mb_src_file (toBlockList cmm_graph)
+labelsToSourcesWithTNTC _ acc _ = acc
+
+{-
+Note [Prefer current-module source ticks for return frames]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+A return frame's source location is taken from the `SourceNote`s of the block
+that *ends* in the frame's call (see
+Note [Stacktraces from Info Table Provenance Entries (IPE based stack unwinding]
+above and `labelsToSourcesWithTNTC`). At `-O`, inlining means such a
+block frequently carries `SourceNote`s for inlined library glue (`>>`, `>>=`,
+`threadDelay`, ...) and the *nearest* note — the one historically chosen — is
+often a library note rather than the user's code. The resulting IPE entry then
+points at the library (its file and label; see `toCgIPE` in
+GHC.StgToCmm.InfoTableProv, which takes the file from the note's own span), so a
+backtrace of a thread blocked in such a primop shows no user frames.
+
+For a concrete example, compile (at -O1)
+
+ -- Scan.hs
+ f3 :: IO ()
+ f3 = threadDelay 1000000 >> putStrLn "done"
+
+The body of `f3` reaches the inlined `threadDelay`'s internal `delay#` in a
+block whose notes are *all* from `Conc.IO`/`Base` — the user's `Scan.hs` tick
+for `f3` sits only in the proc's entry block:
+
+ entry: -- the proc's entry block
+ //tick src<.../Base.hs:2306:5-18>
+ //tick src<Scan.hs:13:1-43> -- the enclosing `f3` span
+ //tick src<.../Conc/IO.hs:(223,1)-(235,10)>
+ ...
+ delayBlk: -- no Scan.hs note here:
+ //tick src<.../Conc/IO.hs:232:5-13>
+ //tick src<.../Base.hs:2268:1-9>
+ //tick src<.../Conc/IO.hs:(232,25)-(235,10)> -- nearest note
+ call stg_delay#(R1) returns to delayCont, args: 8, res: 8, upd: 8;
+
+Naively taking the nearest note attributes `delayCont` to `Conc/IO.hs:232`,
+i.e. an internal of `threadDelay`, rather than `f3`.
+
+To fix this we attribute a return frame's source location in the following
+preference order:
+
+ 1. the nearest tick in the frame's block whose file is that of the module
+ being compiled - the precise user call site. (When the user makes a
+ blocking call directly, e.g. `f v = takeMVar v`, such a note is present in
+ the call's block and this rule suffices; the `delayBlk` above has none.)
+ 2. failing that, the proc's *enclosing* current-module note (the outermost
+ current-module `SourceNote` in the proc, i.e. its function's own span).
+ For `f3` this is `src<Scan.hs:13:1-43>`, so `delayCont` is attributed to
+ `f3` rather than to `threadDelay`'s internals.
+ 3. failing that, the nearest note of any module (the historical behaviour).
+
+This mirrors the same-file preference the DWARF path uses in
+`GHC.Cmm.DebugBlock.bestSrcTick` and that `GHC.Stg.Debug.quickSourcePos` uses
+for closures.
+-}
- maybeTick :: CmmNode O O -> Maybe IpeSourceLocation -> Maybe IpeSourceLocation
- maybeTick _ s@(Just _) = s
- maybeTick (CmmTick (SourceNote span name)) Nothing = Just (span, name)
- maybeTick _ _ = Nothing
-labelsToSourcesWithTNTC acc _ = acc
+-- | Pick the 'IpeSourceLocation' to attribute to a return frame from the
+-- source-note-bearing nodes of its block (in block order).
+--
+-- See Note [Prefer current-module source ticks for return frames].
+preferThisFile :: Maybe FastString -> Maybe IpeSourceLocation -> [CmmNode O O] -> Maybe IpeSourceLocation
+preferThisFile mb_src_file procFallback nodes =
+ nearest fromThisFile <|> procFallback <|> nearest sourceNotes
+ where
+ sourceNotes = [ (span, name) | CmmTick (SourceNote span name) <- nodes ]
+ fromThisFile = case mb_src_file of
+ Just f -> filter ((== f) . srcSpanFile . fst) sourceNotes
+ Nothing -> []
+ nearest = listToMaybe . reverse
+
+-- | The outermost 'SourceNote' from the module being compiled across a proc's
+-- blocks (in 'toBlockList' order, so the entry block's note — the function's own
+-- span — comes first). Used as a fallback so inlined cross-module code is still
+-- labelled with the enclosing user function. 'Nothing' when the proc has no
+-- current-module note (e.g. when compiling the library itself).
+enclosingThisFileTick :: Maybe FastString -> [CmmBlock] -> Maybe IpeSourceLocation
+enclosingThisFileTick mb_src_file blocks =
+ listToMaybe
+ [ (span, name)
+ | b <- blocks
+ , let (_, mid, _) = blockSplit b
+ , CmmTick (SourceNote span name) <- blockToList mid
+ , Just (srcSpanFile span) == mb_src_file ]
-- | See Note [Stacktraces from Info Table Provenance Entries (IPE based stack unwinding)]
labelsToSourcesSansTNTC
- :: Map CLabel IpeSourceLocation
+ :: Maybe FastString -- ^ source file of the module being compiled
+ -> Map CLabel IpeSourceLocation
-> GenCmmDecl RawCmmStatics CmmTopInfo CmmGraph
-> Map CLabel IpeSourceLocation
-labelsToSourcesSansTNTC acc (CmmProc _ _ _ cmm_graph) =
+labelsToSourcesSansTNTC mb_src_file acc (CmmProc _ _ _ cmm_graph) =
foldl' go acc (toBlockList cmm_graph)
where
+ -- See 'enclosingThisFileTick'.
+ procFallback = enclosingThisFileTick mb_src_file (toBlockList cmm_graph)
+
go :: Map CLabel IpeSourceLocation -> CmmBlock -> Map CLabel IpeSourceLocation
- go acc block = fst $ foldl' collectLabels (acc, Nothing) (blockToList middleBlock)
+ go acc block = fst $ foldl' collectLabels (acc, (Nothing, Nothing)) (blockToList middleBlock)
where
(_, middleBlock, _) = blockSplit block
+ -- We track the nearest preceding SourceNote from the module being
+ -- compiled and the nearest of any module, and prefer the former (then
+ -- the proc's enclosing current-module note) when attributing a return
+ -- frame. See Note [Prefer current-module source ticks for return frames].
collectLabels
- :: (Map CLabel IpeSourceLocation, Maybe IpeSourceLocation)
+ :: (Map CLabel IpeSourceLocation, (Maybe IpeSourceLocation, Maybe IpeSourceLocation))
-> CmmNode O O
- -> (Map CLabel IpeSourceLocation, Maybe IpeSourceLocation)
- collectLabels (!acc, lastTick) b =
- case (b, lastTick) of
- (CmmStore _ (CmmLit (CmmLabel l)) _, Just src_loc) ->
- (Map.insert l src_loc acc, Nothing)
- (CmmTick (SourceNote span name), _) ->
- (acc, Just (span, name))
- _ -> (acc, lastTick)
-labelsToSourcesSansTNTC acc _ = acc
+ -> (Map CLabel IpeSourceLocation, (Maybe IpeSourceLocation, Maybe IpeSourceLocation))
+ collectLabels (!acc, st@(lastThis, lastAny)) b =
+ case b of
+ CmmStore _ (CmmLit (CmmLabel l)) _ ->
+ case lastThis <|> procFallback <|> lastAny of
+ Just src_loc -> (Map.insert l src_loc acc, (Nothing, Nothing))
+ Nothing -> (acc, st)
+ CmmTick (SourceNote span name) ->
+ let tick = (span, name)
+ lastThis'
+ | Just (srcSpanFile span) == mb_src_file = Just tick
+ | otherwise = lastThis
+ in (acc, (lastThis', Just tick))
+ _ -> (acc, st)
+labelsToSourcesSansTNTC _ acc _ = acc
=====================================
compiler/GHC/Driver/Main/Compile.hs
=====================================
@@ -699,7 +699,7 @@ hscGenHardCode hsc_env cgguts mod_loc output_filename = do
_ ->
do
cmms <- {-# SCC "StgToCmm" #-}
- doCodeGen hsc_env this_mod denv tycons
+ doCodeGen hsc_env this_mod mod_loc denv tycons
cost_centre_info
stg_binds
@@ -956,14 +956,17 @@ This reduces residency towards the end of the CodeGen phase significantly
(5-10%).
-}
-doCodeGen :: HscEnv -> Module -> InfoTableProvMap -> [TyCon]
+doCodeGen :: HscEnv -> Module
+ -> ModLocation -- ^ location of the module being compiled, used to
+ -- prefer current-module IPE source locations
+ -> InfoTableProvMap -> [TyCon]
-> CollectedCCs
-> [CgStgTopBinding] -- ^ Bindings come already annotated with fvs
-> IO (CgStream CmmGroupSRTs CmmCgInfos)
-- Note we produce a 'Stream' of CmmGroups, so that the
-- backend can be run incrementally. Otherwise it generates all
-- the C-- up front, which has a significant space cost.
-doCodeGen hsc_env this_mod denv tycons
+doCodeGen hsc_env this_mod mod_location denv tycons
cost_centre_info stg_binds_w_fvs = do
let dflags = hsc_dflags hsc_env
logger = hsc_logger hsc_env
@@ -1032,7 +1035,7 @@ doCodeGen hsc_env this_mod denv tycons
-- Positions] in GHC.Stg.Debug.
(ipes', stats') <-
if (gopt Opt_InfoTableMap dflags) then
- liftIO $ lookupEstimatedTicks hsc_env ipes stats cmm_srts
+ liftIO $ lookupEstimatedTicks hsc_env mod_location ipes stats cmm_srts
else
return (ipes, stats)
=====================================
libraries/base/src/Control/Exception/Base.hs
=====================================
@@ -85,7 +85,6 @@ module Control.Exception.Base
patError,
noMethodBindingError,
typeError,
- nonTermination,
nestedAtomically,
noMatchingContinuationPrompt
) where
=====================================
libraries/base/src/GHC/IO/Exception.hs
=====================================
@@ -19,8 +19,8 @@
--
module GHC.IO.Exception (
- BlockedIndefinitelyOnMVar(..), blockedIndefinitelyOnMVar,
- BlockedIndefinitelyOnSTM(..), blockedIndefinitelyOnSTM,
+ BlockedIndefinitelyOnMVar(..),
+ BlockedIndefinitelyOnSTM(..),
Deadlock(..),
AllocationLimitExceeded(..), allocationLimitExceeded,
AssertionFailed(..),
=====================================
libraries/ghc-internal/include/RtsIfaceSymbols.h
=====================================
@@ -15,12 +15,12 @@ CLOSURE(GHCziInternalziWeakziFinalizze, runFinalizzerBatch_closure)
CLOSURE(GHCziInternalziIOziException, stackOverflow_closure)
CLOSURE(GHCziInternalziIOziException, heapOverflow_closure)
CLOSURE(GHCziInternalziIOziException, allocationLimitExceeded_closure)
-CLOSURE(GHCziInternalziIOziException, blockedIndefinitelyOnMVar_closure)
-CLOSURE(GHCziInternalziIOziException, blockedIndefinitelyOnSTM_closure)
+CLOSURE(GHCziInternalziIOziException, blockedIndefinitelyOnMVarError_closure)
+CLOSURE(GHCziInternalziIOziException, blockedIndefinitelyOnSTMError_closure)
CLOSURE(GHCziInternalziIOziException, cannotCompactFunction_closure)
CLOSURE(GHCziInternalziIOziException, cannotCompactPinned_closure)
CLOSURE(GHCziInternalziIOziException, cannotCompactMutable_closure)
-CLOSURE(GHCziInternalziControlziExceptionziBase, nonTermination_closure)
+CLOSURE(GHCziInternalziControlziExceptionziBase, nonTerminationError_closure)
CLOSURE(GHCziInternalziControlziExceptionziBase, nestedAtomically_closure)
CLOSURE(GHCziInternalziControlziExceptionziBase, noMatchingContinuationPrompt_closure)
#if defined(mingw32_HOST_OS)
=====================================
libraries/ghc-internal/src/GHC/Internal/Control/Exception/Base.hs
=====================================
@@ -108,7 +108,7 @@ module GHC.Internal.Control.Exception.Base (
impossibleError, impossibleConstraintError,
nonExhaustiveGuardsError, patError, noMethodBindingError,
typeError,
- nonTermination, nestedAtomically, noMatchingContinuationPrompt,
+ nonTerminationError, nestedAtomically, noMatchingContinuationPrompt,
) where
import GHC.Internal.Base (
@@ -448,8 +448,9 @@ impossibleConstraintError s = errorWithoutStackTrace (unpackCStringUtf8# s)
-- GHC's RTS calls this
-nonTermination :: SomeException
-nonTermination = toException NonTermination
+nonTerminationError :: IO ()
+nonTerminationError = throwIO NonTermination
+
-- GHC's RTS calls this
nestedAtomically :: SomeException
=====================================
libraries/ghc-internal/src/GHC/Internal/IO/Exception.hs
=====================================
@@ -24,8 +24,8 @@
-----------------------------------------------------------------------------
module GHC.Internal.IO.Exception (
- BlockedIndefinitelyOnMVar(..), blockedIndefinitelyOnMVar,
- BlockedIndefinitelyOnSTM(..), blockedIndefinitelyOnSTM,
+ BlockedIndefinitelyOnMVar(..), blockedIndefinitelyOnMVarError,
+ BlockedIndefinitelyOnSTM(..), blockedIndefinitelyOnSTMError,
Deadlock(..),
AllocationLimitExceeded(..), allocationLimitExceeded,
AssertionFailed(..),
@@ -84,8 +84,8 @@ instance Exception BlockedIndefinitelyOnMVar
instance Show BlockedIndefinitelyOnMVar where
showsPrec _ BlockedIndefinitelyOnMVar = showString "thread blocked indefinitely in an MVar operation"
-blockedIndefinitelyOnMVar :: SomeException -- for the RTS
-blockedIndefinitelyOnMVar = toException BlockedIndefinitelyOnMVar
+blockedIndefinitelyOnMVarError :: IO () -- for the RTS
+blockedIndefinitelyOnMVarError = throwIO BlockedIndefinitelyOnMVar
-----
@@ -100,8 +100,8 @@ instance Exception BlockedIndefinitelyOnSTM
instance Show BlockedIndefinitelyOnSTM where
showsPrec _ BlockedIndefinitelyOnSTM = showString "thread blocked indefinitely in an STM transaction"
-blockedIndefinitelyOnSTM :: SomeException -- for the RTS
-blockedIndefinitelyOnSTM = toException BlockedIndefinitelyOnSTM
+blockedIndefinitelyOnSTMError :: IO () -- for the RTS
+blockedIndefinitelyOnSTMError = throwIO BlockedIndefinitelyOnSTM
-----
=====================================
rts/Prelude.h
=====================================
@@ -53,12 +53,12 @@ extern StgClosure ZCMain_main_closure;
#define stackOverflow_closure ghc_hs_iface->stackOverflow_closure
#define heapOverflow_closure ghc_hs_iface->heapOverflow_closure
#define allocationLimitExceeded_closure ghc_hs_iface->allocationLimitExceeded_closure
-#define blockedIndefinitelyOnMVar_closure ghc_hs_iface->blockedIndefinitelyOnMVar_closure
-#define blockedIndefinitelyOnSTM_closure ghc_hs_iface->blockedIndefinitelyOnSTM_closure
+#define blockedIndefinitelyOnMVarError_closure ghc_hs_iface->blockedIndefinitelyOnMVarError_closure
+#define blockedIndefinitelyOnSTMError_closure ghc_hs_iface->blockedIndefinitelyOnSTMError_closure
#define cannotCompactFunction_closure ghc_hs_iface->cannotCompactFunction_closure
#define cannotCompactPinned_closure ghc_hs_iface->cannotCompactPinned_closure
#define cannotCompactMutable_closure ghc_hs_iface->cannotCompactMutable_closure
-#define nonTermination_closure ghc_hs_iface->nonTermination_closure
+#define nonTerminationError_closure ghc_hs_iface->nonTerminationError_closure
#define nestedAtomically_closure ghc_hs_iface->nestedAtomically_closure
#define absentSumFieldError_closure ghc_hs_iface->absentSumFieldError_closure
#define underflowException_closure ghc_hs_iface->underflowException_closure
=====================================
rts/RaiseAsync.c
=====================================
@@ -87,6 +87,55 @@ suspendComputation (Capability *cap, StgTSO *tso, StgUpdateFrame *stop_here)
throwToSingleThreaded__ (cap, tso, NULL, false, stop_here);
}
+/* -----------------------------------------------------------------------------
+ scheduleRaiseViaIO
+
+ Schedule `tso` to raise an exception by running `io_action`, an IO () that
+ performs `throwIO`. Unlike throwToSingleThreaded (which injects an exception
+ *value* via raiseAsync), the exception is raised by throwIO *within* the
+ thread, so it acquires a backtrace of the thread's stack. This is used by
+ resurrectThreads to deliver the "blocked indefinitely" exceptions
+ (BlockedIndefinitelyOnMVar, BlockedIndefinitelyOnSTM, NonTermination).
+
+ We push a "run this IO action" frame on top of the thread's existing
+ (suspended) stack and make it runnable; when the thread runs, throwIO raises
+ the exception and its own stack unwinding handles any CATCH_FRAME /
+ ATOMICALLY_FRAME (e.g. aborting a blocked STM transaction).
+
+ removeFromQueues takes care of unlinking the thread from any blocking queue
+ (notably the MVar blocked queue) and appends it to the run queue. As with
+ throwToSingleThreaded, the caller must own the TSO (e.g. hold all
+ capabilities during GC); in particular this relies on the thread not being
+ scheduled between removeFromQueues' enqueue and our stack push.
+ -------------------------------------------------------------------------- */
+
+void
+scheduleRaiseViaIO (Capability *cap, StgTSO *tso, StgClosure *io_action)
+{
+ // Thread already dead?
+ if (tso->what_next == ThreadComplete || tso->what_next == ThreadKilled) {
+ return;
+ }
+
+ // Unlink from any blocking queues; sets why_blocked = NotBlocked and
+ // appends the thread to the run queue.
+ removeFromQueues(cap, tso);
+
+ StgStack *stack = tso->stackobj;
+
+ // We are about to mutate the stack, so dirty it for the GC write barrier
+ // (resurrectThreads runs right after GC).
+ dirty_TSO(cap, tso);
+ dirty_STACK(cap, stack);
+
+ // Push a frame that enters `io_action` and applies the resulting IO action
+ // to the void (RealWorld) argument: [stg_enter_info, io_action, stg_ap_v_info]
+ stack->sp -= 3;
+ stack->sp[0] = (W_)&stg_enter_info;
+ stack->sp[1] = (W_)io_action;
+ stack->sp[2] = (W_)&stg_ap_v_info;
+}
+
/* -----------------------------------------------------------------------------
throwToSelf
=====================================
rts/RaiseAsync.h
=====================================
@@ -38,6 +38,10 @@ void suspendComputation (Capability *cap,
StgTSO *tso,
StgUpdateFrame *stop_here);
+void scheduleRaiseViaIO (Capability *cap,
+ StgTSO *tso,
+ StgClosure *io_action);
+
MessageThrowTo *throwTo (Capability *cap, // the Capability we hold
StgTSO *source,
StgTSO *target,
=====================================
rts/RtsStartup.c
=====================================
@@ -192,9 +192,9 @@ static void initBuiltinGcRoots(void)
getStablePtr((StgPtr)stackOverflow_closure);
getStablePtr((StgPtr)heapOverflow_closure);
getStablePtr((StgPtr)unpackCString_closure);
- getStablePtr((StgPtr)blockedIndefinitelyOnMVar_closure);
- getStablePtr((StgPtr)nonTermination_closure);
- getStablePtr((StgPtr)blockedIndefinitelyOnSTM_closure);
+ getStablePtr((StgPtr)blockedIndefinitelyOnMVarError_closure);
+ getStablePtr((StgPtr)nonTerminationError_closure);
+ getStablePtr((StgPtr)blockedIndefinitelyOnSTMError_closure);
getStablePtr((StgPtr)allocationLimitExceeded_closure);
getStablePtr((StgPtr)cannotCompactFunction_closure);
getStablePtr((StgPtr)cannotCompactPinned_closure);
=====================================
rts/Schedule.c
=====================================
@@ -3309,16 +3309,16 @@ resurrectThreads (StgTSO *threads)
case BlockedOnMVar:
case BlockedOnMVarRead:
/* Called by GC - sched_mutex lock is currently held. */
- throwToSingleThreaded(cap, tso,
- (StgClosure *)blockedIndefinitelyOnMVar_closure);
+ scheduleRaiseViaIO(cap, tso,
+ (StgClosure *)blockedIndefinitelyOnMVarError_closure);
break;
case BlockedOnBlackHole:
- throwToSingleThreaded(cap, tso,
- (StgClosure *)nonTermination_closure);
+ scheduleRaiseViaIO(cap, tso,
+ (StgClosure *)nonTerminationError_closure);
break;
case BlockedOnSTM:
- throwToSingleThreaded(cap, tso,
- (StgClosure *)blockedIndefinitelyOnSTM_closure);
+ scheduleRaiseViaIO(cap, tso,
+ (StgClosure *)blockedIndefinitelyOnSTMError_closure);
break;
case NotBlocked:
/* This might happen if the thread was blocked on a black hole
=====================================
rts/include/rts/RtsToHsIface.h
=====================================
@@ -20,12 +20,12 @@ typedef struct {
StgClosure *stackOverflow_closure; // GHC.Internal.IO.Exception.stackOverflow_closure
StgClosure *heapOverflow_closure; // GHC.Internal.IO.Exception.heapOverflow_closure
StgClosure *allocationLimitExceeded_closure; // GHC.Internal.IO.Exception.allocationLimitExceeded_closure
- StgClosure *blockedIndefinitelyOnMVar_closure; // GHC.Internal.IO.Exception.blockedIndefinitelyOnMVar_closure
- StgClosure *blockedIndefinitelyOnSTM_closure; // GHC.Internal.IO.Exception.blockedIndefinitelyOnSTM_closure
+ StgClosure *blockedIndefinitelyOnMVarError_closure; // GHC.Internal.IO.Exception.blockedIndefinitelyOnMVarError_closure
+ StgClosure *blockedIndefinitelyOnSTMError_closure; // GHC.Internal.IO.Exception.blockedIndefinitelyOnSTMError_closure
StgClosure *cannotCompactFunction_closure; // GHC.Internal.IO.Exception.cannotCompactFunction_closure
StgClosure *cannotCompactPinned_closure; // GHC.Internal.IO.Exception.cannotCompactPinned_closure
StgClosure *cannotCompactMutable_closure; // GHC.Internal.IO.Exception.cannotCompactMutable_closure
- StgClosure *nonTermination_closure; // GHC.Internal.Control.Exception.Base.nonTermination_closure
+ StgClosure *nonTerminationError_closure; // GHC.Internal.Control.Exception.Base.nonTerminationError_closure
StgClosure *nestedAtomically_closure; // GHC.Internal.Control.Exception.Base.nestedAtomically_closure
StgClosure *noMatchingContinuationPrompt_closure; // GHC.Internal.Control.Exception.Base.noMatchingContinuationPrompt_closure
StgClosure *blockedOnBadFD_closure; // GHC.Internal.Event.Thread.blockedOnBadFD_closure
=====================================
testsuite/tests/rts/LoopBacktrace.hs
=====================================
@@ -0,0 +1,17 @@
+{-# OPTIONS_GHC -finfo-table-map -forig-thunk-info #-}
+
+import GHC.Exception.Backtrace.Experimental
+
+x :: Integer
+x = x + 1
+
+testing :: IO ()
+testing = do
+ putStrLn "hello"
+ print x
+ putStrLn "world"
+
+main :: IO ()
+main = do
+ setBacktraceMechanismState IPEBacktrace True
+ testing
=====================================
testsuite/tests/rts/LoopBacktrace.stderr
=====================================
@@ -0,0 +1,21 @@
+LoopBacktrace: Uncaught exception ghc-internal:GHC.Internal.Control.Exception.Base.NonTermination:
+
+<<loop>>
+
+IPE backtrace:
+ GHC.Internal.Exception.Backtrace.collectBacktraces' (libraries/ghc-internal/src/GHC/Internal/Exception/Backtrace.hs:(179,1)-(202,25))
+ GHC.Internal.Exception.Backtrace.collectBacktraces (libraries/ghc-internal/src/GHC/Internal/Exception/Backtrace.hs:174:39-56)
+ GHC.Internal.Exception.toExceptionWithBacktrace (libraries/ghc-internal/src/GHC/Internal/Exception.hs:(179,26)-(181,53))
+ GHC.Internal.IO.throwIO (libraries/ghc-internal/src/GHC/Internal/IO.hs:293:36)
+ Cmm$rts/HeapStackCheck.cmm. (:)
+ GHC.Internal.Bignum.Integer.integerAdd (libraries/ghc-internal/src/GHC/Internal/Bignum/Integer.hs:(547,1)-(571,52))
+ Cmm$rts/Updates.cmm. (:)
+ Main.x (LoopBacktrace.hs:6:1-9)
+ GHC.Internal.Show.show (libraries/ghc-internal/src/GHC/Internal/Show.hs:497:10-21)
+ GHC.Internal.IO.Handle.Text.hPutStr' (libraries/ghc-internal/src/GHC/Internal/IO/Handle/Text.hs:667:29-37)
+ GHC.Internal.Base.thenIO (libraries/ghc-internal/src/GHC/Internal/Base.hs:2337:1-72)
+ Cmm$rts/Exception.cmm. (:)
+ Cmm$rts/StgStartup.cmm. (:)
+HasCallStack backtrace:
+ throwIO, called at libraries/ghc-internal/src/GHC/Internal/Control/Exception/Base.hs:452:23 in ghc-internal:GHC.Internal.Control.Exception.Base
+
=====================================
testsuite/tests/rts/LoopBacktrace.stdout
=====================================
@@ -0,0 +1 @@
+hello
=====================================
testsuite/tests/rts/MVarDeadlockBacktrace.hs
=====================================
@@ -0,0 +1,13 @@
+{-# OPTIONS_GHC -finfo-table-map #-}
+
+-- | Check that a @BlockedIndefinitelyOnMVar@ deadlock exception carries a
+-- backtrace mentioning the blocking site in this module.
+import Control.Concurrent.MVar
+import GHC.Exception.Backtrace.Experimental
+
+main :: IO ()
+main = do
+ setBacktraceMechanismState IPEBacktrace True
+ mv <- newEmptyMVar :: IO (MVar ())
+ x <- takeMVar mv
+ print x
=====================================
testsuite/tests/rts/MVarDeadlockBacktrace.stderr
=====================================
@@ -0,0 +1,2 @@
+MVarDeadlockBacktrace: Uncaught exception ghc-internal:GHC.Internal.IO.Exception.BlockedIndefinitelyOnMVar:
+ Main.main (MVarDeadlockBacktrace.hs:16:3-36)
=====================================
testsuite/tests/rts/STMDeadlockBacktrace.hs
=====================================
@@ -0,0 +1,12 @@
+{-# OPTIONS_GHC -finfo-table-map #-}
+
+-- | Check that a @BlockedIndefinitelyOnSTM@ deadlock exception carries a
+-- backtrace mentioning the blocking site in this module.
+import GHC.Conc (atomically, retry)
+import GHC.Exception.Backtrace.Experimental
+
+main :: IO ()
+main = do
+ setBacktraceMechanismState IPEBacktrace True
+ x <- atomically retry :: IO ()
+ print x
=====================================
testsuite/tests/rts/STMDeadlockBacktrace.stderr
=====================================
@@ -0,0 +1,2 @@
+STMDeadlockBacktrace: Uncaught exception ghc-internal:GHC.Internal.IO.Exception.BlockedIndefinitelyOnSTM:
+ Main.main (STMDeadlockBacktrace.hs:16:3-32)
=====================================
testsuite/tests/rts/all.T
=====================================
@@ -687,3 +687,11 @@ test('ClosureTable',
['-debug -O0 ClosureTable_c.c -I{top}/../rts -I{top}/../rts/include'])
test('resizeMutableByteArrayInPlace', [req_cmm, extra_ways(['optasm', 'sanity']), only_ways(['optasm', 'sanity'])], compile_and_run, [''])
+
+test('LoopBacktrace', [exit_code(1)], compile_and_run, [''])
+
+deadlock_backtrace_norm = grep_errmsg(r'(Uncaught exception|Main\.)')
+test('MVarDeadlockBacktrace', [exit_code(1), only_ways(['normal']), deadlock_backtrace_norm],
+ compile_and_run, ['-O'])
+test('STMDeadlockBacktrace', [exit_code(1), only_ways(['normal']), deadlock_backtrace_norm],
+ compile_and_run, ['-O'])
=====================================
testsuite/tests/rts/ipe/T27408.hs
=====================================
@@ -0,0 +1,18 @@
+module Main where
+
+import GHC.Stack.CloneStack (StackEntry(..), cloneMyStack, decode)
+
+userFunction :: IO [StackEntry]
+userFunction = do
+ putStr ""
+ stk <- cloneMyStack
+ putStr ""
+ es <- decode stk
+ putStr ""
+ return es
+
+main :: IO ()
+main = do
+ entries <- userFunction
+ let ours = filter ((== "Main") . moduleName) entries
+ mapM_ (\e -> putStrLn (moduleName e ++ "\t" ++ functionName e)) ours
=====================================
testsuite/tests/rts/ipe/T27408.stdout
=====================================
@@ -0,0 +1,3 @@
+Main
+Main main
+Main userFunction
=====================================
testsuite/tests/rts/ipe/all.T
=====================================
@@ -4,6 +4,16 @@ def noCapabilityOutputFilter(s):
test('ipeMap', [extra_files(['ipe_lib.c', 'ipe_lib.h']), c_src, omit_ghci], compile_and_run, ['ipe_lib.c'])
+# Return frames whose Cmm block is dominated by inlined library ticks (e.g. the
+# (>>)/(>>=) of a user do-block at -O) should still be attributed to the user's
+# module. See Note [Prefer current-module source ticks for return frames] in
+# GHC.Driver.GenerateCgIPEStub.
+test('T27408',
+ [ omit_ghci # cloneMyStack# is not available in ghci
+ , js_broken(22261) # cloneMyStack# not yet implemented in the JS backend
+ ],
+ compile_and_run, ['-O1 -finfo-table-map -g3'])
+
# Manually create IPE entries and dump them to event log (stderr).
test('ipeEventLog',
[ c_src,
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/cba8d183b41c980df79898d76b2ad3…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/cba8d183b41c980df79898d76b2ad3…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc][wip/loop-backtrace] 3 commits: compiler/ipe: Prefer source ticks in current module for return frames
by Ben Gamari (@bgamari) 21 Jun '26
by Ben Gamari (@bgamari) 21 Jun '26
21 Jun '26
Ben Gamari pushed to branch wip/loop-backtrace at Glasgow Haskell Compiler / GHC
Commits:
8fbf9c06 by Ben Gamari at 2026-06-21T08:23:11-04:00
compiler/ipe: Prefer source ticks in current module for return frames
Previously we would simply attribute return frames to the nearest
enclosing tick in the calling frame. However, this will very frequently
produce unhelpful results (e.g. pointing to `(>>)` rather than the
calling function).
- - - - -
abd9dc0e by Ben Gamari at 2026-06-21T11:20:56-04:00
testsuite: Add LoopBacktrace test
- - - - -
4010d192 by Ben Gamari at 2026-06-21T11:20:56-04:00
Throw nontermination exceptions via `throw`
This ensures that the exception that results gets the usual backtrace
annotations.
- - - - -
16 changed files:
- compiler/GHC/Driver/GenerateCgIPEStub.hs
- compiler/GHC/Driver/Main/Compile.hs
- libraries/base/src/Control/Exception/Base.hs
- libraries/ghc-internal/include/RtsIfaceSymbols.h
- libraries/ghc-internal/src/GHC/Internal/Control/Exception/Base.hs
- rts/Prelude.h
- rts/RtsStartup.c
- rts/Schedule.c
- rts/include/rts/RtsToHsIface.h
- + testsuite/tests/rts/LoopBacktrace.hs
- + testsuite/tests/rts/LoopBacktrace.stderr
- + testsuite/tests/rts/LoopBacktrace.stdout
- testsuite/tests/rts/all.T
- + testsuite/tests/rts/ipe/T27408.hs
- + testsuite/tests/rts/ipe/T27408.stdout
- testsuite/tests/rts/ipe/all.T
Changes:
=====================================
compiler/GHC/Driver/GenerateCgIPEStub.hs
=====================================
@@ -1,7 +1,9 @@
module GHC.Driver.GenerateCgIPEStub (generateCgIPEStub, lookupEstimatedTicks) where
+import Control.Applicative ((<|>))
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
+import Data.Maybe (listToMaybe)
import Data.Semigroup ((<>))
import GHC.Cmm
import GHC.Cmm.CLabel (CLabel, mkAsmTempLabel)
@@ -10,6 +12,7 @@ import GHC.Cmm.Dataflow.Block (blockSplit, blockToList)
import GHC.Cmm.Dataflow.Label
import GHC.Cmm.Info.Build (emptySRT)
import GHC.Cmm.Pipeline (cmmPipeline)
+import GHC.Data.FastString (FastString, mkFastString)
import GHC.Data.Stream (liftIO, liftEff)
import qualified GHC.Data.Stream as Stream
import GHC.Driver.Env (hsc_dflags, hsc_logger)
@@ -28,9 +31,10 @@ import GHC.StgToCmm.Utils
import GHC.StgToCmm.CgUtils (CgStream)
import GHC.Types.IPE (InfoTableProvMap (provInfoTables), IpeSourceLocation)
import GHC.Types.Name.Set (NonCaffySet)
+import GHC.Types.SrcLoc (srcSpanFile)
import GHC.Types.Tickish (GenTickish (SourceNote))
import GHC.Unit.Types (Module, moduleName)
-import GHC.Unit.Module (moduleNameString)
+import GHC.Unit.Module (moduleNameString, ModLocation, ml_hs_file)
import qualified GHC.Utils.Logger as Logger
import GHC.Utils.Outputable (ppr)
import GHC.Types.Unique.DSM
@@ -257,11 +261,12 @@ generateCgIPEStub hsc_env this_mod denv (nonCaffySet, moduleLFInfos, infoTablesW
-- performance suffered considerably as a result (see #23103).
lookupEstimatedTicks
:: HscEnv
+ -> ModLocation -- ^ location of the module being compiled, for IPE provenance
-> Map CmmInfoTable (Maybe IpeSourceLocation)
-> IPEStats
-> CmmGroupSRTs
-> IO (Map CmmInfoTable (Maybe IpeSourceLocation), IPEStats)
-lookupEstimatedTicks hsc_env ipes stats cmm_group_srts =
+lookupEstimatedTicks hsc_env mod_location ipes stats cmm_group_srts =
-- Pass 2: Create an entry in the IPE map for every info table listed in
-- this CmmGroupSRTs. If the info table is a stack info table and
-- -finfo-table-map-with-stack is enabled, look up its estimated source
@@ -276,19 +281,24 @@ lookupEstimatedTicks hsc_env ipes stats cmm_group_srts =
dflags = hsc_dflags hsc_env
platform = targetPlatform dflags
- -- Pass 1: Map every label meeting the conditions described in Note
- -- [Stacktraces from Info Table Provenance Entries (IPE based stack
- -- unwinding)] to the estimated source location (also as described in the
- -- aformentioned note)
+ -- Source file of the module being compiled, used to prefer current-module
+ -- source ticks for return frames. See Note [Prefer current-module source
+ -- ticks for return frames].
+ mb_src_file = mkFastString <$> ml_hs_file mod_location
+
+ -- Pass 1: Map every label meeting the conditions described in
+ -- Note [Stacktraces from Info Table Provenance Entries (IPE based stack unwinding)]
+ -- to the estimated source location (also as described in the aformentioned
+ -- note).
--
-- Note: It's important that this remains a thunk so we do not compute this
-- map if -fno-info-table-with-stack is given
labelsToSources :: Map CLabel IpeSourceLocation
labelsToSources =
if platformTablesNextToCode platform then
- foldl' labelsToSourcesWithTNTC Map.empty cmm_group_srts
+ foldl' (labelsToSourcesWithTNTC mb_src_file) Map.empty cmm_group_srts
else
- foldl' labelsToSourcesSansTNTC Map.empty cmm_group_srts
+ foldl' (labelsToSourcesSansTNTC mb_src_file) Map.empty cmm_group_srts
collectInfoTables
:: (Map CmmInfoTable (Maybe IpeSourceLocation), IPEStats)
@@ -331,15 +341,16 @@ lookupEstimatedTicks hsc_env ipes stats cmm_group_srts =
-- | See Note [Stacktraces from Info Table Provenance Entries (IPE based stack unwinding)]
labelsToSourcesWithTNTC
- :: Map CLabel IpeSourceLocation
+ :: Maybe FastString -- ^ source file of the module being compiled
+ -> Map CLabel IpeSourceLocation
-> GenCmmDecl RawCmmStatics CmmTopInfo CmmGraph
-> Map CLabel IpeSourceLocation
-labelsToSourcesWithTNTC acc (CmmProc _ _ _ cmm_graph) =
+labelsToSourcesWithTNTC mb_src_file acc (CmmProc _ _ _ cmm_graph) =
foldl' go acc (toBlockList cmm_graph)
where
go :: Map CLabel IpeSourceLocation -> CmmBlock -> Map CLabel IpeSourceLocation
go acc block =
- case (,) <$> returnFrameLabel <*> lastTickInBlock of
+ case (,) <$> returnFrameLabel <*> bestTickInBlock of
Just (clabel, src_loc) -> Map.insert clabel src_loc acc
Nothing -> acc
where
@@ -351,36 +362,135 @@ labelsToSourcesWithTNTC acc (CmmProc _ _ _ cmm_graph) =
(CmmCall _ (Just l) _ _ _ _) -> Just $ mkAsmTempLabel l
_ -> Nothing
- lastTickInBlock = foldr maybeTick Nothing (blockToList middleBlock)
+ -- All SourceNotes in the block, in block order.
+ -- See Note [Prefer current-module source ticks for return frames].
+ bestTickInBlock = preferThisFile mb_src_file procFallback (blockToList middleBlock)
+
+ -- Enclosing current-module note for the whole proc (its function's own
+ -- span), used when a return frame's own block has no current-module tick.
+ procFallback = enclosingThisFileTick mb_src_file (toBlockList cmm_graph)
+labelsToSourcesWithTNTC _ acc _ = acc
+
+{-
+Note [Prefer current-module source ticks for return frames]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+A return frame's source location is taken from the `SourceNote`s of the block
+that *ends* in the frame's call (see
+Note [Stacktraces from Info Table Provenance Entries (IPE based stack unwinding]
+above and `labelsToSourcesWithTNTC`). At `-O`, inlining means such a
+block frequently carries `SourceNote`s for inlined library glue (`>>`, `>>=`,
+`threadDelay`, ...) and the *nearest* note — the one historically chosen — is
+often a library note rather than the user's code. The resulting IPE entry then
+points at the library (its file and label; see `toCgIPE` in
+GHC.StgToCmm.InfoTableProv, which takes the file from the note's own span), so a
+backtrace of a thread blocked in such a primop shows no user frames.
+
+For a concrete example, compile (at -O1)
+
+ -- Scan.hs
+ f3 :: IO ()
+ f3 = threadDelay 1000000 >> putStrLn "done"
+
+The body of `f3` reaches the inlined `threadDelay`'s internal `delay#` in a
+block whose notes are *all* from `Conc.IO`/`Base` — the user's `Scan.hs` tick
+for `f3` sits only in the proc's entry block:
+
+ entry: -- the proc's entry block
+ //tick src<.../Base.hs:2306:5-18>
+ //tick src<Scan.hs:13:1-43> -- the enclosing `f3` span
+ //tick src<.../Conc/IO.hs:(223,1)-(235,10)>
+ ...
+ delayBlk: -- no Scan.hs note here:
+ //tick src<.../Conc/IO.hs:232:5-13>
+ //tick src<.../Base.hs:2268:1-9>
+ //tick src<.../Conc/IO.hs:(232,25)-(235,10)> -- nearest note
+ call stg_delay#(R1) returns to delayCont, args: 8, res: 8, upd: 8;
+
+Naively taking the nearest note attributes `delayCont` to `Conc/IO.hs:232`,
+i.e. an internal of `threadDelay`, rather than `f3`.
+
+To fix this we attribute a return frame's source location in the following
+preference order:
+
+ 1. the nearest tick in the frame's block whose file is that of the module
+ being compiled - the precise user call site. (When the user makes a
+ blocking call directly, e.g. `f v = takeMVar v`, such a note is present in
+ the call's block and this rule suffices; the `delayBlk` above has none.)
+ 2. failing that, the proc's *enclosing* current-module note (the outermost
+ current-module `SourceNote` in the proc, i.e. its function's own span).
+ For `f3` this is `src<Scan.hs:13:1-43>`, so `delayCont` is attributed to
+ `f3` rather than to `threadDelay`'s internals.
+ 3. failing that, the nearest note of any module (the historical behaviour).
+
+This mirrors the same-file preference the DWARF path uses in
+`GHC.Cmm.DebugBlock.bestSrcTick` and that `GHC.Stg.Debug.quickSourcePos` uses
+for closures.
+-}
- maybeTick :: CmmNode O O -> Maybe IpeSourceLocation -> Maybe IpeSourceLocation
- maybeTick _ s@(Just _) = s
- maybeTick (CmmTick (SourceNote span name)) Nothing = Just (span, name)
- maybeTick _ _ = Nothing
-labelsToSourcesWithTNTC acc _ = acc
+-- | Pick the 'IpeSourceLocation' to attribute to a return frame from the
+-- source-note-bearing nodes of its block (in block order).
+--
+-- See Note [Prefer current-module source ticks for return frames].
+preferThisFile :: Maybe FastString -> Maybe IpeSourceLocation -> [CmmNode O O] -> Maybe IpeSourceLocation
+preferThisFile mb_src_file procFallback nodes =
+ nearest fromThisFile <|> procFallback <|> nearest sourceNotes
+ where
+ sourceNotes = [ (span, name) | CmmTick (SourceNote span name) <- nodes ]
+ fromThisFile = case mb_src_file of
+ Just f -> filter ((== f) . srcSpanFile . fst) sourceNotes
+ Nothing -> []
+ nearest = listToMaybe . reverse
+
+-- | The outermost 'SourceNote' from the module being compiled across a proc's
+-- blocks (in 'toBlockList' order, so the entry block's note — the function's own
+-- span — comes first). Used as a fallback so inlined cross-module code is still
+-- labelled with the enclosing user function. 'Nothing' when the proc has no
+-- current-module note (e.g. when compiling the library itself).
+enclosingThisFileTick :: Maybe FastString -> [CmmBlock] -> Maybe IpeSourceLocation
+enclosingThisFileTick mb_src_file blocks =
+ listToMaybe
+ [ (span, name)
+ | b <- blocks
+ , let (_, mid, _) = blockSplit b
+ , CmmTick (SourceNote span name) <- blockToList mid
+ , Just (srcSpanFile span) == mb_src_file ]
-- | See Note [Stacktraces from Info Table Provenance Entries (IPE based stack unwinding)]
labelsToSourcesSansTNTC
- :: Map CLabel IpeSourceLocation
+ :: Maybe FastString -- ^ source file of the module being compiled
+ -> Map CLabel IpeSourceLocation
-> GenCmmDecl RawCmmStatics CmmTopInfo CmmGraph
-> Map CLabel IpeSourceLocation
-labelsToSourcesSansTNTC acc (CmmProc _ _ _ cmm_graph) =
+labelsToSourcesSansTNTC mb_src_file acc (CmmProc _ _ _ cmm_graph) =
foldl' go acc (toBlockList cmm_graph)
where
+ -- See 'enclosingThisFileTick'.
+ procFallback = enclosingThisFileTick mb_src_file (toBlockList cmm_graph)
+
go :: Map CLabel IpeSourceLocation -> CmmBlock -> Map CLabel IpeSourceLocation
- go acc block = fst $ foldl' collectLabels (acc, Nothing) (blockToList middleBlock)
+ go acc block = fst $ foldl' collectLabels (acc, (Nothing, Nothing)) (blockToList middleBlock)
where
(_, middleBlock, _) = blockSplit block
+ -- We track the nearest preceding SourceNote from the module being
+ -- compiled and the nearest of any module, and prefer the former (then
+ -- the proc's enclosing current-module note) when attributing a return
+ -- frame. See Note [Prefer current-module source ticks for return frames].
collectLabels
- :: (Map CLabel IpeSourceLocation, Maybe IpeSourceLocation)
+ :: (Map CLabel IpeSourceLocation, (Maybe IpeSourceLocation, Maybe IpeSourceLocation))
-> CmmNode O O
- -> (Map CLabel IpeSourceLocation, Maybe IpeSourceLocation)
- collectLabels (!acc, lastTick) b =
- case (b, lastTick) of
- (CmmStore _ (CmmLit (CmmLabel l)) _, Just src_loc) ->
- (Map.insert l src_loc acc, Nothing)
- (CmmTick (SourceNote span name), _) ->
- (acc, Just (span, name))
- _ -> (acc, lastTick)
-labelsToSourcesSansTNTC acc _ = acc
+ -> (Map CLabel IpeSourceLocation, (Maybe IpeSourceLocation, Maybe IpeSourceLocation))
+ collectLabels (!acc, st@(lastThis, lastAny)) b =
+ case b of
+ CmmStore _ (CmmLit (CmmLabel l)) _ ->
+ case lastThis <|> procFallback <|> lastAny of
+ Just src_loc -> (Map.insert l src_loc acc, (Nothing, Nothing))
+ Nothing -> (acc, st)
+ CmmTick (SourceNote span name) ->
+ let tick = (span, name)
+ lastThis'
+ | Just (srcSpanFile span) == mb_src_file = Just tick
+ | otherwise = lastThis
+ in (acc, (lastThis', Just tick))
+ _ -> (acc, st)
+labelsToSourcesSansTNTC _ acc _ = acc
=====================================
compiler/GHC/Driver/Main/Compile.hs
=====================================
@@ -699,7 +699,7 @@ hscGenHardCode hsc_env cgguts mod_loc output_filename = do
_ ->
do
cmms <- {-# SCC "StgToCmm" #-}
- doCodeGen hsc_env this_mod denv tycons
+ doCodeGen hsc_env this_mod mod_loc denv tycons
cost_centre_info
stg_binds
@@ -956,14 +956,17 @@ This reduces residency towards the end of the CodeGen phase significantly
(5-10%).
-}
-doCodeGen :: HscEnv -> Module -> InfoTableProvMap -> [TyCon]
+doCodeGen :: HscEnv -> Module
+ -> ModLocation -- ^ location of the module being compiled, used to
+ -- prefer current-module IPE source locations
+ -> InfoTableProvMap -> [TyCon]
-> CollectedCCs
-> [CgStgTopBinding] -- ^ Bindings come already annotated with fvs
-> IO (CgStream CmmGroupSRTs CmmCgInfos)
-- Note we produce a 'Stream' of CmmGroups, so that the
-- backend can be run incrementally. Otherwise it generates all
-- the C-- up front, which has a significant space cost.
-doCodeGen hsc_env this_mod denv tycons
+doCodeGen hsc_env this_mod mod_location denv tycons
cost_centre_info stg_binds_w_fvs = do
let dflags = hsc_dflags hsc_env
logger = hsc_logger hsc_env
@@ -1032,7 +1035,7 @@ doCodeGen hsc_env this_mod denv tycons
-- Positions] in GHC.Stg.Debug.
(ipes', stats') <-
if (gopt Opt_InfoTableMap dflags) then
- liftIO $ lookupEstimatedTicks hsc_env ipes stats cmm_srts
+ liftIO $ lookupEstimatedTicks hsc_env mod_location ipes stats cmm_srts
else
return (ipes, stats)
=====================================
libraries/base/src/Control/Exception/Base.hs
=====================================
@@ -85,7 +85,6 @@ module Control.Exception.Base
patError,
noMethodBindingError,
typeError,
- nonTermination,
nestedAtomically,
noMatchingContinuationPrompt
) where
=====================================
libraries/ghc-internal/include/RtsIfaceSymbols.h
=====================================
@@ -20,7 +20,7 @@ CLOSURE(GHCziInternalziIOziException, blockedIndefinitelyOnSTM_closure)
CLOSURE(GHCziInternalziIOziException, cannotCompactFunction_closure)
CLOSURE(GHCziInternalziIOziException, cannotCompactPinned_closure)
CLOSURE(GHCziInternalziIOziException, cannotCompactMutable_closure)
-CLOSURE(GHCziInternalziControlziExceptionziBase, nonTermination_closure)
+CLOSURE(GHCziInternalziControlziExceptionziBase, nonTerminationError_closure)
CLOSURE(GHCziInternalziControlziExceptionziBase, nestedAtomically_closure)
CLOSURE(GHCziInternalziControlziExceptionziBase, noMatchingContinuationPrompt_closure)
#if defined(mingw32_HOST_OS)
=====================================
libraries/ghc-internal/src/GHC/Internal/Control/Exception/Base.hs
=====================================
@@ -108,7 +108,7 @@ module GHC.Internal.Control.Exception.Base (
impossibleError, impossibleConstraintError,
nonExhaustiveGuardsError, patError, noMethodBindingError,
typeError,
- nonTermination, nestedAtomically, noMatchingContinuationPrompt,
+ nonTerminationError, nestedAtomically, noMatchingContinuationPrompt,
) where
import GHC.Internal.Base (
@@ -448,8 +448,9 @@ impossibleConstraintError s = errorWithoutStackTrace (unpackCStringUtf8# s)
-- GHC's RTS calls this
-nonTermination :: SomeException
-nonTermination = toException NonTermination
+nonTerminationError :: IO ()
+nonTerminationError = throwIO NonTermination
+
-- GHC's RTS calls this
nestedAtomically :: SomeException
=====================================
rts/Prelude.h
=====================================
@@ -58,7 +58,7 @@ extern StgClosure ZCMain_main_closure;
#define cannotCompactFunction_closure ghc_hs_iface->cannotCompactFunction_closure
#define cannotCompactPinned_closure ghc_hs_iface->cannotCompactPinned_closure
#define cannotCompactMutable_closure ghc_hs_iface->cannotCompactMutable_closure
-#define nonTermination_closure ghc_hs_iface->nonTermination_closure
+#define nonTerminationError_closure ghc_hs_iface->nonTerminationError_closure
#define nestedAtomically_closure ghc_hs_iface->nestedAtomically_closure
#define absentSumFieldError_closure ghc_hs_iface->absentSumFieldError_closure
#define underflowException_closure ghc_hs_iface->underflowException_closure
=====================================
rts/RtsStartup.c
=====================================
@@ -193,7 +193,7 @@ static void initBuiltinGcRoots(void)
getStablePtr((StgPtr)heapOverflow_closure);
getStablePtr((StgPtr)unpackCString_closure);
getStablePtr((StgPtr)blockedIndefinitelyOnMVar_closure);
- getStablePtr((StgPtr)nonTermination_closure);
+ getStablePtr((StgPtr)nonTerminationError_closure);
getStablePtr((StgPtr)blockedIndefinitelyOnSTM_closure);
getStablePtr((StgPtr)allocationLimitExceeded_closure);
getStablePtr((StgPtr)cannotCompactFunction_closure);
=====================================
rts/Schedule.c
=====================================
@@ -3276,6 +3276,17 @@ findAtomicallyFrameHelper (Capability *cap, StgTSO *tso)
}
}
+static void throwNontermination(Capability *cap, StgTSO *tso) {
+ StgStack *stack = tso->stackobj;
+ stack->sp -= 3;
+ stack->sp[0] = (W_)&stg_enter_info;
+ stack->sp[1] = (W_)nonTerminationError_closure;
+ stack->sp[2] = (W_)&stg_ap_v_info;
+ tso->why_blocked = NotBlocked;
+ appendToRunQueue(cap,tso);
+}
+
+
/* -----------------------------------------------------------------------------
resurrectThreads is called after garbage collection on the list of
threads found to be garbage. Each of these threads will be woken
@@ -3313,8 +3324,7 @@ resurrectThreads (StgTSO *threads)
(StgClosure *)blockedIndefinitelyOnMVar_closure);
break;
case BlockedOnBlackHole:
- throwToSingleThreaded(cap, tso,
- (StgClosure *)nonTermination_closure);
+ throwNontermination(cap, tso);
break;
case BlockedOnSTM:
throwToSingleThreaded(cap, tso,
=====================================
rts/include/rts/RtsToHsIface.h
=====================================
@@ -25,7 +25,7 @@ typedef struct {
StgClosure *cannotCompactFunction_closure; // GHC.Internal.IO.Exception.cannotCompactFunction_closure
StgClosure *cannotCompactPinned_closure; // GHC.Internal.IO.Exception.cannotCompactPinned_closure
StgClosure *cannotCompactMutable_closure; // GHC.Internal.IO.Exception.cannotCompactMutable_closure
- StgClosure *nonTermination_closure; // GHC.Internal.Control.Exception.Base.nonTermination_closure
+ StgClosure *nonTerminationError_closure; // GHC.Internal.Control.Exception.Base.nonTerminationError_closure
StgClosure *nestedAtomically_closure; // GHC.Internal.Control.Exception.Base.nestedAtomically_closure
StgClosure *noMatchingContinuationPrompt_closure; // GHC.Internal.Control.Exception.Base.noMatchingContinuationPrompt_closure
StgClosure *blockedOnBadFD_closure; // GHC.Internal.Event.Thread.blockedOnBadFD_closure
=====================================
testsuite/tests/rts/LoopBacktrace.hs
=====================================
@@ -0,0 +1,17 @@
+{-# OPTIONS_GHC -finfo-table-map -forig-thunk-info #-}
+
+import GHC.Exception.Backtrace.Experimental
+
+x :: Integer
+x = x + 1
+
+testing :: IO ()
+testing = do
+ putStrLn "hello"
+ print x
+ putStrLn "world"
+
+main :: IO ()
+main = do
+ setBacktraceMechanismState IPEBacktrace True
+ testing
=====================================
testsuite/tests/rts/LoopBacktrace.stderr
=====================================
@@ -0,0 +1,21 @@
+LoopBacktrace: Uncaught exception ghc-internal:GHC.Internal.Control.Exception.Base.NonTermination:
+
+<<loop>>
+
+IPE backtrace:
+ GHC.Internal.Exception.Backtrace.collectBacktraces' (libraries/ghc-internal/src/GHC/Internal/Exception/Backtrace.hs:(179,1)-(202,25))
+ GHC.Internal.Exception.Backtrace.collectBacktraces (libraries/ghc-internal/src/GHC/Internal/Exception/Backtrace.hs:174:39-56)
+ GHC.Internal.Exception.toExceptionWithBacktrace (libraries/ghc-internal/src/GHC/Internal/Exception.hs:(179,26)-(181,53))
+ GHC.Internal.IO.throwIO (libraries/ghc-internal/src/GHC/Internal/IO.hs:293:36)
+ Cmm$rts/HeapStackCheck.cmm. (:)
+ GHC.Internal.Bignum.Integer.integerAdd (libraries/ghc-internal/src/GHC/Internal/Bignum/Integer.hs:(547,1)-(571,52))
+ Cmm$rts/Updates.cmm. (:)
+ Main.x (LoopBacktrace.hs:6:1-9)
+ GHC.Internal.Show.show (libraries/ghc-internal/src/GHC/Internal/Show.hs:497:10-21)
+ GHC.Internal.IO.Handle.Text.hPutStr' (libraries/ghc-internal/src/GHC/Internal/IO/Handle/Text.hs:667:29-37)
+ GHC.Internal.Base.thenIO (libraries/ghc-internal/src/GHC/Internal/Base.hs:2337:1-72)
+ Cmm$rts/Exception.cmm. (:)
+ Cmm$rts/StgStartup.cmm. (:)
+HasCallStack backtrace:
+ throwIO, called at libraries/ghc-internal/src/GHC/Internal/Control/Exception/Base.hs:452:23 in ghc-internal:GHC.Internal.Control.Exception.Base
+
=====================================
testsuite/tests/rts/LoopBacktrace.stdout
=====================================
@@ -0,0 +1 @@
+hello
=====================================
testsuite/tests/rts/all.T
=====================================
@@ -687,3 +687,5 @@ test('ClosureTable',
['-debug -O0 ClosureTable_c.c -I{top}/../rts -I{top}/../rts/include'])
test('resizeMutableByteArrayInPlace', [req_cmm, extra_ways(['optasm', 'sanity']), only_ways(['optasm', 'sanity'])], compile_and_run, [''])
+
+test('LoopBacktrace', [exit_code(1)], compile_and_run, [''])
=====================================
testsuite/tests/rts/ipe/T27408.hs
=====================================
@@ -0,0 +1,18 @@
+module Main where
+
+import GHC.Stack.CloneStack (StackEntry(..), cloneMyStack, decode)
+
+userFunction :: IO [StackEntry]
+userFunction = do
+ putStr ""
+ stk <- cloneMyStack
+ putStr ""
+ es <- decode stk
+ putStr ""
+ return es
+
+main :: IO ()
+main = do
+ entries <- userFunction
+ let ours = filter ((== "Main") . moduleName) entries
+ mapM_ (\e -> putStrLn (moduleName e ++ "\t" ++ functionName e)) ours
=====================================
testsuite/tests/rts/ipe/T27408.stdout
=====================================
@@ -0,0 +1,3 @@
+Main
+Main main
+Main userFunction
=====================================
testsuite/tests/rts/ipe/all.T
=====================================
@@ -4,6 +4,16 @@ def noCapabilityOutputFilter(s):
test('ipeMap', [extra_files(['ipe_lib.c', 'ipe_lib.h']), c_src, omit_ghci], compile_and_run, ['ipe_lib.c'])
+# Return frames whose Cmm block is dominated by inlined library ticks (e.g. the
+# (>>)/(>>=) of a user do-block at -O) should still be attributed to the user's
+# module. See Note [Prefer current-module source ticks for return frames] in
+# GHC.Driver.GenerateCgIPEStub.
+test('T27408',
+ [ omit_ghci # cloneMyStack# is not available in ghci
+ , js_broken(22261) # cloneMyStack# not yet implemented in the JS backend
+ ],
+ compile_and_run, ['-O1 -finfo-table-map -g3'])
+
# Manually create IPE entries and dump them to event log (stderr).
test('ipeEventLog',
[ c_src,
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/0d15e5c07946bc36198706eefd457b…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/0d15e5c07946bc36198706eefd457b…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc][wip/sol/interactive-error-hints] Add a flag to control GHCi specific error hints (close #27409)
by Simon Hengel (@sol) 21 Jun '26
by Simon Hengel (@sol) 21 Jun '26
21 Jun '26
Simon Hengel pushed to branch wip/sol/interactive-error-hints at Glasgow Haskell Compiler / GHC
Commits:
a350548f by Simon Hengel at 2026-06-21T21:23:29+07:00
Add a flag to control GHCi specific error hints (close #27409)
- - - - -
10 changed files:
- + changelog.d/interactive-error-hints
- compiler/GHC/Driver/DynFlags.hs
- compiler/GHC/Driver/Flags.hs
- compiler/GHC/Driver/Session.hs
- compiler/GHC/Types/Hint/Ppr.hs
- compiler/GHC/Utils/Outputable.hs
- docs/users_guide/ghci.rst
- ghc/GHCi/UI/Exception.hs
- ghc/Main.hs
- testsuite/tests/ghci/scripts/ghci024.stdout
Changes:
=====================================
changelog.d/interactive-error-hints
=====================================
@@ -0,0 +1,6 @@
+section: compiler
+synopsis:
+ Add flags `-finteractive-error-hints` / `-fno-interactive-error-hints` that
+ control whether GHCi specific error hints are printed.
+issues: #27409
+mrs: !16225
=====================================
compiler/GHC/Driver/DynFlags.hs
=====================================
@@ -1570,6 +1570,7 @@ initSDocContext dflags style = SDC
, sdocLineLength = pprCols dflags
, sdocCanUseUnicode = useUnicode dflags
, sdocPrintErrIndexLinks = overrideWith (canUseErrorLinks dflags) (useErrorLinks dflags)
+ , sdocInteractiveErrorHints = gopt Opt_InteractiveErrorHints dflags
, sdocHexWordLiterals = gopt Opt_HexWordLiterals dflags
, sdocPprDebug = dopt Opt_D_ppr_debug dflags
, sdocPrintUnicodeSyntax = gopt Opt_PrintUnicodeSyntax dflags
=====================================
compiler/GHC/Driver/Flags.hs
=====================================
@@ -861,6 +861,7 @@ data GeneralFlag
-- Error message suppression
| Opt_ShowErrorContext
+ | Opt_InteractiveErrorHints
-- Object code determinism
| Opt_ObjectDeterminism
=====================================
compiler/GHC/Driver/Session.hs
=====================================
@@ -2665,6 +2665,7 @@ fFlagsDeps = [
(addWarn "-compact-unwind is only implemented by the darwin platform. Ignoring.")
return dflags)),
flagSpec "show-error-context" Opt_ShowErrorContext,
+ flagSpec "interactive-error-hints" Opt_InteractiveErrorHints,
flagSpec "cmm-thread-sanitizer" Opt_CmmThreadSanitizer,
flagSpec "split-sections" Opt_SplitSections,
flagSpec "break-points" Opt_InsertBreakpoints,
=====================================
compiler/GHC/Types/Hint/Ppr.hs
=====================================
@@ -43,19 +43,44 @@ instance Outputable GhcHint where
-> case extHint of
SuggestSingleExtension extraUserInfo ext ->
("Perhaps you intended to use" <+> extension_with_implied ext)
- $$ extraUserInfo
+ $$ interactiveErrorHints [ext] extraUserInfo False
SuggestAnyExtension extraUserInfo exts ->
(enable "any" <+> unquotedListWith "or" (map implied exts))
- $$ extraUserInfo
+ $$ interactiveErrorHints exts extraUserInfo True
SuggestExtensions extraUserInfo exts ->
(enable "all" <+> unquotedListWith "and" (map implied exts))
- $$ extraUserInfo
+ $$ interactiveErrorHints exts extraUserInfo False
SuggestExtensionInOrderTo extraUserInfo ext ->
("Use" <+> extension_with_implied ext)
- $$ extraUserInfo
+ $$ interactiveErrorHints [ext] extraUserInfo False
where extension_with_implied ext = "the" <+> quotes (ppr ext) <+> "extension" <+> pprImpliedExtensions ext
implied ext = quotes (ppr ext) <+> pprImpliedExtensions ext
enable any_or_all = "Enable" <+> any_or_all <+> "of the following extensions" <> colon
+
+ interactiveErrorHints :: [LangExt.Extension] -> SDoc -> Bool -> SDoc
+ interactiveErrorHints exts doc enable_any = sdocOption sdocInteractiveErrorHints $ \ case
+ True -> suggestSetExt exts doc enable_any
+ False -> doc
+
+ -- Suggest enabling extension with :set -X<ext>
+ -- SuggestAnyExtension will be on multiple lines so the user can select which to enable without editing
+ suggestSetExt :: [LangExt.Extension] -> SDoc -> Bool -> SDoc
+ suggestSetExt exts doc enable_any = doc $$ hang header 2 exts_cmds
+ where
+ header = text "You may enable" <+> which <+> text "language extension" <> plural exts <+> text "in GHCi with:"
+ which
+ | [ _ext ] <- exts
+ = text "this"
+ | otherwise
+ = if enable_any
+ then text "these"
+ else text "all of these"
+ exts_cmds
+ | enable_any
+ = vcat $ map (\ext -> text ":set -X" <> ppr ext) exts
+ | otherwise
+ = text ":set" <> hcat (map (\ext -> text " -X" <> ppr ext) exts)
+
SuggestCorrectPragmaName suggestions
-> text "Perhaps you meant" <+> quotedListWithOr (map text suggestions)
SuggestMissingDo
=====================================
compiler/GHC/Utils/Outputable.hs
=====================================
@@ -398,6 +398,7 @@ data SDocContext = SDC
-- ^ True if Unicode encoding is supported
-- and not disabled by GHC_NO_UNICODE environment variable
, sdocPrintErrIndexLinks :: !Bool
+ , sdocInteractiveErrorHints :: !Bool
, sdocHexWordLiterals :: !Bool
, sdocPprDebug :: !Bool
, sdocPrintUnicodeSyntax :: !Bool
@@ -466,6 +467,7 @@ defaultSDocContext = SDC
, sdocLineLength = defaultSDocCols
, sdocCanUseUnicode = False
, sdocPrintErrIndexLinks = False
+ , sdocInteractiveErrorHints = False
, sdocHexWordLiterals = False
, sdocPprDebug = False
, sdocPrintUnicodeSyntax = False
=====================================
docs/users_guide/ghci.rst
=====================================
@@ -2114,6 +2114,27 @@ mostly obvious.
If disabled, only the target given last on the command line interface will be
added to interactive context of the GHCi session.
+.. ghc-flag:: -finteractive-error-hints
+ :shortdesc: Print GHCi specific error hints.
+ :type: dynamic
+ :reverse: -fno-interactive-error-hints
+ :category:
+
+ :default: on
+ :since: XXX
+
+ By default, GHCi extends error hints with GHCi specific instructions.
+
+ Example:
+
+ .. code-block:: none
+
+ Perhaps you intended to use the ‘BlockArguments’ extension
+ You may enable this language extension in GHCi with:
+ :set -XBlockArguments
+
+ This can be disabled with `-fno-interactive-error-hints`.
+
Packages
~~~~~~~~
=====================================
ghc/GHCi/UI/Exception.hs
=====================================
@@ -23,8 +23,6 @@ import GHC.Driver.Errors.Types
import GHC.Iface.Errors.Ppr
import GHC.Iface.Errors.Types
-import qualified GHC.LanguageExtensions as LangExt
-
import GHC.Tc.Errors.Ppr
import GHC.Tc.Errors.Types
@@ -137,7 +135,7 @@ instance Diagnostic GhciMessage where
GhciUnknownMessage m -> diagnosticReason m
diagnosticHints = \case
- GhciGhcMessage m -> map GhciGhcHint (ghciDiagnosticHints m)
+ GhciGhcMessage m -> map GhciGhcHint (diagnosticHints m)
GhciCommandMessage m -> map GhciCommandHint (diagnosticHints m)
GhciUnknownMessage m -> diagnosticHints m
@@ -146,40 +144,6 @@ instance Diagnostic GhciMessage where
GhciCommandMessage m -> diagnosticCode m
GhciUnknownMessage m -> diagnosticCode m
-
--- | Modifications to hint messages which we want to display in GHCi.
-ghciDiagnosticHints :: GhcMessage -> [GhcHint]
-ghciDiagnosticHints msg = map modifyHintForGHCi (diagnosticHints msg)
- where
- modifyHintForGHCi :: GhcHint -> GhcHint
- modifyHintForGHCi = \case
- SuggestExtension extHint -> SuggestExtension $ modifyExtHintForGHCi extHint
- hint -> hint
- modifyExtHintForGHCi :: LanguageExtensionHint -> LanguageExtensionHint
- modifyExtHintForGHCi = \case
- SuggestSingleExtension doc ext -> SuggestSingleExtension (suggestSetExt [ext] doc False) ext
- SuggestExtensionInOrderTo doc ext -> SuggestExtensionInOrderTo (suggestSetExt [ext] doc False) ext
- SuggestAnyExtension doc exts -> SuggestAnyExtension (suggestSetExt exts doc True ) exts
- SuggestExtensions doc exts -> SuggestExtensions (suggestSetExt exts doc False) exts
- -- Suggest enabling extension with :set -X<ext>
- -- SuggestAnyExtension will be on multiple lines so the user can select which to enable without editing
- suggestSetExt :: [LangExt.Extension] -> SDoc -> Bool -> SDoc
- suggestSetExt exts doc enable_any = doc $$ hang header 2 exts_cmds
- where
- header = text "You may enable" <+> which <+> text "language extension" <> plural exts <+> text "in GHCi with:"
- which
- | [ _ext ] <- exts
- = text "this"
- | otherwise
- = if enable_any
- then text "these"
- else text "all of these"
- exts_cmds
- | enable_any
- = vcat $ map (\ext -> text ":set -X" <> ppr ext) exts
- | otherwise
- = text ":set" <> hcat (map (\ext -> text " -X" <> ppr ext) exts)
-
-- | Modifications to error messages which we want to display in GHCi
ghciDiagnosticMessage :: GhcMessageOpts -> GhcMessage -> DecoratedSDoc
ghciDiagnosticMessage ghc_opts msg =
=====================================
ghc/Main.hs
=====================================
@@ -199,6 +199,7 @@ main' postLoadMode units dflags0 args flagWarnings = do
| DoRun <- postLoadMode = def_ghci_flags
| otherwise = dflags1
where def_ghci_flags = dflags1 `gopt_set` Opt_ImplicitImportQualified
+ `gopt_set` Opt_InteractiveErrorHints
`gopt_set` Opt_IgnoreOptimChanges
`gopt_set` Opt_IgnoreHpcChanges
-- Setting this by default has the nice effect that
=====================================
testsuite/tests/ghci/scripts/ghci024.stdout
=====================================
@@ -12,6 +12,7 @@ other dynamic, non-language, flag settings:
-fimplicit-import-qualified
-fshow-warning-groups
-fprefer-byte-code
+ -finteractive-error-hints
-fbreak-points
warning settings:
-Wpattern-namespace-specifier
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/a350548f23f793f4244e76d5a606d57…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/a350548f23f793f4244e76d5a606d57…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc][wip/decode-orig-update-frames] ghc-heap: Expose OrigThunkInfo frames
by Ben Gamari (@bgamari) 21 Jun '26
by Ben Gamari (@bgamari) 21 Jun '26
21 Jun '26
Ben Gamari pushed to branch wip/decode-orig-update-frames at Glasgow Haskell Compiler / GHC
Commits:
d0789dfe by Ben Gamari at 2026-06-21T10:19:10-04:00
ghc-heap: Expose OrigThunkInfo frames
Introduce a new constructor to StgStackFrame which exposes
orig_update_frames, allowing better backtraces when this feature is
enabled.
- - - - -
3 changed files:
- libraries/ghc-internal/cbits/Stack.cmm
- libraries/ghc-internal/src/GHC/Internal/Heap/Closures.hs
- libraries/ghc-internal/src/GHC/Internal/Stack/Decode.hs
Changes:
=====================================
libraries/ghc-internal/cbits/Stack.cmm
=====================================
@@ -3,6 +3,10 @@
#include "Cmm.h"
+#if !defined(UnregisterisedCompiler)
+import CLOSURE stg_orig_thunk_info_frame_info;
+#endif
+
// StgStack_marking was not available in the Stage0 compiler at the time of
// writing. Because, it has been added to derivedConstants when Stack.cmm was
// developed.
@@ -168,6 +172,18 @@ getStackInfoTableAddrzh(P_ stack) {
return (info);
}
+// (StgInfoTable*) getOrigThunkInfoPtrzh(StgStack* stack, StgWord offsetWords)
+getOrigThunkInfoPtrzh(P_ stack, W_ offsetWords) {
+ P_ p;
+ p = StgStack_sp(stack) + WDS(offsetWords);
+ ASSERT(LOOKS_LIKE_CLOSURE_PTR(p));
+ if (%INFO_PTR(UNTAG(p)) == stg_orig_thunk_info_frame_info) {
+ return (StgOrigThunkInfoFrame_info_ptr(UNTAG(p)));
+ } else {
+ return (NULL);
+ }
+}
+
// (StgClosure*) getStackClosurezh(StgStack* stack, StgWord offsetWords)
getStackClosurezh(P_ stack, W_ offsetWords) {
P_ ptr;
=====================================
libraries/ghc-internal/src/GHC/Internal/Heap/Closures.hs
=====================================
@@ -568,6 +568,17 @@ data GenStackFrame b =
, stack_payload :: ![GenStackField b]
}
+ -- | An @stg_orig_thunk_info_frame@ pushed by @-forig-thunk-info@. It records
+ -- the original info table of the thunk being updated (in 'orig_info_tbl'),
+ -- which is otherwise lost when the thunk is blackholed.
+ -- See @Note [Original thunk info table frames]@ in "GHC.StgToCmm.Bind".
+ | OrigThunkInfo
+ { info_tbl :: !StgInfoTable
+ -- ^ the frame's own (RTS) info table
+ , orig_info_tbl :: !(Ptr StgInfoTable)
+ -- ^ the original info table of the thunk being updated
+ }
+
| RetFun
{ info_tbl :: !StgInfoTable
, retFunSize :: !Word
=====================================
libraries/ghc-internal/src/GHC/Internal/Stack/Decode.hs
=====================================
@@ -192,6 +192,16 @@ foreign import prim "getInfoTableAddrszh" getInfoTableAddrs# :: StackSnapshot# -
foreign import prim "getStackInfoTableAddrzh" getStackInfoTableAddr# :: StackSnapshot# -> Addr#
+foreign import prim "getOrigThunkInfoPtrzh" getOrigThunkInfoPtr# :: StackSnapshot# -> Word# -> Addr#
+
+-- | @'Just' itbl@ if the frame at the given offset is an
+-- @stg_orig_thunk_info_frame@ (where @itbl@ is the recorded original thunk
+-- info table), otherwise 'Nothing'.
+origThunkInfoFrame :: StackSnapshot# -> WordOffset -> Maybe (Ptr StgInfoTable)
+origThunkInfoFrame stackSnapshot# index =
+ let p = Ptr (getOrigThunkInfoPtr# stackSnapshot# (wordOffsetToWord# index))
+ in if p == nullPtr then Nothing else Just p
+
-- | Get the 'StgInfoTable' of the stack frame.
-- Additionally, provides 'InfoProv' for the 'StgInfoTable' if there is any.
getInfoTableOnStack :: StackSnapshot# -> WordOffset -> IO (StgInfoTable, Maybe InfoProv)
@@ -380,6 +390,15 @@ unpackStackFrameTo (StackSnapshot stackSnapshot#, index) unpackUnderflowFrame fi
bco = bco',
bcoArgs = bcoArgs'
}
+ RET_SMALL
+ | Just orig_itbl <- origThunkInfoFrame stackSnapshot# index -> do
+ orig_info_prov <- lookupIPE (castPtr orig_itbl)
+ finaliseStackFrame
+ OrigThunkInfo
+ { info_tbl = info,
+ orig_info_tbl = orig_itbl
+ }
+ orig_info_prov
RET_SMALL ->
let payload' = decodeSmallBitmap getSmallBitmap# stackSnapshot# index offsetStgClosurePayload
in
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/d0789dfe174f086897f84a3b483e288…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/d0789dfe174f086897f84a3b483e288…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
21 Jun '26
Ben Gamari pushed new branch wip/decode-orig-update-frames at Glasgow Haskell Compiler / GHC
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/tree/wip/decode-orig-update-frames
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc][wip/tick-prefer-anf] Core: Preserve ANF when ticking a tick-headed expression (#27415)
by Ben Gamari (@bgamari) 21 Jun '26
by Ben Gamari (@bgamari) 21 Jun '26
21 Jun '26
Ben Gamari pushed to branch wip/tick-prefer-anf at Glasgow Haskell Compiler / GHC
Commits:
5db84ad6 by Ben Gamari at 2026-06-21T10:16:00-04:00
Core: Preserve ANF when ticking a tick-headed expression (#27415)
`mkTickCpe` promises not to push a tick onto the arguments of an
application, so that CorePrep's ANF invariant is maintained: an argument
must be trivial and, in particular, tick-free (see the `arg`
non-terminal in Note [CorePrep invariants] and Note [mkTick breaks
ANF]). `getStgArgFromTrivialArg` relies on this.
However, its handling of a tick-headed expression delegated to
`tickTickedExpr`, which, when commuting the new tick past the existing
tick stack and pushing it inwards, called `mkTick` (i.e. `mk_tick
False`) directly -- silently dropping ANF preservation and re-enabling
PSCC3.
With `-prof -fprof-auto -finfo-table-map`, a definition such as
foo = Just id
has its body wrapped in an SCC by `-fprof-auto`. The SCC is commuted past the
source notes heading the `Just` application and, with `preserve_anf`
lost, pushed onto the (function-valued) argument as `(scc<foo>
breakpoint) @a`. That argument is no longer trivial, so
`getStgArgFromTrivialArg` panicked:
getStgArgFromTrivialArg
(scc<foo> id) @a_aiNd
Fixes #27415.
- - - - -
4 changed files:
- compiler/GHC/Core/Utils.hs
- compiler/GHC/CoreToStg/Prep.hs
- + testsuite/tests/profiling/should_compile/T27415.hs
- testsuite/tests/profiling/should_compile/all.T
Changes:
=====================================
compiler/GHC/Core/Utils.hs
=====================================
@@ -343,7 +343,7 @@ mk_tick preserve_anf t orig_expr = mkTick' orig_expr
mkTick' expr
-- Deal with ticking a expression headed by one or more ticks.
| Just (ts, e) <- tickedExpr_maybe expr
- = tickTickedExpr t ts e
+ = tickTickedExpr preserve_anf t ts e
mkTick' expr = case expr of
Lam x e
@@ -413,11 +413,13 @@ mk_tick preserve_anf t orig_expr = mkTick' orig_expr
-- | Apply a tick to an expression headed by ticks.
tickTickedExpr
- :: CoreTickish -- ^ tick to add
+ :: Bool -- ^ preserve ANF? (See 'mkTickCpe' and
+ -- Note [mkTick breaks ANF] in GHC.CoreToStg.Prep)
+ -> CoreTickish -- ^ tick to add
-> NE.NonEmpty CoreTickish -- ^ existing stack of ticks
-> CoreExpr -- ^ inner core expression
-> CoreExpr
-tickTickedExpr t1 t2s e
+tickTickedExpr preserve_anf t1 t2s e
-- Case 1: common up 't1' with a tick in the stack.
--
@@ -431,8 +433,24 @@ tickTickedExpr t1 t2s e
-- Case 2: 't1' can be commuted past all the ticks in the stack, e.g. because
-- it has tighter placement properties than all the ticks in the stack.
-- Push it inwards to expose cancellation opportunities.
+ --
+ -- We must thread 'preserve_anf' through here. Otherwise we may turn the
+ -- argument
+ --
+ -- scc<f> (src<l> (Just x))
+ --
+ -- into the ill-formed argument
+ --
+ -- src<l> (Just (scc<f> x)) -- SCC on an argument: not a CpeArg!
+ --
+ -- whereas with preserve_anf we keep the SCC outside the application,
+ -- respecting ANF:
+ --
+ -- src<l> (scc<f> (Just breakpoint))
+ --
+ -- See Note [mkTick breaks ANF] in GHC.CoreToStg.Prep and #27415.
| all (tickishCommutable t1) t2s
- = apply_ticks t2s $ mkTick t1 e
+ = apply_ticks t2s $ mk_tick preserve_anf t1 e
-- Fallback: keep the new tick on the outside.
| otherwise
@@ -455,7 +473,7 @@ tickTickedExpr t1 t2s e
{- Note [Pushing SCCs inwards]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Amongst all ticks, SCCs have the laxest placement properties (PlaceCostCentre,
+Amongst all ticks, SCCs have the most lax placement properties (PlaceCostCentre,
as described in Note [Tickish placement] GHC.Types.Tickish):
(PSCC1) SCCs around non-function variables can be eliminated.
=====================================
compiler/GHC/CoreToStg/Prep.hs
=====================================
@@ -918,10 +918,14 @@ mkTick will push the SCC into the constructor application, resulting in:
\ (eta :: Char -> Bool) -> BindP (p :: Int) (scc<oneM> eta)
+i.e. the SCC ends up on the argument `eta`, which is no longer a valid `arg`.
+
To avoid this problem (at least until 'mkTick' is more thoroughly reworked to
avoid this infelicity, see #27141), we define a variant of 'mkTick', called
'mkTickCpe', which does not push ticks into constructor applications (this is
-the only optimisation done by 'mkTick' that can break ANF).
+the only optimisation done by 'mkTick' that can break ANF). This is the concrete
+meaning of the 'preserve_anf' flag threaded through 'GHC.Core.Utils.mk_tick':
+when set, 'mk_tick' will not push a tick onto an application's arguments (PSCC3).
We prefer using a small variant of 'mkTick' rather than using the 'Tick'
constructor, as the latter can slightly degrade profiling reports by failing to
=====================================
testsuite/tests/profiling/should_compile/T27415.hs
=====================================
@@ -0,0 +1,10 @@
+{-# LANGUAGE ExistentialQuantification #-}
+
+module T27415 where
+
+import Control.Concurrent.MVar
+
+data ResultVar b = forall a . ResultVar (a -> b) (MVar (Maybe a))
+
+mkResultVar :: MVar (Maybe a) -> ResultVar a
+mkResultVar = ResultVar id
=====================================
testsuite/tests/profiling/should_compile/all.T
=====================================
@@ -23,3 +23,4 @@ test('T20938', [test_opts], compile, ['-O -prof'])
test('T26056', [test_opts], compile, ['-O -prof'])
test('T27121', [test_opts, extra_files(['T27121_aux.hs'])], multimod_compile, ['T27121', '-v0 -O -prof -fprof-auto'])
test('T27182', [test_opts], compile, ['-O -prof -fprof-late'])
+test('T27415', [test_opts], compile, ['-O -prof -fprof-auto -finfo-table-map'])
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/5db84ad62896fbf5f8e2f2d70dd9e46…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/5db84ad62896fbf5f8e2f2d70dd9e46…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
Ben Gamari pushed new branch wip/tick-prefer-anf at Glasgow Haskell Compiler / GHC
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/tree/wip/tick-prefer-anf
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc][wip/sol/interactive-error-hints] Add a flag to control GHCi specific error hints (close #27409)
by Simon Hengel (@sol) 21 Jun '26
by Simon Hengel (@sol) 21 Jun '26
21 Jun '26
Simon Hengel pushed to branch wip/sol/interactive-error-hints at Glasgow Haskell Compiler / GHC
Commits:
ba2cf957 by Simon Hengel at 2026-06-21T13:01:45+07:00
Add a flag to control GHCi specific error hints (close #27409)
- - - - -
9 changed files:
- + changelog.d/interactive-error-hints
- compiler/GHC/Driver/DynFlags.hs
- compiler/GHC/Driver/Flags.hs
- compiler/GHC/Driver/Session.hs
- compiler/GHC/Types/Hint/Ppr.hs
- compiler/GHC/Utils/Outputable.hs
- docs/users_guide/ghci.rst
- ghc/GHCi/UI/Exception.hs
- ghc/Main.hs
Changes:
=====================================
changelog.d/interactive-error-hints
=====================================
@@ -0,0 +1,6 @@
+section: compiler
+synopsis:
+ Add flags `-finteractive-error-hints` / `-fno-interactive-error-hints` that
+ control whether GHCi specific error hints are printed.
+issues: #27409
+mrs: !16225
=====================================
compiler/GHC/Driver/DynFlags.hs
=====================================
@@ -1570,6 +1570,7 @@ initSDocContext dflags style = SDC
, sdocLineLength = pprCols dflags
, sdocCanUseUnicode = useUnicode dflags
, sdocPrintErrIndexLinks = overrideWith (canUseErrorLinks dflags) (useErrorLinks dflags)
+ , sdocInteractiveErrorHints = gopt Opt_InteractiveErrorHints dflags
, sdocHexWordLiterals = gopt Opt_HexWordLiterals dflags
, sdocPprDebug = dopt Opt_D_ppr_debug dflags
, sdocPrintUnicodeSyntax = gopt Opt_PrintUnicodeSyntax dflags
=====================================
compiler/GHC/Driver/Flags.hs
=====================================
@@ -861,6 +861,7 @@ data GeneralFlag
-- Error message suppression
| Opt_ShowErrorContext
+ | Opt_InteractiveErrorHints
-- Object code determinism
| Opt_ObjectDeterminism
=====================================
compiler/GHC/Driver/Session.hs
=====================================
@@ -2665,6 +2665,7 @@ fFlagsDeps = [
(addWarn "-compact-unwind is only implemented by the darwin platform. Ignoring.")
return dflags)),
flagSpec "show-error-context" Opt_ShowErrorContext,
+ flagSpec "interactive-error-hints" Opt_InteractiveErrorHints,
flagSpec "cmm-thread-sanitizer" Opt_CmmThreadSanitizer,
flagSpec "split-sections" Opt_SplitSections,
flagSpec "break-points" Opt_InsertBreakpoints,
=====================================
compiler/GHC/Types/Hint/Ppr.hs
=====================================
@@ -43,19 +43,44 @@ instance Outputable GhcHint where
-> case extHint of
SuggestSingleExtension extraUserInfo ext ->
("Perhaps you intended to use" <+> extension_with_implied ext)
- $$ extraUserInfo
+ $$ interactiveErrorHints [ext] extraUserInfo False
SuggestAnyExtension extraUserInfo exts ->
(enable "any" <+> unquotedListWith "or" (map implied exts))
- $$ extraUserInfo
+ $$ interactiveErrorHints exts extraUserInfo True
SuggestExtensions extraUserInfo exts ->
(enable "all" <+> unquotedListWith "and" (map implied exts))
- $$ extraUserInfo
+ $$ interactiveErrorHints exts extraUserInfo False
SuggestExtensionInOrderTo extraUserInfo ext ->
("Use" <+> extension_with_implied ext)
- $$ extraUserInfo
+ $$ interactiveErrorHints [ext] extraUserInfo False
where extension_with_implied ext = "the" <+> quotes (ppr ext) <+> "extension" <+> pprImpliedExtensions ext
implied ext = quotes (ppr ext) <+> pprImpliedExtensions ext
enable any_or_all = "Enable" <+> any_or_all <+> "of the following extensions" <> colon
+
+ interactiveErrorHints :: [LangExt.Extension] -> SDoc -> Bool -> SDoc
+ interactiveErrorHints exts doc enable_any = sdocOption sdocInteractiveErrorHints $ \ case
+ True -> suggestSetExt exts doc enable_any
+ False -> doc
+
+ -- Suggest enabling extension with :set -X<ext>
+ -- SuggestAnyExtension will be on multiple lines so the user can select which to enable without editing
+ suggestSetExt :: [LangExt.Extension] -> SDoc -> Bool -> SDoc
+ suggestSetExt exts doc enable_any = doc $$ hang header 2 exts_cmds
+ where
+ header = text "You may enable" <+> which <+> text "language extension" <> plural exts <+> text "in GHCi with:"
+ which
+ | [ _ext ] <- exts
+ = text "this"
+ | otherwise
+ = if enable_any
+ then text "these"
+ else text "all of these"
+ exts_cmds
+ | enable_any
+ = vcat $ map (\ext -> text ":set -X" <> ppr ext) exts
+ | otherwise
+ = text ":set" <> hcat (map (\ext -> text " -X" <> ppr ext) exts)
+
SuggestCorrectPragmaName suggestions
-> text "Perhaps you meant" <+> quotedListWithOr (map text suggestions)
SuggestMissingDo
=====================================
compiler/GHC/Utils/Outputable.hs
=====================================
@@ -398,6 +398,7 @@ data SDocContext = SDC
-- ^ True if Unicode encoding is supported
-- and not disabled by GHC_NO_UNICODE environment variable
, sdocPrintErrIndexLinks :: !Bool
+ , sdocInteractiveErrorHints :: !Bool
, sdocHexWordLiterals :: !Bool
, sdocPprDebug :: !Bool
, sdocPrintUnicodeSyntax :: !Bool
@@ -466,6 +467,7 @@ defaultSDocContext = SDC
, sdocLineLength = defaultSDocCols
, sdocCanUseUnicode = False
, sdocPrintErrIndexLinks = False
+ , sdocInteractiveErrorHints = False
, sdocHexWordLiterals = False
, sdocPprDebug = False
, sdocPrintUnicodeSyntax = False
=====================================
docs/users_guide/ghci.rst
=====================================
@@ -2114,6 +2114,27 @@ mostly obvious.
If disabled, only the target given last on the command line interface will be
added to interactive context of the GHCi session.
+.. ghc-flag:: -finteractive-error-hints
+ :shortdesc: Print GHCi specific error hints.
+ :type: dynamic
+ :reverse: -fno-interactive-error-hints
+ :category:
+
+ :default: on
+ :since: XXX
+
+ By default, GHCi extends error hints with GHCi specific instructions.
+
+ Example:
+
+ .. code-block:: none
+
+ Perhaps you intended to use the ‘BlockArguments’ extension
+ You may enable this language extension in GHCi with:
+ :set -XBlockArguments
+
+ This can be disabled with `-fno-interactive-error-hints`.
+
Packages
~~~~~~~~
=====================================
ghc/GHCi/UI/Exception.hs
=====================================
@@ -23,8 +23,6 @@ import GHC.Driver.Errors.Types
import GHC.Iface.Errors.Ppr
import GHC.Iface.Errors.Types
-import qualified GHC.LanguageExtensions as LangExt
-
import GHC.Tc.Errors.Ppr
import GHC.Tc.Errors.Types
@@ -137,7 +135,7 @@ instance Diagnostic GhciMessage where
GhciUnknownMessage m -> diagnosticReason m
diagnosticHints = \case
- GhciGhcMessage m -> map GhciGhcHint (ghciDiagnosticHints m)
+ GhciGhcMessage m -> map GhciGhcHint (diagnosticHints m)
GhciCommandMessage m -> map GhciCommandHint (diagnosticHints m)
GhciUnknownMessage m -> diagnosticHints m
@@ -146,40 +144,6 @@ instance Diagnostic GhciMessage where
GhciCommandMessage m -> diagnosticCode m
GhciUnknownMessage m -> diagnosticCode m
-
--- | Modifications to hint messages which we want to display in GHCi.
-ghciDiagnosticHints :: GhcMessage -> [GhcHint]
-ghciDiagnosticHints msg = map modifyHintForGHCi (diagnosticHints msg)
- where
- modifyHintForGHCi :: GhcHint -> GhcHint
- modifyHintForGHCi = \case
- SuggestExtension extHint -> SuggestExtension $ modifyExtHintForGHCi extHint
- hint -> hint
- modifyExtHintForGHCi :: LanguageExtensionHint -> LanguageExtensionHint
- modifyExtHintForGHCi = \case
- SuggestSingleExtension doc ext -> SuggestSingleExtension (suggestSetExt [ext] doc False) ext
- SuggestExtensionInOrderTo doc ext -> SuggestExtensionInOrderTo (suggestSetExt [ext] doc False) ext
- SuggestAnyExtension doc exts -> SuggestAnyExtension (suggestSetExt exts doc True ) exts
- SuggestExtensions doc exts -> SuggestExtensions (suggestSetExt exts doc False) exts
- -- Suggest enabling extension with :set -X<ext>
- -- SuggestAnyExtension will be on multiple lines so the user can select which to enable without editing
- suggestSetExt :: [LangExt.Extension] -> SDoc -> Bool -> SDoc
- suggestSetExt exts doc enable_any = doc $$ hang header 2 exts_cmds
- where
- header = text "You may enable" <+> which <+> text "language extension" <> plural exts <+> text "in GHCi with:"
- which
- | [ _ext ] <- exts
- = text "this"
- | otherwise
- = if enable_any
- then text "these"
- else text "all of these"
- exts_cmds
- | enable_any
- = vcat $ map (\ext -> text ":set -X" <> ppr ext) exts
- | otherwise
- = text ":set" <> hcat (map (\ext -> text " -X" <> ppr ext) exts)
-
-- | Modifications to error messages which we want to display in GHCi
ghciDiagnosticMessage :: GhcMessageOpts -> GhcMessage -> DecoratedSDoc
ghciDiagnosticMessage ghc_opts msg =
=====================================
ghc/Main.hs
=====================================
@@ -199,6 +199,7 @@ main' postLoadMode units dflags0 args flagWarnings = do
| DoRun <- postLoadMode = def_ghci_flags
| otherwise = dflags1
where def_ghci_flags = dflags1 `gopt_set` Opt_ImplicitImportQualified
+ `gopt_set` Opt_InteractiveErrorHints
`gopt_set` Opt_IgnoreOptimChanges
`gopt_set` Opt_IgnoreHpcChanges
-- Setting this by default has the nice effect that
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/ba2cf95766b9123dd6d1107c60ac22c…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/ba2cf95766b9123dd6d1107c60ac22c…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
21 Jun '26
Simon Hengel pushed to branch wip/sol/interactive-error-hints at Glasgow Haskell Compiler / GHC
Commits:
bda3322d by Simon Hengel at 2026-06-21T05:58:52+00:00
Add new file
- - - - -
1 changed file:
- + changelog.d/interactive-error-hints
Changes:
=====================================
changelog.d/interactive-error-hints
=====================================
@@ -0,0 +1,4 @@
+section: compiler
+synopsis: Add flags `-finteractive-error-hints` / `-fno-interactive-error-hints` that control whether GHCi specific error hints are printed.
+issues: #27409
+mrs: !16225
\ No newline at end of file
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/bda3322d62013931a6713cceb4889a9…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/bda3322d62013931a6713cceb4889a9…
You're receiving this email because of your account on gitlab.haskell.org.
1
0