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

Commits:

6 changed files:

Changes:

  • changelog.d/27368
    1
    +section: compiler
    
    2
    +synopsis: Fix a ``setInfoTableStackMap`` panic caused by common block elimination
    
    3
    +  leaving references to eliminated blocks.
    
    4
    +issues: #27368
    
    5
    +mrs: !16543

  • compiler/GHC/Cmm/CommonBlockElim.hs
    ... ... @@ -58,18 +58,57 @@ import qualified Data.List.NonEmpty as NE
    58 58
     -- hashes, and at most once otherwise. Previously, we were slower, and people
    
    59 59
     -- rightfully complained: #10397
    
    60 60
     
    
    61
    +-- Note that this pass both consumes and produces graphs that may
    
    62
    +-- contain unreachable blocks: the control-flow optimiser that runs
    
    63
    +-- before it can orphan blocks without deleting them, and such orphans
    
    64
    +-- pass through this pass untouched. (The losing copy of each merge,
    
    65
    +-- by contrast, is deleted here; see deleteLosers.) See
    
    66
    +-- Note [unreachable blocks] in GHC.Cmm.Pipeline.
    
    67
    +
    
    61 68
     -- TODO: Use optimization fuel
    
    62 69
     elimCommonBlocks :: CmmGraph -> CmmGraph
    
    63
    -elimCommonBlocks g = replaceLabels env $ copyTicks env g
    
    70
    +elimCommonBlocks g = replaceLabels env $ deleteLosers $ copyTicks env g
    
    64 71
       where
    
    65
    -     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.
    
    72
    +     -- Safe: replaceLabels is about to rewrite every in-graph reference
    
    73
    +     -- to a loser to its surviving chain end.
    
    74
    +     -- See Note [Resolving the CBE substitution].
    
    75
    +     deleteLosers g'
    
    76
    +       | mapNull env = g'
    
    77
    +       | otherwise   = ofBlockMap (g_entry g')
    
    78
    +                                  (toBlockMap g' `mapDifference` env)
    
    79
    +     env = resolveSubst (iterate mapEmpty blocks_with_key)
    
    80
    +     -- The order of blocks doesn't matter here, so we use toBlockList,
    
    81
    +     -- which is faster than revPostorder.
    
    70 82
          groups = groupByInt hash_block (toBlockList g) :: [[CmmBlock]]
    
    71 83
          blocks_with_key = [ [ (successors b, [b]) | b <- bs] | bs <- groups]
    
    72 84
     
    
    85
    +{- Note [Resolving the CBE substitution]
    
    86
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    87
    +The substitution that `iterate` produces may contain chains
    
    88
    +(k1 :-> k2, k2 :-> k3): the winner of one merge can lose a later one.
    
    89
    +Its consumers, however, apply it non-transitively: replaceLabels
    
    90
    +rewrites each label with a single map lookup, and copyTicks inverts
    
    91
    +the substitution only one level deep. So before applying it we
    
    92
    +resolve each entry to the end of its chain (resolveSubst, via
    
    93
    +lookupBid, which does follow chains); the resolved substitution maps
    
    94
    +every eliminated label directly to its final surviving
    
    95
    +representative.
    
    96
    +
    
    97
    +Applying the unresolved substitution instead would let replaceLabels
    
    98
    +leave edges pointing at eliminated labels. Such edges can occur only
    
    99
    +in blocks that themselves lost a merge, i.e. in unreachable code, but
    
    100
    +even there they are harmful (#27368; analysed on the ticket). See
    
    101
    +Note [unreachable blocks] in GHC.Cmm.Pipeline.
    
    102
    +-}
    
    103
    +
    
    104
    +-- | Resolve the substitution: follow chains (@k1 :-> k2@, @k2 :-> k3@)
    
    105
    +-- to their ends, so that every eliminated label maps directly to its
    
    106
    +-- final surviving representative.
    
    107
    +--
    
    108
    +-- See Note [Resolving the CBE substitution].
    
    109
    +resolveSubst :: Subst -> Subst
    
    110
    +resolveSubst env = mapMap (lookupBid env) env
    
    111
    +
    
    73 112
     -- Invariant: The blocks in the list are pairwise distinct
    
    74 113
     -- (so avoid comparing them again)
    
    75 114
     type DistinctBlocks = [CmmBlock]
    

  • compiler/GHC/Cmm/ContFlowOpt.hs
    ... ... @@ -44,7 +44,8 @@ import Control.Monad
    44 44
     --
    
    45 45
     -- In this situation we say that we shortcut L2 to L3. One of
    
    46 46
     -- consequences of shortcutting is that some blocks of code may become
    
    47
    --- unreachable (in the example above this is true for L2).
    
    47
    +-- unreachable (in the example above this is true for L2). See
    
    48
    +-- Note [unreachable blocks] in GHC.Cmm.Pipeline.
    
    48 49
     
    
    49 50
     
    
    50 51
     -- Note [Control-flow optimisations]
    
    ... ... @@ -66,7 +67,9 @@ import Control.Monad
    66 67
     --
    
    67 68
     -- Blocks are processed using postorder DFS traversal. A side effect
    
    68 69
     -- of determining traversal order with a graph search is elimination
    
    69
    --- of any blocks that are unreachable.
    
    70
    +-- of any blocks that are unreachable in the input; shortcutting can in
    
    71
    +-- turn leave blocks unreachable in the output, see
    
    72
    +-- Note [unreachable blocks] in GHC.Cmm.Pipeline.
    
    70 73
     --
    
    71 74
     -- Transformations are improved by working from the end of the graph
    
    72 75
     -- towards the beginning, because we may be able to perform many
    
    ... ... @@ -341,8 +344,7 @@ blockConcat splitting_procs g@CmmGraph { g_entry = entry_id }
    341 344
     -- Invariant: if a block has no predecessors it should be dropped from the
    
    342 345
     -- graph because it is unreachable. maybe_concat is constructed to maintain
    
    343 346
     -- that invariant, but calling replaceLabels may introduce unreachable blocks.
    
    344
    --- We rely on subsequent passes in the Cmm pipeline to remove unreachable
    
    345
    --- blocks.
    
    347
    +-- See Note [unreachable blocks] in GHC.Cmm.Pipeline.
    
    346 348
     incPreds, decPreds :: BlockId -> LabelMap Int -> LabelMap Int
    
    347 349
     incPreds bid edges = mapInsertWith (+) bid 1 edges
    
    348 350
     decPreds bid edges = case mapLookup bid edges of
    

  • compiler/GHC/Cmm/Pipeline.hs
    ... ... @@ -358,6 +358,22 @@ containing junk code. These aren't necessarily a problem, but
    358 358
     removing them is good because it might save time in the native code
    
    359 359
     generator later.
    
    360 360
     
    
    361
    +Who removes them, and when?  Any pass that rebuilds the graph from
    
    362
    +revPostorder drops them as a side effect.  Normally the first pass to
    
    363
    +do so is stack layout.  In addition, removeUnreachableBlocksProc at the
    
    364
    +very end of cpsTop removes the blocks that the second round of
    
    365
    +control-flow optimisation orphans, and prunes their info tables.  No
    
    366
    +pass between the first control-flow optimisation and stack layout
    
    367
    +removes unreachable blocks, so every pass in that window may encounter
    
    368
    +them.
    
    369
    +
    
    370
    +Unreachable blocks are not entirely harmless, though.  Later passes do
    
    371
    +not uniformly restrict themselves to reachable code -- callProcPoints,
    
    372
    +for example, folds over the whole block map.  So every pass must keep
    
    373
    +even unreachable code well-formed.  Violating this caused the panic in
    
    374
    +#27368 (analysed on the ticket).  See
    
    375
    +Note [Resolving the CBE substitution] in GHC.Cmm.CommonBlockElim.
    
    376
    +
    
    361 377
     To make unreachable blocks visible in -ddump-cmm-* output, add -dppr-debug.
    
    362 378
     -}
    
    363 379
     
    

  • testsuite/tests/codeGen/should_compile/T27368.hs
    1
    +-- The two branches share an identical suffix from the inner case
    
    2
    +-- onwards, so common block elimination merges the duplicated call
    
    3
    +-- blocks over several rounds, building a substitution chain. Without
    
    4
    +-- resolving that chain, compiling this module at -O2 panicked in
    
    5
    +-- setInfoTableStackMap (#27368). See
    
    6
    +-- Note [Resolving the CBE substitution] in GHC.Cmm.CommonBlockElim.
    
    7
    +
    
    8
    +module T27368 (f) where
    
    9
    +
    
    10
    +{-# NOINLINE put #-}
    
    11
    +put :: Int -> Int -> IO ()
    
    12
    +put h x = if h + x == 12345 then errorWithoutStackTrace "boom" else pure ()
    
    13
    +
    
    14
    +data T = J Int | K
    
    15
    +
    
    16
    +f :: Int -> Bool -> T -> IO ()
    
    17
    +f h a t = do
    
    18
    +  if a
    
    19
    +    then do put h 1; case t of { J _ -> put h 3; K -> put h 4 }; put h 0; put h 0
    
    20
    +    else do put h 2; case t of { J _ -> put h 3; K -> put h 4 }; put h 0; put h 0
    
    21
    +  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'])