Simon Jakobi pushed to branch wip/sjakobi/T27368 at Glasgow Haskell Compiler / GHC

Commits:

6 changed files:

Changes:

  • changelog.d/27368
    1
    +section: compiler
    
    2
    +synopsis: Fix a ``setInfoTableStackMap`` panic caused by calls in unreachable Cmm blocks.
    
    3
    +issues: #27368
    
    4
    +mrs: !16169

  • compiler/GHC/Cmm/CommonBlockElim.hs
    ... ... @@ -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
     
    

  • compiler/GHC/Cmm/Pipeline.hs
    ... ... @@ -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
     -}
    

  • compiler/GHC/Cmm/ProcPoint.hs
    ... ... @@ -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
    

  • testsuite/tests/codeGen/should_compile/T27368.hs
    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

  • testsuite/tests/codeGen/should_compile/all.T
    ... ... @@ -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'])