Simon Jakobi pushed to branch wip/sjakobi/T27619-liveness-fixpoint-formats at Glasgow Haskell Compiler / GHC
Commits:
-
e50eaaeb
by Simon Jakobi at 2026-08-16T15:43:20+02:00
4 changed files:
- compiler/GHC/CmmToAsm/Format.hs
- compiler/GHC/CmmToAsm/PPC/CodeGen.hs
- compiler/GHC/CmmToAsm/Reg/Liveness.hs
- compiler/GHC/CmmToAsm/Reg/Regs.hs
Changes:
| ... | ... | @@ -23,6 +23,7 @@ module GHC.CmmToAsm.Format ( |
| 23 | 23 | vecFormat,
|
| 24 | 24 | isVecFormat,
|
| 25 | 25 | cmmTypeFormat,
|
| 26 | + compareFormat,
|
|
| 26 | 27 | formatToWidth,
|
| 27 | 28 | scalarWidth,
|
| 28 | 29 | formatInBytes,
|
| ... | ... | @@ -40,11 +41,13 @@ where |
| 40 | 41 | |
| 41 | 42 | import GHC.Prelude
|
| 42 | 43 | |
| 44 | +import Data.Semigroup ( (<>) )
|
|
| 45 | + |
|
| 43 | 46 | import GHC.Cmm
|
| 44 | 47 | import GHC.Platform.Reg ( Reg(..), RealReg, VirtualReg )
|
| 45 | 48 | import GHC.Types.Unique ( Uniquable(..) )
|
| 46 | 49 | import GHC.Types.Unique.Set
|
| 47 | -import GHC.Utils.Outputable
|
|
| 50 | +import GHC.Utils.Outputable hiding ( (<>) )
|
|
| 48 | 51 | import GHC.Utils.Panic
|
| 49 | 52 | |
| 50 | 53 | {- Note [GHC's data format representations]
|
| ... | ... | @@ -92,7 +95,8 @@ data Format |
| 92 | 95 | | FF64
|
| 93 | 96 | | VecFormat !Length -- ^ number of elements (always at least 2)
|
| 94 | 97 | !ScalarFormat -- ^ format of each element
|
| 95 | - deriving (Show, Eq, Ord)
|
|
| 98 | + deriving (Show, Eq)
|
|
| 99 | + -- No Ord: see Note [Format order is width-major]
|
|
| 96 | 100 | |
| 97 | 101 | pattern IntegerFormat :: Format
|
| 98 | 102 | pattern IntegerFormat <- ( isIntegerFormat -> True )
|
| ... | ... | @@ -117,7 +121,7 @@ data ScalarFormat |
| 117 | 121 | | FmtInt64
|
| 118 | 122 | | FmtFloat
|
| 119 | 123 | | FmtDouble
|
| 120 | - deriving (Show, Eq, Ord)
|
|
| 124 | + deriving (Show, Eq)
|
|
| 121 | 125 | |
| 122 | 126 | scalarFormatFormat :: ScalarFormat -> Format
|
| 123 | 127 | scalarFormatFormat = \case
|
| ... | ... | @@ -248,6 +252,50 @@ scalarWidth = \case |
| 248 | 252 | formatInBytes :: Format -> Int
|
| 249 | 253 | formatInBytes = widthInBytes . formatToWidth
|
| 250 | 254 | |
| 255 | +{- Note [Format order is width-major]
|
|
| 256 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 257 | +'Format' has no 'Ord' instance: the only meaningful order on formats is by
|
|
| 258 | +width, and code that needs "at least as wide" should say so by comparing
|
|
| 259 | +'formatToWidth' results.
|
|
| 260 | + |
|
| 261 | +'compareFormat' exists for one caller: 'maxRegWithFormat' in
|
|
| 262 | +GHC.CmmToAsm.Reg.Regs, which joins the formats recorded for a register during
|
|
| 263 | +liveness analysis. That join must be a max in a *total* order. Width alone is
|
|
| 264 | +not total: distinct formats can share a width, e.g. VecFormat 2 FmtDouble and
|
|
| 265 | +VecFormat 4 FmtFloat. If the join broke such ties by argument order, two
|
|
| 266 | +blocks in a loop could swap same-width formats on every iteration of the
|
|
| 267 | +liveness fixpoint, and the fixpoint would never converge. See
|
|
| 268 | +Note [Convergence of the liveness fixpoint] in GHC.CmmToAsm.Reg.Liveness.
|
|
| 269 | +So 'compareFormat' refines width order with an arbitrary but fixed tiebreak.
|
|
| 270 | +-}
|
|
| 271 | + |
|
| 272 | +-- | Total order on formats: by width, with an arbitrary but fixed tiebreak
|
|
| 273 | +-- between distinct formats of the same width.
|
|
| 274 | +--
|
|
| 275 | +-- See Note [Format order is width-major].
|
|
| 276 | +compareFormat :: Format -> Format -> Ordering
|
|
| 277 | +compareFormat f1 f2 =
|
|
| 278 | + compare (formatToWidth f1) (formatToWidth f2) <> compare (tag f1) (tag f2)
|
|
| 279 | + where
|
|
| 280 | + tag :: Format -> (Int, Length)
|
|
| 281 | + tag = \case
|
|
| 282 | + II8 -> (0, 0)
|
|
| 283 | + II16 -> (1, 0)
|
|
| 284 | + II32 -> (2, 0)
|
|
| 285 | + II64 -> (3, 0)
|
|
| 286 | + FF32 -> (4, 0)
|
|
| 287 | + FF64 -> (5, 0)
|
|
| 288 | + VecFormat l s -> (6 + scalarTag s, l)
|
|
| 289 | + |
|
| 290 | + scalarTag :: ScalarFormat -> Int
|
|
| 291 | + scalarTag = \case
|
|
| 292 | + FmtInt8 -> 0
|
|
| 293 | + FmtInt16 -> 1
|
|
| 294 | + FmtInt32 -> 2
|
|
| 295 | + FmtInt64 -> 3
|
|
| 296 | + FmtFloat -> 4
|
|
| 297 | + FmtDouble -> 5
|
|
| 298 | + |
|
| 251 | 299 | --------------------------------------------------------------------------------
|
| 252 | 300 | |
| 253 | 301 | -- | A typed virtual register: a virtual register, together with the specific
|
| ... | ... | @@ -484,7 +484,7 @@ getRegister' _ _ (CmmMachOp (MO_SS_Conv src tgt) [CmmLoad mem pk _]) |
| 484 | 484 | , src < tgt = do
|
| 485 | 485 | let format = cmmTypeFormat pk
|
| 486 | 486 | -- lwa is DS-form. See Note [Power instruction format]
|
| 487 | - let form = if format >= II32 then DS else D
|
|
| 487 | + let form = if formatToWidth format >= W32 then DS else D
|
|
| 488 | 488 | Amode addr addr_code <- getAmode form mem
|
| 489 | 489 | let code dst = assert (format == intFormat src)
|
| 490 | 490 | $ addr_code `snocOL` LA format dst addr
|
| ... | ... | @@ -939,7 +939,8 @@ test: |
| 939 | 939 | |
| 940 | 940 | The fixpoint terminates because the entries can only grow: registers are
|
| 941 | 941 | only added, formats only increase via 'maxRegWithFormat' joins, and both
|
| 942 | -lattices are finite.
|
|
| 942 | +lattices are finite. This needs the join to be a max in a total order on
|
|
| 943 | +formats. See Note [Format order is width-major] in GHC.CmmToAsm.Format.
|
|
| 943 | 944 | -}
|
| 944 | 945 | |
| 945 | 946 |
| ... | ... | @@ -20,7 +20,8 @@ module GHC.CmmToAsm.Reg.Regs ( |
| 20 | 20 | import GHC.Prelude
|
| 21 | 21 | |
| 22 | 22 | import GHC.Platform.Reg ( Reg )
|
| 23 | -import GHC.CmmToAsm.Format ( Format, RegWithFormat(..), isVecFormat )
|
|
| 23 | +import GHC.CmmToAsm.Format ( Format, RegWithFormat(..), isVecFormat,
|
|
| 24 | + compareFormat, formatToWidth )
|
|
| 24 | 25 | |
| 25 | 26 | import GHC.Utils.Outputable ( Outputable )
|
| 26 | 27 | import GHC.Types.Unique ( Uniquable(..) )
|
| ... | ... | @@ -39,11 +40,13 @@ newtype Regs = Regs { getRegs :: UniqSet RegWithFormat } |
| 39 | 40 | |
| 40 | 41 | maxRegWithFormat :: RegWithFormat -> RegWithFormat -> RegWithFormat
|
| 41 | 42 | maxRegWithFormat r1@(RegWithFormat _ fmt1) r2@(RegWithFormat _ fmt2)
|
| 42 | - = if fmt1 >= fmt2
|
|
| 43 | - then r1
|
|
| 44 | - else r2
|
|
| 45 | - -- Re-using one of the arguments avoids allocating a new 'RegWithFormat',
|
|
| 46 | - -- compared with returning 'RegWithFormat r1 (max fmt1 fmt2)'.
|
|
| 43 | + | LT <- compareFormat fmt1 fmt2 = r2
|
|
| 44 | + | otherwise = r1
|
|
| 45 | + -- The join must be a max in a total order ('compareFormat', not a width
|
|
| 46 | + -- comparison), or the liveness fixpoint may not converge.
|
|
| 47 | + -- See Note [Format order is width-major] in GHC.CmmToAsm.Format.
|
|
| 48 | + --
|
|
| 49 | + -- Re-using one of the arguments avoids allocating a new 'RegWithFormat'.
|
|
| 47 | 50 | |
| 48 | 51 | noRegs :: Regs
|
| 49 | 52 | noRegs = Regs emptyUniqSet
|
| ... | ... | @@ -66,7 +69,7 @@ minusCoveredRegs = coerce $ minusUniqSet_C f |
| 66 | 69 | where
|
| 67 | 70 | f :: RegWithFormat -> RegWithFormat -> Maybe RegWithFormat
|
| 68 | 71 | f r1@(RegWithFormat _ fmt1) (RegWithFormat _ fmt2) =
|
| 69 | - if fmt2 >= fmt1
|
|
| 72 | + if formatToWidth fmt2 >= formatToWidth fmt1
|
|
| 70 | 73 | ||
|
| 71 | 74 | not ( isVecFormat fmt1 )
|
| 72 | 75 | -- See Wrinkle [Don't allow scalar partial writes]
|
| ... | ... | @@ -99,7 +102,7 @@ shrinkingRegs = coerce $ minusUniqSet_C f |
| 99 | 102 | where
|
| 100 | 103 | f :: RegWithFormat -> RegWithFormat -> Maybe RegWithFormat
|
| 101 | 104 | f (RegWithFormat _ fmt1) r2@(RegWithFormat _ fmt2)
|
| 102 | - | fmt2 < fmt1
|
|
| 105 | + | formatToWidth fmt2 < formatToWidth fmt1
|
|
| 103 | 106 | = Just r2
|
| 104 | 107 | | otherwise
|
| 105 | 108 | = Nothing
|