Simon Jakobi pushed to branch wip/sjakobi/T27296-stable-simpl at Glasgow Haskell Compiler / GHC

Commits:

12 changed files:

Changes:

  • changelog.d/stable-core-dump-order-27296
    1
    +section: compiler
    
    2
    +synopsis: Add :ghc-flag:`-dstable-core-dump-order` to print top-level Core bindings in a stable, source-location-based order that does not depend on uniques, making Core dumps easier to diff.
    
    3
    +issues: #27296
    
    4
    +mrs: !16143

  • compiler/GHC/Core/Lint.hs
    ... ... @@ -327,12 +327,17 @@ dumpPassResult logger dump_core_sizes name_ppr_ctx mb_flag hdr extra_info binds
    327 327
       where
    
    328 328
         size_doc = sep [text "Result size of" <+> text hdr, nest 2 (equals <+> ppr (coreBindsStats binds))]
    
    329 329
     
    
    330
    +    -- See Note [Stable Core dump order] in GHC.Core.Ppr
    
    331
    +    binds' | sdocStableCoreDumpOrder (log_default_dump_context (logFlags logger))
    
    332
    +                        = sortCoreBindingsForDump binds
    
    333
    +           | otherwise  = binds
    
    334
    +
    
    330 335
         dump_doc  = vcat [ nest 2 extra_info
    
    331 336
                          , size_doc
    
    332 337
                          , blankLine
    
    333 338
                          , if dump_core_sizes
    
    334
    -                        then pprCoreBindingsWithSize binds
    
    335
    -                        else pprCoreBindings         binds
    
    339
    +                        then pprCoreBindingsWithSize binds'
    
    340
    +                        else pprCoreBindings         binds'
    
    336 341
                          , ppUnless (null rules) pp_rules ]
    
    337 342
         pp_rules = vcat [ blankLine
    
    338 343
                         , text "------ Local rules for imported ids --------"
    

  • compiler/GHC/Core/Ppr.hs
    ... ... @@ -19,6 +19,7 @@ module GHC.Core.Ppr (
    19 19
             pprCoreExpr, pprParendExpr,
    
    20 20
             pprCoreBinding, pprCoreBindings, pprCoreAlt,
    
    21 21
             pprCoreBindingWithSize, pprCoreBindingsWithSize,
    
    22
    +        sortCoreBindingsForDump,
    
    22 23
             pprCoreBinder, pprCoreBinders, pprId, pprIds,
    
    23 24
             pprRule, pprRules, pprOptCo,
    
    24 25
             pprOcc, pprOccWithTick
    
    ... ... @@ -30,7 +31,7 @@ import GHC.Core
    30 31
     import GHC.Core.Stats (exprStats)
    
    31 32
     import GHC.Types.Fixity (LexicalFixity(..))
    
    32 33
     import GHC.Types.Literal( pprLiteral )
    
    33
    -import GHC.Types.Name( pprInfixName, pprPrefixName )
    
    34
    +import GHC.Types.Name( pprInfixName, pprPrefixName, getOccString, getSrcSpan )
    
    34 35
     import GHC.Types.Var
    
    35 36
     import GHC.Types.Id
    
    36 37
     import GHC.Types.Id.Info
    
    ... ... @@ -44,9 +45,13 @@ import GHC.Core.Coercion
    44 45
     import GHC.Types.Basic
    
    45 46
     import GHC.Utils.Misc
    
    46 47
     import GHC.Utils.Outputable
    
    47
    -import GHC.Types.SrcLoc ( pprUserRealSpan )
    
    48
    +import GHC.Utils.Panic (panic)
    
    49
    +import GHC.Types.SrcLoc ( SrcSpan(..), srcSpanStartLine, srcSpanStartCol
    
    50
    +                        , pprUserRealSpan )
    
    48 51
     import GHC.Types.Tickish
    
    49 52
     
    
    53
    +import Data.List ( sortOn )
    
    54
    +
    
    50 55
     {-
    
    51 56
     ************************************************************************
    
    52 57
     *                                                                      *
    
    ... ... @@ -71,6 +76,70 @@ pprCoreBindingWithSize :: CoreBind -> SDoc
    71 76
     pprCoreBindingsWithSize = pprTopBinds sizeAnn
    
    72 77
     pprCoreBindingWithSize = pprTopBind sizeAnn
    
    73 78
     
    
    79
    +{- Note [Stable Core dump order]
    
    80
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    81
    +The order of top-level bindings in a Core dump (-ddump-simpl etc.) is the
    
    82
    +compiler's internal processing order, which is sensitive to Uniques. Uniques
    
    83
    +can shift whenever an unrelated upstream module changes, so the bindings get
    
    84
    +re-ordered and a textual diff of two dumps fails to line up the real changes
    
    85
    +(#27296).
    
    86
    +
    
    87
    +With -dstable-core-dump-order we reorder the top-level bindings at dump time into
    
    88
    +a stable, diffable order. 'sortCoreBindingsForDump' sorts by a key that is
    
    89
    +*independent of Uniques* (so the output is diffable even without
    
    90
    +-dsuppress-uniques):
    
    91
    +
    
    92
    +  1. the binder's source span (real spans in source order; noSrcSpan last).
    
    93
    +     Workers and specialisations inherit their origin's source span (see
    
    94
    +     'mkWorkerId' and 'newSpecIdSM'), so they cluster next to the binding they
    
    95
    +     come from.
    
    96
    +  2. a "$-rank" so that within one source span the compiler-derived binders sort
    
    97
    +     *before* the origin they come from (e.g. @$wfoo@ before @foo@), mirroring
    
    98
    +     GHC's default dependency order (the wrapper calls the worker, so the worker
    
    99
    +     comes first; specialisations likewise precede their origin). We rank by
    
    100
    +     whether the OccName *contains* a '$', which marks a derived binder: a worker
    
    101
    +     is @$wfoo@, but a call-site specialisation is tidied to @bar_$sfoo@ (no
    
    102
    +     leading '$'), so a leading-'$' test would miss it.
    
    103
    +  3. the OccName string, as a final lexical, deterministic tie-break.
    
    104
    +
    
    105
    +Recursive groups are never split: a 'Rec' is one 'CoreBind', placed as a unit by
    
    106
    +its earliest-source member, with its members sorted by the same key.
    
    107
    +
    
    108
    +-dstable-core-dump-order is opt-in; the default order is retained because it is
    
    109
    +useful for debugging the compiler itself.
    
    110
    +-}
    
    111
    +
    
    112
    +-- | Reorder a 'CoreProgram' into a stable, source-location-driven order for
    
    113
    +-- dumping. See Note [Stable Core dump order]. Used by 'dumpPassResult' when
    
    114
    +-- -dstable-core-dump-order is enabled.
    
    115
    +sortCoreBindingsForDump :: CoreProgram -> CoreProgram
    
    116
    +sortCoreBindingsForDump = sortOn bindKey . map sortRecMembers
    
    117
    +  where
    
    118
    +    sortRecMembers (Rec prs) = Rec (sortOn (bndrKey . fst) prs)
    
    119
    +    sortRecMembers b         = b
    
    120
    +
    
    121
    +    -- 'sortRecMembers' runs first, so a 'Rec' is already sorted by 'bndrKey'
    
    122
    +    -- when 'bindKey' sees it; its first member is therefore the minimum key.
    
    123
    +    bindKey :: CoreBind -> (Int, Int, Int, Int, String)
    
    124
    +    bindKey (NonRec b _)     = bndrKey b
    
    125
    +    bindKey (Rec ((b,_):_))  = bndrKey b
    
    126
    +    bindKey (Rec [])         = panic "sortCoreBindingsForDump: empty Rec"
    
    127
    +
    
    128
    +    bndrKey :: CoreBndr -> (Int, Int, Int, Int, String)
    
    129
    +    bndrKey b = (bucket, line, col, dollar_rank, s)
    
    130
    +      where
    
    131
    +        s = getOccString b
    
    132
    +        (bucket, line, col) = case getSrcSpan b of
    
    133
    +          RealSrcSpan rs _ -> (0, srcSpanStartLine rs, srcSpanStartCol rs)
    
    134
    +          _                -> (1, 0, 0)  -- noSrcSpan: sort last
    
    135
    +        -- A '$' anywhere in a tidied top-level OccName marks a compiler-derived
    
    136
    +        -- binder ($wfoo, but also call-site specialisations tidied to
    
    137
    +        -- bar_$sfoo); rank those before their origin within a shared source span,
    
    138
    +        -- mirroring GHC's default dependency order (the wrapper calls the worker,
    
    139
    +        -- so the worker comes first).
    
    140
    +        dollar_rank | '$' `elem` s = 0
    
    141
    +                    | otherwise    = 1
    
    142
    +
    
    74 143
     instance OutputableBndr b => Outputable (Bind b) where
    
    75 144
         ppr bind = ppr_bind noAnn bind
    
    76 145
     
    

  • compiler/GHC/Driver/DynFlags.hs
    ... ... @@ -1594,6 +1594,7 @@ initSDocContext dflags style = SDC
    1594 1594
       , sdocSuppressModulePrefixes      = gopt Opt_SuppressModulePrefixes dflags
    
    1595 1595
       , sdocSuppressStgExts             = gopt Opt_SuppressStgExts dflags
    
    1596 1596
       , sdocSuppressStgReps             = gopt Opt_SuppressStgReps dflags
    
    1597
    +  , sdocStableCoreDumpOrder         = gopt Opt_StableCoreDumpOrder dflags
    
    1597 1598
       , sdocErrorSpans                  = gopt Opt_ErrorSpans dflags
    
    1598 1599
       , sdocStarIsType                  = xopt LangExt.StarIsType dflags
    
    1599 1600
       , sdocLinearTypes                 = xopt LangExt.LinearTypes dflags
    

  • compiler/GHC/Driver/Flags.hs
    ... ... @@ -859,6 +859,10 @@ data GeneralFlag
    859 859
        | Opt_SuppressTimestamps -- ^ Suppress timestamps in dumps
    
    860 860
        | Opt_SuppressCoreSizes  -- ^ Suppress per binding Core size stats in dumps
    
    861 861
     
    
    862
    +   -- | Reorder top-level bindings in Core dumps into a stable, diffable order.
    
    863
    +   -- See Note [Stable Core dump order] in GHC.Core.Ppr.
    
    864
    +   | Opt_StableCoreDumpOrder
    
    865
    +
    
    862 866
        -- Error message suppression
    
    863 867
        | Opt_ShowErrorContext
    
    864 868
     
    

  • compiler/GHC/Driver/Session.hs
    ... ... @@ -2468,6 +2468,7 @@ dFlagsDeps = [
    2468 2468
       flagSpec "ppr-case-as-let"            Opt_PprCaseAsLet,
    
    2469 2469
       depFlagSpec' "ppr-ticks"              Opt_PprShowTicks
    
    2470 2470
          (\turn_on -> useInstead "-d" "suppress-ticks" (not turn_on)),
    
    2471
    +  flagSpec "stable-core-dump-order"     Opt_StableCoreDumpOrder,
    
    2471 2472
       flagSpec "suppress-ticks"             Opt_SuppressTicks,
    
    2472 2473
       depFlagSpec' "suppress-stg-free-vars" Opt_SuppressStgExts
    
    2473 2474
          (useInstead "-d" "suppress-stg-exts"),
    

  • compiler/GHC/Utils/Outputable.hs
    ... ... @@ -422,6 +422,7 @@ data SDocContext = SDC
    422 422
       , sdocSuppressModulePrefixes      :: !Bool
    
    423 423
       , sdocSuppressStgExts             :: !Bool
    
    424 424
       , sdocSuppressStgReps             :: !Bool
    
    425
    +  , sdocStableCoreDumpOrder         :: !Bool
    
    425 426
       , sdocErrorSpans                  :: !Bool
    
    426 427
       , sdocStarIsType                  :: !Bool
    
    427 428
       , sdocLinearTypes                 :: !Bool
    
    ... ... @@ -490,6 +491,7 @@ defaultSDocContext = SDC
    490 491
       , sdocSuppressModulePrefixes      = False
    
    491 492
       , sdocSuppressStgExts             = False
    
    492 493
       , sdocSuppressStgReps             = True
    
    494
    +  , sdocStableCoreDumpOrder         = False
    
    493 495
       , sdocErrorSpans                  = False
    
    494 496
       , sdocStarIsType                  = False
    
    495 497
       , sdocLinearTypes                 = False
    

  • docs/users_guide/debugging.rst
    ... ... @@ -959,6 +959,33 @@ parts that you are not interested in.
    959 959
         has shown you where to look, you can try again without
    
    960 960
         :ghc-flag:`-dsuppress-uniques`
    
    961 961
     
    
    962
    +.. ghc-flag:: -dstable-core-dump-order
    
    963
    +    :shortdesc: Reorder top-level bindings in Core dumps into a stable,
    
    964
    +        diffable order
    
    965
    +    :type: dynamic
    
    966
    +    :reverse: -dno-stable-core-dump-order
    
    967
    +    :category: verbosity
    
    968
    +
    
    969
    +    :since: FIXME
    
    970
    +
    
    971
    +    Normally the order of top-level bindings in a Core dump (such as the
    
    972
    +    output of :ghc-flag:`-ddump-simpl`) reflects the compiler's internal
    
    973
    +    processing order, which depends on ``Unique`` values. Those uniques can
    
    974
    +    shift whenever an unrelated upstream module changes, so the bindings get
    
    975
    +    re-ordered and a textual ``diff`` of two dumps fails to line up the real
    
    976
    +    changes.
    
    977
    +
    
    978
    +    This flag is opt-in and reorders the top-level bindings of Core dumps that
    
    979
    +    go through the pass-result printer (e.g. :ghc-flag:`-ddump-simpl`,
    
    980
    +    :ghc-flag:`-ddump-prep`, :ghc-flag:`-ddump-ds`,
    
    981
    +    :ghc-flag:`-ddump-simpl-iterations`) into a stable, source-location-driven
    
    982
    +    order that does not depend on uniques.
    
    983
    +
    
    984
    +    It is intended to be combined with :ghc-flag:`-dsuppress-uniques` when
    
    985
    +    diffing two dumps, but because the ordering does not depend on uniques the
    
    986
    +    output is also more diffable without it. The default (in-compiler) order is
    
    987
    +    retained because it is useful when debugging the compiler itself.
    
    988
    +
    
    962 989
     .. ghc-flag:: -dsuppress-idinfo
    
    963 990
         :shortdesc: Suppress extended information about identifiers where they
    
    964 991
             are bound
    

  • testsuite/tests/simplCore/should_compile/Makefile
    ... ... @@ -298,3 +298,15 @@ T17901:
    298 298
     	$(RM) -f T17901.o T17901.hi
    
    299 299
     	'$(TEST_HC)' $(TEST_HC_OPTS) -O -c -ddump-simpl -dsuppress-uniques T17901.hs | grep 'wombat'
    
    300 300
             # All three functions should get their case alternatives combined
    
    301
    +
    
    302
    +# T27296: check -dstable-core-dump-order on a small Data.Map-style module. The
    
    303
    +# sed allow-list prints, deduplicated, the top-level binders we care about in
    
    304
    +# dump order. It inspects names only, so it is insensitive to unrelated
    
    305
    +# Core-format churn.
    
    306
    +T27296:
    
    307
    +	$(RM) -f T27296.o T27296.hi
    
    308
    +	'$(TEST_HC)' $(TEST_HC_OPTS) -O -c -ddump-simpl -dsuppress-uniques \
    
    309
    +	    -dsuppress-idinfo -dsuppress-module-prefixes -dno-typeable-binds \
    
    310
    +	    -dstable-core-dump-order T27296.hs 2> /dev/null \
    
    311
    +	    | sed -nE 's/^(\$$fEqKey|\$$fOrdKey|\$$fOrdKey_\$$ccompare|size|lookupG|member|findI|insertG|insertManyI|insertTwoI|weight|balance|ratios|fromAscI|\$$wweight|findI_\$$slookupG|fromAscI_\$$sinsertG|lvl)( .*)?$$/\1/p' \
    
    312
    +	    | uniq

  • testsuite/tests/simplCore/should_compile/T27296.hs
    1
    +{-# LANGUAGE BangPatterns #-}
    
    2
    +
    
    3
    +-- See #27296 and Note [Stable Core dump order] in GHC.Core.Ppr.
    
    4
    +--
    
    5
    +-- A small Data.Map-style module exercising the trickier parts of the stable
    
    6
    +-- dump ordering: under -O it produces specialisations ($slookupG, $sinsertG),
    
    7
    +-- a worker ($wweight), a floated lvl, and derived Eq/Ord instances for a custom
    
    8
    +-- Key type, alongside the user functions. The source order is deliberately
    
    9
    +-- neither alphabetical nor the default dump order (insertG forward-references
    
    10
    +-- balance), so the test pins source-position ordering specifically.
    
    11
    +module T27296
    
    12
    +  ( Key(..), size, lookupG, member, findI, insertG, insertManyI, insertTwoI
    
    13
    +  , weight, balance, ratios, fromAscI )
    
    14
    +  where
    
    15
    +
    
    16
    +-- A custom key with a derived Ord instance: the derived $fEqKey/$fOrdKey
    
    17
    +-- binders inherit this declaration's source span, so they cluster here.
    
    18
    +data Key = Key Int deriving (Eq, Ord)
    
    19
    +
    
    20
    +data Map k a = Tip | Bin !Int k a !(Map k a) !(Map k a)
    
    21
    +
    
    22
    +data Sizes = Sizes !Int !Int
    
    23
    +
    
    24
    +size :: Map k a -> Int
    
    25
    +size Tip              = 0
    
    26
    +size (Bin sz _ _ _ _) = sz
    
    27
    +
    
    28
    +lookupG :: Ord k => k -> Map k a -> Maybe a
    
    29
    +lookupG _ Tip = Nothing
    
    30
    +lookupG k (Bin _ kx x l r) = case compare k kx of
    
    31
    +  LT -> lookupG k l
    
    32
    +  GT -> lookupG k r
    
    33
    +  EQ -> Just x
    
    34
    +{-# SPECIALISE lookupG :: Key -> Map Key a -> Maybe a #-}
    
    35
    +
    
    36
    +member :: Key -> Map Key a -> Bool
    
    37
    +member k m = case lookupG k m of
    
    38
    +  Nothing -> False
    
    39
    +  Just _  -> True
    
    40
    +
    
    41
    +findI :: Key -> Map Key a -> a -> a
    
    42
    +findI k m def = case lookupG k m of
    
    43
    +  Nothing -> def
    
    44
    +  Just v  -> v
    
    45
    +
    
    46
    +-- insertG references 'balance', which is defined further down (forward ref).
    
    47
    +insertG :: Ord k => k -> a -> Map k a -> Map k a
    
    48
    +insertG k x Tip = Bin 1 k x Tip Tip
    
    49
    +insertG k x (Bin sz kx kv l r) = case compare k kx of
    
    50
    +  LT -> balance kx kv (insertG k x l) r
    
    51
    +  GT -> balance kx kv l (insertG k x r)
    
    52
    +  EQ -> Bin sz k x l r
    
    53
    +{-# SPECIALISE insertG :: Key -> a -> Map Key a -> Map Key a #-}
    
    54
    +
    
    55
    +insertManyI :: [(Key, a)] -> Map Key a -> Map Key a
    
    56
    +insertManyI xs m0 = foldr (\(k, x) m -> insertG k x m) m0 xs
    
    57
    +
    
    58
    +insertTwoI :: Key -> Key -> a -> Map Key a
    
    59
    +insertTwoI k1 k2 x = insertG k1 x (insertG k2 x Tip)
    
    60
    +
    
    61
    +-- weight unboxes the strict fields of Sizes -> worker/wrapper $wweight.
    
    62
    +weight :: Sizes -> Int
    
    63
    +weight (Sizes a b) = a * a + 3 * b * b + a * b + 1
    
    64
    +
    
    65
    +balance :: k -> a -> Map k a -> Map k a -> Map k a
    
    66
    +balance k x l r = Bin (weight (Sizes sl sr)) k x l r
    
    67
    +  where
    
    68
    +    sl = size l
    
    69
    +    sr = size r
    
    70
    +
    
    71
    +-- baseRatios is a closed constant under a lambda -> floated to a top-level lvl.
    
    72
    +ratios :: Int -> [Int]
    
    73
    +ratios n = map (n +) baseRatios
    
    74
    +  where baseRatios = [2, 3, 5, 7, 11, 13]
    
    75
    +
    
    76
    +fromAscI :: [(Key, a)] -> Map Key a
    
    77
    +fromAscI = foldr (\(k, x) m -> insertG k x m) Tip

  • testsuite/tests/simplCore/should_compile/T27296.stdout
    1
    +$fEqKey
    
    2
    +$fOrdKey
    
    3
    +$fOrdKey_$ccompare
    
    4
    +size
    
    5
    +findI_$slookupG
    
    6
    +lookupG
    
    7
    +member
    
    8
    +findI
    
    9
    +fromAscI_$sinsertG
    
    10
    +insertG
    
    11
    +insertManyI
    
    12
    +insertTwoI
    
    13
    +$wweight
    
    14
    +weight
    
    15
    +balance
    
    16
    +ratios
    
    17
    +fromAscI
    
    18
    +lvl

  • testsuite/tests/simplCore/should_compile/all.T
    ... ... @@ -602,3 +602,4 @@ test('T25718b', normal, compile, ['-O -ddump-simpl -dsuppress-uniques -dsuppress
    602 602
     test('T25718c', normal, compile, ['-O -ddump-simpl -dsuppress-uniques -dsuppress-all -dno-typeable-binds'])
    
    603 603
     test('T19166', normal, compile, ['-O -ddump-simpl -dsuppress-uniques -dsuppress-all -dno-typeable-binds'])
    
    604 604
     test('T27261', [extra_files(['T27261_aux.hs'])], multimod_compile, ['T27261', '-v0 -O'])
    
    605
    +test('T27296', [], makefile_test, ['T27296'])