Simon Jakobi pushed to branch wip/sjakobi/T27619-liveness-fixpoint-formats at Glasgow Haskell Compiler / GHC
Commits:
-
6adeca12
by Simon Jakobi at 2026-08-16T00:25:54+02:00
5 changed files:
- compiler/GHC/CmmToAsm/Reg/Liveness.hs
- compiler/GHC/CmmToAsm/Reg/Regs.hs
- compiler/GHC/Types/Unique/FM.hs
- + testsuite/tests/regalloc/T27619.hs
- testsuite/tests/regalloc/all.T
Changes:
| ... | ... | @@ -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 |
| ... | ... | @@ -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 |
| ... | ... | @@ -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.
|
| 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 #-} |
| ... | ... | @@ -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']) |