Simon Jakobi pushed to branch wip/sjakobi/T27619-liveness-fixpoint-formats at Glasgow Haskell Compiler / GHC

Commits:

5 changed files:

Changes:

  • compiler/GHC/CmmToAsm/Reg/Liveness.hs
    ... ... @@ -879,7 +879,7 @@ computeLiveness platform sccs
    879 879
                                             , ppr sccs'])
    
    880 880
     
    
    881 881
     livenessSCCs
    
    882
    -       :: Instruction instr
    
    882
    +       :: forall instr. Instruction instr
    
    883 883
            => Platform
    
    884 884
            -> BlockMap Regs
    
    885 885
            -> [SCC (LiveBasicBlock instr)]          -- accum
    
    ... ... @@ -897,36 +897,49 @@ livenessSCCs platform blockmap done (AcyclicSCC block : sccs)
    897 897
     livenessSCCs platform blockmap done
    
    898 898
             (CyclicSCC blocks : sccs) =
    
    899 899
             livenessSCCs platform blockmap' (CyclicSCC blocks':done) sccs
    
    900
    - where      (blockmap', blocks')
    
    901
    -                = iterateUntilUnchanged linearLiveness equalBlockMaps
    
    902
    -                                      blockmap blocks
    
    900
    + where      (blockmap', blocks') = fixpoint blockmap
    
    903 901
     
    
    904
    -            iterateUntilUnchanged
    
    905
    -                :: (a -> b -> (a,c)) -> (a -> a -> Bool)
    
    906
    -                -> a -> b
    
    907
    -                -> (a,c)
    
    908
    -
    
    909
    -            iterateUntilUnchanged f eq aa b = go aa
    
    902
    +            -- See Note [Convergence of the liveness fixpoint]
    
    903
    +            fixpoint :: BlockMap Regs -> (BlockMap Regs, [LiveBasicBlock instr])
    
    904
    +            fixpoint bm
    
    905
    +                | all unchanged blocks = (bm', blocks'')
    
    906
    +                | otherwise            = fixpoint bm'
    
    910 907
                   where
    
    911
    -                go a = if eq a a' then ac else go a'
    
    912
    -                  where
    
    913
    -                    ac@(a', _) = f a b
    
    914
    -
    
    915
    -            linearLiveness
    
    916
    -                :: Instruction instr
    
    917
    -                => BlockMap Regs -> [LiveBasicBlock instr]
    
    918
    -                -> (BlockMap Regs, [LiveBasicBlock instr])
    
    919
    -
    
    920
    -            linearLiveness = mapAccumL (livenessBlock platform)
    
    921
    -
    
    922
    -                -- probably the least efficient way to compare two
    
    923
    -                -- BlockMaps for equality.
    
    924
    -            equalBlockMaps :: BlockMap Regs -> BlockMap Regs -> Bool
    
    925
    -            equalBlockMaps a b
    
    926
    -                = a' == b'
    
    927
    -              where a' = mapToList a
    
    928
    -                    b' = mapToList b
    
    929
    -                    -- See Note [Unique Determinism and code generation]
    
    908
    +                (bm', blocks'') = mapAccumL (livenessBlock platform) bm blocks
    
    909
    +
    
    910
    +                unchanged :: LiveBasicBlock instr -> Bool
    
    911
    +                unchanged block =
    
    912
    +                    case (mapLookup bid bm, mapLookup bid bm') of
    
    913
    +                        (Just old, Just new) -> old `equalRegs` new
    
    914
    +                        (Nothing,  _       ) -> False  -- first iteration
    
    915
    +                        (Just _,   Nothing ) -> False  -- cannot happen
    
    916
    +                  where bid = blockId block
    
    917
    +
    
    918
    +{- Note [Convergence of the liveness fixpoint]
    
    919
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    920
    +For a cyclic SCC, we iterate 'livenessBlock' over the SCC's blocks until the
    
    921
    +recorded entry liveness stops changing. Two subtleties in the convergence
    
    922
    +test:
    
    923
    +
    
    924
    +* It must compare register formats, not just the sets of live registers
    
    925
    +  (hence the format-aware 'equalRegs'). A block's
    
    926
    +  live-in set joins the successors' entries with 'unionRegsMaxFmt', so a wide
    
    927
    +  format needs one iteration per control-flow edge to propagate backwards
    
    928
    +  around a loop, while the register *sets* are typically already complete
    
    929
    +  after the first iteration. Stopping when the sets stabilise can record a
    
    930
    +  register at a format narrower than the reads it flows into, violating
    
    931
    +  (FmtBwd1) of Note [Register formats in liveness analysis] — which the
    
    932
    +  register allocator relies on for spill/reload widths (#27619).
    
    933
    +
    
    934
    +* Only the entries of the SCC's own blocks can change between iterations:
    
    935
    +  'livenessBlock' inserts only the block it processes, and successor SCCs are
    
    936
    +  already final since SCCs are processed in reverse dependency order.
    
    937
    +  Comparing the whole accumulated block map made the fixpoint quadratic in
    
    938
    +  procedure size (#27437).
    
    939
    +
    
    940
    +Termination: entries only grow — registers are only added, and formats only
    
    941
    +increase via 'maxRegWithFormat' joins — and both lattices are finite.
    
    942
    +-}
    
    930 943
     
    
    931 944
     
    
    932 945
     
    

  • compiler/GHC/CmmToAsm/Reg/Regs.hs
    ... ... @@ -13,6 +13,7 @@ module GHC.CmmToAsm.Reg.Regs (
    13 13
             shrinkingRegs,
    
    14 14
             mapRegs,
    
    15 15
             elemRegs, lookupReg,
    
    16
    +        equalRegs,
    
    16 17
     
    
    17 18
       ) where
    
    18 19
     
    
    ... ... @@ -23,6 +24,7 @@ import GHC.CmmToAsm.Format ( Format, RegWithFormat(..), isVecFormat )
    23 24
     
    
    24 25
     import GHC.Utils.Outputable ( Outputable )
    
    25 26
     import GHC.Types.Unique     ( Uniquable(..) )
    
    27
    +import GHC.Types.Unique.FM  ( equalUFMBy )
    
    26 28
     import GHC.Types.Unique.Set
    
    27 29
     
    
    28 30
     import Data.Coerce ( coerce )
    
    ... ... @@ -33,7 +35,7 @@ import Data.Coerce ( coerce )
    33 35
     -- register liveness analysis.  See Note [Register formats in liveness analysis]
    
    34 36
     -- in GHC.CmmToAsm.Reg.Liveness.
    
    35 37
     newtype Regs = Regs { getRegs :: UniqSet RegWithFormat }
    
    36
    -  deriving newtype (Eq, Outputable)
    
    38
    +  deriving newtype (Outputable)
    
    37 39
     
    
    38 40
     maxRegWithFormat :: RegWithFormat -> RegWithFormat -> RegWithFormat
    
    39 41
     maxRegWithFormat r1@(RegWithFormat _ fmt1) r2@(RegWithFormat _ fmt2)
    
    ... ... @@ -117,3 +119,10 @@ elemRegs r (Regs live) = elemUniqSet_Directly (getUnique r) live
    117 119
     lookupReg :: Reg -> Regs -> Maybe Format
    
    118 120
     lookupReg r (Regs live) =
    
    119 121
       regWithFormat_format <$> lookupUniqSet_Directly live (getUnique r)
    
    122
    +
    
    123
    +-- | Do the two sets contain the same registers, at the same formats?
    
    124
    +equalRegs :: Regs -> Regs -> Bool
    
    125
    +equalRegs (Regs a) (Regs b) = equalUFMBy sameFormat (getUniqSet a) (getUniqSet b)
    
    126
    +  where
    
    127
    +    -- Registers with equal uniques are equal, so only compare the formats.
    
    128
    +    sameFormat (RegWithFormat _ fmt1) (RegWithFormat _ fmt2) = fmt1 == fmt2

  • compiler/GHC/Types/Unique/FM.hs
    ... ... @@ -67,6 +67,7 @@ module GHC.Types.Unique.FM (
    67 67
             strictIntersectUFM_C,
    
    68 68
             disjointUFM,
    
    69 69
             equalKeysUFM,
    
    70
    +        equalUFMBy,
    
    70 71
             diffUFM,
    
    71 72
             nonDetStrictFoldUFM, nonDetFoldUFM, nonDetStrictFoldUFM_DirectlyM,
    
    72 73
             nonDetFoldWithKeyUFM,
    
    ... ... @@ -590,7 +591,12 @@ unsafeCastUFMKey (UFM m) = UFM m
    590 591
     
    
    591 592
     -- Determines whether two 'UniqFM's contain the same keys.
    
    592 593
     equalKeysUFM :: UniqFM key a -> UniqFM key b -> Bool
    
    593
    -equalKeysUFM (UFM m1) (UFM m2) = liftEq (\_ _ -> True) m1 m2
    
    594
    +equalKeysUFM = equalUFMBy (\_ _ -> True)
    
    595
    +
    
    596
    +-- | Determines whether two 'UniqFM's contain the same keys, with values
    
    597
    +-- that agree according to the given predicate.
    
    598
    +equalUFMBy :: (a -> b -> Bool) -> UniqFM key a -> UniqFM key b -> Bool
    
    599
    +equalUFMBy eq (UFM m1) (UFM m2) = liftEq eq m1 m2
    
    594 600
     
    
    595 601
     -- | An edit on type @a@, relating an element of a container (like an entry in a
    
    596 602
     -- map or a line in a file) before and after.
    

  • testsuite/tests/regalloc/T27619.hs
    1
    +{-# LANGUAGE MagicHash, UnboxedTuples #-}
    
    2
    +
    
    3
    +-- The native code for this loop is a three-block cycle H -> X -> W -> H:
    
    4
    +--
    
    5
    +--   H (loop head): reads v at FF64   (movsd lane-0 extract for the guard)
    
    6
    +--   X:             reads v at FF64   (another movsd lane-0 extract)
    
    7
    +--   W:             reads v at F64x2  (movhlps from the full unpack)
    
    8
    +--
    
    9
    +-- v is loop-invariant, so its live format on entry to every block in the
    
    10
    +-- cycle must be F64x2. -fno-cse only keeps the two syntactically identical
    
    11
    +-- lane-0 extracts from being merged.
    
    12
    +--
    
    13
    +-- The test greps -ddump-asm-liveness for a 128-bit vector register recorded
    
    14
    +-- at FF64, which the too-early convergence of the liveness fixpoint produced.
    
    15
    +module T27619 where
    
    16
    +
    
    17
    +import GHC.Exts
    
    18
    +
    
    19
    +loop :: Int# -> DoubleX2# -> Double# -> Double#
    
    20
    +loop i v acc =
    
    21
    +  case unpackDoubleX2# v of
    
    22
    +    (# a1, _ #) ->
    
    23
    +      if isTrue# (a1 <## int2Double# i)
    
    24
    +      then acc
    
    25
    +      else case unpackDoubleX2# v of
    
    26
    +        (# a2, _ #) ->
    
    27
    +          if isTrue# (a2 *## 2.0## <## int2Double# i)
    
    28
    +          then acc *## 2.0##
    
    29
    +          else case unpackDoubleX2# v of
    
    30
    +            (# x, y #) -> loop (i -# 1#) v (acc +## (x *## y))
    
    31
    +{-# NOINLINE loop #-}

  • testsuite/tests/regalloc/all.T
    ... ... @@ -6,3 +6,14 @@ test('regalloc_unit_tests',
    6 6
            [ignore_stderr, only_ways(['normal'])], extra_run_opts('"' + config.libdir + '"') ],
    
    7 7
          compile_and_run,
    
    8 8
          ['-package ghc'])
    
    9
    +
    
    10
    +# The liveness fixpoint must iterate until the register formats converge, not
    
    11
    +# just the sets of live registers (#27619). A vector register live at
    
    12
    +# VecFormat 2 FmtDouble around the loop must not be recorded at FF64.
    
    13
    +test('T27619',
    
    14
    +     [ unless(arch('x86_64'), skip),
    
    15
    +       when(not have_ncg(), skip),
    
    16
    +       only_ways(['normal']),
    
    17
    +       grep_errmsg(r'%vV128_\S+ :: FF64') ],
    
    18
    +     compile,
    
    19
    +     ['-O -fno-cse -ddump-asm-liveness'])