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
-
abd9dc0e
by Ben Gamari at 2026-06-21T11:20:56-04:00
-
4010d192
by Ben Gamari at 2026-06-21T11:20:56-04:00
-
f8b70be2
by Ben Gamari at 2026-06-21T11:38:17-04:00
-
fadf9d48
by Ben Gamari at 2026-06-21T11:38:17-04:00
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:
| 1 | 1 | module GHC.Driver.GenerateCgIPEStub (generateCgIPEStub, lookupEstimatedTicks) where
|
| 2 | 2 | |
| 3 | +import Control.Applicative ((<|>))
|
|
| 3 | 4 | import Data.Map.Strict (Map)
|
| 4 | 5 | import qualified Data.Map.Strict as Map
|
| 6 | +import Data.Maybe (listToMaybe)
|
|
| 5 | 7 | import Data.Semigroup ((<>))
|
| 6 | 8 | import GHC.Cmm
|
| 7 | 9 | import GHC.Cmm.CLabel (CLabel, mkAsmTempLabel)
|
| ... | ... | @@ -10,6 +12,7 @@ import GHC.Cmm.Dataflow.Block (blockSplit, blockToList) |
| 10 | 12 | import GHC.Cmm.Dataflow.Label
|
| 11 | 13 | import GHC.Cmm.Info.Build (emptySRT)
|
| 12 | 14 | import GHC.Cmm.Pipeline (cmmPipeline)
|
| 15 | +import GHC.Data.FastString (FastString, mkFastString)
|
|
| 13 | 16 | import GHC.Data.Stream (liftIO, liftEff)
|
| 14 | 17 | import qualified GHC.Data.Stream as Stream
|
| 15 | 18 | import GHC.Driver.Env (hsc_dflags, hsc_logger)
|
| ... | ... | @@ -28,9 +31,10 @@ import GHC.StgToCmm.Utils |
| 28 | 31 | import GHC.StgToCmm.CgUtils (CgStream)
|
| 29 | 32 | import GHC.Types.IPE (InfoTableProvMap (provInfoTables), IpeSourceLocation)
|
| 30 | 33 | import GHC.Types.Name.Set (NonCaffySet)
|
| 34 | +import GHC.Types.SrcLoc (srcSpanFile)
|
|
| 31 | 35 | import GHC.Types.Tickish (GenTickish (SourceNote))
|
| 32 | 36 | import GHC.Unit.Types (Module, moduleName)
|
| 33 | -import GHC.Unit.Module (moduleNameString)
|
|
| 37 | +import GHC.Unit.Module (moduleNameString, ModLocation, ml_hs_file)
|
|
| 34 | 38 | import qualified GHC.Utils.Logger as Logger
|
| 35 | 39 | import GHC.Utils.Outputable (ppr)
|
| 36 | 40 | import GHC.Types.Unique.DSM
|
| ... | ... | @@ -257,11 +261,12 @@ generateCgIPEStub hsc_env this_mod denv (nonCaffySet, moduleLFInfos, infoTablesW |
| 257 | 261 | -- performance suffered considerably as a result (see #23103).
|
| 258 | 262 | lookupEstimatedTicks
|
| 259 | 263 | :: HscEnv
|
| 264 | + -> ModLocation -- ^ location of the module being compiled, for IPE provenance
|
|
| 260 | 265 | -> Map CmmInfoTable (Maybe IpeSourceLocation)
|
| 261 | 266 | -> IPEStats
|
| 262 | 267 | -> CmmGroupSRTs
|
| 263 | 268 | -> IO (Map CmmInfoTable (Maybe IpeSourceLocation), IPEStats)
|
| 264 | -lookupEstimatedTicks hsc_env ipes stats cmm_group_srts =
|
|
| 269 | +lookupEstimatedTicks hsc_env mod_location ipes stats cmm_group_srts =
|
|
| 265 | 270 | -- Pass 2: Create an entry in the IPE map for every info table listed in
|
| 266 | 271 | -- this CmmGroupSRTs. If the info table is a stack info table and
|
| 267 | 272 | -- -finfo-table-map-with-stack is enabled, look up its estimated source
|
| ... | ... | @@ -276,19 +281,24 @@ lookupEstimatedTicks hsc_env ipes stats cmm_group_srts = |
| 276 | 281 | dflags = hsc_dflags hsc_env
|
| 277 | 282 | platform = targetPlatform dflags
|
| 278 | 283 | |
| 279 | - -- Pass 1: Map every label meeting the conditions described in Note
|
|
| 280 | - -- [Stacktraces from Info Table Provenance Entries (IPE based stack
|
|
| 281 | - -- unwinding)] to the estimated source location (also as described in the
|
|
| 282 | - -- aformentioned note)
|
|
| 284 | + -- Source file of the module being compiled, used to prefer current-module
|
|
| 285 | + -- source ticks for return frames. See Note [Prefer current-module source
|
|
| 286 | + -- ticks for return frames].
|
|
| 287 | + mb_src_file = mkFastString <$> ml_hs_file mod_location
|
|
| 288 | + |
|
| 289 | + -- Pass 1: Map every label meeting the conditions described in
|
|
| 290 | + -- Note [Stacktraces from Info Table Provenance Entries (IPE based stack unwinding)]
|
|
| 291 | + -- to the estimated source location (also as described in the aformentioned
|
|
| 292 | + -- note).
|
|
| 283 | 293 | --
|
| 284 | 294 | -- Note: It's important that this remains a thunk so we do not compute this
|
| 285 | 295 | -- map if -fno-info-table-with-stack is given
|
| 286 | 296 | labelsToSources :: Map CLabel IpeSourceLocation
|
| 287 | 297 | labelsToSources =
|
| 288 | 298 | if platformTablesNextToCode platform then
|
| 289 | - foldl' labelsToSourcesWithTNTC Map.empty cmm_group_srts
|
|
| 299 | + foldl' (labelsToSourcesWithTNTC mb_src_file) Map.empty cmm_group_srts
|
|
| 290 | 300 | else
|
| 291 | - foldl' labelsToSourcesSansTNTC Map.empty cmm_group_srts
|
|
| 301 | + foldl' (labelsToSourcesSansTNTC mb_src_file) Map.empty cmm_group_srts
|
|
| 292 | 302 | |
| 293 | 303 | collectInfoTables
|
| 294 | 304 | :: (Map CmmInfoTable (Maybe IpeSourceLocation), IPEStats)
|
| ... | ... | @@ -331,15 +341,16 @@ lookupEstimatedTicks hsc_env ipes stats cmm_group_srts = |
| 331 | 341 | |
| 332 | 342 | -- | See Note [Stacktraces from Info Table Provenance Entries (IPE based stack unwinding)]
|
| 333 | 343 | labelsToSourcesWithTNTC
|
| 334 | - :: Map CLabel IpeSourceLocation
|
|
| 344 | + :: Maybe FastString -- ^ source file of the module being compiled
|
|
| 345 | + -> Map CLabel IpeSourceLocation
|
|
| 335 | 346 | -> GenCmmDecl RawCmmStatics CmmTopInfo CmmGraph
|
| 336 | 347 | -> Map CLabel IpeSourceLocation
|
| 337 | -labelsToSourcesWithTNTC acc (CmmProc _ _ _ cmm_graph) =
|
|
| 348 | +labelsToSourcesWithTNTC mb_src_file acc (CmmProc _ _ _ cmm_graph) =
|
|
| 338 | 349 | foldl' go acc (toBlockList cmm_graph)
|
| 339 | 350 | where
|
| 340 | 351 | go :: Map CLabel IpeSourceLocation -> CmmBlock -> Map CLabel IpeSourceLocation
|
| 341 | 352 | go acc block =
|
| 342 | - case (,) <$> returnFrameLabel <*> lastTickInBlock of
|
|
| 353 | + case (,) <$> returnFrameLabel <*> bestTickInBlock of
|
|
| 343 | 354 | Just (clabel, src_loc) -> Map.insert clabel src_loc acc
|
| 344 | 355 | Nothing -> acc
|
| 345 | 356 | where
|
| ... | ... | @@ -351,36 +362,135 @@ labelsToSourcesWithTNTC acc (CmmProc _ _ _ cmm_graph) = |
| 351 | 362 | (CmmCall _ (Just l) _ _ _ _) -> Just $ mkAsmTempLabel l
|
| 352 | 363 | _ -> Nothing
|
| 353 | 364 | |
| 354 | - lastTickInBlock = foldr maybeTick Nothing (blockToList middleBlock)
|
|
| 365 | + -- All SourceNotes in the block, in block order.
|
|
| 366 | + -- See Note [Prefer current-module source ticks for return frames].
|
|
| 367 | + bestTickInBlock = preferThisFile mb_src_file procFallback (blockToList middleBlock)
|
|
| 368 | + |
|
| 369 | + -- Enclosing current-module note for the whole proc (its function's own
|
|
| 370 | + -- span), used when a return frame's own block has no current-module tick.
|
|
| 371 | + procFallback = enclosingThisFileTick mb_src_file (toBlockList cmm_graph)
|
|
| 372 | +labelsToSourcesWithTNTC _ acc _ = acc
|
|
| 373 | + |
|
| 374 | +{-
|
|
| 375 | +Note [Prefer current-module source ticks for return frames]
|
|
| 376 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 377 | +A return frame's source location is taken from the `SourceNote`s of the block
|
|
| 378 | +that *ends* in the frame's call (see
|
|
| 379 | +Note [Stacktraces from Info Table Provenance Entries (IPE based stack unwinding]
|
|
| 380 | +above and `labelsToSourcesWithTNTC`). At `-O`, inlining means such a
|
|
| 381 | +block frequently carries `SourceNote`s for inlined library glue (`>>`, `>>=`,
|
|
| 382 | +`threadDelay`, ...) and the *nearest* note — the one historically chosen — is
|
|
| 383 | +often a library note rather than the user's code. The resulting IPE entry then
|
|
| 384 | +points at the library (its file and label; see `toCgIPE` in
|
|
| 385 | +GHC.StgToCmm.InfoTableProv, which takes the file from the note's own span), so a
|
|
| 386 | +backtrace of a thread blocked in such a primop shows no user frames.
|
|
| 387 | + |
|
| 388 | +For a concrete example, compile (at -O1)
|
|
| 389 | + |
|
| 390 | + -- Scan.hs
|
|
| 391 | + f3 :: IO ()
|
|
| 392 | + f3 = threadDelay 1000000 >> putStrLn "done"
|
|
| 393 | + |
|
| 394 | +The body of `f3` reaches the inlined `threadDelay`'s internal `delay#` in a
|
|
| 395 | +block whose notes are *all* from `Conc.IO`/`Base` — the user's `Scan.hs` tick
|
|
| 396 | +for `f3` sits only in the proc's entry block:
|
|
| 397 | + |
|
| 398 | + entry: -- the proc's entry block
|
|
| 399 | + //tick src<.../Base.hs:2306:5-18>
|
|
| 400 | + //tick src<Scan.hs:13:1-43> -- the enclosing `f3` span
|
|
| 401 | + //tick src<.../Conc/IO.hs:(223,1)-(235,10)>
|
|
| 402 | + ...
|
|
| 403 | + delayBlk: -- no Scan.hs note here:
|
|
| 404 | + //tick src<.../Conc/IO.hs:232:5-13>
|
|
| 405 | + //tick src<.../Base.hs:2268:1-9>
|
|
| 406 | + //tick src<.../Conc/IO.hs:(232,25)-(235,10)> -- nearest note
|
|
| 407 | + call stg_delay#(R1) returns to delayCont, args: 8, res: 8, upd: 8;
|
|
| 408 | + |
|
| 409 | +Naively taking the nearest note attributes `delayCont` to `Conc/IO.hs:232`,
|
|
| 410 | +i.e. an internal of `threadDelay`, rather than `f3`.
|
|
| 411 | + |
|
| 412 | +To fix this we attribute a return frame's source location in the following
|
|
| 413 | +preference order:
|
|
| 414 | + |
|
| 415 | + 1. the nearest tick in the frame's block whose file is that of the module
|
|
| 416 | + being compiled - the precise user call site. (When the user makes a
|
|
| 417 | + blocking call directly, e.g. `f v = takeMVar v`, such a note is present in
|
|
| 418 | + the call's block and this rule suffices; the `delayBlk` above has none.)
|
|
| 419 | + 2. failing that, the proc's *enclosing* current-module note (the outermost
|
|
| 420 | + current-module `SourceNote` in the proc, i.e. its function's own span).
|
|
| 421 | + For `f3` this is `src<Scan.hs:13:1-43>`, so `delayCont` is attributed to
|
|
| 422 | + `f3` rather than to `threadDelay`'s internals.
|
|
| 423 | + 3. failing that, the nearest note of any module (the historical behaviour).
|
|
| 424 | + |
|
| 425 | +This mirrors the same-file preference the DWARF path uses in
|
|
| 426 | +`GHC.Cmm.DebugBlock.bestSrcTick` and that `GHC.Stg.Debug.quickSourcePos` uses
|
|
| 427 | +for closures.
|
|
| 428 | +-}
|
|
| 355 | 429 | |
| 356 | - maybeTick :: CmmNode O O -> Maybe IpeSourceLocation -> Maybe IpeSourceLocation
|
|
| 357 | - maybeTick _ s@(Just _) = s
|
|
| 358 | - maybeTick (CmmTick (SourceNote span name)) Nothing = Just (span, name)
|
|
| 359 | - maybeTick _ _ = Nothing
|
|
| 360 | -labelsToSourcesWithTNTC acc _ = acc
|
|
| 430 | +-- | Pick the 'IpeSourceLocation' to attribute to a return frame from the
|
|
| 431 | +-- source-note-bearing nodes of its block (in block order).
|
|
| 432 | +--
|
|
| 433 | +-- See Note [Prefer current-module source ticks for return frames].
|
|
| 434 | +preferThisFile :: Maybe FastString -> Maybe IpeSourceLocation -> [CmmNode O O] -> Maybe IpeSourceLocation
|
|
| 435 | +preferThisFile mb_src_file procFallback nodes =
|
|
| 436 | + nearest fromThisFile <|> procFallback <|> nearest sourceNotes
|
|
| 437 | + where
|
|
| 438 | + sourceNotes = [ (span, name) | CmmTick (SourceNote span name) <- nodes ]
|
|
| 439 | + fromThisFile = case mb_src_file of
|
|
| 440 | + Just f -> filter ((== f) . srcSpanFile . fst) sourceNotes
|
|
| 441 | + Nothing -> []
|
|
| 442 | + nearest = listToMaybe . reverse
|
|
| 443 | + |
|
| 444 | +-- | The outermost 'SourceNote' from the module being compiled across a proc's
|
|
| 445 | +-- blocks (in 'toBlockList' order, so the entry block's note — the function's own
|
|
| 446 | +-- span — comes first). Used as a fallback so inlined cross-module code is still
|
|
| 447 | +-- labelled with the enclosing user function. 'Nothing' when the proc has no
|
|
| 448 | +-- current-module note (e.g. when compiling the library itself).
|
|
| 449 | +enclosingThisFileTick :: Maybe FastString -> [CmmBlock] -> Maybe IpeSourceLocation
|
|
| 450 | +enclosingThisFileTick mb_src_file blocks =
|
|
| 451 | + listToMaybe
|
|
| 452 | + [ (span, name)
|
|
| 453 | + | b <- blocks
|
|
| 454 | + , let (_, mid, _) = blockSplit b
|
|
| 455 | + , CmmTick (SourceNote span name) <- blockToList mid
|
|
| 456 | + , Just (srcSpanFile span) == mb_src_file ]
|
|
| 361 | 457 | |
| 362 | 458 | -- | See Note [Stacktraces from Info Table Provenance Entries (IPE based stack unwinding)]
|
| 363 | 459 | labelsToSourcesSansTNTC
|
| 364 | - :: Map CLabel IpeSourceLocation
|
|
| 460 | + :: Maybe FastString -- ^ source file of the module being compiled
|
|
| 461 | + -> Map CLabel IpeSourceLocation
|
|
| 365 | 462 | -> GenCmmDecl RawCmmStatics CmmTopInfo CmmGraph
|
| 366 | 463 | -> Map CLabel IpeSourceLocation
|
| 367 | -labelsToSourcesSansTNTC acc (CmmProc _ _ _ cmm_graph) =
|
|
| 464 | +labelsToSourcesSansTNTC mb_src_file acc (CmmProc _ _ _ cmm_graph) =
|
|
| 368 | 465 | foldl' go acc (toBlockList cmm_graph)
|
| 369 | 466 | where
|
| 467 | + -- See 'enclosingThisFileTick'.
|
|
| 468 | + procFallback = enclosingThisFileTick mb_src_file (toBlockList cmm_graph)
|
|
| 469 | + |
|
| 370 | 470 | go :: Map CLabel IpeSourceLocation -> CmmBlock -> Map CLabel IpeSourceLocation
|
| 371 | - go acc block = fst $ foldl' collectLabels (acc, Nothing) (blockToList middleBlock)
|
|
| 471 | + go acc block = fst $ foldl' collectLabels (acc, (Nothing, Nothing)) (blockToList middleBlock)
|
|
| 372 | 472 | where
|
| 373 | 473 | (_, middleBlock, _) = blockSplit block
|
| 374 | 474 | |
| 475 | + -- We track the nearest preceding SourceNote from the module being
|
|
| 476 | + -- compiled and the nearest of any module, and prefer the former (then
|
|
| 477 | + -- the proc's enclosing current-module note) when attributing a return
|
|
| 478 | + -- frame. See Note [Prefer current-module source ticks for return frames].
|
|
| 375 | 479 | collectLabels
|
| 376 | - :: (Map CLabel IpeSourceLocation, Maybe IpeSourceLocation)
|
|
| 480 | + :: (Map CLabel IpeSourceLocation, (Maybe IpeSourceLocation, Maybe IpeSourceLocation))
|
|
| 377 | 481 | -> CmmNode O O
|
| 378 | - -> (Map CLabel IpeSourceLocation, Maybe IpeSourceLocation)
|
|
| 379 | - collectLabels (!acc, lastTick) b =
|
|
| 380 | - case (b, lastTick) of
|
|
| 381 | - (CmmStore _ (CmmLit (CmmLabel l)) _, Just src_loc) ->
|
|
| 382 | - (Map.insert l src_loc acc, Nothing)
|
|
| 383 | - (CmmTick (SourceNote span name), _) ->
|
|
| 384 | - (acc, Just (span, name))
|
|
| 385 | - _ -> (acc, lastTick)
|
|
| 386 | -labelsToSourcesSansTNTC acc _ = acc |
|
| 482 | + -> (Map CLabel IpeSourceLocation, (Maybe IpeSourceLocation, Maybe IpeSourceLocation))
|
|
| 483 | + collectLabels (!acc, st@(lastThis, lastAny)) b =
|
|
| 484 | + case b of
|
|
| 485 | + CmmStore _ (CmmLit (CmmLabel l)) _ ->
|
|
| 486 | + case lastThis <|> procFallback <|> lastAny of
|
|
| 487 | + Just src_loc -> (Map.insert l src_loc acc, (Nothing, Nothing))
|
|
| 488 | + Nothing -> (acc, st)
|
|
| 489 | + CmmTick (SourceNote span name) ->
|
|
| 490 | + let tick = (span, name)
|
|
| 491 | + lastThis'
|
|
| 492 | + | Just (srcSpanFile span) == mb_src_file = Just tick
|
|
| 493 | + | otherwise = lastThis
|
|
| 494 | + in (acc, (lastThis', Just tick))
|
|
| 495 | + _ -> (acc, st)
|
|
| 496 | +labelsToSourcesSansTNTC _ acc _ = acc |
| ... | ... | @@ -699,7 +699,7 @@ hscGenHardCode hsc_env cgguts mod_loc output_filename = do |
| 699 | 699 | _ ->
|
| 700 | 700 | do
|
| 701 | 701 | cmms <- {-# SCC "StgToCmm" #-}
|
| 702 | - doCodeGen hsc_env this_mod denv tycons
|
|
| 702 | + doCodeGen hsc_env this_mod mod_loc denv tycons
|
|
| 703 | 703 | cost_centre_info
|
| 704 | 704 | stg_binds
|
| 705 | 705 | |
| ... | ... | @@ -956,14 +956,17 @@ This reduces residency towards the end of the CodeGen phase significantly |
| 956 | 956 | (5-10%).
|
| 957 | 957 | -}
|
| 958 | 958 | |
| 959 | -doCodeGen :: HscEnv -> Module -> InfoTableProvMap -> [TyCon]
|
|
| 959 | +doCodeGen :: HscEnv -> Module
|
|
| 960 | + -> ModLocation -- ^ location of the module being compiled, used to
|
|
| 961 | + -- prefer current-module IPE source locations
|
|
| 962 | + -> InfoTableProvMap -> [TyCon]
|
|
| 960 | 963 | -> CollectedCCs
|
| 961 | 964 | -> [CgStgTopBinding] -- ^ Bindings come already annotated with fvs
|
| 962 | 965 | -> IO (CgStream CmmGroupSRTs CmmCgInfos)
|
| 963 | 966 | -- Note we produce a 'Stream' of CmmGroups, so that the
|
| 964 | 967 | -- backend can be run incrementally. Otherwise it generates all
|
| 965 | 968 | -- the C-- up front, which has a significant space cost.
|
| 966 | -doCodeGen hsc_env this_mod denv tycons
|
|
| 969 | +doCodeGen hsc_env this_mod mod_location denv tycons
|
|
| 967 | 970 | cost_centre_info stg_binds_w_fvs = do
|
| 968 | 971 | let dflags = hsc_dflags hsc_env
|
| 969 | 972 | logger = hsc_logger hsc_env
|
| ... | ... | @@ -1032,7 +1035,7 @@ doCodeGen hsc_env this_mod denv tycons |
| 1032 | 1035 | -- Positions] in GHC.Stg.Debug.
|
| 1033 | 1036 | (ipes', stats') <-
|
| 1034 | 1037 | if (gopt Opt_InfoTableMap dflags) then
|
| 1035 | - liftIO $ lookupEstimatedTicks hsc_env ipes stats cmm_srts
|
|
| 1038 | + liftIO $ lookupEstimatedTicks hsc_env mod_location ipes stats cmm_srts
|
|
| 1036 | 1039 | else
|
| 1037 | 1040 | return (ipes, stats)
|
| 1038 | 1041 |
| ... | ... | @@ -85,7 +85,6 @@ module Control.Exception.Base |
| 85 | 85 | patError,
|
| 86 | 86 | noMethodBindingError,
|
| 87 | 87 | typeError,
|
| 88 | - nonTermination,
|
|
| 89 | 88 | nestedAtomically,
|
| 90 | 89 | noMatchingContinuationPrompt
|
| 91 | 90 | ) where
|
| ... | ... | @@ -19,8 +19,8 @@ |
| 19 | 19 | --
|
| 20 | 20 | |
| 21 | 21 | module GHC.IO.Exception (
|
| 22 | - BlockedIndefinitelyOnMVar(..), blockedIndefinitelyOnMVar,
|
|
| 23 | - BlockedIndefinitelyOnSTM(..), blockedIndefinitelyOnSTM,
|
|
| 22 | + BlockedIndefinitelyOnMVar(..),
|
|
| 23 | + BlockedIndefinitelyOnSTM(..),
|
|
| 24 | 24 | Deadlock(..),
|
| 25 | 25 | AllocationLimitExceeded(..), allocationLimitExceeded,
|
| 26 | 26 | AssertionFailed(..),
|
| ... | ... | @@ -15,12 +15,12 @@ CLOSURE(GHCziInternalziWeakziFinalizze, runFinalizzerBatch_closure) |
| 15 | 15 | CLOSURE(GHCziInternalziIOziException, stackOverflow_closure)
|
| 16 | 16 | CLOSURE(GHCziInternalziIOziException, heapOverflow_closure)
|
| 17 | 17 | CLOSURE(GHCziInternalziIOziException, allocationLimitExceeded_closure)
|
| 18 | -CLOSURE(GHCziInternalziIOziException, blockedIndefinitelyOnMVar_closure)
|
|
| 19 | -CLOSURE(GHCziInternalziIOziException, blockedIndefinitelyOnSTM_closure)
|
|
| 18 | +CLOSURE(GHCziInternalziIOziException, blockedIndefinitelyOnMVarError_closure)
|
|
| 19 | +CLOSURE(GHCziInternalziIOziException, blockedIndefinitelyOnSTMError_closure)
|
|
| 20 | 20 | CLOSURE(GHCziInternalziIOziException, cannotCompactFunction_closure)
|
| 21 | 21 | CLOSURE(GHCziInternalziIOziException, cannotCompactPinned_closure)
|
| 22 | 22 | CLOSURE(GHCziInternalziIOziException, cannotCompactMutable_closure)
|
| 23 | -CLOSURE(GHCziInternalziControlziExceptionziBase, nonTermination_closure)
|
|
| 23 | +CLOSURE(GHCziInternalziControlziExceptionziBase, nonTerminationError_closure)
|
|
| 24 | 24 | CLOSURE(GHCziInternalziControlziExceptionziBase, nestedAtomically_closure)
|
| 25 | 25 | CLOSURE(GHCziInternalziControlziExceptionziBase, noMatchingContinuationPrompt_closure)
|
| 26 | 26 | #if defined(mingw32_HOST_OS)
|
| ... | ... | @@ -108,7 +108,7 @@ module GHC.Internal.Control.Exception.Base ( |
| 108 | 108 | impossibleError, impossibleConstraintError,
|
| 109 | 109 | nonExhaustiveGuardsError, patError, noMethodBindingError,
|
| 110 | 110 | typeError,
|
| 111 | - nonTermination, nestedAtomically, noMatchingContinuationPrompt,
|
|
| 111 | + nonTerminationError, nestedAtomically, noMatchingContinuationPrompt,
|
|
| 112 | 112 | ) where
|
| 113 | 113 | |
| 114 | 114 | import GHC.Internal.Base (
|
| ... | ... | @@ -448,8 +448,9 @@ impossibleConstraintError s = errorWithoutStackTrace (unpackCStringUtf8# s) |
| 448 | 448 | |
| 449 | 449 | |
| 450 | 450 | -- GHC's RTS calls this
|
| 451 | -nonTermination :: SomeException
|
|
| 452 | -nonTermination = toException NonTermination
|
|
| 451 | +nonTerminationError :: IO ()
|
|
| 452 | +nonTerminationError = throwIO NonTermination
|
|
| 453 | + |
|
| 453 | 454 | |
| 454 | 455 | -- GHC's RTS calls this
|
| 455 | 456 | nestedAtomically :: SomeException
|
| ... | ... | @@ -24,8 +24,8 @@ |
| 24 | 24 | -----------------------------------------------------------------------------
|
| 25 | 25 | |
| 26 | 26 | module GHC.Internal.IO.Exception (
|
| 27 | - BlockedIndefinitelyOnMVar(..), blockedIndefinitelyOnMVar,
|
|
| 28 | - BlockedIndefinitelyOnSTM(..), blockedIndefinitelyOnSTM,
|
|
| 27 | + BlockedIndefinitelyOnMVar(..), blockedIndefinitelyOnMVarError,
|
|
| 28 | + BlockedIndefinitelyOnSTM(..), blockedIndefinitelyOnSTMError,
|
|
| 29 | 29 | Deadlock(..),
|
| 30 | 30 | AllocationLimitExceeded(..), allocationLimitExceeded,
|
| 31 | 31 | AssertionFailed(..),
|
| ... | ... | @@ -84,8 +84,8 @@ instance Exception BlockedIndefinitelyOnMVar |
| 84 | 84 | instance Show BlockedIndefinitelyOnMVar where
|
| 85 | 85 | showsPrec _ BlockedIndefinitelyOnMVar = showString "thread blocked indefinitely in an MVar operation"
|
| 86 | 86 | |
| 87 | -blockedIndefinitelyOnMVar :: SomeException -- for the RTS
|
|
| 88 | -blockedIndefinitelyOnMVar = toException BlockedIndefinitelyOnMVar
|
|
| 87 | +blockedIndefinitelyOnMVarError :: IO () -- for the RTS
|
|
| 88 | +blockedIndefinitelyOnMVarError = throwIO BlockedIndefinitelyOnMVar
|
|
| 89 | 89 | |
| 90 | 90 | -----
|
| 91 | 91 | |
| ... | ... | @@ -100,8 +100,8 @@ instance Exception BlockedIndefinitelyOnSTM |
| 100 | 100 | instance Show BlockedIndefinitelyOnSTM where
|
| 101 | 101 | showsPrec _ BlockedIndefinitelyOnSTM = showString "thread blocked indefinitely in an STM transaction"
|
| 102 | 102 | |
| 103 | -blockedIndefinitelyOnSTM :: SomeException -- for the RTS
|
|
| 104 | -blockedIndefinitelyOnSTM = toException BlockedIndefinitelyOnSTM
|
|
| 103 | +blockedIndefinitelyOnSTMError :: IO () -- for the RTS
|
|
| 104 | +blockedIndefinitelyOnSTMError = throwIO BlockedIndefinitelyOnSTM
|
|
| 105 | 105 | |
| 106 | 106 | -----
|
| 107 | 107 |
| ... | ... | @@ -53,12 +53,12 @@ extern StgClosure ZCMain_main_closure; |
| 53 | 53 | #define stackOverflow_closure ghc_hs_iface->stackOverflow_closure
|
| 54 | 54 | #define heapOverflow_closure ghc_hs_iface->heapOverflow_closure
|
| 55 | 55 | #define allocationLimitExceeded_closure ghc_hs_iface->allocationLimitExceeded_closure
|
| 56 | -#define blockedIndefinitelyOnMVar_closure ghc_hs_iface->blockedIndefinitelyOnMVar_closure
|
|
| 57 | -#define blockedIndefinitelyOnSTM_closure ghc_hs_iface->blockedIndefinitelyOnSTM_closure
|
|
| 56 | +#define blockedIndefinitelyOnMVarError_closure ghc_hs_iface->blockedIndefinitelyOnMVarError_closure
|
|
| 57 | +#define blockedIndefinitelyOnSTMError_closure ghc_hs_iface->blockedIndefinitelyOnSTMError_closure
|
|
| 58 | 58 | #define cannotCompactFunction_closure ghc_hs_iface->cannotCompactFunction_closure
|
| 59 | 59 | #define cannotCompactPinned_closure ghc_hs_iface->cannotCompactPinned_closure
|
| 60 | 60 | #define cannotCompactMutable_closure ghc_hs_iface->cannotCompactMutable_closure
|
| 61 | -#define nonTermination_closure ghc_hs_iface->nonTermination_closure
|
|
| 61 | +#define nonTerminationError_closure ghc_hs_iface->nonTerminationError_closure
|
|
| 62 | 62 | #define nestedAtomically_closure ghc_hs_iface->nestedAtomically_closure
|
| 63 | 63 | #define absentSumFieldError_closure ghc_hs_iface->absentSumFieldError_closure
|
| 64 | 64 | #define underflowException_closure ghc_hs_iface->underflowException_closure
|
| ... | ... | @@ -87,6 +87,55 @@ suspendComputation (Capability *cap, StgTSO *tso, StgUpdateFrame *stop_here) |
| 87 | 87 | throwToSingleThreaded__ (cap, tso, NULL, false, stop_here);
|
| 88 | 88 | }
|
| 89 | 89 | |
| 90 | +/* -----------------------------------------------------------------------------
|
|
| 91 | + scheduleRaiseViaIO
|
|
| 92 | + |
|
| 93 | + Schedule `tso` to raise an exception by running `io_action`, an IO () that
|
|
| 94 | + performs `throwIO`. Unlike throwToSingleThreaded (which injects an exception
|
|
| 95 | + *value* via raiseAsync), the exception is raised by throwIO *within* the
|
|
| 96 | + thread, so it acquires a backtrace of the thread's stack. This is used by
|
|
| 97 | + resurrectThreads to deliver the "blocked indefinitely" exceptions
|
|
| 98 | + (BlockedIndefinitelyOnMVar, BlockedIndefinitelyOnSTM, NonTermination).
|
|
| 99 | + |
|
| 100 | + We push a "run this IO action" frame on top of the thread's existing
|
|
| 101 | + (suspended) stack and make it runnable; when the thread runs, throwIO raises
|
|
| 102 | + the exception and its own stack unwinding handles any CATCH_FRAME /
|
|
| 103 | + ATOMICALLY_FRAME (e.g. aborting a blocked STM transaction).
|
|
| 104 | + |
|
| 105 | + removeFromQueues takes care of unlinking the thread from any blocking queue
|
|
| 106 | + (notably the MVar blocked queue) and appends it to the run queue. As with
|
|
| 107 | + throwToSingleThreaded, the caller must own the TSO (e.g. hold all
|
|
| 108 | + capabilities during GC); in particular this relies on the thread not being
|
|
| 109 | + scheduled between removeFromQueues' enqueue and our stack push.
|
|
| 110 | + -------------------------------------------------------------------------- */
|
|
| 111 | + |
|
| 112 | +void
|
|
| 113 | +scheduleRaiseViaIO (Capability *cap, StgTSO *tso, StgClosure *io_action)
|
|
| 114 | +{
|
|
| 115 | + // Thread already dead?
|
|
| 116 | + if (tso->what_next == ThreadComplete || tso->what_next == ThreadKilled) {
|
|
| 117 | + return;
|
|
| 118 | + }
|
|
| 119 | + |
|
| 120 | + // Unlink from any blocking queues; sets why_blocked = NotBlocked and
|
|
| 121 | + // appends the thread to the run queue.
|
|
| 122 | + removeFromQueues(cap, tso);
|
|
| 123 | + |
|
| 124 | + StgStack *stack = tso->stackobj;
|
|
| 125 | + |
|
| 126 | + // We are about to mutate the stack, so dirty it for the GC write barrier
|
|
| 127 | + // (resurrectThreads runs right after GC).
|
|
| 128 | + dirty_TSO(cap, tso);
|
|
| 129 | + dirty_STACK(cap, stack);
|
|
| 130 | + |
|
| 131 | + // Push a frame that enters `io_action` and applies the resulting IO action
|
|
| 132 | + // to the void (RealWorld) argument: [stg_enter_info, io_action, stg_ap_v_info]
|
|
| 133 | + stack->sp -= 3;
|
|
| 134 | + stack->sp[0] = (W_)&stg_enter_info;
|
|
| 135 | + stack->sp[1] = (W_)io_action;
|
|
| 136 | + stack->sp[2] = (W_)&stg_ap_v_info;
|
|
| 137 | +}
|
|
| 138 | + |
|
| 90 | 139 | /* -----------------------------------------------------------------------------
|
| 91 | 140 | throwToSelf
|
| 92 | 141 |
| ... | ... | @@ -38,6 +38,10 @@ void suspendComputation (Capability *cap, |
| 38 | 38 | StgTSO *tso,
|
| 39 | 39 | StgUpdateFrame *stop_here);
|
| 40 | 40 | |
| 41 | +void scheduleRaiseViaIO (Capability *cap,
|
|
| 42 | + StgTSO *tso,
|
|
| 43 | + StgClosure *io_action);
|
|
| 44 | + |
|
| 41 | 45 | MessageThrowTo *throwTo (Capability *cap, // the Capability we hold
|
| 42 | 46 | StgTSO *source,
|
| 43 | 47 | StgTSO *target,
|
| ... | ... | @@ -192,9 +192,9 @@ static void initBuiltinGcRoots(void) |
| 192 | 192 | getStablePtr((StgPtr)stackOverflow_closure);
|
| 193 | 193 | getStablePtr((StgPtr)heapOverflow_closure);
|
| 194 | 194 | getStablePtr((StgPtr)unpackCString_closure);
|
| 195 | - getStablePtr((StgPtr)blockedIndefinitelyOnMVar_closure);
|
|
| 196 | - getStablePtr((StgPtr)nonTermination_closure);
|
|
| 197 | - getStablePtr((StgPtr)blockedIndefinitelyOnSTM_closure);
|
|
| 195 | + getStablePtr((StgPtr)blockedIndefinitelyOnMVarError_closure);
|
|
| 196 | + getStablePtr((StgPtr)nonTerminationError_closure);
|
|
| 197 | + getStablePtr((StgPtr)blockedIndefinitelyOnSTMError_closure);
|
|
| 198 | 198 | getStablePtr((StgPtr)allocationLimitExceeded_closure);
|
| 199 | 199 | getStablePtr((StgPtr)cannotCompactFunction_closure);
|
| 200 | 200 | getStablePtr((StgPtr)cannotCompactPinned_closure);
|
| ... | ... | @@ -3309,16 +3309,16 @@ resurrectThreads (StgTSO *threads) |
| 3309 | 3309 | case BlockedOnMVar:
|
| 3310 | 3310 | case BlockedOnMVarRead:
|
| 3311 | 3311 | /* Called by GC - sched_mutex lock is currently held. */
|
| 3312 | - throwToSingleThreaded(cap, tso,
|
|
| 3313 | - (StgClosure *)blockedIndefinitelyOnMVar_closure);
|
|
| 3312 | + scheduleRaiseViaIO(cap, tso,
|
|
| 3313 | + (StgClosure *)blockedIndefinitelyOnMVarError_closure);
|
|
| 3314 | 3314 | break;
|
| 3315 | 3315 | case BlockedOnBlackHole:
|
| 3316 | - throwToSingleThreaded(cap, tso,
|
|
| 3317 | - (StgClosure *)nonTermination_closure);
|
|
| 3316 | + scheduleRaiseViaIO(cap, tso,
|
|
| 3317 | + (StgClosure *)nonTerminationError_closure);
|
|
| 3318 | 3318 | break;
|
| 3319 | 3319 | case BlockedOnSTM:
|
| 3320 | - throwToSingleThreaded(cap, tso,
|
|
| 3321 | - (StgClosure *)blockedIndefinitelyOnSTM_closure);
|
|
| 3320 | + scheduleRaiseViaIO(cap, tso,
|
|
| 3321 | + (StgClosure *)blockedIndefinitelyOnSTMError_closure);
|
|
| 3322 | 3322 | break;
|
| 3323 | 3323 | case NotBlocked:
|
| 3324 | 3324 | /* This might happen if the thread was blocked on a black hole
|
| ... | ... | @@ -20,12 +20,12 @@ typedef struct { |
| 20 | 20 | StgClosure *stackOverflow_closure; // GHC.Internal.IO.Exception.stackOverflow_closure
|
| 21 | 21 | StgClosure *heapOverflow_closure; // GHC.Internal.IO.Exception.heapOverflow_closure
|
| 22 | 22 | StgClosure *allocationLimitExceeded_closure; // GHC.Internal.IO.Exception.allocationLimitExceeded_closure
|
| 23 | - StgClosure *blockedIndefinitelyOnMVar_closure; // GHC.Internal.IO.Exception.blockedIndefinitelyOnMVar_closure
|
|
| 24 | - StgClosure *blockedIndefinitelyOnSTM_closure; // GHC.Internal.IO.Exception.blockedIndefinitelyOnSTM_closure
|
|
| 23 | + StgClosure *blockedIndefinitelyOnMVarError_closure; // GHC.Internal.IO.Exception.blockedIndefinitelyOnMVarError_closure
|
|
| 24 | + StgClosure *blockedIndefinitelyOnSTMError_closure; // GHC.Internal.IO.Exception.blockedIndefinitelyOnSTMError_closure
|
|
| 25 | 25 | StgClosure *cannotCompactFunction_closure; // GHC.Internal.IO.Exception.cannotCompactFunction_closure
|
| 26 | 26 | StgClosure *cannotCompactPinned_closure; // GHC.Internal.IO.Exception.cannotCompactPinned_closure
|
| 27 | 27 | StgClosure *cannotCompactMutable_closure; // GHC.Internal.IO.Exception.cannotCompactMutable_closure
|
| 28 | - StgClosure *nonTermination_closure; // GHC.Internal.Control.Exception.Base.nonTermination_closure
|
|
| 28 | + StgClosure *nonTerminationError_closure; // GHC.Internal.Control.Exception.Base.nonTerminationError_closure
|
|
| 29 | 29 | StgClosure *nestedAtomically_closure; // GHC.Internal.Control.Exception.Base.nestedAtomically_closure
|
| 30 | 30 | StgClosure *noMatchingContinuationPrompt_closure; // GHC.Internal.Control.Exception.Base.noMatchingContinuationPrompt_closure
|
| 31 | 31 | StgClosure *blockedOnBadFD_closure; // GHC.Internal.Event.Thread.blockedOnBadFD_closure
|
| 1 | +{-# OPTIONS_GHC -finfo-table-map -forig-thunk-info #-}
|
|
| 2 | + |
|
| 3 | +import GHC.Exception.Backtrace.Experimental
|
|
| 4 | + |
|
| 5 | +x :: Integer
|
|
| 6 | +x = x + 1
|
|
| 7 | + |
|
| 8 | +testing :: IO ()
|
|
| 9 | +testing = do
|
|
| 10 | + putStrLn "hello"
|
|
| 11 | + print x
|
|
| 12 | + putStrLn "world"
|
|
| 13 | + |
|
| 14 | +main :: IO ()
|
|
| 15 | +main = do
|
|
| 16 | + setBacktraceMechanismState IPEBacktrace True
|
|
| 17 | + testing |
| 1 | +LoopBacktrace: Uncaught exception ghc-internal:GHC.Internal.Control.Exception.Base.NonTermination:
|
|
| 2 | + |
|
| 3 | +<<loop>>
|
|
| 4 | + |
|
| 5 | +IPE backtrace:
|
|
| 6 | + GHC.Internal.Exception.Backtrace.collectBacktraces' (libraries/ghc-internal/src/GHC/Internal/Exception/Backtrace.hs:(179,1)-(202,25))
|
|
| 7 | + GHC.Internal.Exception.Backtrace.collectBacktraces (libraries/ghc-internal/src/GHC/Internal/Exception/Backtrace.hs:174:39-56)
|
|
| 8 | + GHC.Internal.Exception.toExceptionWithBacktrace (libraries/ghc-internal/src/GHC/Internal/Exception.hs:(179,26)-(181,53))
|
|
| 9 | + GHC.Internal.IO.throwIO (libraries/ghc-internal/src/GHC/Internal/IO.hs:293:36)
|
|
| 10 | + Cmm$rts/HeapStackCheck.cmm. (:)
|
|
| 11 | + GHC.Internal.Bignum.Integer.integerAdd (libraries/ghc-internal/src/GHC/Internal/Bignum/Integer.hs:(547,1)-(571,52))
|
|
| 12 | + Cmm$rts/Updates.cmm. (:)
|
|
| 13 | + Main.x (LoopBacktrace.hs:6:1-9)
|
|
| 14 | + GHC.Internal.Show.show (libraries/ghc-internal/src/GHC/Internal/Show.hs:497:10-21)
|
|
| 15 | + GHC.Internal.IO.Handle.Text.hPutStr' (libraries/ghc-internal/src/GHC/Internal/IO/Handle/Text.hs:667:29-37)
|
|
| 16 | + GHC.Internal.Base.thenIO (libraries/ghc-internal/src/GHC/Internal/Base.hs:2337:1-72)
|
|
| 17 | + Cmm$rts/Exception.cmm. (:)
|
|
| 18 | + Cmm$rts/StgStartup.cmm. (:)
|
|
| 19 | +HasCallStack backtrace:
|
|
| 20 | + throwIO, called at libraries/ghc-internal/src/GHC/Internal/Control/Exception/Base.hs:452:23 in ghc-internal:GHC.Internal.Control.Exception.Base
|
|
| 21 | + |
| 1 | +hello |
| 1 | +{-# OPTIONS_GHC -finfo-table-map #-}
|
|
| 2 | + |
|
| 3 | +-- | Check that a @BlockedIndefinitelyOnMVar@ deadlock exception carries a
|
|
| 4 | +-- backtrace mentioning the blocking site in this module.
|
|
| 5 | +import Control.Concurrent.MVar
|
|
| 6 | +import GHC.Exception.Backtrace.Experimental
|
|
| 7 | + |
|
| 8 | +main :: IO ()
|
|
| 9 | +main = do
|
|
| 10 | + setBacktraceMechanismState IPEBacktrace True
|
|
| 11 | + mv <- newEmptyMVar :: IO (MVar ())
|
|
| 12 | + x <- takeMVar mv
|
|
| 13 | + print x |
| 1 | +MVarDeadlockBacktrace: Uncaught exception ghc-internal:GHC.Internal.IO.Exception.BlockedIndefinitelyOnMVar:
|
|
| 2 | + Main.main (MVarDeadlockBacktrace.hs:16:3-36) |
| 1 | +{-# OPTIONS_GHC -finfo-table-map #-}
|
|
| 2 | + |
|
| 3 | +-- | Check that a @BlockedIndefinitelyOnSTM@ deadlock exception carries a
|
|
| 4 | +-- backtrace mentioning the blocking site in this module.
|
|
| 5 | +import GHC.Conc (atomically, retry)
|
|
| 6 | +import GHC.Exception.Backtrace.Experimental
|
|
| 7 | + |
|
| 8 | +main :: IO ()
|
|
| 9 | +main = do
|
|
| 10 | + setBacktraceMechanismState IPEBacktrace True
|
|
| 11 | + x <- atomically retry :: IO ()
|
|
| 12 | + print x |
| 1 | +STMDeadlockBacktrace: Uncaught exception ghc-internal:GHC.Internal.IO.Exception.BlockedIndefinitelyOnSTM:
|
|
| 2 | + Main.main (STMDeadlockBacktrace.hs:16:3-32) |
| ... | ... | @@ -687,3 +687,11 @@ test('ClosureTable', |
| 687 | 687 | ['-debug -O0 ClosureTable_c.c -I{top}/../rts -I{top}/../rts/include'])
|
| 688 | 688 | |
| 689 | 689 | test('resizeMutableByteArrayInPlace', [req_cmm, extra_ways(['optasm', 'sanity']), only_ways(['optasm', 'sanity'])], compile_and_run, [''])
|
| 690 | + |
|
| 691 | +test('LoopBacktrace', [exit_code(1)], compile_and_run, [''])
|
|
| 692 | + |
|
| 693 | +deadlock_backtrace_norm = grep_errmsg(r'(Uncaught exception|Main\.)')
|
|
| 694 | +test('MVarDeadlockBacktrace', [exit_code(1), only_ways(['normal']), deadlock_backtrace_norm],
|
|
| 695 | + compile_and_run, ['-O'])
|
|
| 696 | +test('STMDeadlockBacktrace', [exit_code(1), only_ways(['normal']), deadlock_backtrace_norm],
|
|
| 697 | + compile_and_run, ['-O']) |
| 1 | +module Main where
|
|
| 2 | + |
|
| 3 | +import GHC.Stack.CloneStack (StackEntry(..), cloneMyStack, decode)
|
|
| 4 | + |
|
| 5 | +userFunction :: IO [StackEntry]
|
|
| 6 | +userFunction = do
|
|
| 7 | + putStr ""
|
|
| 8 | + stk <- cloneMyStack
|
|
| 9 | + putStr ""
|
|
| 10 | + es <- decode stk
|
|
| 11 | + putStr ""
|
|
| 12 | + return es
|
|
| 13 | + |
|
| 14 | +main :: IO ()
|
|
| 15 | +main = do
|
|
| 16 | + entries <- userFunction
|
|
| 17 | + let ours = filter ((== "Main") . moduleName) entries
|
|
| 18 | + mapM_ (\e -> putStrLn (moduleName e ++ "\t" ++ functionName e)) ours |
| 1 | +Main
|
|
| 2 | +Main main
|
|
| 3 | +Main userFunction |
| ... | ... | @@ -4,6 +4,16 @@ def noCapabilityOutputFilter(s): |
| 4 | 4 | |
| 5 | 5 | test('ipeMap', [extra_files(['ipe_lib.c', 'ipe_lib.h']), c_src, omit_ghci], compile_and_run, ['ipe_lib.c'])
|
| 6 | 6 | |
| 7 | +# Return frames whose Cmm block is dominated by inlined library ticks (e.g. the
|
|
| 8 | +# (>>)/(>>=) of a user do-block at -O) should still be attributed to the user's
|
|
| 9 | +# module. See Note [Prefer current-module source ticks for return frames] in
|
|
| 10 | +# GHC.Driver.GenerateCgIPEStub.
|
|
| 11 | +test('T27408',
|
|
| 12 | + [ omit_ghci # cloneMyStack# is not available in ghci
|
|
| 13 | + , js_broken(22261) # cloneMyStack# not yet implemented in the JS backend
|
|
| 14 | + ],
|
|
| 15 | + compile_and_run, ['-O1 -finfo-table-map -g3'])
|
|
| 16 | + |
|
| 7 | 17 | # Manually create IPE entries and dump them to event log (stderr).
|
| 8 | 18 | test('ipeEventLog',
|
| 9 | 19 | [ c_src,
|