Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC
Commits:
-
2f6a5534
by Simon Jakobi at 2026-06-23T15:46:20+02:00
14 changed files:
- + changelog.d/stable-core-dump-order-27296
- compiler/GHC/Core/Lint.hs
- compiler/GHC/Core/Ppr.hs
- compiler/GHC/Driver/DynFlags.hs
- compiler/GHC/Driver/Flags.hs
- compiler/GHC/Driver/Session.hs
- compiler/GHC/Utils/Outputable.hs
- docs/users_guide/debugging.rst
- testsuite/tests/simplCore/should_compile/Makefile
- + testsuite/tests/simplCore/should_compile/T27296.hs
- + testsuite/tests/simplCore/should_compile/T27296.stdout
- + testsuite/tests/simplCore/should_compile/T27296b.hs
- + testsuite/tests/simplCore/should_compile/T27296b.stdout
- testsuite/tests/simplCore/should_compile/all.T
Changes:
| 1 | +section: compiler
|
|
| 2 | +synopsis: Add :ghc-flag:`-dstable-core-dump-order`, a debugging flag that prints top-level Core bindings in a stable, source-location-based order that does not depend on uniques, making intermediate-compiler dumps (e.g. with :ghc-flag:`-ddump-simpl` or :ghc-flag:`-dverbose-core2core`) easier to diff. This affects only the compiler's intermediate output; it does not change generated code.
|
|
| 3 | +issues: #27296
|
|
| 4 | +mrs: !16143 |
| ... | ... | @@ -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 --------"
|
| ... | ... | @@ -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
|
| ... | ... | @@ -27,10 +28,11 @@ module GHC.Core.Ppr ( |
| 27 | 28 | import GHC.Prelude
|
| 28 | 29 | |
| 29 | 30 | import GHC.Core
|
| 30 | -import GHC.Core.Stats (exprStats)
|
|
| 31 | +import GHC.Core.Stats (CoreStats(..), exprStats)
|
|
| 32 | +import GHC.Data.FastString (LexicalFastString(..), fastStringToShortByteString)
|
|
| 31 | 33 | import GHC.Types.Fixity (LexicalFixity(..))
|
| 32 | -import GHC.Types.Literal( pprLiteral )
|
|
| 33 | -import GHC.Types.Name( pprInfixName, pprPrefixName )
|
|
| 34 | +import GHC.Types.Literal( Literal, pprLiteral )
|
|
| 35 | +import GHC.Types.Name( getOccFS, getSrcSpan, pprInfixName, pprPrefixName )
|
|
| 34 | 36 | import GHC.Types.Var
|
| 35 | 37 | import GHC.Types.Id
|
| 36 | 38 | import GHC.Types.Id.Info
|
| ... | ... | @@ -44,9 +46,15 @@ import GHC.Core.Coercion |
| 44 | 46 | import GHC.Types.Basic
|
| 45 | 47 | import GHC.Utils.Misc
|
| 46 | 48 | import GHC.Utils.Outputable
|
| 47 | -import GHC.Types.SrcLoc ( pprUserRealSpan )
|
|
| 49 | +import GHC.Utils.Panic (panic)
|
|
| 50 | +import GHC.Types.SrcLoc ( SrcSpan(..), pprUserRealSpan, srcSpanStartCol
|
|
| 51 | + , srcSpanStartLine )
|
|
| 48 | 52 | import GHC.Types.Tickish
|
| 49 | 53 | |
| 54 | +import Data.List ( sortOn )
|
|
| 55 | +import Data.Char ( ord )
|
|
| 56 | +import qualified Data.ByteString.Short as SBS
|
|
| 57 | + |
|
| 50 | 58 | {-
|
| 51 | 59 | ************************************************************************
|
| 52 | 60 | * *
|
| ... | ... | @@ -71,6 +79,117 @@ pprCoreBindingWithSize :: CoreBind -> SDoc |
| 71 | 79 | pprCoreBindingsWithSize = pprTopBinds sizeAnn
|
| 72 | 80 | pprCoreBindingWithSize = pprTopBind sizeAnn
|
| 73 | 81 | |
| 82 | +{- Note [Stable Core dump order]
|
|
| 83 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 84 | +The order of top-level bindings in a Core dump (-ddump-simpl etc.) is the
|
|
| 85 | +compiler's internal processing order, which is sensitive to Uniques. Uniques
|
|
| 86 | +can shift whenever an unrelated upstream module changes, so the bindings get
|
|
| 87 | +re-ordered and a textual diff of two dumps fails to line up the real changes
|
|
| 88 | +(#27296).
|
|
| 89 | + |
|
| 90 | +With -dstable-core-dump-order we reorder the top-level bindings at dump time into
|
|
| 91 | +a stable order. 'sortCoreBindingsForDump' sorts by a key that is *independent of
|
|
| 92 | +Uniques*, so two dumps line up across rebuilds. The sort key is:
|
|
| 93 | + |
|
| 94 | + 1. the binder's source span (real spans in source order; noSrcSpan last).
|
|
| 95 | + Workers and specialisations inherit their origin's source span (see
|
|
| 96 | + 'mkWorkerId' and 'newSpecIdSM'), so they cluster next to the binding they
|
|
| 97 | + come from.
|
|
| 98 | + 2. a "$-rank" so that within one source span the compiler-derived binders sort
|
|
| 99 | + *before* the origin they come from (e.g. @$wfoo@ before @foo@), mirroring
|
|
| 100 | + GHC's default dependency order (the wrapper calls the worker, so the worker
|
|
| 101 | + comes first; specialisations likewise precede their origin). We rank by
|
|
| 102 | + whether the OccName *contains* a '$', which marks a derived binder: a worker
|
|
| 103 | + is @$wfoo@, but a call-site specialisation is tidied to @bar_$sfoo@ (no
|
|
| 104 | + leading '$'), so a leading-'$' test would miss it.
|
|
| 105 | + 3. the OccName string, as a lexical, deterministic tie-break.
|
|
| 106 | + 4. a content-based tie-break on the right-hand side ('rhsKey'): the floated
|
|
| 107 | + literal, if any, then the RHS size statistics. This matters for the
|
|
| 108 | + anonymous floats: 'newLvlVar' builds them all with OccName "lvl" and
|
|
| 109 | + noSrcSpan, so keys 1-3 are identical and without it their order would fall
|
|
| 110 | + back to the Unique-driven input order -- the churn we set out to remove.
|
|
| 111 | + (Tidied dumps like -ddump-simpl give the floats distinct names lvl,
|
|
| 112 | + lvl1, ...; this additionally stabilises untidied dumps such as
|
|
| 113 | + -ddump-simpl-iterations.) It is only a best-effort tie-break -- RHSs
|
|
| 114 | + agreeing on both components keep their input order -- and Unique-independent
|
|
| 115 | + for the numeric CAFs we target (a rubbish literal is the exception: its
|
|
| 116 | + 'cmpLit' falls back to the Unique-dependent 'nonDetCmpType').
|
|
| 117 | + |
|
| 118 | +Recursive groups are never split: a 'Rec' is one 'CoreBind', placed as a unit by
|
|
| 119 | +its earliest-source member, with its members sorted by the same key.
|
|
| 120 | + |
|
| 121 | +Only *top-level* bindings (and the members of a top-level 'Rec') are reordered.
|
|
| 122 | +Bindings nested inside a right-hand side (a 'let'/'letrec' within an expression)
|
|
| 123 | +are left in their original order: their position in the dump is fixed by the
|
|
| 124 | +surrounding expression rather than chosen by a Unique-keyed sort, so they don't
|
|
| 125 | +suffer the cross-module churn this flag addresses.
|
|
| 126 | + |
|
| 127 | +-dstable-core-dump-order is opt-in; the default order is retained because it is
|
|
| 128 | +useful for debugging the compiler itself.
|
|
| 129 | +-}
|
|
| 130 | + |
|
| 131 | +-- | The sort key for one top-level binder. The trailing 'RhsKey' is a
|
|
| 132 | +-- content-based tiebreak, used only when two binders agree on everything
|
|
| 133 | +-- before it. See Note [Stable Core dump order].
|
|
| 134 | +type DumpSortKey =
|
|
| 135 | + ( Int -- source-span bucket: 0 = real span, 1 = noSrcSpan (sorts last)
|
|
| 136 | + , Int -- source-span start line
|
|
| 137 | + , Int -- source-span start column
|
|
| 138 | + , Int -- dollar-rank: 0 = derived ($w/$s) binder, 1 = its origin
|
|
| 139 | + , LexicalFastString -- the OccName, compared lexically
|
|
| 140 | + , RhsKey -- content-based tiebreak (see 'rhsKey')
|
|
| 141 | + )
|
|
| 142 | + |
|
| 143 | +-- | Reorder a 'CoreProgram' into a stable, source-location-driven order for
|
|
| 144 | +-- dumping. See Note [Stable Core dump order]. Used by 'dumpPassResult' when
|
|
| 145 | +-- -dstable-core-dump-order is enabled.
|
|
| 146 | +sortCoreBindingsForDump :: CoreProgram -> CoreProgram
|
|
| 147 | +sortCoreBindingsForDump = sortOn bindKey . map sortRecMembers
|
|
| 148 | + where
|
|
| 149 | + sortRecMembers (Rec prs) = Rec (sortOn (uncurry elemKey) prs)
|
|
| 150 | + sortRecMembers b = b
|
|
| 151 | + |
|
| 152 | + -- 'sortRecMembers' runs first, so a 'Rec' is already sorted by 'elemKey'
|
|
| 153 | + -- when 'bindKey' sees it; its first member is therefore the minimum key.
|
|
| 154 | + bindKey :: CoreBind -> DumpSortKey
|
|
| 155 | + bindKey (NonRec b rhs) = elemKey b rhs
|
|
| 156 | + bindKey (Rec ((b,rhs):_)) = elemKey b rhs
|
|
| 157 | + bindKey (Rec []) = panic "sortCoreBindingsForDump: empty Rec"
|
|
| 158 | + |
|
| 159 | + elemKey :: CoreBndr -> CoreExpr -> DumpSortKey
|
|
| 160 | + elemKey b rhs = (bucket, line, col, dollar_rank, LexicalFastString nm, rhsKey rhs)
|
|
| 161 | + where
|
|
| 162 | + nm = getOccFS b
|
|
| 163 | + (bucket, line, col) = case getSrcSpan b of
|
|
| 164 | + RealSrcSpan rs _ -> (0, srcSpanStartLine rs, srcSpanStartCol rs)
|
|
| 165 | + _ -> (1, 0, 0) -- noSrcSpan: sort last
|
|
| 166 | + -- A '$' anywhere in a tidied top-level OccName marks a compiler-derived
|
|
| 167 | + -- binder ($wfoo, but also call-site specialisations tidied to
|
|
| 168 | + -- bar_$sfoo); rank those before their origin within a shared source span,
|
|
| 169 | + -- mirroring GHC's default dependency order (the wrapper calls the worker,
|
|
| 170 | + -- so the worker comes first).
|
|
| 171 | + dollar_rank | dollarByte `SBS.elem` fastStringToShortByteString nm = 0
|
|
| 172 | + | otherwise = 1
|
|
| 173 | + |
|
| 174 | + dollarByte = fromIntegral (ord '$')
|
|
| 175 | + |
|
| 176 | +-- | A content-based tie-break on a binder's right-hand side: see point 4 of
|
|
| 177 | +-- Note [Stable Core dump order].
|
|
| 178 | +type RhsKey =
|
|
| 179 | + ( Maybe Literal -- the floated literal, if any (Nothing sorts first)
|
|
| 180 | + , (Int, Int, Int, Int, Int) -- exprStats counts: terms, types, coercions, value binds, join binds
|
|
| 181 | + )
|
|
| 182 | + |
|
| 183 | +rhsKey :: CoreExpr -> RhsKey
|
|
| 184 | +rhsKey rhs = (litOf rhs, statsTuple (exprStats rhs))
|
|
| 185 | + where
|
|
| 186 | + statsTuple (CS tm ty co vb jb) = (tm, ty, co, vb, jb)
|
|
| 187 | + litOf (Lit l) = Just l
|
|
| 188 | + litOf (App f a) = case a of { Lit l -> Just l; _ -> litOf f }
|
|
| 189 | + litOf (Cast e _) = litOf e
|
|
| 190 | + litOf (Tick _ e) = litOf e
|
|
| 191 | + litOf _ = Nothing
|
|
| 192 | + |
|
| 74 | 193 | instance OutputableBndr b => Outputable (Bind b) where
|
| 75 | 194 | ppr bind = ppr_bind noAnn bind
|
| 76 | 195 |
| ... | ... | @@ -1595,6 +1595,7 @@ initSDocContext dflags style = SDC |
| 1595 | 1595 | , sdocSuppressModulePrefixes = gopt Opt_SuppressModulePrefixes dflags
|
| 1596 | 1596 | , sdocSuppressStgExts = gopt Opt_SuppressStgExts dflags
|
| 1597 | 1597 | , sdocSuppressStgReps = gopt Opt_SuppressStgReps dflags
|
| 1598 | + , sdocStableCoreDumpOrder = gopt Opt_StableCoreDumpOrder dflags
|
|
| 1598 | 1599 | , sdocErrorSpans = gopt Opt_ErrorSpans dflags
|
| 1599 | 1600 | , sdocStarIsType = xopt LangExt.StarIsType dflags
|
| 1600 | 1601 | , sdocLinearTypes = xopt LangExt.LinearTypes dflags
|
| ... | ... | @@ -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 |
| ... | ... | @@ -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"),
|
| ... | ... | @@ -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
|
| ... | ... | @@ -959,6 +959,32 @@ 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: 10.2.1
|
|
| 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.
|
|
| 987 | + |
|
| 962 | 988 | .. ghc-flag:: -dsuppress-idinfo
|
| 963 | 989 | :shortdesc: Suppress extended information about identifiers where they
|
| 964 | 990 | are bound
|
| ... | ... | @@ -298,3 +298,31 @@ 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 | +# 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 | +#
|
|
| 307 | +# The allow-list covers one binder of each interesting category, so the test
|
|
| 308 | +# exercises the clustering of generated binders next to their origin:
|
|
| 309 | +# * derived instances ($fEqKey/$fOrdKey/$fOrdKey_$ccompare),
|
|
| 310 | +# * a call-site specialisation (findI_$slookupG, from lookupG's SPECIALISE), and
|
|
| 311 | +# * a recursive worker ($wrotate).
|
|
| 312 | +T27296:
|
|
| 313 | + $(RM) -f T27296.o T27296.hi
|
|
| 314 | + '$(TEST_HC)' $(TEST_HC_OPTS) -O -c -ddump-simpl -dsuppress-uniques \
|
|
| 315 | + -dsuppress-idinfo -dsuppress-module-prefixes -dno-typeable-binds \
|
|
| 316 | + -dstable-core-dump-order T27296.hs 2> /dev/null \
|
|
| 317 | + | sed -nE 's/^(\$$fEqKey|\$$fOrdKey|\$$fOrdKey_\$$ccompare|size|findI_\$$slookupG|lookupG|member|findI|\$$wrotate|rotate|insertG|insertManyI|insertTwoI|weight|balance|ratios|fromAscI)( .*)?$$/\1/p' \
|
|
| 318 | + | uniq
|
|
| 319 | + |
|
| 320 | +# See T27296b.hs for what this pins and why. The six floated "lvl" constants
|
|
| 321 | +# are scrambled in source order; grep them out and the dump coming out
|
|
| 322 | +# 1000..6000 confirms the stable, value-ordered float ordering.
|
|
| 323 | +T27296b:
|
|
| 324 | + $(RM) -f T27296b.o T27296b.hi
|
|
| 325 | + '$(TEST_HC)' $(TEST_HC_OPTS) -O -c -ddump-float-out -dsuppress-uniques \
|
|
| 326 | + -dsuppress-idinfo -dsuppress-module-prefixes -dno-typeable-binds \
|
|
| 327 | + -dstable-core-dump-order T27296b.hs 2> /dev/null \
|
|
| 328 | + | grep '^lvl = I#' |
| 1 | +{-# LANGUAGE BangPatterns #-}
|
|
| 2 | + |
|
| 3 | +-- See 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, alongside the user functions:
|
|
| 7 | +-- * derived Eq/Ord instances for a custom Key type ($fEqKey/$fOrdKey/...),
|
|
| 8 | +-- * a call-site specialisation of lookupG (findI_$slookupG), and
|
|
| 9 | +-- * a worker/wrapper split of the recursive, strict rotate ($wrotate).
|
|
| 10 | +-- Each generated binder inherits its origin's source span, so the stable order
|
|
| 11 | +-- clusters it next to that origin. The source order is deliberately neither
|
|
| 12 | +-- alphabetical nor the default dump order (insertG forward-references balance),
|
|
| 13 | +-- so the test pins source-position ordering specifically.
|
|
| 14 | +module T27296
|
|
| 15 | + ( Key(..), size, lookupG, member, findI, rotate, insertG, insertManyI
|
|
| 16 | + , insertTwoI, weight, balance, ratios, fromAscI )
|
|
| 17 | + where
|
|
| 18 | + |
|
| 19 | +-- A custom key with a derived Ord instance: the derived $fEqKey/$fOrdKey
|
|
| 20 | +-- binders inherit this declaration's source span, so they cluster here.
|
|
| 21 | +data Key = Key Int deriving (Eq, Ord)
|
|
| 22 | + |
|
| 23 | +data Map k a = Tip | Bin !Int k a !(Map k a) !(Map k a)
|
|
| 24 | + |
|
| 25 | +data Sizes = Sizes !Int !Int
|
|
| 26 | + |
|
| 27 | +size :: Map k a -> Int
|
|
| 28 | +size Tip = 0
|
|
| 29 | +size (Bin sz _ _ _ _) = sz
|
|
| 30 | + |
|
| 31 | +lookupG :: Ord k => k -> Map k a -> Maybe a
|
|
| 32 | +lookupG _ Tip = Nothing
|
|
| 33 | +lookupG k (Bin _ kx x l r) = case compare k kx of
|
|
| 34 | + LT -> lookupG k l
|
|
| 35 | + GT -> lookupG k r
|
|
| 36 | + EQ -> Just x
|
|
| 37 | +{-# SPECIALISE lookupG :: Key -> Map Key a -> Maybe a #-}
|
|
| 38 | + |
|
| 39 | +member :: Key -> Map Key a -> Bool
|
|
| 40 | +member k m = case lookupG k m of
|
|
| 41 | + Nothing -> False
|
|
| 42 | + Just _ -> True
|
|
| 43 | + |
|
| 44 | +findI :: Key -> Map Key a -> a -> a
|
|
| 45 | +findI k m def = case lookupG k m of
|
|
| 46 | + Nothing -> def
|
|
| 47 | + Just v -> v
|
|
| 48 | + |
|
| 49 | +-- rotate is recursive and strict in the product 'Sizes', so worker/wrapper
|
|
| 50 | +-- unboxes it into a recursive worker ($wrotate). The loop only repackages the
|
|
| 51 | +-- fields (no arithmetic), so the worker is stable across build flavours.
|
|
| 52 | +rotate :: Sizes -> [a] -> Sizes
|
|
| 53 | +rotate s [] = s
|
|
| 54 | +rotate (Sizes a b) (_:xs) = rotate (Sizes b a) xs
|
|
| 55 | + |
|
| 56 | +-- insertG references 'balance', which is defined further down (forward ref).
|
|
| 57 | +insertG :: Ord k => k -> a -> Map k a -> Map k a
|
|
| 58 | +insertG k x Tip = Bin 1 k x Tip Tip
|
|
| 59 | +insertG k x (Bin sz kx kv l r) = case compare k kx of
|
|
| 60 | + LT -> balance kx kv (insertG k x l) r
|
|
| 61 | + GT -> balance kx kv l (insertG k x r)
|
|
| 62 | + EQ -> Bin sz k x l r
|
|
| 63 | +{-# SPECIALISE insertG :: Key -> a -> Map Key a -> Map Key a #-}
|
|
| 64 | + |
|
| 65 | +insertManyI :: [(Key, a)] -> Map Key a -> Map Key a
|
|
| 66 | +insertManyI xs m0 = foldr (\(k, x) m -> insertG k x m) m0 xs
|
|
| 67 | + |
|
| 68 | +insertTwoI :: Key -> Key -> a -> Map Key a
|
|
| 69 | +insertTwoI k1 k2 x = insertG k1 x (insertG k2 x Tip)
|
|
| 70 | + |
|
| 71 | +-- weight unboxes the strict fields of Sizes -> worker/wrapper $wweight.
|
|
| 72 | +weight :: Sizes -> Int
|
|
| 73 | +weight (Sizes a b) = a * a + 3 * b * b + a * b + 1
|
|
| 74 | + |
|
| 75 | +balance :: k -> a -> Map k a -> Map k a -> Map k a
|
|
| 76 | +balance k x l r = Bin (weight (Sizes sl sr)) k x l r
|
|
| 77 | + where
|
|
| 78 | + sl = size l
|
|
| 79 | + sr = size r
|
|
| 80 | + |
|
| 81 | +-- baseRatios is a closed constant under a lambda -> floated to a top-level lvl.
|
|
| 82 | +ratios :: Int -> [Int]
|
|
| 83 | +ratios n = map (n +) baseRatios
|
|
| 84 | + where baseRatios = [2, 3, 5, 7, 11, 13]
|
|
| 85 | + |
|
| 86 | +fromAscI :: [(Key, a)] -> Map Key a
|
|
| 87 | +fromAscI = foldr (\(k, x) m -> insertG k x m) Tip |
| 1 | +$fEqKey
|
|
| 2 | +$fOrdKey
|
|
| 3 | +$fOrdKey_$ccompare
|
|
| 4 | +size
|
|
| 5 | +findI_$slookupG
|
|
| 6 | +lookupG
|
|
| 7 | +member
|
|
| 8 | +findI
|
|
| 9 | +$wrotate
|
|
| 10 | +rotate
|
|
| 11 | +insertG
|
|
| 12 | +insertManyI
|
|
| 13 | +insertTwoI
|
|
| 14 | +weight
|
|
| 15 | +balance
|
|
| 16 | +ratios
|
|
| 17 | +fromAscI |
| 1 | +-- See Note [Stable Core dump order] in GHC.Core.Ppr.
|
|
| 2 | +--
|
|
| 3 | +-- Companion to T27296 that pins the ordering of *anonymous* top-level floats.
|
|
| 4 | +-- Under -O the boxed Int constants in sel's branches are floated to top level
|
|
| 5 | +-- as separate CAFs, all of which the compiler names "lvl" with noSrcSpan (see
|
|
| 6 | +-- newLvlVar). Before -dstable-core-dump-order their dump order was the
|
|
| 7 | +-- unique-driven processing order; the flag's content-based tie-break (rhsKey)
|
|
| 8 | +-- now orders them by literal value -- here 1000..6000, despite the scrambled
|
|
| 9 | +-- source order. This dump is intentionally *untidied* (-ddump-float-out), the
|
|
| 10 | +-- only place the "lvl" collision is observable; tidied dumps like -ddump-simpl
|
|
| 11 | +-- already give the floats distinct names (lvl, lvl1, ...).
|
|
| 12 | +module T27296b (sel) where
|
|
| 13 | + |
|
| 14 | +{-# NOINLINE sel #-}
|
|
| 15 | +sel :: Int -> Int
|
|
| 16 | +sel 0 = 5000
|
|
| 17 | +sel 1 = 1000
|
|
| 18 | +sel 2 = 4000
|
|
| 19 | +sel 3 = 2000
|
|
| 20 | +sel 4 = 3000
|
|
| 21 | +sel _ = 6000 |
| 1 | +lvl = I# 1000#
|
|
| 2 | +lvl = I# 2000#
|
|
| 3 | +lvl = I# 3000#
|
|
| 4 | +lvl = I# 4000#
|
|
| 5 | +lvl = I# 5000#
|
|
| 6 | +lvl = I# 6000# |
| ... | ... | @@ -604,3 +604,5 @@ test('T19166', normal, compile, ['-O -ddump-simpl -dsuppress-uniques -dsuppress- |
| 604 | 604 | # #4081: the strict field of T should be unboxed once, outside the loop.
|
| 605 | 605 | test('T4081', normal, compile, ['-O -ddump-simpl -dsuppress-uniques -dsuppress-all -dno-typeable-binds'])
|
| 606 | 606 | test('T27261', [extra_files(['T27261_aux.hs'])], multimod_compile, ['T27261', '-v0 -O'])
|
| 607 | +test('T27296', [], makefile_test, ['T27296'])
|
|
| 608 | +test('T27296b', [], makefile_test, ['T27296b']) |