[Git][ghc/ghc][wip/sjakobi/T27296-stable-simpl] 2 commits: Add -dstable-core-dump-order for stable Core dump ordering (#27296)
by Simon Jakobi (@sjakobi2) 08 Jun '26
by Simon Jakobi (@sjakobi2) 08 Jun '26
08 Jun '26
Simon Jakobi pushed to branch wip/sjakobi/T27296-stable-simpl at Glasgow Haskell Compiler / GHC
Commits:
b63f205e by Simon Jakobi at 2026-06-08T10:04:50+02:00
Add -dstable-core-dump-order for stable Core dump ordering (#27296)
The order of top-level bindings in Core dumps (-ddump-simpl etc.) is the
compiler's internal processing order, which is sensitive to Uniques.
Uniques can shift whenever an unrelated upstream module changes, so the
bindings get re-ordered and a textual diff of two dumps fails to line up
the real changes.
This adds an opt-in flag -dstable-core-dump-order that reorders the
top-level bindings of Core dumps routed through dumpPassResult into a
stable, unique-independent order: by source location, then a $-rank so a
derived $w/$s binder sorts before its origin (mirroring GHC's default
dependency order, where the wrapper calls the worker), then the OccName.
Workers and specialisations inherit their origin's source span, so they
cluster next to the binding they come from; anonymous floats (noSrcSpan)
sort to the end; Rec groups are kept intact. Only top-level bindings are
reordered; nested bindings are left as-is. The default order is retained
as it is useful when debugging the compiler itself.
The ordering is unique-independent, so two dumps line up across rebuilds.
See Note [Stable Core dump order] in GHC.Core.Ppr.
Adds test T27296, a small Data.Map-style module whose binders GHC emits
in a non-source order by default, asserting they come out stably ordered
under the flag.
Co-Authored-By: Claude Opus 4.7 <noreply(a)anthropic.com>
- - - - -
5a4d2cda by Simon Jakobi at 2026-06-08T12:21:30+02:00
WIP: stabilise anonymous float ordering in untidied Core dumps
Anonymous floats are all built with OccName "lvl" and noSrcSpan
(newLvlVar), so the source-span/name sort key is identical for every
one of them; sortOn then falls back to the unique-driven input order --
the very churn -dstable-core-dump-order is meant to remove. (Tidied
dumps like -ddump-simpl are unaffected, as tidy gives the floats
distinct names lvl, lvl1, ...)
Add a content-based, unique-independent tie-break (rhsKey): the floated
literal, if any, then the RHS size statistics. Laziness means it is
computed only when the earlier key components tie.
Add test T27296b pinning the float ordering in an untidied
-ddump-float-out dump.
Co-Authored-By: Claude Opus 4.7 <noreply(a)anthropic.com>
- - - - -
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.stderr
- testsuite/tests/simplCore/should_compile/all.T
Changes:
=====================================
changelog.d/stable-core-dump-order-27296
=====================================
@@ -0,0 +1,4 @@
+section: compiler
+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.
+issues: #27296
+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
where
size_doc = sep [text "Result size of" <+> text hdr, nest 2 (equals <+> ppr (coreBindsStats binds))]
+ -- See Note [Stable Core dump order] in GHC.Core.Ppr
+ binds' | sdocStableCoreDumpOrder (log_default_dump_context (logFlags logger))
+ = sortCoreBindingsForDump binds
+ | otherwise = binds
+
dump_doc = vcat [ nest 2 extra_info
, size_doc
, blankLine
, if dump_core_sizes
- then pprCoreBindingsWithSize binds
- else pprCoreBindings binds
+ then pprCoreBindingsWithSize binds'
+ else pprCoreBindings binds'
, ppUnless (null rules) pp_rules ]
pp_rules = vcat [ blankLine
, text "------ Local rules for imported ids --------"
=====================================
compiler/GHC/Core/Ppr.hs
=====================================
@@ -19,6 +19,7 @@ module GHC.Core.Ppr (
pprCoreExpr, pprParendExpr,
pprCoreBinding, pprCoreBindings, pprCoreAlt,
pprCoreBindingWithSize, pprCoreBindingsWithSize,
+ sortCoreBindingsForDump,
pprCoreBinder, pprCoreBinders, pprId, pprIds,
pprRule, pprRules, pprOptCo,
pprOcc, pprOccWithTick
@@ -27,10 +28,10 @@ module GHC.Core.Ppr (
import GHC.Prelude
import GHC.Core
-import GHC.Core.Stats (exprStats)
+import GHC.Core.Stats (exprStats, CoreStats(..))
import GHC.Types.Fixity (LexicalFixity(..))
-import GHC.Types.Literal( pprLiteral )
-import GHC.Types.Name( pprInfixName, pprPrefixName )
+import GHC.Types.Literal( pprLiteral, Literal )
+import GHC.Types.Name( pprInfixName, pprPrefixName, getOccString, getSrcSpan )
import GHC.Types.Var
import GHC.Types.Id
import GHC.Types.Id.Info
@@ -44,9 +45,13 @@ import GHC.Core.Coercion
import GHC.Types.Basic
import GHC.Utils.Misc
import GHC.Utils.Outputable
-import GHC.Types.SrcLoc ( pprUserRealSpan )
+import GHC.Utils.Panic (panic)
+import GHC.Types.SrcLoc ( SrcSpan(..), srcSpanStartLine, srcSpanStartCol
+ , pprUserRealSpan )
import GHC.Types.Tickish
+import Data.List ( sortOn )
+
{-
************************************************************************
* *
@@ -71,6 +76,119 @@ pprCoreBindingWithSize :: CoreBind -> SDoc
pprCoreBindingsWithSize = pprTopBinds sizeAnn
pprCoreBindingWithSize = pprTopBind sizeAnn
+{- Note [Stable Core dump order]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+The order of top-level bindings in a Core dump (-ddump-simpl etc.) is the
+compiler's internal processing order, which is sensitive to Uniques. Uniques
+can shift whenever an unrelated upstream module changes, so the bindings get
+re-ordered and a textual diff of two dumps fails to line up the real changes
+(#27296).
+
+With -dstable-core-dump-order we reorder the top-level bindings at dump time into
+a stable order. 'sortCoreBindingsForDump' sorts by a key that is *independent of
+Uniques*, so two dumps line up across rebuilds. The sort key is:
+
+ 1. the binder's source span (real spans in source order; noSrcSpan last).
+ Workers and specialisations inherit their origin's source span (see
+ 'mkWorkerId' and 'newSpecIdSM'), so they cluster next to the binding they
+ come from.
+ 2. a "$-rank" so that within one source span the compiler-derived binders sort
+ *before* the origin they come from (e.g. @$wfoo@ before @foo@), mirroring
+ GHC's default dependency order (the wrapper calls the worker, so the worker
+ comes first; specialisations likewise precede their origin). We rank by
+ whether the OccName *contains* a '$', which marks a derived binder: a worker
+ is @$wfoo@, but a call-site specialisation is tidied to @bar_$sfoo@ (no
+ leading '$'), so a leading-'$' test would miss it.
+ 3. the OccName string, as a lexical, deterministic tie-break.
+ 4. a content-based tie-break on the right-hand side ('rhsKey'): the floated
+ literal, if any, then the RHS size statistics. This matters for the
+ anonymous floats: 'newLvlVar' builds them all with OccName "lvl" and
+ noSrcSpan, so keys 1-3 are identical and, without this, their order would
+ fall back to the Unique-driven input order -- exactly the churn we set out
+ to remove. (In tidied dumps like -ddump-simpl the floats already have
+ distinct names lvl, lvl1, ...; this tie-break additionally stabilises the
+ untidied dumps -ddump-simpl-iterations, -dverbose-core2core etc.) It is
+ also Unique-independent, and laziness means it is only computed when keys
+ 1-3 tie.
+
+Recursive groups are never split: a 'Rec' is one 'CoreBind', placed as a unit by
+its earliest-source member, with its members sorted by the same key.
+
+Only *top-level* bindings (and the members of a top-level 'Rec') are reordered.
+Bindings nested inside a right-hand side (a 'let'/'letrec' within an expression)
+are left in their original order: their position in the dump is fixed by the
+surrounding expression rather than chosen by a Unique-keyed sort, so they don't
+suffer the cross-module churn this flag addresses.
+
+-dstable-core-dump-order is opt-in; the default order is retained because it is
+useful for debugging the compiler itself.
+-}
+
+-- | The sort key for one top-level binder. The trailing 'RhsKey' is a
+-- content-based tiebreak; thanks to laziness it is forced only when two binders
+-- agree on everything before it. See Note [Stable Core dump order].
+type DumpSortKey =
+ ( Int -- ^ source-span bucket: @0@ = real span, @1@ = 'noSrcSpan' (sorts last)
+ , Int -- ^ source-span start line
+ , Int -- ^ source-span start column
+ , Int -- ^ @$@-rank: @0@ = derived (@$w@\/@$s@) binder, @1@ = its origin
+ , String -- ^ the 'OccName' string, a lexical tiebreak
+ , RhsKey -- ^ content-based tiebreak (see 'rhsKey')
+ )
+
+-- | Reorder a 'CoreProgram' into a stable, source-location-driven order for
+-- dumping. See Note [Stable Core dump order]. Used by 'dumpPassResult' when
+-- -dstable-core-dump-order is enabled.
+sortCoreBindingsForDump :: CoreProgram -> CoreProgram
+sortCoreBindingsForDump = sortOn bindKey . map sortRecMembers
+ where
+ sortRecMembers (Rec prs) = Rec (sortOn (uncurry elemKey) prs)
+ sortRecMembers b = b
+
+ -- 'sortRecMembers' runs first, so a 'Rec' is already sorted by 'elemKey'
+ -- when 'bindKey' sees it; its first member is therefore the minimum key.
+ bindKey :: CoreBind -> DumpSortKey
+ bindKey (NonRec b rhs) = elemKey b rhs
+ bindKey (Rec ((b,rhs):_)) = elemKey b rhs
+ bindKey (Rec []) = panic "sortCoreBindingsForDump: empty Rec"
+
+ elemKey :: CoreBndr -> CoreExpr -> DumpSortKey
+ elemKey b rhs = (bucket, line, col, dollar_rank, s, rhsKey rhs)
+ where
+ s = getOccString b
+ (bucket, line, col) = case getSrcSpan b of
+ RealSrcSpan rs _ -> (0, srcSpanStartLine rs, srcSpanStartCol rs)
+ _ -> (1, 0, 0) -- noSrcSpan: sort last
+ -- A '$' anywhere in a tidied top-level OccName marks a compiler-derived
+ -- binder ($wfoo, but also call-site specialisations tidied to
+ -- bar_$sfoo); rank those before their origin within a shared source span,
+ -- mirroring GHC's default dependency order (the wrapper calls the worker,
+ -- so the worker comes first).
+ dollar_rank | '$' `elem` s = 0
+ | otherwise = 1
+
+-- | A cheap, Unique-independent tiebreak on a binder's right-hand side, used to
+-- order binders that are otherwise indistinguishable -- in practice the
+-- anonymous floats, which all share OccName \"lvl\" and 'noSrcSpan' (see
+-- 'GHC.Core.Opt.SetLevels.newLvlVar'). It pairs the floated literal (if any,
+-- looking through the @I#@-style box and casts/ticks) with the RHS size
+-- statistics; both are independent of Uniques, so the order is stable even
+-- without -dsuppress-uniques. See Note [Stable Core dump order].
+type RhsKey =
+ ( Maybe Literal -- ^ the floated literal, if any ('Nothing' sorts first)
+ , (Int, Int, Int, Int, Int) -- ^ 'exprStats' counts: terms, types, coercions, value binds, join binds
+ )
+
+rhsKey :: CoreExpr -> RhsKey
+rhsKey rhs = (litOf rhs, statsTuple (exprStats rhs))
+ where
+ statsTuple (CS tm ty co vb jb) = (tm, ty, co, vb, jb)
+ litOf (Lit l) = Just l
+ litOf (App f a) = case a of { Lit l -> Just l; _ -> litOf f }
+ litOf (Cast e _) = litOf e
+ litOf (Tick _ e) = litOf e
+ litOf _ = Nothing
+
instance OutputableBndr b => Outputable (Bind b) where
ppr bind = ppr_bind noAnn bind
=====================================
compiler/GHC/Driver/DynFlags.hs
=====================================
@@ -1594,6 +1594,7 @@ initSDocContext dflags style = SDC
, sdocSuppressModulePrefixes = gopt Opt_SuppressModulePrefixes dflags
, sdocSuppressStgExts = gopt Opt_SuppressStgExts dflags
, sdocSuppressStgReps = gopt Opt_SuppressStgReps dflags
+ , sdocStableCoreDumpOrder = gopt Opt_StableCoreDumpOrder dflags
, sdocErrorSpans = gopt Opt_ErrorSpans dflags
, sdocStarIsType = xopt LangExt.StarIsType dflags
, sdocLinearTypes = xopt LangExt.LinearTypes dflags
=====================================
compiler/GHC/Driver/Flags.hs
=====================================
@@ -859,6 +859,10 @@ data GeneralFlag
| Opt_SuppressTimestamps -- ^ Suppress timestamps in dumps
| Opt_SuppressCoreSizes -- ^ Suppress per binding Core size stats in dumps
+ -- | Reorder top-level bindings in Core dumps into a stable, diffable order.
+ -- See Note [Stable Core dump order] in GHC.Core.Ppr.
+ | Opt_StableCoreDumpOrder
+
-- Error message suppression
| Opt_ShowErrorContext
=====================================
compiler/GHC/Driver/Session.hs
=====================================
@@ -2468,6 +2468,7 @@ dFlagsDeps = [
flagSpec "ppr-case-as-let" Opt_PprCaseAsLet,
depFlagSpec' "ppr-ticks" Opt_PprShowTicks
(\turn_on -> useInstead "-d" "suppress-ticks" (not turn_on)),
+ flagSpec "stable-core-dump-order" Opt_StableCoreDumpOrder,
flagSpec "suppress-ticks" Opt_SuppressTicks,
depFlagSpec' "suppress-stg-free-vars" Opt_SuppressStgExts
(useInstead "-d" "suppress-stg-exts"),
=====================================
compiler/GHC/Utils/Outputable.hs
=====================================
@@ -422,6 +422,7 @@ data SDocContext = SDC
, sdocSuppressModulePrefixes :: !Bool
, sdocSuppressStgExts :: !Bool
, sdocSuppressStgReps :: !Bool
+ , sdocStableCoreDumpOrder :: !Bool
, sdocErrorSpans :: !Bool
, sdocStarIsType :: !Bool
, sdocLinearTypes :: !Bool
@@ -490,6 +491,7 @@ defaultSDocContext = SDC
, sdocSuppressModulePrefixes = False
, sdocSuppressStgExts = False
, sdocSuppressStgReps = True
+ , sdocStableCoreDumpOrder = False
, sdocErrorSpans = False
, sdocStarIsType = False
, sdocLinearTypes = False
=====================================
docs/users_guide/debugging.rst
=====================================
@@ -959,6 +959,33 @@ parts that you are not interested in.
has shown you where to look, you can try again without
:ghc-flag:`-dsuppress-uniques`
+.. ghc-flag:: -dstable-core-dump-order
+ :shortdesc: Reorder top-level bindings in Core dumps into a stable,
+ diffable order
+ :type: dynamic
+ :reverse: -dno-stable-core-dump-order
+ :category: verbosity
+
+ :since: 10.2.1
+
+ Normally the order of top-level bindings in a Core dump (such as the
+ output of :ghc-flag:`-ddump-simpl`) reflects the compiler's internal
+ processing order, which depends on ``Unique`` values. Those uniques can
+ shift whenever an unrelated upstream module changes, so the bindings get
+ re-ordered and a textual ``diff`` of two dumps fails to line up the real
+ changes.
+
+ This flag is opt-in and reorders the top-level bindings of Core dumps that
+ go through the pass-result printer (e.g. :ghc-flag:`-ddump-simpl`,
+ :ghc-flag:`-ddump-prep`, :ghc-flag:`-ddump-ds`,
+ :ghc-flag:`-ddump-simpl-iterations`) into a stable, source-location-driven
+ order that does not depend on uniques.
+
+ It is intended to be combined with :ghc-flag:`-dsuppress-uniques` when
+ diffing two dumps, but because the ordering does not depend on uniques the
+ output is also more diffable without it. The default (in-compiler) order is
+ retained because it is useful when debugging the compiler itself.
+
.. ghc-flag:: -dsuppress-idinfo
:shortdesc: Suppress extended information about identifiers where they
are bound
=====================================
testsuite/tests/simplCore/should_compile/Makefile
=====================================
@@ -298,3 +298,21 @@ T17901:
$(RM) -f T17901.o T17901.hi
'$(TEST_HC)' $(TEST_HC_OPTS) -O -c -ddump-simpl -dsuppress-uniques T17901.hs | grep 'wombat'
# All three functions should get their case alternatives combined
+
+# Check -dstable-core-dump-order on a small Data.Map-style module. The
+# sed allow-list prints, deduplicated, the top-level binders we care about in
+# dump order. It inspects names only, so it is insensitive to unrelated
+# Core-format churn.
+#
+# The allow-list covers one binder of each interesting category, so the test
+# exercises the clustering of generated binders next to their origin:
+# * derived instances ($fEqKey/$fOrdKey/$fOrdKey_$ccompare),
+# * a call-site specialisation (findI_$slookupG, from lookupG's SPECIALISE), and
+# * a recursive worker ($wrotate).
+T27296:
+ $(RM) -f T27296.o T27296.hi
+ '$(TEST_HC)' $(TEST_HC_OPTS) -O -c -ddump-simpl -dsuppress-uniques \
+ -dsuppress-idinfo -dsuppress-module-prefixes -dno-typeable-binds \
+ -dstable-core-dump-order T27296.hs 2> /dev/null \
+ | sed -nE 's/^(\$$fEqKey|\$$fOrdKey|\$$fOrdKey_\$$ccompare|size|findI_\$$slookupG|lookupG|member|findI|\$$wrotate|rotate|insertG|insertManyI|insertTwoI|weight|balance|ratios|fromAscI)( .*)?$$/\1/p' \
+ | uniq
=====================================
testsuite/tests/simplCore/should_compile/T27296.hs
=====================================
@@ -0,0 +1,87 @@
+{-# LANGUAGE BangPatterns #-}
+
+-- See Note [Stable Core dump order] in GHC.Core.Ppr.
+--
+-- A small Data.Map-style module exercising the trickier parts of the stable
+-- dump ordering. Under -O it produces, alongside the user functions:
+-- * derived Eq/Ord instances for a custom Key type ($fEqKey/$fOrdKey/...),
+-- * a call-site specialisation of lookupG (findI_$slookupG), and
+-- * a worker/wrapper split of the recursive, strict rotate ($wrotate).
+-- Each generated binder inherits its origin's source span, so the stable order
+-- clusters it next to that origin. The source order is deliberately neither
+-- alphabetical nor the default dump order (insertG forward-references balance),
+-- so the test pins source-position ordering specifically.
+module T27296
+ ( Key(..), size, lookupG, member, findI, rotate, insertG, insertManyI
+ , insertTwoI, weight, balance, ratios, fromAscI )
+ where
+
+-- A custom key with a derived Ord instance: the derived $fEqKey/$fOrdKey
+-- binders inherit this declaration's source span, so they cluster here.
+data Key = Key Int deriving (Eq, Ord)
+
+data Map k a = Tip | Bin !Int k a !(Map k a) !(Map k a)
+
+data Sizes = Sizes !Int !Int
+
+size :: Map k a -> Int
+size Tip = 0
+size (Bin sz _ _ _ _) = sz
+
+lookupG :: Ord k => k -> Map k a -> Maybe a
+lookupG _ Tip = Nothing
+lookupG k (Bin _ kx x l r) = case compare k kx of
+ LT -> lookupG k l
+ GT -> lookupG k r
+ EQ -> Just x
+{-# SPECIALISE lookupG :: Key -> Map Key a -> Maybe a #-}
+
+member :: Key -> Map Key a -> Bool
+member k m = case lookupG k m of
+ Nothing -> False
+ Just _ -> True
+
+findI :: Key -> Map Key a -> a -> a
+findI k m def = case lookupG k m of
+ Nothing -> def
+ Just v -> v
+
+-- rotate is recursive and strict in the product 'Sizes', so worker/wrapper
+-- unboxes it into a recursive worker ($wrotate). The loop only repackages the
+-- fields (no arithmetic), so the worker is stable across build flavours.
+rotate :: Sizes -> [a] -> Sizes
+rotate s [] = s
+rotate (Sizes a b) (_:xs) = rotate (Sizes b a) xs
+
+-- insertG references 'balance', which is defined further down (forward ref).
+insertG :: Ord k => k -> a -> Map k a -> Map k a
+insertG k x Tip = Bin 1 k x Tip Tip
+insertG k x (Bin sz kx kv l r) = case compare k kx of
+ LT -> balance kx kv (insertG k x l) r
+ GT -> balance kx kv l (insertG k x r)
+ EQ -> Bin sz k x l r
+{-# SPECIALISE insertG :: Key -> a -> Map Key a -> Map Key a #-}
+
+insertManyI :: [(Key, a)] -> Map Key a -> Map Key a
+insertManyI xs m0 = foldr (\(k, x) m -> insertG k x m) m0 xs
+
+insertTwoI :: Key -> Key -> a -> Map Key a
+insertTwoI k1 k2 x = insertG k1 x (insertG k2 x Tip)
+
+-- weight unboxes the strict fields of Sizes -> worker/wrapper $wweight.
+weight :: Sizes -> Int
+weight (Sizes a b) = a * a + 3 * b * b + a * b + 1
+
+balance :: k -> a -> Map k a -> Map k a -> Map k a
+balance k x l r = Bin (weight (Sizes sl sr)) k x l r
+ where
+ sl = size l
+ sr = size r
+
+-- baseRatios is a closed constant under a lambda -> floated to a top-level lvl.
+ratios :: Int -> [Int]
+ratios n = map (n +) baseRatios
+ where baseRatios = [2, 3, 5, 7, 11, 13]
+
+fromAscI :: [(Key, a)] -> Map Key a
+fromAscI = foldr (\(k, x) m -> insertG k x m) Tip
=====================================
testsuite/tests/simplCore/should_compile/T27296.stdout
=====================================
@@ -0,0 +1,17 @@
+$fEqKey
+$fOrdKey
+$fOrdKey_$ccompare
+size
+findI_$slookupG
+lookupG
+member
+findI
+$wrotate
+rotate
+insertG
+insertManyI
+insertTwoI
+weight
+balance
+ratios
+fromAscI
=====================================
testsuite/tests/simplCore/should_compile/T27296b.hs
=====================================
@@ -0,0 +1,21 @@
+-- See Note [Stable Core dump order] in GHC.Core.Ppr.
+--
+-- Companion to T27296 that pins the ordering of *anonymous* top-level floats.
+-- Under -O the boxed Int constants in sel's branches are floated to top level
+-- as separate CAFs, all of which the compiler names "lvl" with noSrcSpan (see
+-- newLvlVar). Before -dstable-core-dump-order their dump order was the
+-- unique-driven processing order; the flag's content-based tie-break (rhsKey)
+-- now orders them by literal value -- here 1000..6000, despite the scrambled
+-- source order. This dump is intentionally *untidied* (-ddump-float-out), the
+-- only place the "lvl" collision is observable; tidied dumps like -ddump-simpl
+-- already give the floats distinct names (lvl, lvl1, ...).
+module T27296b (sel) where
+
+{-# NOINLINE sel #-}
+sel :: Int -> Int
+sel 0 = 5000
+sel 1 = 1000
+sel 2 = 4000
+sel 3 = 2000
+sel 4 = 3000
+sel _ = 6000
=====================================
testsuite/tests/simplCore/should_compile/T27296b.stderr
=====================================
@@ -0,0 +1,84 @@
+
+==================== Float out(FOS {Lam = Just 0,
+ Consts = True,
+ JoinsToTop = False,
+ OverSatApps = False}) ====================
+Result size of Float out(FOS {Lam = Just 0,
+ Consts = True,
+ JoinsToTop = False,
+ OverSatApps = False})
+ = {terms: 37, types: 12, coercions: 0, joins: 0/0}
+
+-- RHS size: {terms: 18, types: 4, coercions: 0, joins: 0/0}
+sel :: Int -> Int
+sel
+ = \ (ds :: Int) ->
+ case ds of { I# ds ->
+ case ds of {
+ __DEFAULT -> lvl;
+ 0# -> lvl;
+ 1# -> lvl;
+ 2# -> lvl;
+ 3# -> lvl;
+ 4# -> lvl
+ }
+ }
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+lvl = I# 1000#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+lvl = I# 2000#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+lvl = I# 3000#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+lvl = I# 4000#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+lvl = I# 5000#
+
+-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
+lvl :: Int
+lvl = I# 6000#
+
+
+
+
+==================== Float out(FOS {Lam = Just 0,
+ Consts = True,
+ JoinsToTop = True,
+ OverSatApps = True}) ====================
+Result size of Float out(FOS {Lam = Just 0,
+ Consts = True,
+ JoinsToTop = True,
+ OverSatApps = True})
+ = {terms: 27, types: 10, coercions: 0, joins: 0/0}
+
+-- RHS size: {terms: 15, types: 2, coercions: 0, joins: 0/0}
+$wsel :: Int# -> Int#
+$wsel
+ = \ (ww :: Int#) ->
+ case ww of {
+ __DEFAULT -> 6000#;
+ 0# -> 5000#;
+ 1# -> 1000#;
+ 2# -> 4000#;
+ 3# -> 2000#;
+ 4# -> 3000#
+ }
+
+-- RHS size: {terms: 10, types: 4, coercions: 0, joins: 0/0}
+sel :: Int -> Int
+sel
+ = \ (ds :: Int) ->
+ case ds of { I# ww -> case $wsel ww of ww { __DEFAULT -> I# ww } }
+
+
+
=====================================
testsuite/tests/simplCore/should_compile/all.T
=====================================
@@ -602,3 +602,6 @@ test('T25718b', normal, compile, ['-O -ddump-simpl -dsuppress-uniques -dsuppress
test('T25718c', normal, compile, ['-O -ddump-simpl -dsuppress-uniques -dsuppress-all -dno-typeable-binds'])
test('T19166', normal, compile, ['-O -ddump-simpl -dsuppress-uniques -dsuppress-all -dno-typeable-binds'])
test('T27261', [extra_files(['T27261_aux.hs'])], multimod_compile, ['T27261', '-v0 -O'])
+test('T27296', [], makefile_test, ['T27296'])
+test('T27296b', only_ways(['optasm']), compile,
+ ['-O -ddump-float-out -dsuppress-uniques -dsuppress-idinfo -dsuppress-module-prefixes -dno-typeable-binds -dstable-core-dump-order'])
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/572b3a913ea825365c4d677363a926…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/572b3a913ea825365c4d677363a926…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc][wip/amg/castz] WIP: introduce cast zapping as an alternative to coercion zapping
by Adam Gundry (@adamgundry) 08 Jun '26
by Adam Gundry (@adamgundry) 08 Jun '26
08 Jun '26
Adam Gundry pushed to branch wip/amg/castz at Glasgow Haskell Compiler / GHC
Commits:
79e28c78 by Adam Gundry at 2026-06-08T11:46:16+02:00
WIP: introduce cast zapping as an alternative to coercion zapping
- - - - -
59 changed files:
- compiler/GHC/Core.hs
- compiler/GHC/Core/Coercion.hs
- compiler/GHC/Core/Coercion/Opt.hs
- compiler/GHC/Core/FVs.hs
- compiler/GHC/Core/LateCC/OverloadedCalls.hs
- compiler/GHC/Core/Lint.hs
- compiler/GHC/Core/Lint/SubstTypeLets.hs
- compiler/GHC/Core/Map/Expr.hs
- compiler/GHC/Core/Map/Type.hs
- compiler/GHC/Core/Opt/Arity.hs
- compiler/GHC/Core/Opt/CSE.hs
- compiler/GHC/Core/Opt/CprAnal.hs
- compiler/GHC/Core/Opt/DmdAnal.hs
- compiler/GHC/Core/Opt/OccurAnal.hs
- compiler/GHC/Core/Opt/SetLevels.hs
- compiler/GHC/Core/Opt/Simplify/Env.hs
- compiler/GHC/Core/Opt/Simplify/Iteration.hs
- compiler/GHC/Core/Opt/Simplify/Utils.hs
- compiler/GHC/Core/Opt/SpecConstr.hs
- compiler/GHC/Core/Opt/Specialise.hs
- compiler/GHC/Core/Ppr.hs
- compiler/GHC/Core/Rules.hs
- compiler/GHC/Core/Seq.hs
- compiler/GHC/Core/SimpleOpt.hs
- compiler/GHC/Core/Stats.hs
- compiler/GHC/Core/Subst.hs
- compiler/GHC/Core/Tidy.hs
- compiler/GHC/Core/TyCo/FVs.hs
- compiler/GHC/Core/TyCo/Ppr.hs
- compiler/GHC/Core/TyCo/Ppr.hs-boot
- compiler/GHC/Core/TyCo/Rep.hs
- compiler/GHC/Core/TyCo/Rep.hs-boot
- compiler/GHC/Core/TyCo/Subst.hs
- compiler/GHC/Core/TyCo/Tidy.hs
- compiler/GHC/Core/Utils.hs
- compiler/GHC/CoreToIface.hs
- compiler/GHC/CoreToStg/Prep.hs
- compiler/GHC/Driver/DynFlags.hs
- compiler/GHC/Driver/Flags.hs
- compiler/GHC/Driver/Session.hs
- compiler/GHC/HsToCore/Binds.hs
- compiler/GHC/HsToCore/Foreign/C.hs
- compiler/GHC/HsToCore/Foreign/JavaScript.hs
- compiler/GHC/HsToCore/Foreign/Prim.hs
- compiler/GHC/HsToCore/Foreign/Wasm.hs
- compiler/GHC/HsToCore/Utils.hs
- compiler/GHC/Iface/Rename.hs
- compiler/GHC/Iface/Syntax.hs
- compiler/GHC/Iface/Type.hs
- compiler/GHC/IfaceToCore.hs
- compiler/GHC/Tc/Solver/Solve.hs
- compiler/GHC/Tc/Types/Evidence.hs
- compiler/GHC/Tc/Zonk/Type.hs
- compiler/GHC/Types/Id/Make.hs
- docs/core-spec/CoreLint.ott
- docs/core-spec/CoreSyn.ott
- docs/users_guide/debugging.rst
- testsuite/tests/ghci/prog-mhu002/prog-mhu002c.stdout
- testsuite/tests/ghci/scripts/ghci024.stdout
The diff was not included because it is too large.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/79e28c789b20770ce41471699f8a5cf…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/79e28c789b20770ce41471699f8a5cf…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc] Pushed new branch wip/sjakobi/T21176-integer-bits
by Simon Jakobi (@sjakobi2) 08 Jun '26
by Simon Jakobi (@sjakobi2) 08 Jun '26
08 Jun '26
Simon Jakobi pushed new branch wip/sjakobi/T21176-integer-bits at Glasgow Haskell Compiler / GHC
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/tree/wip/sjakobi/T21176-integer-bits
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc][wip/dcoutts/issue-27105-stopTicker-2] 7 commits: Promote HAVE_PREEMPTION from Timer.c to OSThreads.h
by Duncan Coutts (@dcoutts) 08 Jun '26
by Duncan Coutts (@dcoutts) 08 Jun '26
08 Jun '26
Duncan Coutts pushed to branch wip/dcoutts/issue-27105-stopTicker-2 at Glasgow Haskell Compiler / GHC
Commits:
c5f03b4e by Duncan Coutts at 2026-06-08T10:19:39+02:00
Promote HAVE_PREEMPTION from Timer.c to OSThreads.h
We will want to know about HAVE_PREEMPTION in more places.
HAVE_PREEMPTION tells us that we do have OS threads available,
irrespective of whether THREADED is defined. In particular,
HAVE_PREEMPTION is defined on all proper OSs, but not on WASM (and
hyopthetically may not be true on some other platforms like
micro-controllers, RTOSs, VM hypervisors etc).
- - - - -
3e0fbc85 by Duncan Coutts at 2026-06-08T10:19:49+02:00
Define ACQUIRE_LOCK_ALWAYS and friends
Fix issue #27335
Like the atomic _ALWAYS variants, these lock actions are always defined,
rather than being dependent on whether we are in the THREADED case. All
the "normal" LOCK macros are defined to be no-ops when !THREADED.
The use case for the _ALWAYS variants is where we are using OS threads
even in the non-threaded RTS. This includes everything to do with the
timer/ticker thread, which is used in the non-threaded RTS too.
In particular, we will want to use this for eventlog things, because the
timer thread performs eventlogging concurrently with the main
capability, even in the non-threaded RTS.
- - - - -
d6d2f4f3 by Duncan Coutts at 2026-06-08T10:19:49+02:00
Use ACQUIRE/RELEASE_LOCK_ALWAYS with eventBufMutex
Even in the non-threaded RTS the eventBufMutex is needed by both the
main capability and the timer/ticker thread, so always use the mutex.
This should fix #25165 which is about the main capability and the timer
thread posting events to the eventlog buffer concurrently and thereby
corrupting the buffer data.
- - - - -
f7286545 by Duncan Coutts at 2026-06-08T10:19:49+02:00
Expose eventBufMutex in the EventLog interface/header
We will need it in forkProcess to ensure we don't write to the global
eventlog buffer concurrently with trying to flush eventlog buffers and
do the fork().
- - - - -
21911f5e by Duncan Coutts at 2026-06-08T10:23:17+02:00
Split flushAllCapsEventsBufs into safe and unlocked version
Following the convention that unlocked versions have a trailing _
underscore in their name. This one requires the caller to hold the
eventlog global buffer mutex. We will need this in forkProcess.
- - - - -
f240aa83 by Duncan Coutts at 2026-06-08T10:23:20+02:00
Remove redundant use of stopTimer in setNumCapabilities
Historically, the comment here was:
We must stop the interval timer while we are changing the
capabilities array lest handle_tick may try to context switch
an old capability. See #17289.
and
We must disable the timer while we do this since the tick handler may
call contextSwitchAllCapabilities, which may see the capabilities array
as we free it.
What this refers to is that historically, when changing the number of
capabilities, the array of capabilities was reallocated to a new size,
allocating new ones and freeing the old ones, thus invalidating all
existing capbility pointers.
Strangely, for good measure the code used to call stopTimer twice (hence
the two similar comments above).
However, since commit a3eccf06292dd666b24606251a52da2b466a9612, the
capabilities array is no longer reallocated. Instead the array is
allcoated once on RTS startup to the maximum size it could ever be
allowed to be, and then capabilities get enabled/disabled at runtime. So
the capability pointers never become invalid anymore. At worst, they may
point to capabilities that are disabled.
Thus we no longer need to stop the timer (twice) while we change the
number of enabled capabilities. This also partially solves issue #27105,
which notes that stopTimer is being used as if it were synchronous, when
it is not. At least for this case, the solution is that stopTimer is not
needed at all!
- - - - -
7d07b187 by Duncan Coutts at 2026-06-08T10:23:20+02:00
Remove redundant use of stopTimer in forkProcess
but replace it with taking the eventlog buffer lock during the fork.
Fixes issue #27105
The original reason to block the timer during a fork was that
historically the timer was implemented using a periodic timer signal,
and the signal itself would interrupt the fork system call (returning
EINTR). For large processes (where fork() takes a while) this could
permanently livelock: the timer always would go off before the fork
could complete, which got retried in a loop forever.
The timer is no longer implemented as a unix signal, but uses threads.
Thus the original problem no longer exists. The only remaining reason to
block the timer tick is to prevent actions taken by the tick from
interfering with the delicate process involved in fork (taking a load of
locks and pausing everything).
The only thing we need to do is to prevent the eventlog from being
written to or flushed while the fork is taking place. To achieve this
all we need to do is hold the mutex for the global eventlog buffer.
This removes the last use of stopTimer that expects stopTimer to work
synchronously (which it was not) and thus solves issue #27105. To be
clear, we solve issue #27105 not by making stopTimer synchronous, but by
eliminating the use sites that expected it to be synchronous.
- - - - -
6 changed files:
- rts/Capability.c
- rts/Schedule.c
- rts/Timer.c
- rts/eventlog/EventLog.c
- rts/eventlog/EventLog.h
- rts/include/rts/OSThreads.h
Changes:
=====================================
rts/Capability.c
=====================================
@@ -443,13 +443,6 @@ void
moreCapabilities (uint32_t from USED_IF_THREADS, uint32_t to USED_IF_THREADS)
{
#if defined(THREADED_RTS)
- // We must disable the timer while we do this since the tick handler may
- // call contextSwitchAllCapabilities, which may see the capabilities array
- // as we free it. The alternative would be to protect the capabilities
- // array with a lock but this seems more expensive than necessary.
- // See #17289.
- stopTimer();
-
if (to == 1) {
// THREADED_RTS must work on builds that don't have a mutable
// BaseReg (eg. unregisterised), so in this case
@@ -470,8 +463,6 @@ moreCapabilities (uint32_t from USED_IF_THREADS, uint32_t to USED_IF_THREADS)
}
debugTrace(DEBUG_sched, "allocated %d more capabilities", to - from);
-
- startTimer();
#endif
}
=====================================
rts/Schedule.c
=====================================
@@ -37,6 +37,7 @@
#include "win32/AsyncWinIO.h"
#endif
#include "Trace.h"
+#include "eventlog/EventLog.h"
#include "RaiseAsync.h"
#include "Threads.h"
#include "Timer.h"
@@ -2100,24 +2101,31 @@ forkProcess(HsStablePtr *entry
ACQUIRE_LOCK(&all_tasks_mutex);
#endif
- stopTimer(); // See #4074
-
#if defined(TRACING)
- flushAllCapsEventsBufs(); // so that child won't inherit dirty file buffers
+#if defined(HAVE_PREEMPTION)
+ // We must hold the eventlog global mutex over the fork to prevent the
+ // timer thread from trying to post events. While holding the mutex we need
+ // to flush the eventlogs (global and per-cap) so that child won't inherit
+ // dirty eventlog buffers or file buffers.
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
+#endif
+ flushAllCapsEventsBufs_();
#endif
pid = fork();
if (pid) { // parent
- startTimer(); // #4074
-
RELEASE_LOCK(&sched_mutex);
RELEASE_LOCK(&sm_mutex);
RELEASE_LOCK(&stable_ptr_mutex);
RELEASE_LOCK(&stable_name_mutex);
RELEASE_LOCK(&task->lock);
+#if defined(TRACING) && defined(HAVE_PREEMPTION)
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
+#endif
+
#if defined(THREADED_RTS)
/* N.B. releaseCapability_ below may need to take all_tasks_mutex */
RELEASE_LOCK(&all_tasks_mutex);
@@ -2303,12 +2311,6 @@ setNumCapabilities (uint32_t new_n_capabilities USED_IF_THREADS)
cap = rts_lock();
task = cap->running_task;
-
- // N.B. We must stop the interval timer while we are changing the
- // capabilities array lest handle_tick may try to context switch
- // an old capability. See #17289.
- stopTimer();
-
stopAllCapabilities(&cap, task);
if (new_n_capabilities < enabled_capabilities)
@@ -2364,9 +2366,7 @@ setNumCapabilities (uint32_t new_n_capabilities USED_IF_THREADS)
tracingAddCapabilities(n_capabilities, new_n_capabilities);
#endif
- // Resize the capabilities array
- // NB. after this, capabilities points somewhere new. Any pointers
- // of type (Capability *) are now invalid.
+ // Allocate and initialise the extra capabilities
moreCapabilities(n_capabilities, new_n_capabilities);
// Resize and update storage manager data structures
@@ -2394,8 +2394,6 @@ setNumCapabilities (uint32_t new_n_capabilities USED_IF_THREADS)
// Notify IO manager that the number of capabilities has changed.
notifyIOManagerCapabilitiesChanged(&cap);
- startTimer();
-
rts_unlock(cap);
#endif // THREADED_RTS
=====================================
rts/Timer.c
=====================================
@@ -28,11 +28,6 @@
#include "RtsSignals.h"
#include "rts/EventLogWriter.h"
-// See Note [No timer on wasm32]
-#if !defined(wasm32_HOST_ARCH)
-#define HAVE_PREEMPTION
-#endif
-
// This global counter is used to allow multiple threads to stop the
// timer temporarily with a stopTimer()/startTimer() pair. If
// timer_enabled == 0 timer is enabled
=====================================
rts/eventlog/EventLog.c
=====================================
@@ -129,8 +129,11 @@ typedef struct _EventsBuf {
static EventsBuf *capEventBuf; // one EventsBuf for each Capability
static EventsBuf eventBuf; // an EventsBuf not associated with any Capability
-#if defined(THREADED_RTS)
-static Mutex eventBufMutex; // protected by this mutex
+#if defined(HAVE_PREEMPTION)
+// Note that this mutex is used even in the non-threaded RTS, since the timer
+// thread posts events and flushes. So _all_ uses of this mutex must use
+// ACQUIRE_LOCK_ALWAYS/RELEASE_LOCK_ALWAYS.
+Mutex eventBufMutex; // protects eventBuf above
#endif
// Event type
@@ -393,8 +396,10 @@ initEventLogging(void)
moreCapEventBufs(0, get_n_capabilities());
initEventsBuf(&eventBuf, EVENT_LOG_SIZE, (EventCapNo)(-1));
-#if defined(THREADED_RTS)
+#if defined(HAVE_PREEMPTION)
initMutex(&eventBufMutex);
+#endif
+#if defined(THREADED_RTS)
initMutex(&state_change_mutex);
#endif
}
@@ -416,7 +421,7 @@ startEventLogging_(void)
{
initEventLogWriter();
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
postHeaderEvents();
/*
@@ -425,7 +430,7 @@ startEventLogging_(void)
*/
printAndClearEventBuf(&eventBuf);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
return true;
}
@@ -495,7 +500,7 @@ endEventLogging(void)
flushEventLog_(NULL);
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
// Mark end of events (data).
postEventTypeNum(&eventBuf, EVENT_DATA_END);
@@ -503,7 +508,7 @@ endEventLogging(void)
// Flush the end of data marker.
printAndClearEventBuf(&eventBuf);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
stopEventLogWriter();
event_log_writer = NULL;
@@ -666,7 +671,7 @@ void
postCapEvent (EventTypeNum tag,
EventCapNo capno)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
ensureRoomForEvent(&eventBuf, tag);
postEventHeader(&eventBuf, tag);
@@ -685,14 +690,14 @@ postCapEvent (EventTypeNum tag,
barf("postCapEvent: unknown event tag %d", tag);
}
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
void postCapsetEvent (EventTypeNum tag,
EventCapsetID capset,
StgWord info)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
ensureRoomForEvent(&eventBuf, tag);
postEventHeader(&eventBuf, tag);
@@ -726,7 +731,7 @@ void postCapsetEvent (EventTypeNum tag,
barf("postCapsetEvent: unknown event tag %d", tag);
}
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
void postCapsetStrEvent (EventTypeNum tag,
@@ -740,14 +745,14 @@ void postCapsetStrEvent (EventTypeNum tag,
return;
}
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
if (!hasRoomForVariableEvent(&eventBuf, size)){
printAndClearEventBuf(&eventBuf);
if (!hasRoomForVariableEvent(&eventBuf, size)){
errorBelch("Event size exceeds buffer size, bail out");
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
return;
}
}
@@ -758,7 +763,7 @@ void postCapsetStrEvent (EventTypeNum tag,
postBuf(&eventBuf, (StgWord8*) msg, strsize);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
void postCapsetVecEvent (EventTypeNum tag,
@@ -783,14 +788,14 @@ void postCapsetVecEvent (EventTypeNum tag,
}
}
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
if (!hasRoomForVariableEvent(&eventBuf, size)){
printAndClearEventBuf(&eventBuf);
if(!hasRoomForVariableEvent(&eventBuf, size)){
errorBelch("Event size exceeds buffer size, bail out");
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
return;
}
}
@@ -804,7 +809,7 @@ void postCapsetVecEvent (EventTypeNum tag,
postBuf(&eventBuf, (StgWord8*) argv[i], 1 + strlen(argv[i]));
}
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
void postWallClockTime (EventCapsetID capset)
@@ -813,7 +818,7 @@ void postWallClockTime (EventCapsetID capset)
StgWord64 sec;
StgWord32 nsec;
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
/* The EVENT_WALL_CLOCK_TIME event is intended to allow programs
reading the eventlog to match up the event timestamps with wall
@@ -846,7 +851,7 @@ void postWallClockTime (EventCapsetID capset)
postWord64(&eventBuf, sec);
postWord32(&eventBuf, nsec);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
/*
@@ -885,7 +890,7 @@ void postEventHeapInfo (EventCapsetID heap_capset,
W_ mblockSize,
W_ blockSize)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
ensureRoomForEvent(&eventBuf, EVENT_HEAP_INFO_GHC);
postEventHeader(&eventBuf, EVENT_HEAP_INFO_GHC);
@@ -899,7 +904,7 @@ void postEventHeapInfo (EventCapsetID heap_capset,
postWord64(&eventBuf, mblockSize);
postWord64(&eventBuf, blockSize);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
void postEventGcStats (Capability *cap,
@@ -952,7 +957,7 @@ void postTaskCreateEvent (EventTaskId taskId,
EventCapNo capno,
EventKernelThreadId tid)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
ensureRoomForEvent(&eventBuf, EVENT_TASK_CREATE);
postEventHeader(&eventBuf, EVENT_TASK_CREATE);
@@ -961,14 +966,14 @@ void postTaskCreateEvent (EventTaskId taskId,
postCapNo(&eventBuf, capno);
postKernelThreadId(&eventBuf, tid);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
void postTaskMigrateEvent (EventTaskId taskId,
EventCapNo capno,
EventCapNo new_capno)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
ensureRoomForEvent(&eventBuf, EVENT_TASK_MIGRATE);
postEventHeader(&eventBuf, EVENT_TASK_MIGRATE);
@@ -977,28 +982,28 @@ void postTaskMigrateEvent (EventTaskId taskId,
postCapNo(&eventBuf, capno);
postCapNo(&eventBuf, new_capno);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
void postTaskDeleteEvent (EventTaskId taskId)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
ensureRoomForEvent(&eventBuf, EVENT_TASK_DELETE);
postEventHeader(&eventBuf, EVENT_TASK_DELETE);
/* EVENT_TASK_DELETE (taskID) */
postTaskId(&eventBuf, taskId);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
void
postEventNoCap (EventTypeNum tag)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
ensureRoomForEvent(&eventBuf, tag);
postEventHeader(&eventBuf, tag);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
void
@@ -1042,9 +1047,9 @@ void postLogMsg(EventsBuf *eb, EventTypeNum type, char *msg, va_list ap)
void postMsg(char *msg, va_list ap)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
postLogMsg(&eventBuf, EVENT_LOG_MSG, msg, ap);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
void postCapMsg(Capability *cap, char *msg, va_list ap)
@@ -1138,32 +1143,32 @@ void postConcUpdRemSetFlush(Capability *cap)
void postConcMarkEnd(StgWord32 marked_obj_count)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
ensureRoomForEvent(&eventBuf, EVENT_CONC_MARK_END);
postEventHeader(&eventBuf, EVENT_CONC_MARK_END);
postWord32(&eventBuf, marked_obj_count);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
void postNonmovingHeapCensus(uint16_t blk_size,
const struct NonmovingAllocCensus *census)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
postEventHeader(&eventBuf, EVENT_NONMOVING_HEAP_CENSUS);
postWord16(&eventBuf, blk_size);
postWord32(&eventBuf, census->n_active_segs);
postWord32(&eventBuf, census->n_filled_segs);
postWord32(&eventBuf, census->n_live_blocks);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
void postNonmovingPrunedSegments(uint32_t pruned_segments, uint32_t free_segments)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
postEventHeader(&eventBuf, EVENT_NONMOVING_PRUNED_SEGMENTS);
postWord32(&eventBuf, pruned_segments);
postWord32(&eventBuf, free_segments);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
void closeBlockMarker (EventsBuf *ebuf)
@@ -1224,7 +1229,7 @@ static HeapProfBreakdown getHeapProfBreakdown(void)
void postHeapProfBegin(void)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
PROFILING_FLAGS *flags = &RtsFlags.ProfFlags;
StgWord modSelector_len =
flags->modSelector ? strlen(flags->modSelector) : 0;
@@ -1258,42 +1263,42 @@ void postHeapProfBegin(void)
postStringLen(&eventBuf, flags->ccsSelector, ccsSelector_len);
postStringLen(&eventBuf, flags->retainerSelector, retainerSelector_len);
postStringLen(&eventBuf, flags->bioSelector, bioSelector_len);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
void postHeapProfSampleBegin(StgInt era)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
ensureRoomForEvent(&eventBuf, EVENT_HEAP_PROF_SAMPLE_BEGIN);
postEventHeader(&eventBuf, EVENT_HEAP_PROF_SAMPLE_BEGIN);
postWord64(&eventBuf, era);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
void postHeapBioProfSampleBegin(StgInt era, StgWord64 time)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
ensureRoomForEvent(&eventBuf, EVENT_HEAP_BIO_PROF_SAMPLE_BEGIN);
postEventHeader(&eventBuf, EVENT_HEAP_BIO_PROF_SAMPLE_BEGIN);
postWord64(&eventBuf, era);
postWord64(&eventBuf, time);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
void postHeapProfSampleEnd(StgInt era)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
ensureRoomForEvent(&eventBuf, EVENT_HEAP_PROF_SAMPLE_END);
postEventHeader(&eventBuf, EVENT_HEAP_PROF_SAMPLE_END);
postWord64(&eventBuf, era);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
void postHeapProfSampleString(const char *label,
StgWord64 residency)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
StgWord label_len = strlen(label);
StgWord len = 1+8+label_len+1;
CHECK(!ensureRoomForVariableEvent(&eventBuf, len));
@@ -1303,7 +1308,7 @@ void postHeapProfSampleString(const char *label,
postWord8(&eventBuf, 0);
postWord64(&eventBuf, residency);
postStringLen(&eventBuf, label, label_len);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
#if defined(PROFILING)
@@ -1313,7 +1318,7 @@ void postHeapProfCostCentre(StgWord32 ccID,
const char *srcloc,
StgBool is_caf)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
StgWord label_len = strlen(label);
StgWord module_len = strlen(module);
StgWord srcloc_len = strlen(srcloc);
@@ -1326,13 +1331,13 @@ void postHeapProfCostCentre(StgWord32 ccID,
postStringLen(&eventBuf, module, module_len);
postStringLen(&eventBuf, srcloc, srcloc_len);
postWord8(&eventBuf, is_caf);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
void postHeapProfSampleCostCentre(CostCentreStack *stack,
StgWord64 residency)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
StgWord depth = 0;
CostCentreStack *ccs;
for (ccs = stack; ccs != NULL && ccs != CCS_MAIN; ccs = ccs->prevStack)
@@ -1351,7 +1356,7 @@ void postHeapProfSampleCostCentre(CostCentreStack *stack,
depth>0 && ccs != NULL && ccs != CCS_MAIN;
ccs = ccs->prevStack, depth--)
postWord32(&eventBuf, ccs->cc->ccID);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
@@ -1359,7 +1364,7 @@ void postProfSampleCostCentre(Capability *cap,
CostCentreStack *stack,
StgWord64 tick)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
StgWord depth = 0;
CostCentreStack *ccs;
for (ccs = stack; ccs != NULL && ccs != CCS_MAIN; ccs = ccs->prevStack)
@@ -1377,7 +1382,7 @@ void postProfSampleCostCentre(Capability *cap,
depth>0 && ccs != NULL && ccs != CCS_MAIN;
ccs = ccs->prevStack, depth--)
postWord32(&eventBuf, ccs->cc->ccID);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
// This event is output at the start of profiling so the tick interval can
@@ -1385,11 +1390,11 @@ void postProfSampleCostCentre(Capability *cap,
// can be calculated from how many samples there are.
void postProfBegin(void)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
postEventHeader(&eventBuf, EVENT_PROF_BEGIN);
// The interval that each tick was sampled, in nanoseconds
postWord64(&eventBuf, TimeToNS(RtsFlags.MiscFlags.tickInterval));
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
#endif /* PROFILING */
@@ -1415,11 +1420,11 @@ static void postTickyCounterDef(EventsBuf *eb, StgEntCounter *p)
void postTickyCounterDefs(StgEntCounter *counters)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
for (StgEntCounter *p = counters; p != NULL; p = p->link) {
postTickyCounterDef(&eventBuf, p);
}
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
static void postTickyCounterSample(EventsBuf *eb, StgEntCounter *p)
@@ -1443,13 +1448,13 @@ static void postTickyCounterSample(EventsBuf *eb, StgEntCounter *p)
void postTickyCounterSamples(StgEntCounter *counters)
{
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
ensureRoomForEvent(&eventBuf, EVENT_TICKY_COUNTER_SAMPLE);
postEventHeader(&eventBuf, EVENT_TICKY_COUNTER_BEGIN_SAMPLE);
for (StgEntCounter *p = counters; p != NULL; p = p->link) {
postTickyCounterSample(&eventBuf, p);
}
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
#endif /* TICKY_TICKY */
void postIPE(const InfoProvEnt *ipe)
@@ -1459,7 +1464,7 @@ void postIPE(const InfoProvEnt *ipe)
// See Note [Maximum event length].
const StgWord MAX_IPE_STRING_LEN = 65535;
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
StgWord table_name_len = MIN(strlen(ipe->prov.table_name), MAX_IPE_STRING_LEN);
StgWord closure_desc_len = MIN(strlen(closure_desc_buf), MAX_IPE_STRING_LEN);
StgWord ty_desc_len = MIN(strlen(ipe->prov.ty_desc), MAX_IPE_STRING_LEN);
@@ -1489,7 +1494,7 @@ void postIPE(const InfoProvEnt *ipe)
postBuf(&eventBuf, &colon, 1);
postStringLen(&eventBuf, ipe->prov.src_span, src_span_len);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
}
void printAndClearEventBuf (EventsBuf *ebuf)
@@ -1601,14 +1606,21 @@ void flushLocalEventsBuf(Capability *cap)
// Flush all capabilities' event buffers when we already hold all capabilities.
// Used during forkProcess.
void flushAllCapsEventsBufs(void)
+{
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
+ flushAllCapsEventsBufs_();
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
+}
+
+// Unsafe version that does not acquire/release eventBufMutex. You must
+// hold the eventBufMutex, which you must acquire with ACQUIRE_LOCK_ALWAYS!
+void flushAllCapsEventsBufs_(void)
{
if (!event_log_writer) {
return;
}
- ACQUIRE_LOCK(&eventBufMutex);
printAndClearEventBuf(&eventBuf);
- RELEASE_LOCK(&eventBufMutex);
for (unsigned int i=0; i < getNumCapabilities(); i++) {
flushLocalEventsBuf(getCapability(i));
@@ -1641,9 +1653,9 @@ static void flushEventLog_(Capability **cap USED_IF_THREADS)
return;
}
- ACQUIRE_LOCK(&eventBufMutex);
+ ACQUIRE_LOCK_ALWAYS(&eventBufMutex);
printAndClearEventBuf(&eventBuf);
- RELEASE_LOCK(&eventBufMutex);
+ RELEASE_LOCK_ALWAYS(&eventBufMutex);
#if defined(THREADED_RTS)
Task *task = newBoundTask();
=====================================
rts/eventlog/EventLog.h
=====================================
@@ -18,6 +18,13 @@
#if defined(TRACING)
extern bool eventlog_enabled;
+#if defined(HAVE_PREEMPTION)
+// Avoid using this mutex directly if at all possible. It is needed in the
+// implementation of forkProcess.
+//
+// All uses of this mutex must use ACQUIRE_LOCK_ALWAYS/RELEASE_LOCK_ALWAYS.
+extern Mutex eventBufMutex;
+#endif
void initEventLogging(void);
void restartEventLogging(void);
@@ -27,6 +34,7 @@ void abortEventLogging(void); // #4512 - after fork child needs to abort
void moreCapEventBufs (uint32_t from, uint32_t to);
void flushLocalEventsBuf(Capability *cap);
void flushAllCapsEventsBufs(void);
+void flushAllCapsEventsBufs_(void);
void flushAllEventsBufs(Capability *cap);
typedef void (*EventlogInitPost)(void);
=====================================
rts/include/rts/OSThreads.h
=====================================
@@ -14,6 +14,44 @@
#pragma once
+/* Note [Threads and preemption]
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ All full-fat OSs that GHC works on have OS threads, and we use them even in
+ the non-threaded RTS for a few features:
+ * Haskell thread preemption;
+ * sample-based profiling;
+ * idle GC;
+ * periodic eventlog flushing.
+
+ We use defined(HAVE_PREEMPTION) to decide if these features are implemented
+ via OS threads.
+
+ On platforms like WASM/js we do not have OS threads in any conventional
+ sense, and the features above are either not available or are implemented
+ differently. See Note [No timer on wasm32].
+
+ In future if GHC is ported to platforms like bare-metal micro-controllers,
+ RTOSs or to run directly under hypervisors then such platforms may also not
+ have threads available and they should not define HAVE_PREEMPTION here. Or
+ for some micro-controller RTOSs like Zeypher one may have a choice about
+ whether to use threads or not (at a size cost). Here would be the right
+ place to control whether the feature list above is supported.
+ */
+#if defined(wasm32_HOST_ARCH)
+ // See Note [No timer on wasm32]
+ // To confuse matters, WASM _does_ have pthread.h but it doesnt work.
+#elif defined(HAVE_PTHREAD_H) || defined(HAVE_WINDOWS_H)
+#define HAVE_PREEMPTION
+#else
+#error Decide if this platform has threads and pre-emption or not.
+#endif
+// And JS does all of this differently, without using this bit of the RTS.
+
+// Configuration sanity check
+#if defined(THREADED_RTS) && !defined(HAVE_PREEMPTION)
+#error Configuration error: THREADED_RTS should imply HAVE_PREEMPTION
+#endif
+
#if defined(HAVE_PTHREAD_H) && !defined(mingw32_HOST_OS)
#if defined(CMINUSMINUS)
@@ -210,9 +248,29 @@ extern bool timedWaitCondition ( Condition* pCond, Mutex* pMut, Time timeout)
//
// Mutexes
//
+// Even in the non-threaded RTS we use threads and mutexes! In particular the
+// timer/ticker is implemented using a thread. And using threads needs locks.
+// In particular we need locks for the data shared between the timer/ticker
+// thread and the thread running the main capability.
+#if defined(HAVE_PREEMPTION)
extern void initMutex ( Mutex* pMut );
extern void closeMutex ( Mutex* pMut );
+// The "always" variants do locking in the threaded and non-threaded RTS.
+// The normal variants below are no-ops in the non-threaded RTS.
+#define ACQUIRE_LOCK_ALWAYS(l) OS_ACQUIRE_LOCK(l)
+#define TRY_ACQUIRE_LOCK_ALWAYS(l) OS_TRY_ACQUIRE_LOCK(l)
+#define RELEASE_LOCK_ALWAYS(l) OS_RELEASE_LOCK(l)
+#define ASSERT_LOCK_HELD_ALWAYS(l) OS_ASSERT_LOCK_HELD(l)
+#else
+// And just to be a bit confusing, the always variants are still no-ops when we
+// do not HAVE_PREEMPTION, since then we don't have threads or mutexes at all.
+#define ACQUIRE_LOCK_ALWAYS(l)
+#define TRY_ACQUIRE_LOCK_ALWAYS(l) 0
+#define RELEASE_LOCK_ALWAYS(l)
+#define ASSERT_LOCK_HELD_ALWAYS(l)
+#endif
+
// Processors and affinity
void setThreadAffinity (uint32_t n, uint32_t m);
void setThreadNode (uint32_t node);
@@ -228,6 +286,7 @@ void releaseThreadNode (void);
#else
+// No-ops in the non-threaded RTS. See also the _ALWAYS variants above.
#define ACQUIRE_LOCK(l)
#define TRY_ACQUIRE_LOCK(l) 0
#define RELEASE_LOCK(l)
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/e2c8b1088a9ff2e6cc597f7dff930b…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/e2c8b1088a9ff2e6cc597f7dff930b…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc][wip/romes/hadrian-cross-stage2-rebase_SVEN_FINAL] testsuite: add GHC build-system regression detection tests
by Sven Tennie (@supersven) 08 Jun '26
by Sven Tennie (@supersven) 08 Jun '26
08 Jun '26
Sven Tennie pushed to branch wip/romes/hadrian-cross-stage2-rebase_SVEN_FINAL at Glasgow Haskell Compiler / GHC
Commits:
f0e5999e by Sven Tennie at 2026-06-08T10:14:59+02:00
testsuite: add GHC build-system regression detection tests
Pin arch-specific GHC config values so that build-system refactors that
accidentally change compiler configuration are caught by the testsuite.
Two tests:
- GhcInfoPins: runs ghc --info and checks 18 pinned fields
- TestsuiteConfigPins: checks 12 Hadrian-supplied config.* values
Both tests print a ready-to-paste entry when run on an unknown platform.
Tests are Python run_command (not compiled Haskell) because wasm32/WASI
targets cannot call runInteractiveProcess from compiled binaries.
- - - - -
4 changed files:
- + testsuite/tests/driver/config-pins/ConfigPinsExpected.py
- + testsuite/tests/driver/config-pins/GhcInfoPins.py
- + testsuite/tests/driver/config-pins/TestsuiteConfigPins.py
- + testsuite/tests/driver/config-pins/all.T
Changes:
=====================================
testsuite/tests/driver/config-pins/ConfigPinsExpected.py
=====================================
@@ -0,0 +1,653 @@
+# Expected GHC config values, keyed by platform key.
+#
+# Platform key construction:
+# base = "target platform string" from ghc --info (or config.platform)
+# key = base
+# + ("-cross" if cross-compiling)
+# + ("-unreg" if Unregisterised,
+# "-no_tntc" elif Tables-next-to-code == NO)
+#
+# When a new platform is encountered the test prints a ready-to-paste entry.
+
+
+def platform_key(platform, is_cross, is_unreg, no_tntc):
+ key = platform
+ if is_cross: key += "-cross"
+ if is_unreg: key += "-unreg"
+ elif no_tntc: key += "-no_tntc"
+ return key
+
+PINNED_GHC_INFO_FIELDS = [
+ "target arch",
+ "target os",
+ "target platform string",
+ "target word size",
+ "target word big endian",
+ "Tables next to code",
+ "Support SMP",
+ "Have interpreter",
+ "Use interpreter",
+ "Have native code generator",
+ "Unregisterised",
+ "Leading underscore",
+ "target has RTS linker",
+ "target RTS linker only supports shared libraries",
+ "cross compiling",
+ "target has GNU nonexec stack",
+ "target has subsections via symbols",
+ "Target default backend",
+]
+
+GHC_INFO_EXPECTED = {
+
+ # ── x86_64 Linux (all distros) ───────────────────────────────────────────
+ "x86_64-unknown-linux": {
+ "target arch": "ArchX86_64",
+ "target os": "OSLinux",
+ "target platform string": "x86_64-unknown-linux",
+ "target word size": "8",
+ "target word big endian": "NO",
+ "Tables next to code": "YES",
+ "Support SMP": "YES",
+ "Have interpreter": "YES",
+ "Use interpreter": "YES",
+ "Have native code generator": "YES",
+ "Unregisterised": "NO",
+ "Leading underscore": "NO",
+ "target has RTS linker": "YES",
+ "target RTS linker only supports shared libraries": "NO",
+ "cross compiling": "NO",
+ "target has GNU nonexec stack": "YES",
+ "target has subsections via symbols": "NO",
+ "Target default backend": "native code generator",
+ },
+
+ # ── x86_64 Linux unregisterised ──────────────────────────────────────────
+ "x86_64-unknown-linux-unreg": {
+ "target arch": "ArchX86_64",
+ "target os": "OSLinux",
+ "target platform string": "x86_64-unknown-linux",
+ "target word size": "8",
+ "target word big endian": "NO",
+ "Tables next to code": "NO",
+ "Support SMP": "NO",
+ "Have interpreter": "YES",
+ "Use interpreter": "YES",
+ "Have native code generator": "NO",
+ "Unregisterised": "YES",
+ "Leading underscore": "NO",
+ "target has RTS linker": "YES",
+ "target RTS linker only supports shared libraries": "NO",
+ "cross compiling": "NO",
+ "target has GNU nonexec stack": "YES",
+ "target has subsections via symbols": "NO",
+ "Target default backend": "compiling via C",
+ },
+
+ # ── x86_64 Linux no tables-next-to-code ──────────────────────────────────
+ "x86_64-unknown-linux-no_tntc": {
+ "target arch": "ArchX86_64",
+ "target os": "OSLinux",
+ "target platform string": "x86_64-unknown-linux",
+ "target word size": "8",
+ "target word big endian": "NO",
+ "Tables next to code": "NO",
+ "Support SMP": "YES",
+ "Have interpreter": "YES",
+ "Use interpreter": "YES",
+ "Have native code generator": "YES",
+ "Unregisterised": "NO",
+ "Leading underscore": "NO",
+ "target has RTS linker": "YES",
+ "target RTS linker only supports shared libraries": "NO",
+ "cross compiling": "NO",
+ "target has GNU nonexec stack": "YES",
+ "target has subsections via symbols": "NO",
+ "Target default backend": "native code generator",
+ },
+
+ # ── aarch64 Linux native ─────────────────────────────────────────────────
+ "aarch64-unknown-linux": {
+ "target arch": "ArchAArch64",
+ "target os": "OSLinux",
+ "target platform string": "aarch64-unknown-linux",
+ "target word size": "8",
+ "target word big endian": "NO",
+ "Tables next to code": "YES",
+ "Support SMP": "YES",
+ "Have interpreter": "YES",
+ "Use interpreter": "YES",
+ "Have native code generator": "YES",
+ "Unregisterised": "NO",
+ "Leading underscore": "NO",
+ "target has RTS linker": "YES",
+ "target RTS linker only supports shared libraries": "NO",
+ "cross compiling": "NO",
+ "target has GNU nonexec stack": "YES",
+ "target has subsections via symbols": "NO",
+ "Target default backend": "native code generator",
+ },
+
+ # ── aarch64 Linux cross ──────────────────────────────────────────────────
+ "aarch64-unknown-linux-cross": {
+ "target arch": "ArchAarch64",
+ "target os": "OSLinux",
+ "target platform string": "aarch64-unknown-linux",
+ "target word size": "8",
+ "target word big endian": "NO",
+ "Tables next to code": "YES",
+ "Support SMP": "YES",
+ "Have interpreter": "NO",
+ "Use interpreter": "NO",
+ "Have native code generator": "YES",
+ "Unregisterised": "NO",
+ "Leading underscore": "NO",
+ "target has RTS linker": "YES",
+ "target RTS linker only supports shared libraries": "NO",
+ "cross compiling": "YES",
+ "target has GNU nonexec stack": "YES",
+ "target has subsections via symbols": "NO",
+ "Target default backend": "native code generator",
+ },
+
+ # ── i386 Linux ───────────────────────────────────────────────────────────
+ "i386-unknown-linux": {
+ "target arch": "ArchX86",
+ "target os": "OSLinux",
+ "target platform string": "i386-unknown-linux",
+ "target word size": "4",
+ "target word big endian": "NO",
+ "Tables next to code": "YES",
+ "Support SMP": "YES",
+ "Have interpreter": "YES",
+ "Use interpreter": "YES",
+ "Have native code generator": "YES",
+ "Unregisterised": "NO",
+ "Leading underscore": "NO",
+ "target has RTS linker": "YES",
+ "target RTS linker only supports shared libraries": "NO",
+ "cross compiling": "NO",
+ "target has GNU nonexec stack": "YES",
+ "target has subsections via symbols": "NO",
+ "Target default backend": "native code generator",
+ },
+
+ # ── aarch64 macOS ────────────────────────────────────────────────────────
+ "aarch64-apple-darwin": {
+ "target arch": "ArchAArch64",
+ "target os": "OSDarwin",
+ "target platform string": "aarch64-apple-darwin",
+ "target word size": "8",
+ "target word big endian": "NO",
+ "Tables next to code": "YES",
+ "Support SMP": "YES",
+ "Have interpreter": "YES",
+ "Use interpreter": "YES",
+ "Have native code generator": "YES",
+ "Unregisterised": "NO",
+ "Leading underscore": "YES",
+ "target has RTS linker": "YES",
+ "target RTS linker only supports shared libraries": "NO",
+ "cross compiling": "NO",
+ "target has GNU nonexec stack": "NO",
+ "target has subsections via symbols": "NO",
+ "Target default backend": "native code generator",
+ },
+
+ # ── x86_64 macOS ─────────────────────────────────────────────────────────
+ "x86_64-apple-darwin": {
+ "target arch": "ArchX86_64",
+ "target os": "OSDarwin",
+ "target platform string": "x86_64-apple-darwin",
+ "target word size": "8",
+ "target word big endian": "NO",
+ "Tables next to code": "YES",
+ "Support SMP": "YES",
+ "Have interpreter": "YES",
+ "Use interpreter": "YES",
+ "Have native code generator": "YES",
+ "Unregisterised": "NO",
+ "Leading underscore": "YES",
+ "target has RTS linker": "YES",
+ "target RTS linker only supports shared libraries": "NO",
+ "cross compiling": "NO",
+ "target has GNU nonexec stack": "NO",
+ "target has subsections via symbols": "YES",
+ "Target default backend": "native code generator",
+ },
+
+ # ── x86_64 FreeBSD ───────────────────────────────────────────────────────
+ "x86_64-portbld-freebsd": {
+ "target arch": "ArchX86_64",
+ "target os": "OSFreeBSD",
+ "target platform string": "x86_64-portbld-freebsd",
+ "target word size": "8",
+ "target word big endian": "NO",
+ "Tables next to code": "YES",
+ "Support SMP": "YES",
+ "Have interpreter": "YES",
+ "Use interpreter": "YES",
+ "Have native code generator": "YES",
+ "Unregisterised": "NO",
+ "Leading underscore": "NO",
+ "target has RTS linker": "YES",
+ "target RTS linker only supports shared libraries": "NO",
+ "cross compiling": "NO",
+ "target has GNU nonexec stack": "YES",
+ "target has subsections via symbols": "NO",
+ "Target default backend": "native code generator",
+ },
+
+ # ── x86_64 Windows native ────────────────────────────────────────────────
+ "x86_64-unknown-mingw32": {
+ "target arch": "ArchX86_64",
+ "target os": "OSMinGW32",
+ "target platform string": "x86_64-unknown-mingw32",
+ "target word size": "8",
+ "target word big endian": "NO",
+ "Tables next to code": "YES",
+ "Support SMP": "YES",
+ "Have interpreter": "YES",
+ "Use interpreter": "YES",
+ "Have native code generator": "YES",
+ "Unregisterised": "NO",
+ "Leading underscore": "NO",
+ "target has RTS linker": "YES",
+ "target RTS linker only supports shared libraries": "NO",
+ "cross compiling": "NO",
+ "target has GNU nonexec stack": "NO",
+ "target has subsections via symbols": "NO",
+ "Target default backend": "native code generator",
+ },
+
+ # ── aarch64 Windows cross ────────────────────────────────────────────────
+ "aarch64-unknown-mingw32-cross": {
+ "target arch": "ArchAarch64",
+ "target os": "OSMinGW32",
+ "target platform string": "aarch64-unknown-mingw32",
+ "target word size": "8",
+ "target word big endian": "NO",
+ "Tables next to code": "YES",
+ "Support SMP": "YES",
+ "Have interpreter": "NO",
+ "Use interpreter": "NO",
+ "Have native code generator": "YES",
+ "Unregisterised": "NO",
+ "Leading underscore": "NO",
+ "target has RTS linker": "YES",
+ "target RTS linker only supports shared libraries": "NO",
+ "cross compiling": "YES",
+ "target has GNU nonexec stack": "NO",
+ "target has subsections via symbols": "NO",
+ "Target default backend": "native code generator",
+ },
+
+ # ── wasm32 cross ─────────────────────────────────────────────────────────
+ "wasm32-unknown-wasi-cross-no_tntc": {
+ "target arch": "ArchWasm32",
+ "target os": "OSWasi",
+ "target platform string": "wasm32-unknown-wasi",
+ "target word size": "4",
+ "target word big endian": "NO",
+ "Tables next to code": "NO",
+ "Support SMP": "NO",
+ "Have interpreter": "YES",
+ "Use interpreter": "YES",
+ "Have native code generator": "YES",
+ "Unregisterised": "NO",
+ "Leading underscore": "NO",
+ "target has RTS linker": "YES",
+ "target RTS linker only supports shared libraries": "NO",
+ "cross compiling": "YES",
+ "target has GNU nonexec stack": "NO",
+ "target has subsections via symbols": "NO",
+ "Target default backend": "native code generator",
+ },
+
+ # ── wasm32 cross unregisterised ──────────────────────────────────────────
+ "wasm32-unknown-wasi-cross-unreg": {
+ "target arch": "ArchWasm32",
+ "target os": "OSWasi",
+ "target platform string": "wasm32-unknown-wasi",
+ "target word size": "4",
+ "target word big endian": "NO",
+ "Tables next to code": "NO",
+ "Support SMP": "NO",
+ "Have interpreter": "NO",
+ "Use interpreter": "NO",
+ "Have native code generator": "NO",
+ "Unregisterised": "YES",
+ "Leading underscore": "NO",
+ "target has RTS linker": "YES",
+ "target RTS linker only supports shared libraries": "NO",
+ "cross compiling": "YES",
+ "target has GNU nonexec stack": "NO",
+ "target has subsections via symbols": "NO",
+ "Target default backend": "compiling via C",
+ },
+
+ # ── JavaScript cross ─────────────────────────────────────────────────────
+ "javascript-unknown-ghcjs-cross": {
+ "target arch": "ArchJavaScript",
+ "target os": "OSGhcjs",
+ "target platform string": "javascript-unknown-ghcjs",
+ "target word size": "4",
+ "target word big endian": "NO",
+ "Tables next to code": "YES",
+ "Support SMP": "NO",
+ "Have interpreter": "NO",
+ "Use interpreter": "NO",
+ "Have native code generator": "NO",
+ "Unregisterised": "NO",
+ "Leading underscore": "NO",
+ "target has RTS linker": "NO",
+ "target RTS linker only supports shared libraries": "NO",
+ "cross compiling": "YES",
+ "target has GNU nonexec stack": "NO",
+ "target has subsections via symbols": "NO",
+ "Target default backend": "compiling to JavaScript",
+ },
+
+ # ── riscv64 Linux cross ──────────────────────────────────────────────────
+ "riscv64-unknown-linux-cross": {
+ "target arch": "ArchRISCV64",
+ "target os": "OSLinux",
+ "target platform string": "riscv64-unknown-linux",
+ "target word size": "8",
+ "target word big endian": "NO",
+ "Tables next to code": "YES",
+ "Support SMP": "YES",
+ "Have interpreter": "NO",
+ "Use interpreter": "NO",
+ "Have native code generator": "YES",
+ "Unregisterised": "NO",
+ "Leading underscore": "NO",
+ "target has RTS linker": "YES",
+ "target RTS linker only supports shared libraries": "NO",
+ "cross compiling": "YES",
+ "target has GNU nonexec stack": "YES",
+ "target has subsections via symbols": "NO",
+ "Target default backend": "native code generator",
+ },
+
+ # ── loongarch64 Linux cross ──────────────────────────────────────────────
+ "loongarch64-unknown-linux-cross": {
+ "target arch": "ArchLoongArch64",
+ "target os": "OSLinux",
+ "target platform string": "loongarch64-unknown-linux",
+ "target word size": "8",
+ "target word big endian": "NO",
+ "Tables next to code": "YES",
+ "Support SMP": "YES",
+ "Have interpreter": "NO",
+ "Use interpreter": "NO",
+ "Have native code generator": "YES",
+ "Unregisterised": "NO",
+ "Leading underscore": "NO",
+ "target has RTS linker": "NO",
+ "target RTS linker only supports shared libraries": "NO",
+ "cross compiling": "YES",
+ "target has GNU nonexec stack": "YES",
+ "target has subsections via symbols": "NO",
+ "Target default backend": "native code generator",
+ },
+}
+
+TESTSUITE_CONFIG_EXPECTED = {
+
+ # ── x86_64 Linux ─────────────────────────────────────────────────────────
+ "x86_64-unknown-linux": {
+ "platform": "x86_64-unknown-linux",
+ "arch": "x86_64",
+ "wordsize": "64",
+ "have_ncg": True,
+ "have_interp": True,
+ "unregisterised": False,
+ "tables_next_to_code": True,
+ "leading_underscore": False,
+ "target_has_smp": True,
+ "have_RTS_linker": True,
+ "interp_force_dyn": False,
+ "cross": False,
+ },
+
+ # ── x86_64 Linux unregisterised ──────────────────────────────────────────
+ "x86_64-unknown-linux-unreg": {
+ "platform": "x86_64-unknown-linux",
+ "arch": "x86_64",
+ "wordsize": "64",
+ "have_ncg": False,
+ "have_interp": True,
+ "unregisterised": True,
+ "tables_next_to_code": False,
+ "leading_underscore": False,
+ "target_has_smp": False,
+ "have_RTS_linker": True,
+ "interp_force_dyn": False,
+ "cross": False,
+ },
+
+ # ── x86_64 Linux no tables-next-to-code ──────────────────────────────────
+ "x86_64-unknown-linux-no_tntc": {
+ "platform": "x86_64-unknown-linux",
+ "arch": "x86_64",
+ "wordsize": "64",
+ "have_ncg": True,
+ "have_interp": True,
+ "unregisterised": False,
+ "tables_next_to_code": False,
+ "leading_underscore": False,
+ "target_has_smp": True,
+ "have_RTS_linker": True,
+ "interp_force_dyn": False,
+ "cross": False,
+ },
+
+ # ── aarch64 Linux native ─────────────────────────────────────────────────
+ "aarch64-unknown-linux": {
+ "platform": "aarch64-unknown-linux",
+ "arch": "aarch64",
+ "wordsize": "64",
+ "have_ncg": True,
+ "have_interp": True,
+ "unregisterised": False,
+ "tables_next_to_code": True,
+ "leading_underscore": False,
+ "target_has_smp": True,
+ "have_RTS_linker": True,
+ "interp_force_dyn": False,
+ "cross": False,
+ },
+
+ # ── aarch64 Linux cross ──────────────────────────────────────────────────
+ "aarch64-unknown-linux-cross": {
+ "platform": "aarch64-unknown-linux",
+ "arch": "aarch64",
+ "wordsize": "64",
+ "have_ncg": True,
+ "have_interp": False,
+ "unregisterised": False,
+ "tables_next_to_code": True,
+ "leading_underscore": False,
+ "target_has_smp": True,
+ "have_RTS_linker": True,
+ "interp_force_dyn": False,
+ "cross": True,
+ },
+
+ # ── i386 Linux ───────────────────────────────────────────────────────────
+ "i386-unknown-linux": {
+ "platform": "i386-unknown-linux",
+ "arch": "i386",
+ "wordsize": "32",
+ "have_ncg": True,
+ "have_interp": True,
+ "unregisterised": False,
+ "tables_next_to_code": True,
+ "leading_underscore": False,
+ "target_has_smp": True,
+ "have_RTS_linker": True,
+ "interp_force_dyn": False,
+ "cross": False,
+ },
+
+ # ── aarch64 macOS ────────────────────────────────────────────────────────
+ "aarch64-apple-darwin": {
+ "platform": "aarch64-apple-darwin",
+ "arch": "aarch64",
+ "wordsize": "64",
+ "have_ncg": True,
+ "have_interp": True,
+ "unregisterised": False,
+ "tables_next_to_code": True,
+ "leading_underscore": True,
+ "target_has_smp": True,
+ "have_RTS_linker": True,
+ "interp_force_dyn": False,
+ "cross": False,
+ },
+
+ # ── x86_64 macOS ─────────────────────────────────────────────────────────
+ "x86_64-apple-darwin": {
+ "platform": "x86_64-apple-darwin",
+ "arch": "x86_64",
+ "wordsize": "64",
+ "have_ncg": True,
+ "have_interp": True,
+ "unregisterised": False,
+ "tables_next_to_code": True,
+ "leading_underscore": True,
+ "target_has_smp": True,
+ "have_RTS_linker": True,
+ "interp_force_dyn": False,
+ "cross": False,
+ },
+
+ # ── x86_64 FreeBSD ───────────────────────────────────────────────────────
+ "x86_64-portbld-freebsd": {
+ "platform": "x86_64-portbld-freebsd",
+ "arch": "x86_64",
+ "wordsize": "64",
+ "have_ncg": True,
+ "have_interp": True,
+ "unregisterised": False,
+ "tables_next_to_code": True,
+ "leading_underscore": False,
+ "target_has_smp": True,
+ "have_RTS_linker": True,
+ "interp_force_dyn": False,
+ "cross": False,
+ },
+
+ # ── x86_64 Windows native ────────────────────────────────────────────────
+ "x86_64-unknown-mingw32": {
+ "platform": "x86_64-unknown-mingw32",
+ "arch": "x86_64",
+ "wordsize": "64",
+ "have_ncg": True,
+ "have_interp": True,
+ "unregisterised": False,
+ "tables_next_to_code": True,
+ "leading_underscore": False,
+ "target_has_smp": True,
+ "have_RTS_linker": True,
+ "interp_force_dyn": False,
+ "cross": False,
+ },
+
+ # ── aarch64 Windows cross ────────────────────────────────────────────────
+ "aarch64-unknown-mingw32-cross": {
+ "platform": "aarch64-unknown-mingw32",
+ "arch": "aarch64",
+ "wordsize": "64",
+ "have_ncg": True,
+ "have_interp": False,
+ "unregisterised": False,
+ "tables_next_to_code": True,
+ "leading_underscore": False,
+ "target_has_smp": True,
+ "have_RTS_linker": True,
+ "interp_force_dyn": False,
+ "cross": True,
+ },
+
+ # ── wasm32 cross ─────────────────────────────────────────────────────────
+ "wasm32-unknown-wasi-cross-no_tntc": {
+ "platform": "wasm32-unknown-wasi",
+ "arch": "wasm32",
+ "wordsize": "32",
+ "have_ncg": True,
+ "have_interp": True,
+ "unregisterised": False,
+ "tables_next_to_code": False,
+ "leading_underscore": False,
+ "target_has_smp": False,
+ "have_RTS_linker": True,
+ "interp_force_dyn": True,
+ "cross": True,
+ },
+
+ # ── wasm32 cross unregisterised ──────────────────────────────────────────
+ "wasm32-unknown-wasi-cross-unreg": {
+ "platform": "wasm32-unknown-wasi",
+ "arch": "wasm32",
+ "wordsize": "32",
+ "have_ncg": False,
+ "have_interp": False,
+ "unregisterised": True,
+ "tables_next_to_code": False,
+ "leading_underscore": False,
+ "target_has_smp": False,
+ "have_RTS_linker": True,
+ "interp_force_dyn": False,
+ "cross": True,
+ },
+
+ # ── JavaScript cross ─────────────────────────────────────────────────────
+ "javascript-unknown-ghcjs-cross": {
+ "platform": "javascript-unknown-ghcjs",
+ "arch": "javascript",
+ "wordsize": "32",
+ "have_ncg": False,
+ "have_interp": False,
+ "unregisterised": False,
+ "tables_next_to_code": True,
+ "leading_underscore": False,
+ "target_has_smp": False,
+ "have_RTS_linker": False,
+ "interp_force_dyn": False,
+ "cross": True,
+ },
+
+ # ── riscv64 Linux cross ──────────────────────────────────────────────────
+ "riscv64-unknown-linux-cross": {
+ "platform": "riscv64-unknown-linux",
+ "arch": "riscv64",
+ "wordsize": "64",
+ "have_ncg": True,
+ "have_interp": False,
+ "unregisterised": False,
+ "tables_next_to_code": True,
+ "leading_underscore": False,
+ "target_has_smp": True,
+ "have_RTS_linker": True,
+ "interp_force_dyn": False,
+ "cross": True,
+ },
+
+ # ── loongarch64 Linux cross ──────────────────────────────────────────────
+ "loongarch64-unknown-linux-cross": {
+ "platform": "loongarch64-unknown-linux",
+ "arch": "loongarch64",
+ "wordsize": "64",
+ "have_ncg": True,
+ "have_interp": False,
+ "unregisterised": False,
+ "tables_next_to_code": True,
+ "leading_underscore": False,
+ "target_has_smp": True,
+ "have_RTS_linker": False,
+ "interp_force_dyn": False,
+ "cross": True,
+ },
+}
=====================================
testsuite/tests/driver/config-pins/GhcInfoPins.py
=====================================
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+"""Check that ghc --info output matches pinned expected values."""
+import ast
+import subprocess
+import sys
+
+sys.path.insert(0, '.')
+from ConfigPinsExpected import GHC_INFO_EXPECTED, PINNED_GHC_INFO_FIELDS, platform_key
+
+
+def info_platform_key(info_map):
+ return platform_key(
+ info_map.get("target platform string", "<unknown>"),
+ info_map.get("cross compiling", "NO") == "YES",
+ info_map.get("Unregisterised", "NO") == "YES",
+ info_map.get("Tables next to code", "YES") == "NO",
+ )
+
+
+def main():
+ if len(sys.argv) != 2:
+ print("Usage: GhcInfoPins.py <compiler-path>")
+ sys.exit(1)
+
+ compiler = sys.argv[1]
+ try:
+ result = subprocess.run([compiler, "--info"], capture_output=True, text=True)
+ except OSError as e:
+ print(f"Failed to run {compiler!r} --info: {e}")
+ sys.exit(1)
+ if result.returncode != 0:
+ print(f"Error running {compiler!r} --info:\n{result.stderr}")
+ sys.exit(1)
+ try:
+ info_map = dict(ast.literal_eval(result.stdout.strip()))
+ except (ValueError, SyntaxError) as e:
+ print(f"Failed to parse {compiler!r} --info output: {e}")
+ sys.exit(1)
+ key = info_platform_key(info_map)
+
+ actual = {f: info_map.get(f, "<missing>") for f in PINNED_GHC_INFO_FIELDS}
+
+ if key not in GHC_INFO_EXPECTED:
+ print(f"Platform key {key!r} not in ConfigPinsExpected.")
+ print("Add this entry to GHC_INFO_EXPECTED in ConfigPinsExpected.py:")
+ print()
+ print(f" {key!r}: {{")
+ for f, v in actual.items():
+ print(f" {f!r}: {v!r},")
+ print(" },")
+ sys.exit(1)
+
+ expected = GHC_INFO_EXPECTED[key]
+ mismatches = [
+ (f, ev, actual.get(f, "<missing>"))
+ for f, ev in sorted(expected.items())
+ if ev != actual.get(f, "<missing>")
+ ]
+
+ if mismatches:
+ print(f"MISMATCH for {key!r}:")
+ for f, ev, av in mismatches:
+ print(f" {f!r}: expected {ev!r}, got {av!r}")
+ sys.exit(1)
+
+ print(f"OK: {key}")
+
+
+if __name__ == "__main__":
+ main()
=====================================
testsuite/tests/driver/config-pins/TestsuiteConfigPins.py
=====================================
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+"""Check that Hadrian-supplied testsuite config values match pinned expected values."""
+import sys
+
+sys.path.insert(0, '.')
+from ConfigPinsExpected import TESTSUITE_CONFIG_EXPECTED, platform_key
+
+
+def main():
+ if len(sys.argv) != 13:
+ print("Usage: TestsuiteConfigPins.py platform arch wordsize "
+ "have_ncg have_interp unreg tntc leading_us has_smp "
+ "rts_linker interp_dyn cross")
+ print(f"Got {len(sys.argv) - 1} args: {sys.argv[1:]}")
+ sys.exit(1)
+
+ (platform, arch, wordsize,
+ have_ncg, have_interp, unreg, tntc,
+ lead_us, has_smp, rts_linker, interp_dyn, cross) = sys.argv[1:]
+
+ b = lambda s: s == "1"
+ actual = {
+ "platform": platform,
+ "arch": arch,
+ "wordsize": wordsize,
+ "have_ncg": b(have_ncg),
+ "have_interp": b(have_interp),
+ "unregisterised": b(unreg),
+ "tables_next_to_code": b(tntc),
+ "leading_underscore": b(lead_us),
+ "target_has_smp": b(has_smp),
+ "have_RTS_linker": b(rts_linker),
+ "interp_force_dyn": b(interp_dyn),
+ "cross": b(cross),
+ }
+ key = platform_key(platform, b(cross), b(unreg), not b(tntc))
+
+ if key not in TESTSUITE_CONFIG_EXPECTED:
+ print(f"Platform key {key!r} not in ConfigPinsExpected.")
+ print("Add this entry to TESTSUITE_CONFIG_EXPECTED in ConfigPinsExpected.py:")
+ print()
+ print(f" {key!r}: {{")
+ for k, v in actual.items():
+ print(f" {k!r}: {v!r},")
+ print(" },")
+ sys.exit(1)
+
+ expected = TESTSUITE_CONFIG_EXPECTED[key]
+ mismatches = [
+ (k, ev, actual[k])
+ for k, ev in expected.items()
+ if ev != actual.get(k)
+ ]
+
+ if mismatches:
+ print(f"MISMATCH for {key!r}:")
+ for k, ev, av in mismatches:
+ print(f" {k!r}: expected {ev!r}, got {av!r}")
+ sys.exit(1)
+
+ print(f"OK: {key}")
+
+
+if __name__ == "__main__":
+ main()
=====================================
testsuite/tests/driver/config-pins/all.T
=====================================
@@ -0,0 +1,30 @@
+import sys
+
+# Python run_command rather than compiled Haskell: wasm32/WASI targets cannot
+# call runInteractiveProcess from compiled binaries (no process creation in
+# WASI). Python scripts run on the host regardless of the target under test.
+
+test('GhcInfoPins',
+ [extra_files(['ConfigPinsExpected.py', 'GhcInfoPins.py']),
+ ignore_stdout],
+ run_command,
+ [sys.executable + ' GhcInfoPins.py "' + config.compiler + '"'])
+
+test('TestsuiteConfigPins',
+ [extra_files(['ConfigPinsExpected.py', 'TestsuiteConfigPins.py']),
+ ignore_stdout],
+ run_command,
+ [sys.executable + ' TestsuiteConfigPins.py ' + ' '.join([
+ config.platform,
+ config.arch,
+ config.wordsize,
+ str(int(config.have_ncg)),
+ str(int(config.have_interp)),
+ str(int(config.unregisterised)),
+ str(int(config.tables_next_to_code)),
+ str(int(config.leading_underscore)),
+ str(int(config.target_has_smp)),
+ str(int(config.have_RTS_linker)),
+ str(int(config.interp_force_dyn)),
+ str(int(config.cross)),
+ ])])
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/f0e5999e4d808f57dec2655cb4476c9…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/f0e5999e4d808f57dec2655cb4476c9…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc][wip/amg/castz] WIP: introduce cast zapping as an alternative to coercion zapping
by Adam Gundry (@adamgundry) 08 Jun '26
by Adam Gundry (@adamgundry) 08 Jun '26
08 Jun '26
Adam Gundry pushed to branch wip/amg/castz at Glasgow Haskell Compiler / GHC
Commits:
2652899b by Adam Gundry at 2026-06-08T09:52:19+02:00
WIP: introduce cast zapping as an alternative to coercion zapping
- - - - -
60 changed files:
- compiler/GHC/Core.hs
- compiler/GHC/Core/Coercion.hs
- compiler/GHC/Core/Coercion/Opt.hs
- compiler/GHC/Core/FVs.hs
- compiler/GHC/Core/LateCC/OverloadedCalls.hs
- compiler/GHC/Core/Lint.hs
- compiler/GHC/Core/Lint/SubstTypeLets.hs
- compiler/GHC/Core/Map/Expr.hs
- compiler/GHC/Core/Map/Type.hs
- compiler/GHC/Core/Opt/Arity.hs
- compiler/GHC/Core/Opt/CSE.hs
- compiler/GHC/Core/Opt/CprAnal.hs
- compiler/GHC/Core/Opt/DmdAnal.hs
- compiler/GHC/Core/Opt/OccurAnal.hs
- compiler/GHC/Core/Opt/SetLevels.hs
- compiler/GHC/Core/Opt/Simplify/Env.hs
- compiler/GHC/Core/Opt/Simplify/Iteration.hs
- compiler/GHC/Core/Opt/Simplify/Utils.hs
- compiler/GHC/Core/Opt/SpecConstr.hs
- compiler/GHC/Core/Opt/Specialise.hs
- compiler/GHC/Core/Ppr.hs
- compiler/GHC/Core/Rules.hs
- compiler/GHC/Core/Seq.hs
- compiler/GHC/Core/SimpleOpt.hs
- compiler/GHC/Core/Stats.hs
- compiler/GHC/Core/Subst.hs
- compiler/GHC/Core/Tidy.hs
- compiler/GHC/Core/TyCo/FVs.hs
- compiler/GHC/Core/TyCo/Ppr.hs
- compiler/GHC/Core/TyCo/Ppr.hs-boot
- compiler/GHC/Core/TyCo/Rep.hs
- compiler/GHC/Core/TyCo/Rep.hs-boot
- compiler/GHC/Core/TyCo/Subst.hs
- compiler/GHC/Core/TyCo/Tidy.hs
- compiler/GHC/Core/Utils.hs
- compiler/GHC/CoreToIface.hs
- compiler/GHC/CoreToStg/Prep.hs
- compiler/GHC/Driver/DynFlags.hs
- compiler/GHC/Driver/Flags.hs
- compiler/GHC/Driver/Session.hs
- compiler/GHC/HsToCore/Binds.hs
- compiler/GHC/HsToCore/Foreign/C.hs
- compiler/GHC/HsToCore/Foreign/JavaScript.hs
- compiler/GHC/HsToCore/Foreign/Prim.hs
- compiler/GHC/HsToCore/Foreign/Wasm.hs
- compiler/GHC/HsToCore/Utils.hs
- compiler/GHC/Iface/Rename.hs
- compiler/GHC/Iface/Syntax.hs
- compiler/GHC/Iface/Type.hs
- compiler/GHC/IfaceToCore.hs
- compiler/GHC/Tc/Solver/Solve.hs
- + compiler/GHC/Tc/Types/EvTerm.hs
- compiler/GHC/Tc/Types/Evidence.hs
- compiler/GHC/Tc/Zonk/Type.hs
- compiler/GHC/Types/Id/Make.hs
- docs/core-spec/CoreLint.ott
- docs/core-spec/CoreSyn.ott
- docs/users_guide/debugging.rst
- testsuite/tests/ghci/prog-mhu002/prog-mhu002c.stdout
- testsuite/tests/ghci/scripts/ghci024.stdout
The diff was not included because it is too large.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/2652899b20d105d9c5c49af8c5f6bd7…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/2652899b20d105d9c5c49af8c5f6bd7…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc][wip/marge_bot_batch_merge_job] 3 commits: testsuite: Deduplicate --only test names
by Marge Bot (@marge-bot) 08 Jun '26
by Marge Bot (@marge-bot) 08 Jun '26
08 Jun '26
Marge Bot pushed to branch wip/marge_bot_batch_merge_job at Glasgow Haskell Compiler / GHC
Commits:
18f6138a by Simon Jakobi at 2026-06-04T20:20:31-04:00
testsuite: Deduplicate --only test names
config.only is assumed to be a set, but supplying --only overwrote it
with the (list) argparse result, which can contain duplicates. When a
test ran, config.only.remove(name) dropped only the first occurrence,
so a duplicated name lingered and was later misreported as a
"test not found" framework failure. Store it as a set instead.
Fixes #27322
Co-Authored-By: Claude Opus 4.7 <noreply(a)anthropic.com>
- - - - -
9c26952d by Simon Jakobi at 2026-06-07T21:35:09-04:00
testsuite: detect fast bignum via ghc-internal, not removed ghc-bignum
The ghc-bignum package was merged into ghc-internal, so the BIGNUM_GMP
probe in test.mk ran `ghc-pkg field ghc-bignum exposed-modules`, which
fails with "cannot find package ghc-bignum". That error went to stderr
and leaked into the captured stderr of every makefile_test, causing
spurious [bad stderr] failures across the suite. The probe also silently
returned empty, so config.have_fast_bignum was wrongly False even on GMP
builds.
Probe ghc-internal's extra-libraries for the gmp library instead: the
GMP backend module is an other-module (not exposed), but GMP_LIBS adds
gmp to extra-libraries only on a GMP build, so this distinguishes the
backends. Redirect stderr to keep any future missing-package error off
the harness's stderr.
This also removes a stale comment as per suggestion from hsyl20.
Co-Authored-By: Claude Opus 4.7 <noreply(a)anthropic.com>
- - - - -
7da2b325 by Alan Zimmerman at 2026-06-07T21:35:10-04:00
EPA: Rename Transform.anchorEof to addModuleCommentOrigDeltas
This now matches what it actually does.
- - - - -
4 changed files:
- testsuite/driver/runtests.py
- testsuite/mk/test.mk
- utils/check-exact/Main.hs
- utils/check-exact/Transform.hs
Changes:
=====================================
testsuite/driver/runtests.py
=====================================
@@ -133,7 +133,7 @@ if args.unexpected_output_dir:
config.unexpected_output_dir = Path(args.unexpected_output_dir)
if args.only:
- config.only = args.only
+ config.only = set(args.only)
config.run_only_some_tests = True
if args.skip:
=====================================
testsuite/mk/test.mk
=====================================
@@ -109,9 +109,11 @@ endif
HAVE_GDB := $(shell if gdb --version > /dev/null 2> /dev/null; then echo YES; else echo NO; fi)
HAVE_READELF := $(shell if readelf --version > /dev/null 2> /dev/null; then echo YES; else echo NO; fi)
-# we need a better way to find which backend is selected and if --check flag is
-# used
-BIGNUM_GMP := $(shell "$(GHC_PKG)" field ghc-bignum exposed-modules | grep GMP)
+# Detect whether the fast (GMP) bignum backend is in use. The GMP backend module
+# in ghc-internal is hidden, so we look instead for the gmp library it links
+# against: GMP_LIBS adds gmp to ghc-internal's extra-libraries only on a GMP
+# build.
+BIGNUM_GMP := $(shell "$(GHC_PKG)" field ghc-internal extra-libraries 2>/dev/null | grep gmp)
ifeq "$(filter thr, $(GhcRTSWays))" "thr"
RUNTEST_OPTS += -e config.ghc_with_threaded_rts=True
=====================================
utils/check-exact/Main.hs
=====================================
@@ -646,7 +646,7 @@ addLocaLDecl3 :: Changer
addLocaLDecl3 libdir top = do
Right newDecl <- withDynFlags libdir (\df -> parseDecl df "decl" "nn = 2")
let
- doAddLocal = replaceDecls (anchorEof lp) [parent',d2']
+ doAddLocal = replaceDecls (addModuleCommentOrigDeltas lp) [parent',d2']
where
lp = top
(de1:d2:_) = hsDecls lp
@@ -667,7 +667,7 @@ addLocaLDecl4 libdir lp = do
Right newDecl <- withDynFlags libdir (\df -> parseDecl df "decl" "nn = 2")
Right newSig <- withDynFlags libdir (\df -> parseDecl df "sig" "nn :: Int")
let
- doAddLocal = replaceDecls (anchorEof lp) (parent':ds)
+ doAddLocal = replaceDecls (addModuleCommentOrigDeltas lp) (parent':ds)
where
(parent:ds) = hsDecls (makeDeltaAst lp)
@@ -781,7 +781,7 @@ rmDecl3 _libdir lp = do
rmDecl4 :: Changer
rmDecl4 _libdir lp = do
let
- doRmDecl = replaceDecls (anchorEof lp) [de1',sd1]
+ doRmDecl = replaceDecls (addModuleCommentOrigDeltas lp) [de1',sd1]
where
[de1] = hsDecls lp
(de1',Just sd1) = modifyValD (getLocA de1) de1 $ \_m [sd1a,sd2] ->
=====================================
utils/check-exact/Transform.hs
=====================================
@@ -65,7 +65,7 @@ module Transform
, balanceComments
, balanceCommentsList
, balanceCommentsListA
- , anchorEof
+ , addModuleCommentOrigDeltas
-- ** Managing lists, pure functions
, captureOrderBinds
@@ -724,8 +724,8 @@ balanceSameLineComments (L la (Match anm mctxt pats (GRHSs x grhss lb)))
-- ---------------------------------------------------------------------
-anchorEof :: ParsedSource -> ParsedSource
-anchorEof (L l m@(HsModule (XModulePs an _lo _ _) _mn _exps _imps _decls)) = L l (m { hsmodExt = (hsmodExt m){ hsmodAnn = an' } })
+addModuleCommentOrigDeltas :: ParsedSource -> ParsedSource
+addModuleCommentOrigDeltas (L l m@(HsModule (XModulePs an _lo _ _) _mn _exps _imps _decls)) = L l (m { hsmodExt = (hsmodExt m){ hsmodAnn = an' } })
where
an' = addCommentOrigDeltasAnn an
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/fcfa08345e2af28169df6963fcb83a…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/fcfa08345e2af28169df6963fcb83a…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc][wip/az/epa-fix-transform-anchoreof] EPA: Rename Transform.anchorEof to addModuleCommentOrigDeltas
by Alan Zimmerman (@alanz) 07 Jun '26
by Alan Zimmerman (@alanz) 07 Jun '26
07 Jun '26
Alan Zimmerman pushed to branch wip/az/epa-fix-transform-anchoreof at Glasgow Haskell Compiler / GHC
Commits:
4588a803 by Alan Zimmerman at 2026-06-07T22:06:43+01:00
EPA: Rename Transform.anchorEof to addModuleCommentOrigDeltas
This now matches what it actually does.
- - - - -
2 changed files:
- utils/check-exact/Main.hs
- utils/check-exact/Transform.hs
Changes:
=====================================
utils/check-exact/Main.hs
=====================================
@@ -646,7 +646,7 @@ addLocaLDecl3 :: Changer
addLocaLDecl3 libdir top = do
Right newDecl <- withDynFlags libdir (\df -> parseDecl df "decl" "nn = 2")
let
- doAddLocal = replaceDecls (anchorEof lp) [parent',d2']
+ doAddLocal = replaceDecls (addModuleCommentOrigDeltas lp) [parent',d2']
where
lp = top
(de1:d2:_) = hsDecls lp
@@ -667,7 +667,7 @@ addLocaLDecl4 libdir lp = do
Right newDecl <- withDynFlags libdir (\df -> parseDecl df "decl" "nn = 2")
Right newSig <- withDynFlags libdir (\df -> parseDecl df "sig" "nn :: Int")
let
- doAddLocal = replaceDecls (anchorEof lp) (parent':ds)
+ doAddLocal = replaceDecls (addModuleCommentOrigDeltas lp) (parent':ds)
where
(parent:ds) = hsDecls (makeDeltaAst lp)
@@ -781,7 +781,7 @@ rmDecl3 _libdir lp = do
rmDecl4 :: Changer
rmDecl4 _libdir lp = do
let
- doRmDecl = replaceDecls (anchorEof lp) [de1',sd1]
+ doRmDecl = replaceDecls (addModuleCommentOrigDeltas lp) [de1',sd1]
where
[de1] = hsDecls lp
(de1',Just sd1) = modifyValD (getLocA de1) de1 $ \_m [sd1a,sd2] ->
=====================================
utils/check-exact/Transform.hs
=====================================
@@ -65,7 +65,7 @@ module Transform
, balanceComments
, balanceCommentsList
, balanceCommentsListA
- , anchorEof
+ , addModuleCommentOrigDeltas
-- ** Managing lists, pure functions
, captureOrderBinds
@@ -724,8 +724,8 @@ balanceSameLineComments (L la (Match anm mctxt pats (GRHSs x grhss lb)))
-- ---------------------------------------------------------------------
-anchorEof :: ParsedSource -> ParsedSource
-anchorEof (L l m@(HsModule (XModulePs an _lo _ _) _mn _exps _imps _decls)) = L l (m { hsmodExt = (hsmodExt m){ hsmodAnn = an' } })
+addModuleCommentOrigDeltas :: ParsedSource -> ParsedSource
+addModuleCommentOrigDeltas (L l m@(HsModule (XModulePs an _lo _ _) _mn _exps _imps _decls)) = L l (m { hsmodExt = (hsmodExt m){ hsmodAnn = an' } })
where
an' = addCommentOrigDeltasAnn an
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/4588a8030dce3834501e31aad55932d…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/4588a8030dce3834501e31aad55932d…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
07 Jun '26
Rodrigo Mesquita pushed new branch wip/romes/fix-test at Glasgow Haskell Compiler / GHC
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/tree/wip/romes/fix-test
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc] Pushed new branch wip/az/epa-fix-transform-anchoreof
by Alan Zimmerman (@alanz) 07 Jun '26
by Alan Zimmerman (@alanz) 07 Jun '26
07 Jun '26
Alan Zimmerman pushed new branch wip/az/epa-fix-transform-anchoreof at Glasgow Haskell Compiler / GHC
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/tree/wip/az/epa-fix-transform-anchoreof
You're receiving this email because of your account on gitlab.haskell.org.
1
0