Simon Jakobi pushed to branch wip/sjakobi/T27368 at Glasgow Haskell Compiler / GHC
Commits:
-
47985b11
by Simon Jakobi at 2026-08-17T14:45:24+02:00
6 changed files:
- + changelog.d/27368
- compiler/GHC/Cmm/CommonBlockElim.hs
- compiler/GHC/Cmm/Pipeline.hs
- compiler/GHC/Cmm/ProcPoint.hs
- + testsuite/tests/codeGen/should_compile/T27368.hs
- testsuite/tests/codeGen/should_compile/all.T
Changes:
| 1 | +section: compiler
|
|
| 2 | +synopsis: Fix a ``setInfoTableStackMap`` panic caused by calls in unreachable Cmm blocks.
|
|
| 3 | +issues: #27368
|
|
| 4 | +mrs: !16169 |
| ... | ... | @@ -30,20 +30,23 @@ import Control.Arrow (first, second) |
| 30 | 30 | import Data.List.NonEmpty (NonEmpty (..))
|
| 31 | 31 | import qualified Data.List.NonEmpty as NE
|
| 32 | 32 | |
| 33 | --- -----------------------------------------------------------------------------
|
|
| 34 | --- Eliminate common blocks
|
|
| 35 | - |
|
| 33 | +-- | Merge identical blocks
|
|
| 34 | +--
|
|
| 36 | 35 | -- If two blocks are identical except for the label on the first node,
|
| 37 | 36 | -- then we can eliminate one of the blocks. To ensure that the semantics
|
| 38 | 37 | -- of the program are preserved, we have to rewrite each predecessor of the
|
| 39 | 38 | -- eliminated block to proceed with the block we keep.
|
| 40 | - |
|
| 39 | +--
|
|
| 40 | +-- Tolerates unreachable blocks in its input, and produces them: the
|
|
| 41 | +-- losing copy of a merged pair stays in the block map, now
|
|
| 42 | +-- unreachable. See Note [unreachable blocks] in GHC.Cmm.Pipeline.
|
|
| 43 | +--
|
|
| 41 | 44 | -- The algorithm iterates over the blocks in the graph,
|
| 42 | 45 | -- checking whether it has seen another block that is equal modulo labels.
|
| 43 | 46 | -- If so, then it adds an entry in a map indicating that the new block
|
| 44 | 47 | -- is made redundant by the old block.
|
| 45 | 48 | -- Otherwise, it is added to the useful blocks.
|
| 46 | - |
|
| 49 | +--
|
|
| 47 | 50 | -- To avoid comparing every block with every other block repeatedly, we group
|
| 48 | 51 | -- them by
|
| 49 | 52 | -- * a hash of the block, ignoring labels (explained below)
|
| ... | ... | @@ -57,16 +60,13 @@ import qualified Data.List.NonEmpty as NE |
| 57 | 60 | -- All in all, two blocks should never be compared if they have different
|
| 58 | 61 | -- hashes, and at most once otherwise. Previously, we were slower, and people
|
| 59 | 62 | -- rightfully complained: #10397
|
| 60 | - |
|
| 61 | --- TODO: Use optimization fuel
|
|
| 62 | 63 | elimCommonBlocks :: CmmGraph -> CmmGraph
|
| 64 | +-- TODO: Use optimization fuel
|
|
| 63 | 65 | elimCommonBlocks g = replaceLabels env $ copyTicks env g
|
| 64 | 66 | where
|
| 65 | 67 | env = iterate mapEmpty blocks_with_key
|
| 66 | - -- The order of blocks doesn't matter here. While we could use
|
|
| 67 | - -- revPostorder which drops unreachable blocks this is done in
|
|
| 68 | - -- ContFlowOpt already which runs before this pass. So we use
|
|
| 69 | - -- toBlockList since it is faster.
|
|
| 68 | + -- Block order doesn't matter here, so we use toBlockList,
|
|
| 69 | + -- which is faster than revPostorder.
|
|
| 70 | 70 | groups = groupByInt hash_block (toBlockList g) :: [[CmmBlock]]
|
| 71 | 71 | blocks_with_key = [ [ (successors b, [b]) | b <- bs] | bs <- groups]
|
| 72 | 72 |
| ... | ... | @@ -353,10 +353,18 @@ _GLOBAL_OFFSET_TABLE_, regardless of which entry point we arrived via. |
| 353 | 353 | |
| 354 | 354 | {- Note [unreachable blocks]
|
| 355 | 355 | ~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 356 | -The control-flow optimiser sometimes leaves unreachable blocks behind
|
|
| 357 | -containing junk code. These aren't necessarily a problem, but
|
|
| 358 | -removing them is good because it might save time in the native code
|
|
| 359 | -generator later.
|
|
| 356 | +The control-flow optimiser and the common block eliminator sometimes
|
|
| 357 | +leave unreachable blocks behind. They are removed only here, at the
|
|
| 358 | +end of the pipeline, to save the cost of extra reachability passes —
|
|
| 359 | +so every pass in between runs on a block map that may contain
|
|
| 360 | +unreachable blocks, and must not let them influence the reachable
|
|
| 361 | +part of the graph.
|
|
| 362 | + |
|
| 363 | +In particular, callProcPoints in GHC.Cmm.ProcPoint must only consider
|
|
| 364 | +reachable blocks: the continuation of a call in an unreachable
|
|
| 365 | +block may itself be unreachable, and must not become a proc point.
|
|
| 366 | +Otherwise attachContInfoTables would attach an info table to it,
|
|
| 367 | +resulting in a setInfoTableStackMap panic (#27368).
|
|
| 360 | 368 | |
| 361 | 369 | To make unreachable blocks visible in -ddump-cmm-* output, add -dppr-debug.
|
| 362 | 370 | -}
|
| ... | ... | @@ -173,8 +173,12 @@ procPointLattice = DataflowLattice unreached add_to |
| 173 | 173 | -- introduced because they're reachable from multiple proc points.
|
| 174 | 174 | --
|
| 175 | 175 | -- Extract the set of Continuation BlockIds, see Note [Continuation BlockIds].
|
| 176 | +--
|
|
| 177 | +-- Fold over reachable blocks only: the continuation of an unreachable
|
|
| 178 | +-- call must not become a proc point. See Note [unreachable blocks]
|
|
| 179 | +-- in GHC.Cmm.Pipeline.
|
|
| 176 | 180 | callProcPoints :: CmmGraph -> ProcPointSet
|
| 177 | -callProcPoints g = foldlGraphBlocks add (setSingleton (g_entry g)) g
|
|
| 181 | +callProcPoints g = foldl' add (setSingleton (g_entry g)) (revPostorder g)
|
|
| 178 | 182 | where add :: LabelSet -> CmmBlock -> LabelSet
|
| 179 | 183 | add set b = case lastNode b of
|
| 180 | 184 | CmmCall {cml_cont = Just k} -> setInsert k set
|
| 1 | +-- Regression test for #27368: setInfoTableStackMap panicked because
|
|
| 2 | +-- callProcPoints collected the continuation of a call in an unreachable
|
|
| 3 | +-- block. The two branches below have identical suffixes from the inner
|
|
| 4 | +-- case onwards; common block elimination merges the duplicated call
|
|
| 5 | +-- blocks but leaves the losing copies in the block map, where the dead
|
|
| 6 | +-- call's continuation label is itself unreachable.
|
|
| 7 | +module T27368 (f) where
|
|
| 8 | + |
|
| 9 | +{-# NOINLINE put #-}
|
|
| 10 | +put :: Int -> Int -> IO ()
|
|
| 11 | +put h x = if h + x == 12345 then errorWithoutStackTrace "boom" else pure ()
|
|
| 12 | + |
|
| 13 | +data T = N | J Int | K
|
|
| 14 | + |
|
| 15 | +f :: Int -> Bool -> T -> IO ()
|
|
| 16 | +f h a t = do
|
|
| 17 | + if a
|
|
| 18 | + then do put h 1; case t of { N -> pure (); J _ -> put h 3; K -> put h 4 }; put h 0; put h 0
|
|
| 19 | + else do put h 2; case t of { N -> pure (); J _ -> put h 3; K -> put h 4 }; put h 0; put h 0
|
|
| 20 | + put h 0 |
| ... | ... | @@ -150,3 +150,5 @@ test('T16351', normal, compile, ['-O2 -ddump-simpl -dno-typeable-binds -dsuppres |
| 150 | 150 | test('T20298a', normal, compile, ['-O2 -ddump-simpl -dno-typeable-binds -dsuppress-all -dsuppress-uniques'])
|
| 151 | 151 | test('T20298b', normal, compile, ['-O2 -dno-bignum-rules -ddump-simpl -dno-typeable-binds -dsuppress-all -dsuppress-uniques'])
|
| 152 | 152 | test('T20298c', normal, compile, ['-O2 -dno-builtin-rules -ddump-simpl -dno-typeable-binds -dsuppress-all -dsuppress-uniques'])
|
| 153 | + |
|
| 154 | +test('T27368', normal, compile, ['-O2']) |