[Git][ghc/ghc][wip/davide/hadrian-aclocal_path] 4 commits: Trim the continuation in mkDupableContWithDmds
by David Eichmann (@DavidEichmann) 28 May '26
by David Eichmann (@DavidEichmann) 28 May '26
28 May '26
David Eichmann pushed to branch wip/davide/hadrian-aclocal_path at Glasgow Haskell Compiler / GHC
Commits:
4a645683 by Simon Peyton Jones at 2026-05-27T21:41:59-04:00
Trim the continuation in mkDupableContWithDmds
When there are no remaining argument demands, it means the application
is bottoming. In this case, we can trim the continuation to avoid the
panic that was observed in #27261.
See Note [Trimming the continuation for bottoming functions] in
GHC.Core.Opt.Simplify.Iteration.
- - - - -
8ab506ff by Cheng Shao at 2026-05-27T21:42:47-04:00
ghci: fix module name string lifetime in hs_hpc_module invocation
This patch makes hpcAddModule pass a properly malloced module name
string to hs_hpc_module, instead of using useAsCString which causes
use-after-free of module name string. Fixes #27297.
Co-authored-by: Codex <codex(a)openai.com>
- - - - -
b0233814 by sheaf at 2026-05-27T21:43:31-04:00
Relax acceptance threshold for T10421
As seen in #27289, the 1% acceptance threshold for this text was
overly narrow, resulting in spurious test failures. This commit widens
the acceptance threshold to 2%. Fixes #27289.
- - - - -
1127299e by David Eichmann at 2026-05-28T11:26:39+01:00
Hadrian: Autoreconf builder on windows converts env variable ACLOCAL_PATH to unix paths
This is needed to fix hadrian bindist builds on windows in the mysy2
clang64 environment
- - - - -
14 changed files:
- boot
- + changelog.d/T27261
- compiler/GHC/Core/Opt/Simplify/Iteration.hs
- compiler/GHC/Core/Opt/Simplify/Utils.hs
- hadrian/src/Rules/BinaryDist.hs
- hadrian/src/Utilities.hs
- + libraries/ghc-boot/GHC/Data/ShortByteString.hs
- libraries/ghc-boot/ghc-boot.cabal.in
- libraries/ghci/GHCi/Coverage.hs
- libraries/ghci/GHCi/Run.hs
- testsuite/tests/perf/compiler/all.T
- + testsuite/tests/simplCore/should_compile/T27261.hs
- + testsuite/tests/simplCore/should_compile/T27261_aux.hs
- testsuite/tests/simplCore/should_compile/all.T
Changes:
=====================================
boot
=====================================
@@ -54,7 +54,9 @@ def autoreconf():
if os.name == 'nt':
# Get the normalized ACLOCAL_PATH for Windows
# This is necessary since on Windows this will be a Windows
- # path, which autoreconf doesn't know doesn't know how to handle.
+ # path, which autoreconf doesn't know how to handle.
+ #
+ # This logic is duplicated in Hadrian. Specifically in hadrian/src/Rules/BinaryDist.hs
ac_local = os.getenv('ACLOCAL_PATH', '')
ac_local_arg = re.sub(r';', r':', ac_local)
ac_local_arg = re.sub(r'\\', r'/', ac_local_arg)
=====================================
changelog.d/T27261
=====================================
@@ -0,0 +1,10 @@
+section: compiler
+issues: #27261
+mrs: !16084
+synopsis:
+ Avoid a crash in ``mkDupableContWithDmds`` when given empty demands
+description:
+ The case of an empty list of remaining argument demands is now explicitly
+ handled by trimming the simplifier continuation, to avoid a compiler crash
+ of the form ``Non-exhaustive patterns in dmd : cont_dmds`` or ``expectNonEmpty``
+ in ``mkDupableContWithDmds``.
=====================================
compiler/GHC/Core/Opt/Simplify/Iteration.hs
=====================================
@@ -62,6 +62,7 @@ import GHC.Types.Var ( isTyCoVar )
import GHC.Builtin.Types.Prim( realWorldStatePrimTy )
import GHC.Builtin.Names( runRWKey, seqHashKey )
+import qualified GHC.Data.List.Infinite as Inf
import GHC.Data.Maybe ( isNothing, orElse, mapMaybe )
import GHC.Data.FastString
import GHC.Unit.Module ( moduleName )
@@ -2444,24 +2445,9 @@ rebuildCall env arg_info _cont
---------- Bottoming applications --------------
rebuildCall env (ArgInfo { ai_fun = fun, ai_args = rev_args, ai_dmds = [] }) cont
- -- When we run out of strictness args, it means
- -- that the call is definitely bottom; see GHC.Core.Opt.Simplify.Utils.mkArgInfo
- -- Then we want to discard the entire strict continuation. E.g.
- -- * case (error "hello") of { ... }
- -- * (error "Hello") arg
- -- * f (error "Hello") where f is strict
- -- etc
- -- Then, especially in the first of these cases, we'd like to discard
- -- the continuation, leaving just the bottoming expression. But the
- -- type might not be right, so we may have to add a coerce.
- | not (contIsTrivial cont) -- Only do this if there is a non-trivial
- -- continuation to discard, else we do it
- -- again and again!
- = seqType cont_ty `seq` -- See Note [Avoiding space leaks in OutType]
- return (emptyFloats env, castBottomExpr res cont_ty)
- where
- res = argInfoExpr fun rev_args
- cont_ty = contResultType cont
+ -- When we run out of demands, it means that the call is definitely bottom.
+ -- See (TC2) in Note [Trimming the continuation for bottoming functions]
+ = rebuild env (argInfoExpr fun rev_args) (mkBottomCont cont)
---------- Simplify type applications --------------
rebuildCall env info (ApplyToTy { sc_arg_ty = arg_ty, sc_hole_ty = hole_ty, sc_cont = cont })
@@ -4045,6 +4031,41 @@ When we have
then we can just duplicate those alts because the A and C cases
will disappear immediately. This is more direct than creating
join points and inlining them away. See #4930.
+
+Note [Trimming the continuation for bottoming functions]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Suppose
+ f :: Int -> Int -> Int
+ f x = error "urk"
+
+ foo = f 3 4
+
+f's demand signature say "after one arg I return bottom". We can drop
+the remaining arguments, thus
+
+ foo = case f 3 of {}
+
+This trimming can also be done with other continuations:
+ * case (error "hello") of { ... }
+ * f (error "Hello") where f is strict
+ etc
+
+We implement the trimming in three parts:
+
+(TC1) In `mkArgInfo`, for a bottoming function, we make a list of `RemainingArgDmds`
+ with a finite list of elements (in the example above, just one).
+
+ For comparison, note that, for non-bottoming functions, the `RemainingArgDmds`
+ always finishes with an infinite list of `topDmd`.
+
+(TC2) In `rebuildCall`, when we run out of `RemainingArgDmds` we discard the
+ remaining continuation.
+
+ After discarding the continuation, the types might not match, in which case
+ we leave behind a (case <hole> of {}) wrapper. See the call to `mkBottomCont`.
+
+(TC3) In `mkDupableContWithDmds`, we similarly discard the continuation when
+ we run out of `RemainingArgDmds`.
-}
--------------------
@@ -4079,10 +4100,10 @@ mkDupableCont env cont
= mkDupableContWithDmds (zapSubstEnv env) (repeat topDmd) cont
mkDupableContWithDmds
- :: SimplEnvIS -> [Demand] -- Demands on arguments; always infinite
+ :: SimplEnvIS -> RemainingArgDmds
-> SimplCont -> SimplM ( SimplFloats, SimplCont)
-mkDupableContWithDmds env _ cont
+mkDupableContWithDmds env remaining_dmds cont
-- Check the invariant
| assertPpr (checkSimplEnvIS env) (pprBadSimplEnvIS env) False
= pprPanic "mkDupableContWithDmds" empty
@@ -4090,6 +4111,13 @@ mkDupableContWithDmds env _ cont
| contIsDupable cont
= return (emptyFloats env, cont)
+ -- No more demands => function is definitely bottom
+ -- => simply trim the continuation
+ -- c.f. the null-demands case in `rebuildCall`
+ -- See (TC3) in Note [Trimming the continuation for bottoming functions]
+ | null remaining_dmds
+ = return (emptyFloats env, mkBottomCont cont)
+
mkDupableContWithDmds _ _ (Stop {}) = panic "mkDupableCont" -- Handled by previous eqn
mkDupableContWithDmds env dmds (CastIt { sc_co = co, sc_opt = opt, sc_cont = cont })
@@ -4134,7 +4162,8 @@ mkDupableContWithDmds env _
, thumbsUpPlanA cont
= -- Use Plan A of Note [Duplicating StrictArg]
-- pprTrace "Using plan A" (ppr (ai_fun fun) $$ text "args" <+> ppr (ai_args fun) $$ text "cont" <+> ppr cont) $
- do { let _ :| dmds = expectNonEmpty $ ai_dmds fun
+ do { let _ :| dmds = expectNonEmpty (ai_dmds fun) -- See Invariant of StrictArg;
+ -- ai_dmds is never empty
; (floats1, cont') <- mkDupableContWithDmds env dmds cont
-- Use the demands from the function to add the right
-- demand info on any bindings we make for further args
@@ -4180,7 +4209,10 @@ mkDupableContWithDmds env dmds
-- let a = ...arg...
-- in [...hole...] a
-- NB: sc_dup /= OkToDup; that is caught earlier by contIsDupable
- do { let dmd:|cont_dmds = expectNonEmpty dmds
+ do { let dmd:|cont_dmds =
+ -- We took care to handle an empty demand list at the start,
+ -- ensuring this call to 'expectNonEmpty' does not panic (#27261).
+ expectNonEmpty dmds
; (floats1, cont') <- mkDupableContWithDmds env cont_dmds cont
; let env' = env `setInScopeFromF` floats1
; arg' <- simplArg env' Nothing hole_ty se arg arg_mco
@@ -4251,7 +4283,7 @@ mkDupableStrictBind env arg_bndr join_rhs res_ty
; let arg_info = ArgInfo { ai_fun = join_bndr
, ai_rules = [], ai_args = []
, ai_encl = False, ai_dmds = repeat topDmd
- , ai_discs = repeat 0 }
+ , ai_discs = Inf.repeat 0 }
; return ( addJoinFloats (emptyFloats env) $
unitJoinFloat $
NonRec join_bndr $
=====================================
compiler/GHC/Core/Opt/Simplify/Utils.hs
=====================================
@@ -25,13 +25,13 @@ module GHC.Core.Opt.Simplify.Utils (
StaticEnv(..),
isSimplified, contIsStop,
contIsDupable, contResultType, contHoleType, contHoleScaling,
- contIsTrivial, contArgs, contIsRhs,
+ contIsTrivial, contArgs, contIsRhs, mkBottomCont,
hasArgs, countArgs, contOutArgs, dropContArgs,
mkBoringStop, mkRhsStop, mkLazyArgStop,
interestingCallContext,
-- ArgInfo
- ArgInfo(..), ArgSpec(..), mkArgInfo,
+ ArgInfo(..), ArgSpec(..), RemainingArgDmds, mkArgInfo,
addValArgTo, addTyArgTo,
argInfoExpr, argSpecArg,
pushOutArgs, pushArgSpecs,
@@ -54,8 +54,10 @@ import GHC.Core.Opt.Stats ( Tick(..) )
import qualified GHC.Core.Subst
import GHC.Core.Ppr
import GHC.Core.TyCo.Ppr ( pprParendType )
+import GHC.Core.TyCo.Compare ( eqTypeIgnoringMultiplicity )
import GHC.Core.FVs
import GHC.Core.Utils
+import GHC.Core.Make( mkWildValBinder )
import GHC.Core.Opt.Arity
import GHC.Core.Unfold
import GHC.Core.Unfold.Make
@@ -75,6 +77,8 @@ import GHC.Types.Var.Set
import GHC.Types.Basic
import GHC.Types.Name.Env
+import GHC.Data.List.Infinite ( Infinite(..) )
+import qualified GHC.Data.List.Infinite as Inf
import GHC.Data.OrdList ( isNilOL )
import GHC.Data.FastString ( fsLit )
@@ -205,10 +209,10 @@ data SimplCont
| StrictArg -- (StrictArg (f e1 ..en) K)[e] = K[ f e1 .. en e ]
{ sc_dup :: DupFlag
- , sc_fun :: ArgInfo -- Specifies f, e1..en, Whether f has rules, etc
+ , sc_fun :: ArgInfo -- Specifies f, e1..en, whether f has rules, etc
-- plus demands and discount flags for *this* arg
-- and further args
- -- So ai_dmds and ai_discs are never empty
+ -- Invariant: ai_dmds and ai_discs are never empty
, sc_fun_ty :: OutType -- Type of the function (f e1 .. en),
-- presumably (arg_ty -> res_ty)
-- where res_ty is expected by sc_cont
@@ -348,32 +352,41 @@ doesn't matter because we'll never compute them all.
data ArgInfo
= ArgInfo {
- ai_fun :: OutId, -- The function
- ai_args :: [ArgSpec], -- ...applied to these args (which are in *reverse* order)
+ ai_fun :: OutId, -- ^ The function
+ ai_args :: [ArgSpec], -- ^ ...applied to these args (which are in *reverse* order)
-- NB: all these argumennts are already simplified
- ai_rules :: [CoreRule], -- Rules for this function
- ai_encl :: Bool, -- Flag saying whether this function
- -- or an enclosing one has rules (recursively)
- -- True => be keener to inline in all args
+ ai_rules :: [CoreRule], -- ^ Rules for this function
+ ai_encl :: Bool,
+ -- ^ Flag saying whether this function or an enclosing one has rules
+ -- (recursively)
+ --
+ -- @True@ means: be keener to inline in all args
- ai_dmds :: [Demand], -- Demands on remaining value arguments (beyond ai_args)
- -- Usually infinite, but if it is finite it guarantees
- -- that the function diverges after being given
- -- that number of args
+ ai_dmds :: RemainingArgDmds,
+ -- ^ Demands on remaining value arguments (beyond 'ai_args')
- ai_discs :: [Int] -- Discounts for remaining value arguments (beyond ai_args)
- -- non-zero => be keener to inline
- -- Always infinite
+ ai_discs :: Infinite Int
+ -- ^ Discounts for remaining value arguments (beyond 'ai_args')
+ --
+ -- A non-zero value means: be keener to inline
}
-data ArgSpec
- = ValArg { as_dmd :: Demand -- Demand placed on this argument
- , as_arg :: OutExpr -- Apply to this (coercion or value); c.f. ApplyToVal
- , as_hole_ty :: OutType } -- Type of the function (presumably t1 -> t2)
+-- | 'RemainingArgDmds' gives the demands on any remaining value arguments.
+--
+-- It is usually infinite (with 'topDmd's in the tail), but if it is finite it
+-- guarantees that the function diverges after being applied to that number
+-- of arguments.
+type RemainingArgDmds = [Demand]
- | TyArg { as_arg_ty :: OutType -- Apply to this type; c.f. ApplyToTy
- , as_hole_ty :: OutType } -- Type of the function (presumably forall a. blah)
+data ArgSpec
+ -- | A value argument
+ = ValArg { as_dmd :: Demand -- ^ Demand placed on this argument
+ , as_arg :: OutExpr -- ^ Apply to this (coercion or value); c.f. 'ApplyToVal'
+ , as_hole_ty :: OutType } -- ^ Type of the function (presumably @t1 -> t2@ for 'ValArg' or @forall a. blah@ for 'TyArg')
+ -- | A type argument
+ | TyArg { as_arg_ty :: OutType -- ^ Apply to this type; c.f. 'ApplyToTy'
+ , as_hole_ty :: OutType } -- ^ Type of the function (presumably @t1 -> t2@ for 'ValArg' or @forall a. blah@ for 'TyArg')
instance Outputable ArgInfo where
ppr (ArgInfo { ai_fun = fun, ai_args = args, ai_dmds = dmds, ai_rules = rules })
@@ -389,7 +402,7 @@ instance Outputable ArgSpec where
addValArgTo :: ArgInfo -> OutExpr -> OutType -> ArgInfo
addValArgTo ai arg hole_ty
- | ArgInfo { ai_dmds = dmd:dmds, ai_discs = _:discs } <- ai
+ | ArgInfo { ai_dmds = dmd:dmds, ai_discs = Inf _ discs } <- ai
-- Pop the top demand and and discounts off
, let arg_spec = ValArg { as_arg = arg, as_hole_ty = hole_ty, as_dmd = dmd }
= ai { ai_args = arg_spec : ai_args ai
@@ -492,12 +505,23 @@ contIsDupable (TickIt _ k) = contIsDupable k
contIsTrivial :: SimplCont -> Bool
contIsTrivial (Stop {}) = True
contIsTrivial (ApplyToTy { sc_cont = k }) = contIsTrivial k
--- This one doesn't look right. A value application is not trivial
--- contIsTrivial (ApplyToVal { sc_arg = Coercion _, sc_cont = k }) = contIsTrivial k
contIsTrivial (CastIt { sc_cont = k }) = contIsTrivial k
contIsTrivial _ = False
-------------------
+contStop :: SimplCont -> SimplCont
+-- ^ Get the 'Stop' at the tail of the continuation
+--
+-- Always returns a continuation of form @(Stop ...)@.
+contStop stop@(Stop {}) = stop
+contStop (CastIt { sc_cont = k }) = contStop k
+contStop (StrictBind { sc_cont = k }) = contStop k
+contStop (StrictArg { sc_cont = k }) = contStop k
+contStop (Select { sc_cont = k }) = contStop k
+contStop (ApplyToTy { sc_cont = k }) = contStop k
+contStop (ApplyToVal { sc_cont = k }) = contStop k
+contStop (TickIt _ k) = contStop k
+
contResultType :: SimplCont -> OutType
contResultType (Stop ty _ _) = ty
contResultType (CastIt { sc_cont = k }) = contResultType k
@@ -651,6 +675,35 @@ contEvalContext bndrs cont = go cont
-- Perhaps reconstruct the demand on the scrutinee by looking at field
-- and case binder dmds, see addCaseBndrDmd. No priority right now.
+-------------------
+mkBottomCont ::SimplCont -> SimplCont
+-- ^ Given a continuation `cont`, return a `cont` /of the same type/,
+-- looking like @(case \<hole\> of {})@.
+--
+-- This is used when we are going to fill in the @<hole>@ with bottom.
+-- See (TC2,3) in Note [Trimming the continuation for bottoming functions]
+--
+-- Don't bother to trim, making a @case <hole> of {}@, if we have only
+-- an essentially-trivial continuation; e.g. @(<hole> \@ty |> co)@.
+mkBottomCont cont = go cont
+ where
+ go k@(Stop {}) = k
+ go (TickIt t k') = TickIt t (go k')
+ go k@(CastIt { sc_cont = k' }) = k { sc_cont = go k' }
+ go k@(ApplyToTy { sc_cont = k' }) = k { sc_cont = go k' }
+ go k@(Select { sc_alts = [], sc_cont = Stop {} }) = k -- Optimisation only
+ go k | Stop res_ty _ _ <- stop_cont
+ , hole_ty `eqTypeIgnoringMultiplicity` res_ty
+ = stop_cont
+ | otherwise
+ = Select { sc_alts = []
+ , sc_bndr = mkWildValBinder OneTy hole_ty
+ , sc_env = Simplified OkDup
+ , sc_cont = stop_cont }
+ where
+ hole_ty = contHoleType k
+ stop_cont = contStop k
+
-------------------
mkArgInfo :: SimplEnv -> Id -> [CoreRule] -> SimplCont -> ArgInfo
mkArgInfo env fun rules_for_fun cont
@@ -672,16 +725,17 @@ mkArgInfo env fun rules_for_fun cont
fun_has_rules = not (null rules_for_fun)
- vanilla_discounts, arg_discounts :: [Int]
- vanilla_discounts = repeat 0
+ vanilla_discounts, arg_discounts :: Infinite Int
+ vanilla_discounts = Inf.repeat 0
arg_discounts = case idUnfolding fun of
CoreUnfolding {uf_guidance = UnfIfGoodArgs {ug_args = discounts}}
- -> discounts ++ vanilla_discounts
+ -> discounts Inf.++ vanilla_discounts
_ -> vanilla_discounts
- vanilla_dmds, arg_dmds :: [Demand]
+ vanilla_dmds :: RemainingArgDmds
vanilla_dmds = repeat topDmd
+ arg_dmds :: RemainingArgDmds
arg_dmds
| not (seInline env)
= vanilla_dmds -- See Note [Do not expose strictness if sm_inline=False]
@@ -689,26 +743,22 @@ mkArgInfo env fun rules_for_fun cont
= -- add_type_str fun_ty $
case splitDmdSig (idDmdSig fun) of
(demands, result_info)
- | not (demands `lengthExceeds` n_val_args)
- -> -- Enough args, use the strictness given.
- -- For bottoming functions we used to pretend that the arg
- -- is lazy, so that we don't treat the arg as an
- -- interesting context. This avoids substituting
- -- top-level bindings for (say) strings into
- -- calls to error. But now we are more careful about
- -- inlining lone variables, so its ok
- -- (see GHC.Core.Op.Simplify.Utils.analyseCont)
- if isDeadEndDiv result_info then
- demands -- Finite => result is bottom
- else
- demands ++ vanilla_dmds
+ | not (demands `lengthExceeds` n_val_args)
+ -> remaining_dmds -- Enough args, use the strictness given.
| otherwise
-> warnPprTrace True "More demands than arity" (ppr fun <+> ppr (idArity fun)
<+> ppr n_val_args <+> ppr demands) $
vanilla_dmds -- Not enough args, or no strictness
- add_type_strictness :: Type -> [Demand] -> [Demand]
- -- If the function arg types are strict, record that in the 'strictness bits'
+ where
+ remaining_dmds :: RemainingArgDmds
+ -- isDeadEndDiv: if remaining_dmds is finite, result is bottom
+ -- See (TC1) in Note [Trimming the continuation for bottoming functions]
+ remaining_dmds | isDeadEndDiv result_info = demands
+ | otherwise = demands ++ vanilla_dmds
+
+ add_type_strictness :: Type -> RemainingArgDmds -> RemainingArgDmds
+ -- If the function arg /types/ are strict, record that in the RemainingArgDmds
-- No need to instantiate because unboxed types (which dominate the strict
-- types) can't instantiate type variables.
-- add_type_strictness is done repeatedly (for each call);
@@ -915,16 +965,16 @@ the incentive to disappear when we inline `f`!
lazyArgContext :: ArgInfo -> CallCtxt
-- Use this for lazy arguments
lazyArgContext (ArgInfo { ai_encl = encl_rules, ai_discs = discs })
- | encl_rules = RuleArgCtxt
- | disc:_ <- discs, disc > 0 = DiscArgCtxt -- Be keener here
- | otherwise = BoringCtxt -- Nothing interesting
+ | encl_rules = RuleArgCtxt
+ | Inf disc _ <- discs, disc > 0 = DiscArgCtxt -- Be keener here
+ | otherwise = BoringCtxt -- Nothing interesting
strictArgContext :: ArgInfo -> CallCtxt
strictArgContext (ArgInfo { ai_encl = encl_rules, ai_discs = discs })
-- Use this for strict arguments
- | encl_rules = RuleArgCtxt
- | disc:_ <- discs, disc > 0 = DiscArgCtxt -- Be keener here
- | otherwise = RhsCtxt NonRecursive
+ | encl_rules = RuleArgCtxt
+ | Inf disc _ <- discs, disc > 0 = DiscArgCtxt -- Be keener here
+ | otherwise = RhsCtxt NonRecursive
-- Why RhsCtxt? if we see f (g x), and f is strict, we
-- want to be a bit more eager to inline g, because it may
-- expose an eval (on x perhaps) that can be eliminated or
=====================================
hadrian/src/Rules/BinaryDist.hs
=====================================
@@ -331,7 +331,22 @@ bindistRules = do
ghcRoot <- topDirectory
copyFile (ghcRoot -/- "aclocal.m4") (ghcRoot -/- "distrib" -/- "aclocal.m4")
copyDirectory (ghcRoot -/- "m4") (ghcRoot -/- "distrib")
- buildWithCmdOptions [] $
+
+ -- Get the normalized ACLOCAL_PATH for Windows
+ -- This is necessary since on Windows this will be a Windows
+ -- path, which autoreconf doesn't know how to handle.
+ --
+ -- This logic is duplicated in the `boot` python script
+ win_host <- isWinHost
+ env <- if not win_host
+ then pure []
+ else do
+ aclocalPathMay <- getEnv "ACLOCAL_PATH"
+ pure [ AddEnv "ACLOCAL_PATH" (msys2WindowsToUnixPath aclocalPath)
+ | Just aclocalPath <- [aclocalPathMay]
+ ]
+
+ buildWithCmdOptions env $
target (vanillaContext Stage1 ghc) (Autoreconf $ ghcRoot -/- "distrib") [] []
-- We clean after ourselves, moving the configure script we generated in
-- our bindist dir
=====================================
hadrian/src/Utilities.hs
=====================================
@@ -3,7 +3,8 @@ module Utilities (
askWithResources,
runBuilder, runBuilderWith,
contextDependencies, stage1Dependencies,
- topsortPackages, cabalDependencies
+ topsortPackages, cabalDependencies,
+ msys2WindowsToUnixPath
) where
import qualified Hadrian.Builder as H
@@ -69,3 +70,17 @@ topsortPackages pkgs = do
let annotated = map (annotateInDeg es) es
inDegZero = map snd $ filter ((== 0). fst) annotated
in inDegZero ++ topSort (es \\ inDegZero)
+
+-- | Converts a windows style path to a unix style path using msys2 conventions.
+-- >>> msys2WindowsToUnixPath "C:\\foo\\bar;C:\\msys2\\bin"
+-- "/C/foo/bar:/c/msys2/bin"
+msys2WindowsToUnixPath :: String -> String
+msys2WindowsToUnixPath =
+ replaceDrivePrefix
+ . replace "\\" "/"
+ . replace ";" ":"
+ where
+ replaceDrivePrefix p = case p of
+ drive : ':' : '/' : rest -> '/' : drive : '/' : replaceDrivePrefix rest
+ c : cs -> c : replaceDrivePrefix cs
+ [] -> []
=====================================
libraries/ghc-boot/GHC/Data/ShortByteString.hs
=====================================
@@ -0,0 +1,17 @@
+module GHC.Data.ShortByteString
+ ( newCStringFromSBS
+ ) where
+
+import Prelude
+
+import qualified Data.ByteString.Short as SBS
+import Foreign
+import Foreign.C
+
+newCStringFromSBS :: SBS.ShortByteString -> IO CString
+newCStringFromSBS sbs =
+ SBS.useAsCStringLen sbs $ \(src, len) -> do
+ dst <- mallocBytes (len + 1)
+ copyBytes dst src len
+ pokeByteOff dst len (0 :: Word8)
+ pure dst
=====================================
libraries/ghc-boot/ghc-boot.cabal.in
=====================================
@@ -51,6 +51,7 @@ Library
exposed-modules:
GHC.BaseDir
+ GHC.Data.ShortByteString
GHC.Data.ShortText
GHC.Data.SizedSeq
GHC.Data.SmallArray
=====================================
libraries/ghci/GHCi/Coverage.hs
=====================================
@@ -9,9 +9,9 @@ import Prelude -- See note [Why do we import Prelude here?]
import Control.Exception
import Data.ByteString.Short (ShortByteString)
-import qualified Data.ByteString.Short as SBS
import Data.Word
import Foreign
+import GHC.Data.ShortByteString
import GHC.Foreign (CString)
import GHC.Utils.Encoding.UTF8 (utf8DecodeShortByteString)
import GHCi.ObjLink (lookupSymbol)
@@ -31,17 +31,19 @@ hpcAddModule ::
-- ^ Name of the ticks array found in the c-stub.
IO ()
hpcAddModule modlName ticks hash tickboxes = do
- SBS.useAsCString modlName $ \modlNameLiteral -> do
- -- we need to find the reference to the ticks array.
- lookupSymbol tickboxes >>= \ case
- Nothing -> do
- -- the symbol is not found, this is a bug!
- throwIO $ ErrorCall $ "hpcAddModule: failed to find symbol " <> utf8DecodeShortByteString tickboxes
- Just tickBoxRef -> do
- -- Calling 'hs_hpc_module' multiple times is safe, it will add the module only once.
- hpc_register_module modlNameLiteral (fromIntegral ticks) (fromIntegral hash) (castPtr tickBoxRef)
- -- calling 'hpc_startup' multiple times is safe, it will only be initialised once.
- hpc_startup
+ -- we need to find the reference to the ticks array.
+ lookupSymbol tickboxes >>= \ case
+ Nothing -> do
+ -- the symbol is not found, this is a bug!
+ throwIO $ ErrorCall $ "hpcAddModule: failed to find symbol " <> utf8DecodeShortByteString tickboxes
+ Just tickBoxRef -> do
+ -- hs_hpc_module stores the module name pointer in the RTS hash table
+ -- until exitHpc, so pass a malloced C string.
+ modlNameLiteral <- newCStringFromSBS modlName
+ -- Calling 'hs_hpc_module' multiple times is safe, it will add the module only once.
+ hpc_register_module modlNameLiteral (fromIntegral ticks) (fromIntegral hash) (castPtr tickBoxRef)
+ -- calling 'hpc_startup' multiple times is safe, it will only be initialised once.
+ hpc_startup
foreign import ccall unsafe "hs_hpc_module"
hpc_register_module :: CString -> Word32 -> Word32 -> Ptr Word64 -> IO ()
=====================================
libraries/ghci/GHCi/Run.hs
=====================================
@@ -37,6 +37,9 @@ import Control.Monad
import Data.ByteString (ByteString)
import qualified Data.ByteString.Short.Internal as BS
import qualified Data.ByteString.Unsafe as B
+#if defined(PROFILING)
+import GHC.Data.ShortByteString
+#endif
import GHC.Exts
import qualified GHC.Exts.Heap as Heap
import GHC.Stack
@@ -447,13 +450,6 @@ mkCostCentres mod ccs = do
c_srcspan <- newCStringFromSBS srcspan
toRemotePtr <$> c_mkCostCentre c_name c_module c_srcspan
- newCStringFromSBS sbs = do
- let len = BS.length sbs
- buf <- mallocBytes $ len + 1
- BS.copyToPtr sbs 0 buf (fromIntegral len)
- pokeByteOff buf len (0 :: Word8)
- pure buf
-
foreign import ccall unsafe "mkCostCentre"
c_mkCostCentre :: Ptr CChar -> Ptr CChar -> Ptr CChar -> IO (Ptr CostCentre)
#else
=====================================
testsuite/tests/perf/compiler/all.T
=====================================
@@ -678,7 +678,7 @@ test ('T18140',
['-v0 -O'])
test('T10421',
[ only_ways(['normal']),
- collect_compiler_runtime(1)
+ collect_compiler_runtime(2) # 1% tolerance was too small (#27289)
],
multimod_compile,
['T10421', '-v0 -O'])
=====================================
testsuite/tests/simplCore/should_compile/T27261.hs
=====================================
@@ -0,0 +1,17 @@
+{-# OPTIONS_GHC -fno-full-laziness #-}
+
+module T27261 (foo) where
+
+import T27261_aux (myError)
+
+foo :: [String] -> (() -> Int) -> Int
+foo cs =
+ \ k -> ( case bar of
+ Just str -> let cs2 = case cs of { [] -> cs; _ -> "stack entry" : cs }
+ in myError cs2 str
+ Nothing -> \ c -> c () )
+ ( \ _ -> k () )
+
+bar :: Maybe String
+bar = Nothing
+{-# NOINLINE bar #-}
=====================================
testsuite/tests/simplCore/should_compile/T27261_aux.hs
=====================================
@@ -0,0 +1,7 @@
+{-# LANGUAGE BangPatterns #-}
+
+module T27261_aux (myError) where
+
+myError :: [String] -> String -> a
+myError !_ _ = undefined
+{-# NOINLINE myError #-}
=====================================
testsuite/tests/simplCore/should_compile/all.T
=====================================
@@ -601,3 +601,4 @@ test('T25718a', normal, compile, ['-O -ddump-simpl -dsuppress-uniques -dsuppress
test('T25718b', normal, compile, ['-O -ddump-simpl -dsuppress-uniques -dsuppress-all -dno-typeable-binds'])
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'])
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/2b6f845ab0a42877a0c0e9a11f5ac5…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/2b6f845ab0a42877a0c0e9a11f5ac5…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc][wip/davide/ghc-internal-def] 7 commits: Revert prog003 acceptance
by David Eichmann (@DavidEichmann) 28 May '26
by David Eichmann (@DavidEichmann) 28 May '26
28 May '26
David Eichmann pushed to branch wip/davide/ghc-internal-def at Glasgow Haskell Compiler / GHC
Commits:
8f991755 by fendor at 2026-05-26T11:02:52-04:00
Revert prog003 acceptance
We thought the commit 286f1adff3e78d775ff325caff71d0cee25d710b fixed the
test, but due to changes to ghci, modules loaded during the GHCi
session, the test was actually no longer testing what it set out to do,
"fixing" the broken test.
As modules are added to the `interactive-session` home unit, the object code needs
to be compiled with `-this-unit-id interactive-session`, otherwise the
object code won't be used.
Once this has been fixed in the test, the test fails as expected again.
- - - - -
277a3687 by mangoiv at 2026-05-26T11:03:40-04:00
libraries/process: bump submodule to v1.6.29.0
This submodule bump resolves a segfault on macos 15.
Fixes #27144
- - - - -
6779bb0c by mangoiv at 2026-05-26T11:03:40-04:00
libraries/unix: in submodule, don't pick branch 2.7
The 2.7 branch is outdated and the module has been advanced far beyond
it anyway, so remove that line.
- - - - -
4a645683 by Simon Peyton Jones at 2026-05-27T21:41:59-04:00
Trim the continuation in mkDupableContWithDmds
When there are no remaining argument demands, it means the application
is bottoming. In this case, we can trim the continuation to avoid the
panic that was observed in #27261.
See Note [Trimming the continuation for bottoming functions] in
GHC.Core.Opt.Simplify.Iteration.
- - - - -
8ab506ff by Cheng Shao at 2026-05-27T21:42:47-04:00
ghci: fix module name string lifetime in hs_hpc_module invocation
This patch makes hpcAddModule pass a properly malloced module name
string to hs_hpc_module, instead of using useAsCString which causes
use-after-free of module name string. Fixes #27297.
Co-authored-by: Codex <codex(a)openai.com>
- - - - -
b0233814 by sheaf at 2026-05-27T21:43:31-04:00
Relax acceptance threshold for T10421
As seen in #27289, the 1% acceptance threshold for this text was
overly narrow, resulting in spurious test failures. This commit widens
the acceptance threshold to 2%. Fixes #27289.
- - - - -
4883d9e3 by David Eichmann at 2026-05-28T11:19:40+01:00
Hadrian: create a ghc-internal .def file per ghc-internal dll
The .def file generated from rts/win32/libHSghc-internal.def.in contains
the name of the ghc-internal dll. The correct dll name differs based
on if the dll is inplace/final and if using the Dynamic way. Previously,
this was not accounted for and inconsistent dlls names where used. That
led to failure when loading dlls at runtime in experiments with windows
dynamic linking.
- - - - -
20 changed files:
- .gitmodules
- + changelog.d/T27261
- + changelog.d/bump-process
- compiler/GHC/Core/Opt/Simplify/Iteration.hs
- compiler/GHC/Core/Opt/Simplify/Utils.hs
- hadrian/src/Rules/Generate.hs
- hadrian/src/Rules/Library.hs
- hadrian/src/Rules/Rts.hs
- + libraries/ghc-boot/GHC/Data/ShortByteString.hs
- libraries/ghc-boot/ghc-boot.cabal.in
- libraries/ghci/GHCi/Coverage.hs
- libraries/ghci/GHCi/Run.hs
- libraries/process
- rts/win32/libHSghc-internal.def.in
- testsuite/tests/ghci/prog003/prog003.T
- testsuite/tests/ghci/prog003/prog003.script
- testsuite/tests/perf/compiler/all.T
- + testsuite/tests/simplCore/should_compile/T27261.hs
- + testsuite/tests/simplCore/should_compile/T27261_aux.hs
- testsuite/tests/simplCore/should_compile/all.T
Changes:
=====================================
.gitmodules
=====================================
@@ -82,7 +82,6 @@
path = libraries/unix
url = https://gitlab.haskell.org/ghc/packages/unix.git
ignore = untracked
- branch = 2.7
[submodule "libraries/semaphore-compat"]
path = libraries/semaphore-compat
url = https://gitlab.haskell.org/ghc/semaphore-compat.git
=====================================
changelog.d/T27261
=====================================
@@ -0,0 +1,10 @@
+section: compiler
+issues: #27261
+mrs: !16084
+synopsis:
+ Avoid a crash in ``mkDupableContWithDmds`` when given empty demands
+description:
+ The case of an empty list of remaining argument demands is now explicitly
+ handled by trimming the simplifier continuation, to avoid a compiler crash
+ of the form ``Non-exhaustive patterns in dmd : cont_dmds`` or ``expectNonEmpty``
+ in ``mkDupableContWithDmds``.
=====================================
changelog.d/bump-process
=====================================
@@ -0,0 +1,8 @@
+section: packaging
+issues: #27144
+mrs: !16096
+synopsis:
+ bump submodule to v1.6.29.0
+description:
+ This submodule bump resolves a segfault on macos 15 with
+ certain command line SDK versions.
=====================================
compiler/GHC/Core/Opt/Simplify/Iteration.hs
=====================================
@@ -62,6 +62,7 @@ import GHC.Types.Var ( isTyCoVar )
import GHC.Builtin.Types.Prim( realWorldStatePrimTy )
import GHC.Builtin.Names( runRWKey, seqHashKey )
+import qualified GHC.Data.List.Infinite as Inf
import GHC.Data.Maybe ( isNothing, orElse, mapMaybe )
import GHC.Data.FastString
import GHC.Unit.Module ( moduleName )
@@ -2444,24 +2445,9 @@ rebuildCall env arg_info _cont
---------- Bottoming applications --------------
rebuildCall env (ArgInfo { ai_fun = fun, ai_args = rev_args, ai_dmds = [] }) cont
- -- When we run out of strictness args, it means
- -- that the call is definitely bottom; see GHC.Core.Opt.Simplify.Utils.mkArgInfo
- -- Then we want to discard the entire strict continuation. E.g.
- -- * case (error "hello") of { ... }
- -- * (error "Hello") arg
- -- * f (error "Hello") where f is strict
- -- etc
- -- Then, especially in the first of these cases, we'd like to discard
- -- the continuation, leaving just the bottoming expression. But the
- -- type might not be right, so we may have to add a coerce.
- | not (contIsTrivial cont) -- Only do this if there is a non-trivial
- -- continuation to discard, else we do it
- -- again and again!
- = seqType cont_ty `seq` -- See Note [Avoiding space leaks in OutType]
- return (emptyFloats env, castBottomExpr res cont_ty)
- where
- res = argInfoExpr fun rev_args
- cont_ty = contResultType cont
+ -- When we run out of demands, it means that the call is definitely bottom.
+ -- See (TC2) in Note [Trimming the continuation for bottoming functions]
+ = rebuild env (argInfoExpr fun rev_args) (mkBottomCont cont)
---------- Simplify type applications --------------
rebuildCall env info (ApplyToTy { sc_arg_ty = arg_ty, sc_hole_ty = hole_ty, sc_cont = cont })
@@ -4045,6 +4031,41 @@ When we have
then we can just duplicate those alts because the A and C cases
will disappear immediately. This is more direct than creating
join points and inlining them away. See #4930.
+
+Note [Trimming the continuation for bottoming functions]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Suppose
+ f :: Int -> Int -> Int
+ f x = error "urk"
+
+ foo = f 3 4
+
+f's demand signature say "after one arg I return bottom". We can drop
+the remaining arguments, thus
+
+ foo = case f 3 of {}
+
+This trimming can also be done with other continuations:
+ * case (error "hello") of { ... }
+ * f (error "Hello") where f is strict
+ etc
+
+We implement the trimming in three parts:
+
+(TC1) In `mkArgInfo`, for a bottoming function, we make a list of `RemainingArgDmds`
+ with a finite list of elements (in the example above, just one).
+
+ For comparison, note that, for non-bottoming functions, the `RemainingArgDmds`
+ always finishes with an infinite list of `topDmd`.
+
+(TC2) In `rebuildCall`, when we run out of `RemainingArgDmds` we discard the
+ remaining continuation.
+
+ After discarding the continuation, the types might not match, in which case
+ we leave behind a (case <hole> of {}) wrapper. See the call to `mkBottomCont`.
+
+(TC3) In `mkDupableContWithDmds`, we similarly discard the continuation when
+ we run out of `RemainingArgDmds`.
-}
--------------------
@@ -4079,10 +4100,10 @@ mkDupableCont env cont
= mkDupableContWithDmds (zapSubstEnv env) (repeat topDmd) cont
mkDupableContWithDmds
- :: SimplEnvIS -> [Demand] -- Demands on arguments; always infinite
+ :: SimplEnvIS -> RemainingArgDmds
-> SimplCont -> SimplM ( SimplFloats, SimplCont)
-mkDupableContWithDmds env _ cont
+mkDupableContWithDmds env remaining_dmds cont
-- Check the invariant
| assertPpr (checkSimplEnvIS env) (pprBadSimplEnvIS env) False
= pprPanic "mkDupableContWithDmds" empty
@@ -4090,6 +4111,13 @@ mkDupableContWithDmds env _ cont
| contIsDupable cont
= return (emptyFloats env, cont)
+ -- No more demands => function is definitely bottom
+ -- => simply trim the continuation
+ -- c.f. the null-demands case in `rebuildCall`
+ -- See (TC3) in Note [Trimming the continuation for bottoming functions]
+ | null remaining_dmds
+ = return (emptyFloats env, mkBottomCont cont)
+
mkDupableContWithDmds _ _ (Stop {}) = panic "mkDupableCont" -- Handled by previous eqn
mkDupableContWithDmds env dmds (CastIt { sc_co = co, sc_opt = opt, sc_cont = cont })
@@ -4134,7 +4162,8 @@ mkDupableContWithDmds env _
, thumbsUpPlanA cont
= -- Use Plan A of Note [Duplicating StrictArg]
-- pprTrace "Using plan A" (ppr (ai_fun fun) $$ text "args" <+> ppr (ai_args fun) $$ text "cont" <+> ppr cont) $
- do { let _ :| dmds = expectNonEmpty $ ai_dmds fun
+ do { let _ :| dmds = expectNonEmpty (ai_dmds fun) -- See Invariant of StrictArg;
+ -- ai_dmds is never empty
; (floats1, cont') <- mkDupableContWithDmds env dmds cont
-- Use the demands from the function to add the right
-- demand info on any bindings we make for further args
@@ -4180,7 +4209,10 @@ mkDupableContWithDmds env dmds
-- let a = ...arg...
-- in [...hole...] a
-- NB: sc_dup /= OkToDup; that is caught earlier by contIsDupable
- do { let dmd:|cont_dmds = expectNonEmpty dmds
+ do { let dmd:|cont_dmds =
+ -- We took care to handle an empty demand list at the start,
+ -- ensuring this call to 'expectNonEmpty' does not panic (#27261).
+ expectNonEmpty dmds
; (floats1, cont') <- mkDupableContWithDmds env cont_dmds cont
; let env' = env `setInScopeFromF` floats1
; arg' <- simplArg env' Nothing hole_ty se arg arg_mco
@@ -4251,7 +4283,7 @@ mkDupableStrictBind env arg_bndr join_rhs res_ty
; let arg_info = ArgInfo { ai_fun = join_bndr
, ai_rules = [], ai_args = []
, ai_encl = False, ai_dmds = repeat topDmd
- , ai_discs = repeat 0 }
+ , ai_discs = Inf.repeat 0 }
; return ( addJoinFloats (emptyFloats env) $
unitJoinFloat $
NonRec join_bndr $
=====================================
compiler/GHC/Core/Opt/Simplify/Utils.hs
=====================================
@@ -25,13 +25,13 @@ module GHC.Core.Opt.Simplify.Utils (
StaticEnv(..),
isSimplified, contIsStop,
contIsDupable, contResultType, contHoleType, contHoleScaling,
- contIsTrivial, contArgs, contIsRhs,
+ contIsTrivial, contArgs, contIsRhs, mkBottomCont,
hasArgs, countArgs, contOutArgs, dropContArgs,
mkBoringStop, mkRhsStop, mkLazyArgStop,
interestingCallContext,
-- ArgInfo
- ArgInfo(..), ArgSpec(..), mkArgInfo,
+ ArgInfo(..), ArgSpec(..), RemainingArgDmds, mkArgInfo,
addValArgTo, addTyArgTo,
argInfoExpr, argSpecArg,
pushOutArgs, pushArgSpecs,
@@ -54,8 +54,10 @@ import GHC.Core.Opt.Stats ( Tick(..) )
import qualified GHC.Core.Subst
import GHC.Core.Ppr
import GHC.Core.TyCo.Ppr ( pprParendType )
+import GHC.Core.TyCo.Compare ( eqTypeIgnoringMultiplicity )
import GHC.Core.FVs
import GHC.Core.Utils
+import GHC.Core.Make( mkWildValBinder )
import GHC.Core.Opt.Arity
import GHC.Core.Unfold
import GHC.Core.Unfold.Make
@@ -75,6 +77,8 @@ import GHC.Types.Var.Set
import GHC.Types.Basic
import GHC.Types.Name.Env
+import GHC.Data.List.Infinite ( Infinite(..) )
+import qualified GHC.Data.List.Infinite as Inf
import GHC.Data.OrdList ( isNilOL )
import GHC.Data.FastString ( fsLit )
@@ -205,10 +209,10 @@ data SimplCont
| StrictArg -- (StrictArg (f e1 ..en) K)[e] = K[ f e1 .. en e ]
{ sc_dup :: DupFlag
- , sc_fun :: ArgInfo -- Specifies f, e1..en, Whether f has rules, etc
+ , sc_fun :: ArgInfo -- Specifies f, e1..en, whether f has rules, etc
-- plus demands and discount flags for *this* arg
-- and further args
- -- So ai_dmds and ai_discs are never empty
+ -- Invariant: ai_dmds and ai_discs are never empty
, sc_fun_ty :: OutType -- Type of the function (f e1 .. en),
-- presumably (arg_ty -> res_ty)
-- where res_ty is expected by sc_cont
@@ -348,32 +352,41 @@ doesn't matter because we'll never compute them all.
data ArgInfo
= ArgInfo {
- ai_fun :: OutId, -- The function
- ai_args :: [ArgSpec], -- ...applied to these args (which are in *reverse* order)
+ ai_fun :: OutId, -- ^ The function
+ ai_args :: [ArgSpec], -- ^ ...applied to these args (which are in *reverse* order)
-- NB: all these argumennts are already simplified
- ai_rules :: [CoreRule], -- Rules for this function
- ai_encl :: Bool, -- Flag saying whether this function
- -- or an enclosing one has rules (recursively)
- -- True => be keener to inline in all args
+ ai_rules :: [CoreRule], -- ^ Rules for this function
+ ai_encl :: Bool,
+ -- ^ Flag saying whether this function or an enclosing one has rules
+ -- (recursively)
+ --
+ -- @True@ means: be keener to inline in all args
- ai_dmds :: [Demand], -- Demands on remaining value arguments (beyond ai_args)
- -- Usually infinite, but if it is finite it guarantees
- -- that the function diverges after being given
- -- that number of args
+ ai_dmds :: RemainingArgDmds,
+ -- ^ Demands on remaining value arguments (beyond 'ai_args')
- ai_discs :: [Int] -- Discounts for remaining value arguments (beyond ai_args)
- -- non-zero => be keener to inline
- -- Always infinite
+ ai_discs :: Infinite Int
+ -- ^ Discounts for remaining value arguments (beyond 'ai_args')
+ --
+ -- A non-zero value means: be keener to inline
}
-data ArgSpec
- = ValArg { as_dmd :: Demand -- Demand placed on this argument
- , as_arg :: OutExpr -- Apply to this (coercion or value); c.f. ApplyToVal
- , as_hole_ty :: OutType } -- Type of the function (presumably t1 -> t2)
+-- | 'RemainingArgDmds' gives the demands on any remaining value arguments.
+--
+-- It is usually infinite (with 'topDmd's in the tail), but if it is finite it
+-- guarantees that the function diverges after being applied to that number
+-- of arguments.
+type RemainingArgDmds = [Demand]
- | TyArg { as_arg_ty :: OutType -- Apply to this type; c.f. ApplyToTy
- , as_hole_ty :: OutType } -- Type of the function (presumably forall a. blah)
+data ArgSpec
+ -- | A value argument
+ = ValArg { as_dmd :: Demand -- ^ Demand placed on this argument
+ , as_arg :: OutExpr -- ^ Apply to this (coercion or value); c.f. 'ApplyToVal'
+ , as_hole_ty :: OutType } -- ^ Type of the function (presumably @t1 -> t2@ for 'ValArg' or @forall a. blah@ for 'TyArg')
+ -- | A type argument
+ | TyArg { as_arg_ty :: OutType -- ^ Apply to this type; c.f. 'ApplyToTy'
+ , as_hole_ty :: OutType } -- ^ Type of the function (presumably @t1 -> t2@ for 'ValArg' or @forall a. blah@ for 'TyArg')
instance Outputable ArgInfo where
ppr (ArgInfo { ai_fun = fun, ai_args = args, ai_dmds = dmds, ai_rules = rules })
@@ -389,7 +402,7 @@ instance Outputable ArgSpec where
addValArgTo :: ArgInfo -> OutExpr -> OutType -> ArgInfo
addValArgTo ai arg hole_ty
- | ArgInfo { ai_dmds = dmd:dmds, ai_discs = _:discs } <- ai
+ | ArgInfo { ai_dmds = dmd:dmds, ai_discs = Inf _ discs } <- ai
-- Pop the top demand and and discounts off
, let arg_spec = ValArg { as_arg = arg, as_hole_ty = hole_ty, as_dmd = dmd }
= ai { ai_args = arg_spec : ai_args ai
@@ -492,12 +505,23 @@ contIsDupable (TickIt _ k) = contIsDupable k
contIsTrivial :: SimplCont -> Bool
contIsTrivial (Stop {}) = True
contIsTrivial (ApplyToTy { sc_cont = k }) = contIsTrivial k
--- This one doesn't look right. A value application is not trivial
--- contIsTrivial (ApplyToVal { sc_arg = Coercion _, sc_cont = k }) = contIsTrivial k
contIsTrivial (CastIt { sc_cont = k }) = contIsTrivial k
contIsTrivial _ = False
-------------------
+contStop :: SimplCont -> SimplCont
+-- ^ Get the 'Stop' at the tail of the continuation
+--
+-- Always returns a continuation of form @(Stop ...)@.
+contStop stop@(Stop {}) = stop
+contStop (CastIt { sc_cont = k }) = contStop k
+contStop (StrictBind { sc_cont = k }) = contStop k
+contStop (StrictArg { sc_cont = k }) = contStop k
+contStop (Select { sc_cont = k }) = contStop k
+contStop (ApplyToTy { sc_cont = k }) = contStop k
+contStop (ApplyToVal { sc_cont = k }) = contStop k
+contStop (TickIt _ k) = contStop k
+
contResultType :: SimplCont -> OutType
contResultType (Stop ty _ _) = ty
contResultType (CastIt { sc_cont = k }) = contResultType k
@@ -651,6 +675,35 @@ contEvalContext bndrs cont = go cont
-- Perhaps reconstruct the demand on the scrutinee by looking at field
-- and case binder dmds, see addCaseBndrDmd. No priority right now.
+-------------------
+mkBottomCont ::SimplCont -> SimplCont
+-- ^ Given a continuation `cont`, return a `cont` /of the same type/,
+-- looking like @(case \<hole\> of {})@.
+--
+-- This is used when we are going to fill in the @<hole>@ with bottom.
+-- See (TC2,3) in Note [Trimming the continuation for bottoming functions]
+--
+-- Don't bother to trim, making a @case <hole> of {}@, if we have only
+-- an essentially-trivial continuation; e.g. @(<hole> \@ty |> co)@.
+mkBottomCont cont = go cont
+ where
+ go k@(Stop {}) = k
+ go (TickIt t k') = TickIt t (go k')
+ go k@(CastIt { sc_cont = k' }) = k { sc_cont = go k' }
+ go k@(ApplyToTy { sc_cont = k' }) = k { sc_cont = go k' }
+ go k@(Select { sc_alts = [], sc_cont = Stop {} }) = k -- Optimisation only
+ go k | Stop res_ty _ _ <- stop_cont
+ , hole_ty `eqTypeIgnoringMultiplicity` res_ty
+ = stop_cont
+ | otherwise
+ = Select { sc_alts = []
+ , sc_bndr = mkWildValBinder OneTy hole_ty
+ , sc_env = Simplified OkDup
+ , sc_cont = stop_cont }
+ where
+ hole_ty = contHoleType k
+ stop_cont = contStop k
+
-------------------
mkArgInfo :: SimplEnv -> Id -> [CoreRule] -> SimplCont -> ArgInfo
mkArgInfo env fun rules_for_fun cont
@@ -672,16 +725,17 @@ mkArgInfo env fun rules_for_fun cont
fun_has_rules = not (null rules_for_fun)
- vanilla_discounts, arg_discounts :: [Int]
- vanilla_discounts = repeat 0
+ vanilla_discounts, arg_discounts :: Infinite Int
+ vanilla_discounts = Inf.repeat 0
arg_discounts = case idUnfolding fun of
CoreUnfolding {uf_guidance = UnfIfGoodArgs {ug_args = discounts}}
- -> discounts ++ vanilla_discounts
+ -> discounts Inf.++ vanilla_discounts
_ -> vanilla_discounts
- vanilla_dmds, arg_dmds :: [Demand]
+ vanilla_dmds :: RemainingArgDmds
vanilla_dmds = repeat topDmd
+ arg_dmds :: RemainingArgDmds
arg_dmds
| not (seInline env)
= vanilla_dmds -- See Note [Do not expose strictness if sm_inline=False]
@@ -689,26 +743,22 @@ mkArgInfo env fun rules_for_fun cont
= -- add_type_str fun_ty $
case splitDmdSig (idDmdSig fun) of
(demands, result_info)
- | not (demands `lengthExceeds` n_val_args)
- -> -- Enough args, use the strictness given.
- -- For bottoming functions we used to pretend that the arg
- -- is lazy, so that we don't treat the arg as an
- -- interesting context. This avoids substituting
- -- top-level bindings for (say) strings into
- -- calls to error. But now we are more careful about
- -- inlining lone variables, so its ok
- -- (see GHC.Core.Op.Simplify.Utils.analyseCont)
- if isDeadEndDiv result_info then
- demands -- Finite => result is bottom
- else
- demands ++ vanilla_dmds
+ | not (demands `lengthExceeds` n_val_args)
+ -> remaining_dmds -- Enough args, use the strictness given.
| otherwise
-> warnPprTrace True "More demands than arity" (ppr fun <+> ppr (idArity fun)
<+> ppr n_val_args <+> ppr demands) $
vanilla_dmds -- Not enough args, or no strictness
- add_type_strictness :: Type -> [Demand] -> [Demand]
- -- If the function arg types are strict, record that in the 'strictness bits'
+ where
+ remaining_dmds :: RemainingArgDmds
+ -- isDeadEndDiv: if remaining_dmds is finite, result is bottom
+ -- See (TC1) in Note [Trimming the continuation for bottoming functions]
+ remaining_dmds | isDeadEndDiv result_info = demands
+ | otherwise = demands ++ vanilla_dmds
+
+ add_type_strictness :: Type -> RemainingArgDmds -> RemainingArgDmds
+ -- If the function arg /types/ are strict, record that in the RemainingArgDmds
-- No need to instantiate because unboxed types (which dominate the strict
-- types) can't instantiate type variables.
-- add_type_strictness is done repeatedly (for each call);
@@ -915,16 +965,16 @@ the incentive to disappear when we inline `f`!
lazyArgContext :: ArgInfo -> CallCtxt
-- Use this for lazy arguments
lazyArgContext (ArgInfo { ai_encl = encl_rules, ai_discs = discs })
- | encl_rules = RuleArgCtxt
- | disc:_ <- discs, disc > 0 = DiscArgCtxt -- Be keener here
- | otherwise = BoringCtxt -- Nothing interesting
+ | encl_rules = RuleArgCtxt
+ | Inf disc _ <- discs, disc > 0 = DiscArgCtxt -- Be keener here
+ | otherwise = BoringCtxt -- Nothing interesting
strictArgContext :: ArgInfo -> CallCtxt
strictArgContext (ArgInfo { ai_encl = encl_rules, ai_discs = discs })
-- Use this for strict arguments
- | encl_rules = RuleArgCtxt
- | disc:_ <- discs, disc > 0 = DiscArgCtxt -- Be keener here
- | otherwise = RhsCtxt NonRecursive
+ | encl_rules = RuleArgCtxt
+ | Inf disc _ <- discs, disc > 0 = DiscArgCtxt -- Be keener here
+ | otherwise = RhsCtxt NonRecursive
-- Why RhsCtxt? if we see f (g x), and f is strict, we
-- want to be a bit more eager to inline g, because it may
-- expose an eval (on x perhaps) that can be eliminated or
=====================================
hadrian/src/Rules/Generate.hs
=====================================
@@ -377,7 +377,6 @@ templateRules = do
, interpolateSetting "ProjectPatchLevel1" ProjectPatchLevel1
, interpolateSetting "ProjectPatchLevel2" ProjectPatchLevel2
]
- templateRule "rts/win32/libHSghc-internal.def" projectVersion
templateRule "docs/index.html" $ packageUnitIds Stage1
templateRule "docs/users_guide/ghc_config.py" $ mconcat
[ projectVersion
=====================================
hadrian/src/Rules/Library.hs
=====================================
@@ -203,10 +203,21 @@ extraObjects context
| package context == rts = do
target <- interpretInContext context getStagedTarget
- builddir <- buildPath context
- return [ builddir -/- "libHSghc-internal.dll.a"
- | archOS_OS (tgtArchOs target) == OSMinGW32
- , Dynamic `wayUnit` way context ]
+ if not (archOS_OS (tgtArchOs target) == OSMinGW32
+ && Dynamic `wayUnit` way context)
+ then return []
+ else do
+ -- Find the ghc-internal library file name
+ let way' = if Profiling `wayUnit` way context then profilingDynamic else dynamic
+ ghcInternalDllName <- takeFileName <$> pkgLibraryFile (Context {
+ stage = stage context,
+ way = way',
+ iplace = iplace context,
+ package = ghcInternal
+ })
+
+ builddir <- buildPath context
+ return [ builddir -/- ghcInternalDllName <> ".a"]
| otherwise = return []
=====================================
hadrian/src/Rules/Rts.hs
=====================================
@@ -13,11 +13,19 @@ rtsRules = priority 3 $ do
-- to be linked into the rts dll.
forM_ [Stage1, Stage2, Stage3 ] $ \ stage -> do
let buildPath = root -/- buildDir (rtsContext stage)
- buildPath -/- "libHSghc-internal.dll.a" %> buildGhcInternalImportLib
+ buildPath -/- "libHSghc-internal-*.def" %> buildGhcInternalImportDef
+ buildPath -/- "libHSghc-internal-*.dll.a" %> buildGhcInternalImportLib
+
+buildGhcInternalImportDef :: FilePath -> Action ()
+buildGhcInternalImportDef target = do
+ templateIn <- readFile' "rts/win32/libHSghc-internal.def.in"
+ let dllName = (dropExtension (takeFileName target)) <.> "dll"
+ templateOut = replace "@GhcInternalDll@" dllName templateIn
+ writeFile' target templateOut
buildGhcInternalImportLib :: FilePath -> Action ()
buildGhcInternalImportLib target = do
- let input = "rts/win32/libHSghc-internal.def"
+ let input = (dropExtension (dropExtension target)) <.> "def" -- the .def file
output = target -- the .dll.a import lib
need [input]
runBuilder Dlltool ["-d", input, "-l", output] [input] [output]
=====================================
libraries/ghc-boot/GHC/Data/ShortByteString.hs
=====================================
@@ -0,0 +1,17 @@
+module GHC.Data.ShortByteString
+ ( newCStringFromSBS
+ ) where
+
+import Prelude
+
+import qualified Data.ByteString.Short as SBS
+import Foreign
+import Foreign.C
+
+newCStringFromSBS :: SBS.ShortByteString -> IO CString
+newCStringFromSBS sbs =
+ SBS.useAsCStringLen sbs $ \(src, len) -> do
+ dst <- mallocBytes (len + 1)
+ copyBytes dst src len
+ pokeByteOff dst len (0 :: Word8)
+ pure dst
=====================================
libraries/ghc-boot/ghc-boot.cabal.in
=====================================
@@ -51,6 +51,7 @@ Library
exposed-modules:
GHC.BaseDir
+ GHC.Data.ShortByteString
GHC.Data.ShortText
GHC.Data.SizedSeq
GHC.Data.SmallArray
=====================================
libraries/ghci/GHCi/Coverage.hs
=====================================
@@ -9,9 +9,9 @@ import Prelude -- See note [Why do we import Prelude here?]
import Control.Exception
import Data.ByteString.Short (ShortByteString)
-import qualified Data.ByteString.Short as SBS
import Data.Word
import Foreign
+import GHC.Data.ShortByteString
import GHC.Foreign (CString)
import GHC.Utils.Encoding.UTF8 (utf8DecodeShortByteString)
import GHCi.ObjLink (lookupSymbol)
@@ -31,17 +31,19 @@ hpcAddModule ::
-- ^ Name of the ticks array found in the c-stub.
IO ()
hpcAddModule modlName ticks hash tickboxes = do
- SBS.useAsCString modlName $ \modlNameLiteral -> do
- -- we need to find the reference to the ticks array.
- lookupSymbol tickboxes >>= \ case
- Nothing -> do
- -- the symbol is not found, this is a bug!
- throwIO $ ErrorCall $ "hpcAddModule: failed to find symbol " <> utf8DecodeShortByteString tickboxes
- Just tickBoxRef -> do
- -- Calling 'hs_hpc_module' multiple times is safe, it will add the module only once.
- hpc_register_module modlNameLiteral (fromIntegral ticks) (fromIntegral hash) (castPtr tickBoxRef)
- -- calling 'hpc_startup' multiple times is safe, it will only be initialised once.
- hpc_startup
+ -- we need to find the reference to the ticks array.
+ lookupSymbol tickboxes >>= \ case
+ Nothing -> do
+ -- the symbol is not found, this is a bug!
+ throwIO $ ErrorCall $ "hpcAddModule: failed to find symbol " <> utf8DecodeShortByteString tickboxes
+ Just tickBoxRef -> do
+ -- hs_hpc_module stores the module name pointer in the RTS hash table
+ -- until exitHpc, so pass a malloced C string.
+ modlNameLiteral <- newCStringFromSBS modlName
+ -- Calling 'hs_hpc_module' multiple times is safe, it will add the module only once.
+ hpc_register_module modlNameLiteral (fromIntegral ticks) (fromIntegral hash) (castPtr tickBoxRef)
+ -- calling 'hpc_startup' multiple times is safe, it will only be initialised once.
+ hpc_startup
foreign import ccall unsafe "hs_hpc_module"
hpc_register_module :: CString -> Word32 -> Word32 -> Ptr Word64 -> IO ()
=====================================
libraries/ghci/GHCi/Run.hs
=====================================
@@ -37,6 +37,9 @@ import Control.Monad
import Data.ByteString (ByteString)
import qualified Data.ByteString.Short.Internal as BS
import qualified Data.ByteString.Unsafe as B
+#if defined(PROFILING)
+import GHC.Data.ShortByteString
+#endif
import GHC.Exts
import qualified GHC.Exts.Heap as Heap
import GHC.Stack
@@ -447,13 +450,6 @@ mkCostCentres mod ccs = do
c_srcspan <- newCStringFromSBS srcspan
toRemotePtr <$> c_mkCostCentre c_name c_module c_srcspan
- newCStringFromSBS sbs = do
- let len = BS.length sbs
- buf <- mallocBytes $ len + 1
- BS.copyToPtr sbs 0 buf (fromIntegral len)
- pokeByteOff buf len (0 :: Word8)
- pure buf
-
foreign import ccall unsafe "mkCostCentre"
c_mkCostCentre :: Ptr CChar -> Ptr CChar -> Ptr CChar -> IO (Ptr CostCentre)
#else
=====================================
libraries/process
=====================================
@@ -1 +1 @@
-Subproject commit 72e5b7c75a17f543262674259b2ebf4a3bda390c
+Subproject commit 92deb52c1781bf10ad390296dbc435abe103bfe4
=====================================
rts/win32/libHSghc-internal.def.in
=====================================
@@ -1,4 +1,4 @@
-LIBRARY libHSghc-internal-@ProjectVersionForLib@.0-ghc@ProjectVersion@.dll
+LIBRARY @GhcInternalDll@
EXPORTS
init_ghc_hs_iface
=====================================
testsuite/tests/ghci/prog003/prog003.T
=====================================
@@ -5,5 +5,6 @@
test('prog003',
[extra_files(['A.hs', 'B.hs', 'C.hs', 'D1.hs', 'D2.hs']),
when(opsys('mingw32'), skip),
+ unless(config.ghc_dynamic, expect_broken(20704)),
cmd_prefix('ghciWayFlags=' + config.ghci_way_flags)],
ghci_script, ['prog003.script'])
=====================================
testsuite/tests/ghci/prog003/prog003.script
=====================================
@@ -25,7 +25,7 @@ a 42
putStrLn "Run 3"
-- compile D, check that :reload doesn't pick it up
-:shell "$HC" $HC_OPTS $ghciWayFlags -c D.hs
+:shell "$HC" $HC_OPTS $ghciWayFlags -this-unit-id interactive-session -c D.hs
:reload
:type (A.a,B.b,C.c,D.d)
a 42
@@ -38,21 +38,21 @@ a 42
putStrLn "Run 5"
-- D,C compiled
-:shell "$HC" $HC_OPTS $ghciWayFlags -c C.hs
+:shell "$HC" $HC_OPTS $ghciWayFlags -this-unit-id interactive-session -c C.hs
:load A
:type (A.a,B.b,C.c,D.d)
a 42
putStrLn "Run 6"
-- D,C,B compiled
-:shell "$HC" $HC_OPTS $ghciWayFlags -c B.hs
+:shell "$HC" $HC_OPTS $ghciWayFlags -this-unit-id interactive-session -c B.hs
:load A
:type (A.a,B.b,C.c,D.d)
a 42
putStrLn "Run 7"
-- D,C,B,A compiled
-:shell "$HC" $HC_OPTS $ghciWayFlags -c A.hs
+:shell "$HC" $HC_OPTS $ghciWayFlags -this-unit-id interactive-session -c A.hs
:load A
:type (A.a,B.b,C.c,D.d)
a 42
@@ -80,7 +80,7 @@ a 42
putStrLn "Run 11"
-- A,B,C compiled (better not use A.o, B.o, C.o)
-:shell "$HC" $HC_OPTS $ghciWayFlags --make -v0 A
+:shell "$HC" $HC_OPTS $ghciWayFlags --make -this-unit-id interactive-session -v0 A
:shell rm D.o
:load A
:type (A.a,B.b,C.c,D.d)
=====================================
testsuite/tests/perf/compiler/all.T
=====================================
@@ -678,7 +678,7 @@ test ('T18140',
['-v0 -O'])
test('T10421',
[ only_ways(['normal']),
- collect_compiler_runtime(1)
+ collect_compiler_runtime(2) # 1% tolerance was too small (#27289)
],
multimod_compile,
['T10421', '-v0 -O'])
=====================================
testsuite/tests/simplCore/should_compile/T27261.hs
=====================================
@@ -0,0 +1,17 @@
+{-# OPTIONS_GHC -fno-full-laziness #-}
+
+module T27261 (foo) where
+
+import T27261_aux (myError)
+
+foo :: [String] -> (() -> Int) -> Int
+foo cs =
+ \ k -> ( case bar of
+ Just str -> let cs2 = case cs of { [] -> cs; _ -> "stack entry" : cs }
+ in myError cs2 str
+ Nothing -> \ c -> c () )
+ ( \ _ -> k () )
+
+bar :: Maybe String
+bar = Nothing
+{-# NOINLINE bar #-}
=====================================
testsuite/tests/simplCore/should_compile/T27261_aux.hs
=====================================
@@ -0,0 +1,7 @@
+{-# LANGUAGE BangPatterns #-}
+
+module T27261_aux (myError) where
+
+myError :: [String] -> String -> a
+myError !_ _ = undefined
+{-# NOINLINE myError #-}
=====================================
testsuite/tests/simplCore/should_compile/all.T
=====================================
@@ -601,3 +601,4 @@ test('T25718a', normal, compile, ['-O -ddump-simpl -dsuppress-uniques -dsuppress
test('T25718b', normal, compile, ['-O -ddump-simpl -dsuppress-uniques -dsuppress-all -dno-typeable-binds'])
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'])
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/d82245416a40aca61493042b35e7aa…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/d82245416a40aca61493042b35e7aa…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc][wip/marge_bot_batch_merge_job] 4 commits: Fixes for black holes
by Marge Bot (@marge-bot) 28 May '26
by Marge Bot (@marge-bot) 28 May '26
28 May '26
Marge Bot pushed to branch wip/marge_bot_batch_merge_job at Glasgow Haskell Compiler / GHC
Commits:
fde9cb2c by Luite Stegeman at 2026-05-28T05:42:39-04:00
Fixes for black holes
- suspend duplicate work for eager black holes
- detect eager black holes in checkBlockingQueues
- don't overwrite existing black holes even if they're not
in an eager blackhole frame
- don't deadlock on self when thunk is already blackholed
Fixes #26936
- - - - -
f2725996 by Tom McLaughlin at 2026-05-28T05:42:51-04:00
Event/Windows.hsc: rethrow exceptions in overlapped IO
This prevents the WinIO manager from swallowing exceptions in overlapped IO. It
was added to make WinIO support possible in the `network` library. See
https://gitlab.haskell.org/ghc/ghc/-/issues/27283.
We also bump __IO_MANAGER_WINIO__ to 2 so libraries can gate on this using CPP.
- - - - -
8b0a4bc5 by Wolfgang Jeltsch at 2026-05-28T05:42:52-04:00
Allow `downsweep` to use nodes of an existing module graph
To this end, `downsweep` has not been able to use the nodes of a module
graph obtained from a previous downsweeping round. In some GHC API
applications, downsweeping is performed somewhat incrementally and
therefore could profit from reusing such existing results. This
contribution makes this possible.
Resolves #27054.
Co-authored-by: Matthew Pickering <matthewtpickering(a)gmail.com>
- - - - -
e9aef297 by Simon Jakobi at 2026-05-28T05:42:53-04:00
Add regression test for T11226
Closes #11226.
- - - - -
28 changed files:
- + changelog.d/fix-blackhole-handling
- + changelog.d/module-graph-reuse-in-downsweep
- + changelog.d/windows-rethrow-overlapped-exception
- compiler/GHC/Driver/Downsweep.hs
- compiler/GHC/Driver/Make.hs
- compiler/GHC/SysTools/Cpp.hs
- libraries/ghc-internal/src/GHC/Internal/Event/Windows.hsc
- rts/Messages.c
- rts/ThreadPaused.c
- rts/Threads.c
- rts/Updates.h
- rts/include/rts/storage/ClosureMacros.h
- + testsuite/tests/ghc-api/downsweep/IncrementalDownsweep.hs
- + testsuite/tests/ghc-api/downsweep/IncrementalDownsweep.modules/A.hs
- + testsuite/tests/ghc-api/downsweep/IncrementalDownsweep.modules/B.hs
- + testsuite/tests/ghc-api/downsweep/IncrementalDownsweep.modules/C.hs
- + testsuite/tests/ghc-api/downsweep/IncrementalDownsweep.modules/D.hs
- + testsuite/tests/ghc-api/downsweep/IncrementalDownsweep.modules/X.hs
- + testsuite/tests/ghc-api/downsweep/IncrementalDownsweep.modules/Y.hs
- + testsuite/tests/ghc-api/downsweep/IncrementalDownsweep.modules/Z.hs
- + testsuite/tests/ghc-api/downsweep/IncrementalDownsweep.stdout
- testsuite/tests/ghc-api/downsweep/OldModLocation.hs
- testsuite/tests/ghc-api/downsweep/PartialDownsweep.hs
- testsuite/tests/ghc-api/downsweep/all.T
- testsuite/tests/ghc-api/fixed-nodes/InterfaceModuleGraph.hs
- + testsuite/tests/perf/should_run/T11226.hs
- + testsuite/tests/perf/should_run/T11226.stdout
- testsuite/tests/perf/should_run/all.T
Changes:
=====================================
changelog.d/fix-blackhole-handling
=====================================
@@ -0,0 +1,6 @@
+section: rts
+synopsis: Fix several black hole handling bugs that could lead to deadlocks
+ or crashes in multithreaded programs. These could show up as the program
+ hanging or "END_TSO_QUEUE object entered" errors.
+issues: #26922 #26936
+mrs: !15640
=====================================
changelog.d/module-graph-reuse-in-downsweep
=====================================
@@ -0,0 +1,9 @@
+section: compiler
+synopsis: Allow `downsweep` to use nodes of an existing module graph
+issues: #27054
+mrs: !16028
+description: {
+ This contribution enables `downsweep` to use the nodes of a module
+ graph obtained from a previous downsweeping round, which allows GHC
+ API applications to build module graphs somewhat incrementally.
+}
=====================================
changelog.d/windows-rethrow-overlapped-exception
=====================================
@@ -0,0 +1,8 @@
+section: rts
+synopsis: Rethrow exceptions in overlapped IO when using the WinIO IO manager.
+issues: #27283
+mrs: !15887
+
+description: {
+ This change was made to support WinIO in the network library; see https://github.com/haskell/network/issues/602.
+}
=====================================
compiler/GHC/Driver/Downsweep.hs
=====================================
@@ -146,6 +146,10 @@ The result is having a uniform graph available for the whole compilation pipelin
-- an import of this module mean.
type DownsweepCache = M.Map (UnitId, PkgQual, ModuleNameWithIsBoot) [Either DriverMessages ModuleNodeInfo]
+moduleGraphNodeMap :: ModuleGraph -> M.Map NodeKey ModuleGraphNode
+moduleGraphNodeMap graph
+ = M.fromList [(mkNodeKey node, node) | node <- mgModSummaries' graph]
+
-----------------------------------------------------------------------------
--
-- | Downsweep (dependency analysis) for --make mode
@@ -158,6 +162,13 @@ type DownsweepCache = M.Map (UnitId, PkgQual, ModuleNameWithIsBoot) [Either Driv
-- cache to avoid recalculating a module summary if the source is
-- unchanged.
--
+-- Downsweeping can start from scratch for from a given module graph. In the
+-- latter case, the given graph is fully included in the resulting graph, even
+-- if parts of it are not reachable from any of the given roots. When an import
+-- is processed, the source of the imported module is not consulted if this
+-- module is already mentioned in the given graph. The sources of the root
+-- modules are always consulted, though.
+--
-- The returned ModuleGraph has one node for each home-package
-- module, plus one for any hs-boot files. The imports of these nodes
-- are all there, including the imports of non-home-package modules.
@@ -172,6 +183,8 @@ downsweep :: HscEnv
-> Maybe Messager
-> [ModSummary]
-- ^ Old summaries
+ -> Maybe ModuleGraph
+ -- ^ Optionally a module graph to extend
-> [ModuleName] -- Ignore dependencies on these; treat
-- them as if they were package modules
-> Bool -- True <=> allow multiple targets to have
@@ -181,7 +194,7 @@ downsweep :: HscEnv
-- The non-error elements of the returned list all have distinct
-- (Modules, IsBoot) identifiers, unless the Bool is true in
-- which case there can be repeats
-downsweep hsc_env diag_wrapper msg old_summaries excl_mods allow_dup_roots = do
+downsweep hsc_env diag_wrapper msg old_summaries maybe_base_graph excl_mods allow_dup_roots = do
n_jobs <- mkWorkerLimit (hsc_dflags hsc_env)
(root_errs, root_summaries) <- rootSummariesParallel n_jobs hsc_env diag_wrapper msg summary
let closure_errs = checkHomeUnitsClosed unit_env
@@ -191,7 +204,7 @@ downsweep hsc_env diag_wrapper msg old_summaries excl_mods allow_dup_roots = do
case all_errs of
[] -> do
- (downsweep_errs, downsweep_nodes) <- downsweepFromRootNodes hsc_env old_summary_map excl_mods allow_dup_roots DownsweepUseCompile (map ModuleNodeCompile root_summaries) []
+ (downsweep_errs, downsweep_nodes) <- downsweepFromRootNodes hsc_env old_summary_map maybe_base_graph excl_mods allow_dup_roots DownsweepUseCompile (map ModuleNodeCompile root_summaries) []
let (other_errs, unit_nodes) = partitionEithers $ HUG.unitEnv_foldWithKey (\nodes uid hue -> nodes ++ unitModuleNodes downsweep_nodes uid hue) [] (hsc_HUG hsc_env)
@@ -232,7 +245,7 @@ downsweep hsc_env diag_wrapper msg old_summaries excl_mods allow_dup_roots = do
downsweepThunk :: HscEnv -> ModSummary -> IO ModuleGraph
downsweepThunk hsc_env mod_summary = unsafeInterleaveIO $ do
debugTraceMsg (hsc_logger hsc_env) 3 $ text "Computing Module Graph thunk..."
- ~(errs, mg) <- downsweepFromRootNodes hsc_env mempty [] True DownsweepUseFixed [ModuleNodeCompile mod_summary] []
+ ~(errs, mg) <- downsweepFromRootNodes hsc_env mempty Nothing [] True DownsweepUseFixed [ModuleNodeCompile mod_summary] []
let dflags = hsc_dflags hsc_env
liftIO $ printOrThrowDiagnostics (hsc_logger hsc_env)
(initPrintConfig dflags)
@@ -360,7 +373,7 @@ downsweepInstalledModules hsc_env mods = do
_ -> throwGhcException $ ProgramError $ showSDoc (hsc_dflags hsc_env) $ text "downsweepInstalledModules: Could not find installed module" <+> ppr i
nodes <- mapM process installed_mods
- (errs, mg) <- downsweepFromRootNodes hsc_env mempty [] True DownsweepUseFixed nodes external_uids
+ (errs, mg) <- downsweepFromRootNodes hsc_env mempty Nothing [] True DownsweepUseFixed nodes external_uids
-- Similarly here, we should really not get any errors, but print them out if we do.
let dflags = hsc_dflags hsc_env
@@ -385,19 +398,21 @@ data DownsweepMode = DownsweepUseCompile | DownsweepUseFixed
-- all the dependencies, all the way to the leaf units.
downsweepFromRootNodes :: HscEnv
-> M.Map (UnitId, OsPath) ModSummary
+ -> Maybe ModuleGraph
-> [ModuleName]
-> Bool
-> DownsweepMode -- ^ Whether to create fixed or compile nodes for dependencies
-> [ModuleNodeInfo] -- ^ The starting ModuleNodeInfo
-> [UnitId] -- ^ The starting units
-> IO ([DriverMessages], [ModuleGraphNode])
-downsweepFromRootNodes hsc_env old_summaries excl_mods allow_dup_roots mode root_nodes root_uids
+downsweepFromRootNodes hsc_env old_summaries maybe_base_graph excl_mods allow_dup_roots mode root_nodes root_uids
= do
let root_map = mkRootMap root_nodes
checkDuplicates root_map
let env = DownsweepEnv hsc_env mode old_summaries excl_mods
(deps', map0) <- runDownsweepM env $ do
- (module_deps, map0) <- loopModuleNodeInfos root_nodes (M.empty, root_map)
+ let base_nodes = maybe M.empty moduleGraphNodeMap maybe_base_graph
+ (module_deps, map0) <- loopModuleNodeInfos root_nodes (base_nodes, root_map)
let all_deps = loopUnit hsc_env module_deps root_uids
let all_instantiations = getHomeUnitInstantiations hsc_env
deps' <- loopInstantiations all_instantiations all_deps
=====================================
compiler/GHC/Driver/Make.hs
=====================================
@@ -232,7 +232,7 @@ depanalPartial diag_wrapper msg excluded_mods allow_dup_roots = do
liftIO $ flushFinderCaches (hsc_FC hsc_env) (hsc_unit_env hsc_env)
(errs, mod_graph) <- liftIO $ downsweep
- hsc_env diag_wrapper msg (mgModSummaries old_graph)
+ hsc_env diag_wrapper msg (mgModSummaries old_graph) Nothing
excluded_mods allow_dup_roots
return (unionManyMessages errs, mod_graph)
=====================================
compiler/GHC/SysTools/Cpp.hs
=====================================
@@ -148,7 +148,7 @@ doCpp logger tmpfs dflags unit_env opts input_fn output_fn = do
-- and BUILD is the same as our HOST.
let io_manager_defs =
- [ "-D__IO_MANAGER_WINIO__=1" | isWindows ] ++
+ [ "-D__IO_MANAGER_WINIO__=2" | isWindows ] ++
[ "-D__IO_MANAGER_MIO__=1" ]
let sse_defs =
=====================================
libraries/ghc-internal/src/GHC/Internal/Event/Windows.hsc
=====================================
@@ -690,7 +690,11 @@ withOverlappedEx mgr fname h async offset startCB completionCB = do
-- can go into an unbounded alertable wait.
delay <- runExpiredTimeouts mgr
registerAlertableWait delay
- return $ IOFailed Nothing
+ -- Re-throw the original exception rather than
+ -- returning IOFailed. This ensures that async
+ -- exceptions (e.g. Timeout from System.Timeout)
+ -- propagate correctly to their handlers.
+ E.throw e
let runner = do debugIO $ (dbgMsg ":: waiting ") ++ " | " ++ show lpol
res <- readMVar signal `catch` cancel
debugIO $ dbgMsg ":: signaled "
=====================================
rts/Messages.c
=====================================
@@ -188,10 +188,7 @@ uint32_t messageBlackHole(Capability *cap, MessageBlackHole *msg)
// BLACKHOLE has already been updated, and GC has shorted out the
// indirection, so the pointer no longer points to a BLACKHOLE at
// all.
- if (bh_info != &stg_BLACKHOLE_info &&
- bh_info != &stg_CAF_BLACKHOLE_info &&
- bh_info != &__stg_EAGER_BLACKHOLE_info &&
- bh_info != &stg_WHITEHOLE_info) {
+ if (!IS_BLACKHOLE_OR_WHITEHOLE_INFO(bh_info)) {
return 0;
}
@@ -350,10 +347,7 @@ StgTSO * blackHoleOwner (StgClosure *bh)
info = RELAXED_LOAD(&bh->header.info);
- if (info != &stg_BLACKHOLE_info &&
- info != &stg_CAF_BLACKHOLE_info &&
- info != &__stg_EAGER_BLACKHOLE_info &&
- info != &stg_WHITEHOLE_info) {
+ if (!IS_BLACKHOLE_OR_WHITEHOLE_INFO(info)) {
return NULL;
}
=====================================
rts/ThreadPaused.c
=====================================
@@ -183,6 +183,30 @@ stackSqueeze(Capability *cap, StgTSO *tso, StgPtr bottom)
}
}
+/*
+ * Check whether tso is the owner of the black hole bh.
+ *
+ * We must call this from the capability that runs tso,
+ * since that guarantees that the writes to bh->indirectee
+ * by tso claiming ownership have been visible. If another
+ * tso has claimed it again afterwards we can safely suspend
+ * our work.
+ */
+static bool
+threadPausedBlackHoleOwner(StgTSO *tso, StgClosure *bh)
+{
+ StgClosure *ind = RELAXED_LOAD(&((StgInd*)bh)->indirectee);
+ if (ind == (StgClosure*)tso) {
+ return true;
+ }
+ const StgInfoTable *ind_info = GET_INFO(UNTAG_CLOSURE(ind));
+ if (ind_info == &stg_BLOCKING_QUEUE_CLEAN_info
+ || ind_info == &stg_BLOCKING_QUEUE_DIRTY_info) {
+ return ((StgBlockingQueue*)UNTAG_CLOSURE(ind))->owner == tso;
+ }
+ return false;
+}
+
/* -----------------------------------------------------------------------------
* Pausing a thread
*
@@ -255,11 +279,10 @@ threadPaused(Capability *cap, StgTSO *tso)
// Note [suspend duplicate work]
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// If the info table is a WHITEHOLE or a BLACKHOLE, then
- // another thread has claimed it (via the SET_INFO()
- // below), or is in the process of doing so. In that case
- // we want to suspend the work that the current thread has
- // done on this thunk and wait until the other thread has
- // finished.
+ // some thread has claimed it, or is in the process of doing
+ // so. In that case we want to suspend the work that the
+ // current thread has done on this thunk and wait until the
+ // other thread has finished.
//
// If eager blackholing is taking place, it could be the
// case that the blackhole points to the current
@@ -287,8 +310,8 @@ threadPaused(Capability *cap, StgTSO *tso)
// Note that great care is required when entering computations
// suspended by this mechanism. See Note [AP_STACKs must be eagerly
// blackholed] for details.
- if (((bh_info == &stg_BLACKHOLE_info)
- && (RELAXED_LOAD(&((StgInd*)bh)->indirectee) != (StgClosure*)tso))
+ if ((IS_BLACKHOLE_INFO(bh_info)
+ && !threadPausedBlackHoleOwner(tso, bh))
|| (bh_info == &stg_WHITEHOLE_info))
{
debugTrace(DEBUG_squeeze,
@@ -318,14 +341,13 @@ threadPaused(Capability *cap, StgTSO *tso)
// If we have a frame that is already eagerly blackholed, we
// shouldn't overwrite its payload: There may already be a blocking
// queue (see #26324).
- if(frame_info == &stg_bh_upd_frame_info) {
- // eager black hole: we do nothing
+ if(frame_info == &stg_bh_upd_frame_info
+ || IS_BLACKHOLE_INFO(bh_info)) {
+ // already a black hole: we do nothing
// it should be a black hole (but we may not own it, as another
// thread could have raced us to claim it)
- ASSERT(bh_info == &stg_BLACKHOLE_info ||
- bh_info == &__stg_EAGER_BLACKHOLE_info ||
- bh_info == &stg_CAF_BLACKHOLE_info);
+ ASSERT(IS_BLACKHOLE_INFO(bh_info));
} else {
// lazy black hole
=====================================
rts/Threads.c
=====================================
@@ -474,7 +474,7 @@ checkBlockingQueues (Capability *cap, StgTSO *tso)
// thing the result would be the same in almost all cases. See #20093.
p = UNTAG_CLOSURE(bq->bh);
const StgInfoTable *pinfo = ACQUIRE_LOAD(&p->header.info);
- if (pinfo != &stg_BLACKHOLE_info ||
+ if (!IS_BLACKHOLE_INFO(pinfo) ||
(RELAXED_LOAD(&((StgInd *)p)->indirectee) != (StgClosure*)bq))
{
wakeBlockingQueue(cap,bq);
@@ -498,10 +498,7 @@ updateThunk (Capability *cap, StgTSO *tso, StgClosure *thunk, StgClosure *val)
const StgInfoTable *i;
i = ACQUIRE_LOAD(&thunk->header.info);
- if (i != &stg_BLACKHOLE_info &&
- i != &stg_CAF_BLACKHOLE_info &&
- i != &__stg_EAGER_BLACKHOLE_info &&
- i != &stg_WHITEHOLE_info) {
+ if (!IS_BLACKHOLE_OR_WHITEHOLE_INFO(i)) {
updateWithIndirection(cap, thunk, val);
return;
}
=====================================
rts/Updates.h
=====================================
@@ -190,9 +190,9 @@
* frame is encountered, it checks the info table of the updatee and:
*
* - if it is `BLACKHOLE`, then the thunk has already been claimed for evaluation
- * by another thread, and the yielding thread is instead added to the
- * `BLACKHOLE`'s blocking queue (see Note [suspend duplicate work] in
- * `ThreadPaused.c`).
+ * by some thread. If that's not the yielding thread itself, the yielding thread
+ * is added to the `BLACKHOLE`'s blocking queue (see Note [suspend duplicate
+ * work] in `ThreadPaused.c`).
*
* - if not, then it blackholes the thunk as done in eager blackholing (but
* using the `BLACKHOLE_info` info table instead of `EAGER_BLACKHOLE_info`).
=====================================
rts/include/rts/storage/ClosureMacros.h
=====================================
@@ -382,6 +382,18 @@ EXTERN_INLINE StgOffset BLACKHOLE_sizeW ( void );
EXTERN_INLINE StgOffset BLACKHOLE_sizeW ( void )
{ return sizeofW(StgInd); } // a BLACKHOLE is a kind of indirection
+/* -----------------------------------------------------------------------------
+ Blackhole predicates
+ -------------------------------------------------------------------------- */
+
+#define IS_BLACKHOLE_INFO(info) \
+ ((info) == &stg_BLACKHOLE_info || \
+ (info) == &__stg_EAGER_BLACKHOLE_info || \
+ (info) == &stg_CAF_BLACKHOLE_info)
+
+#define IS_BLACKHOLE_OR_WHITEHOLE_INFO(info) \
+ (IS_BLACKHOLE_INFO(info) || (info) == &stg_WHITEHOLE_info)
+
/* --------------------------------------------------------------------------
Sizes of closures
------------------------------------------------------------------------*/
=====================================
testsuite/tests/ghc-api/downsweep/IncrementalDownsweep.hs
=====================================
@@ -0,0 +1,105 @@
+{-# LANGUAGE Haskell2010 #-}
+
+{-# OPTIONS_GHC -Wall -Werror #-}
+
+import Control.Monad (unless)
+import Control.Monad.IO.Class (liftIO)
+import Control.Arrow ((>>>))
+import Data.List (sort)
+import System.Environment (getArgs)
+import System.Exit (exitFailure)
+import System.IO (stderr)
+import System.Directory (removeFile)
+import Language.Haskell.Syntax.Module.Name (moduleNameString)
+import GHC.Utils.Ppr (Mode (PageMode))
+import GHC.Utils.Outputable (vcat, defaultSDocContext, printSDocLn, ppr)
+import GHC.Utils.Logger (getLogger)
+import GHC.Types.SrcLoc (noLoc)
+import GHC.Types.Error (mkUnknownDiagnostic)
+import GHC.Unit.Types (moduleName)
+import GHC.Unit.Module.ModSummary (ms_mod)
+import GHC.Unit.Module.Graph (ModuleGraph, mgModSummaries)
+import GHC.Driver.DynFlags (defaultFatalMessager, defaultFlushOut)
+import GHC.Driver.Monad (Ghc, getSession, getSessionDynFlags)
+import GHC.Driver.Make (downsweep)
+import GHC.Driver.Errors.Types (DriverMessages)
+import GHC
+ (
+ defaultErrorHandler,
+ guessTarget,
+ setTargets,
+ parseDynamicFlags,
+ setSessionDynFlags,
+ runGhc
+ )
+
+sourceDirectory :: String
+sourceDirectory = "IncrementalDownsweep.modules"
+
+withSimpleErrorHandler :: Ghc a -> Ghc a
+withSimpleErrorHandler = defaultErrorHandler defaultFatalMessager
+ defaultFlushOut
+
+handleDriverMessages :: [DriverMessages] -> IO ()
+handleDriverMessages driverMsgs
+ = unless (null driverMsgs) $
+ do
+ printSDocLn defaultSDocContext
+ (PageMode True)
+ stderr
+ (vcat (map ppr driverMsgs))
+ exitFailure
+
+performDownsweepTurn :: Maybe ModuleGraph -> String -> Ghc ModuleGraph
+performDownsweepTurn maybeGivenModuleGraph rootModuleName = do
+ target <- guessTarget rootModuleName Nothing Nothing
+ setTargets [target]
+ session <- getSession
+ (driverMsgs, resultingModuleGraph)
+ <- liftIO $ downsweep session
+ mkUnknownDiagnostic
+ Nothing
+ []
+ maybeGivenModuleGraph
+ []
+ False
+ liftIO $ handleDriverMessages driverMsgs
+ return resultingModuleGraph
+
+outputModuleNamesInGraph :: ModuleGraph -> IO ()
+outputModuleNamesInGraph = mgModSummaries >>>
+ map (ms_mod >>> moduleName >>> moduleNameString) >>>
+ sort >>>
+ print
+
+main :: IO ()
+main = do
+ libDir : otherArgs <- getArgs
+ runGhc (Just libDir) $ withSimpleErrorHandler $ do
+
+ -- Setup
+ logger <- getLogger
+ originalDynFlags <- getSessionDynFlags
+ (finalDynFlags, _, _)
+ <- parseDynamicFlags logger originalDynFlags $
+ map noLoc (["-i", "-i" ++ sourceDirectory] ++ otherArgs)
+ _ <- setSessionDynFlags finalDynFlags
+
+ -- Turn 1: From scratch, using 'A' as root
+ moduleGraph1 <- performDownsweepTurn Nothing "A"
+ liftIO $ outputModuleNamesInGraph moduleGraph1
+
+ -- Turn 2: From scratch, using 'X' as root
+ -- NOTE: 'A' is not included, because it is not reachable.
+ moduleGraph2 <- performDownsweepTurn Nothing "X"
+ liftIO $ outputModuleNamesInGraph moduleGraph2
+
+ -- Deletion of the source files used in turn 1
+ _ <- liftIO $
+ mapM_ (((sourceDirectory ++ "/") ++) >>> (++ ".hs") >>> removeFile)
+ ["A", "B", "C", "D"]
+
+ -- Turn 3: Based on the result of turn 1, using 'X' as root
+ -- NOTE: 'A' is included, because the result of turn 1 contains it.
+ moduleGraph3 <- performDownsweepTurn (Just moduleGraph1) "X"
+ liftIO $ outputModuleNamesInGraph moduleGraph3
=====================================
testsuite/tests/ghc-api/downsweep/IncrementalDownsweep.modules/A.hs
=====================================
@@ -0,0 +1,4 @@
+module A where
+
+import B
+import C
=====================================
testsuite/tests/ghc-api/downsweep/IncrementalDownsweep.modules/B.hs
=====================================
@@ -0,0 +1,3 @@
+module B where
+
+import D
=====================================
testsuite/tests/ghc-api/downsweep/IncrementalDownsweep.modules/C.hs
=====================================
@@ -0,0 +1,3 @@
+module C where
+
+import D
=====================================
testsuite/tests/ghc-api/downsweep/IncrementalDownsweep.modules/D.hs
=====================================
@@ -0,0 +1 @@
+module D where
=====================================
testsuite/tests/ghc-api/downsweep/IncrementalDownsweep.modules/X.hs
=====================================
@@ -0,0 +1,4 @@
+module X where
+
+import Y
+import Z
=====================================
testsuite/tests/ghc-api/downsweep/IncrementalDownsweep.modules/Y.hs
=====================================
@@ -0,0 +1,3 @@
+module Y where
+
+import B
=====================================
testsuite/tests/ghc-api/downsweep/IncrementalDownsweep.modules/Z.hs
=====================================
@@ -0,0 +1,3 @@
+module Z where
+
+import C
=====================================
testsuite/tests/ghc-api/downsweep/IncrementalDownsweep.stdout
=====================================
@@ -0,0 +1,3 @@
+["A","B","C","D"]
+["B","C","D","X","Y","Z"]
+["A","B","C","D","X","Y","Z"]
=====================================
testsuite/tests/ghc-api/downsweep/OldModLocation.hs
=====================================
@@ -48,13 +48,13 @@ main = do
liftIO $ do
- _emss <- downsweep hsc_env mkUnknownDiagnostic Nothing [] [] False
+ _emss <- downsweep hsc_env mkUnknownDiagnostic Nothing [] Nothing [] False
flushFinderCaches (hsc_FC hsc_env) (hsc_unit_env hsc_env)
createDirectoryIfMissing False "mydir"
renameFile "B.hs" "mydir/B.hs"
- (_, nodes) <- downsweep hsc_env mkUnknownDiagnostic Nothing [] [] False
+ (_, nodes) <- downsweep hsc_env mkUnknownDiagnostic Nothing [] Nothing [] False
-- If 'checkSummaryTimestamp' were to call 'addHomeModuleToFinder' with
-- (ms_location old_summary) like summariseFile used to instead of
=====================================
testsuite/tests/ghc-api/downsweep/PartialDownsweep.hs
=====================================
@@ -169,7 +169,7 @@ go label mods cnd =
setTargets [tgt]
hsc_env <- getSession
- (_, nodes) <- liftIO $ downsweep hsc_env mkUnknownDiagnostic Nothing [] [] False
+ (_, nodes) <- liftIO $ downsweep hsc_env mkUnknownDiagnostic Nothing [] Nothing [] False
it label $ cnd (mgModSummaries nodes)
=====================================
testsuite/tests/ghc-api/downsweep/all.T
=====================================
@@ -14,3 +14,10 @@ test('OldModLocation',
],
compile_and_run,
['-package ghc'])
+
+test('IncrementalDownsweep',
+ [ extra_files(['IncrementalDownsweep.modules/'])
+ , extra_run_opts('"' + config.libdir + '"')
+ ],
+ compile_and_run,
+ ['-package ghc'])
=====================================
testsuite/tests/ghc-api/fixed-nodes/InterfaceModuleGraph.hs
=====================================
@@ -67,7 +67,7 @@ main = do
keyC = msKey msC
let mkGraph s = do
- ([], nodes) <- downsweepFromRootNodes hsc_env mempty [] True DownsweepUseFixed s []
+ ([], nodes) <- downsweepFromRootNodes hsc_env mempty Nothing [] True DownsweepUseFixed s []
return $ mkModuleGraph nodes
graph <- liftIO $ mkGraph [ModuleNodeCompile msC]
=====================================
testsuite/tests/perf/should_run/T11226.hs
=====================================
@@ -0,0 +1,10 @@
+module Main where
+
+main :: IO ()
+main = print $ sum $ map bitcount [0, 4 .. 2 ^ (24 :: Int) - 1]
+
+bitcount :: Int -> Int
+bitcount x =
+ if x > 0
+ then let (d, m) = divMod x 2 in bitcount d + m
+ else 0
=====================================
testsuite/tests/perf/should_run/T11226.stdout
=====================================
@@ -0,0 +1 @@
+46137344
=====================================
testsuite/tests/perf/should_run/all.T
=====================================
@@ -30,6 +30,13 @@ test('T10359',
compile_and_run,
['-O'])
+test('T11226',
+ [collect_runtime_residency(10),
+ only_ways(['normal'])
+ ],
+ compile_and_run,
+ ['-O2'])
+
test('T14955',
[collect_stats('bytes allocated',5),
only_ways(['normal'])
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/179e22a998c7783116cc7bf92a3611…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/179e22a998c7783116cc7bf92a3611…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
28 May '26
Cheng Shao deleted branch wip/fix-msys2-sysroot-leak at Glasgow Haskell Compiler / GHC
--
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc] Pushed new branch wip/fendor/external-unit-db-cache
by Hannes Siebenhandl (@fendor) 28 May '26
by Hannes Siebenhandl (@fendor) 28 May '26
28 May '26
Hannes Siebenhandl pushed new branch wip/fendor/external-unit-db-cache at Glasgow Haskell Compiler / GHC
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/tree/wip/fendor/external-unit-db-cache
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc][wip/supersven/libDir-setting] 2 commits: Add optional config setting for LibDir (#19174)
by Sven Tennie (@supersven) 28 May '26
by Sven Tennie (@supersven) 28 May '26
28 May '26
Sven Tennie pushed to branch wip/supersven/libDir-setting at Glasgow Haskell Compiler / GHC
Commits:
41114e51 by Sven Tennie at 2026-05-28T10:41:07+02:00
Add optional config setting for LibDir (#19174)
Previously, the `libDir` was derived from `topDir`. This won't work for
inplace stage2 cross-compilers where binaries and libraries are in
different stage dirs (`_build/stage1/` for executables and
`_build/stage2` for libraries).
`LibDir` is set in the inplace `settings` files. For bindists, we
generate a new `settings` file with no `LibDir` entry. GHC then defaults
to use `topDir` as `libDir` again. This keeps the bindist relocatable.
If `LibDir` is a relative path, it is interpreted relatively to
`topDir`.
The global package db is part of the `lib/` folder. If we want to point
for inplace cross-compilers to the succeeding stage's folder, this is
done by setting `LibDir`. Thus, the global package db must be found
relative to `libDir`` (which may default to `topDir` or be set by
`LibDir`).
The complexity of settings becomes scary. So, add a test to ensure
`LibDir` works as expected.
- - - - -
9bbb7658 by Sven Tennie at 2026-05-28T10:41:07+02:00
Add Haddock to FileSettings
Helping to understand the fields' meanings without deeper analyses.
- - - - -
12 changed files:
- + changelog.d/libdir-setting
- compiler/GHC/Driver/Config/Interpreter.hs
- compiler/GHC/Driver/DynFlags.hs
- compiler/GHC/Driver/Session.hs
- compiler/GHC/Settings.hs
- compiler/GHC/Settings/IO.hs
- hadrian/src/Rules/BinaryDist.hs
- hadrian/src/Rules/Generate.hs
- libraries/ghc-boot/GHC/Settings/Utils.hs
- + testsuite/tests/ghc-api/settings/LibDir.hs
- + testsuite/tests/ghc-api/settings/LibDir.stdout
- + testsuite/tests/ghc-api/settings/all.T
Changes:
=====================================
changelog.d/libdir-setting
=====================================
@@ -0,0 +1,18 @@
+section: packaging
+synopsis: Added a new optional configuration setting for `LibDir` to support inplace
+ stage2 cross-compilers where binaries and libraries are in different stage
+ directories.
+issues: #19174
+mrs: !15716
+
+description: {
+ Previously, `libDir` was always derived from `topDir`, which does not work
+ for inplace stage2 cross-compilers where executables live in `_build/stage1/`
+ but libraries live in `_build/stage2/`. A new optional `LibDir` setting in the
+ `settings` file allows overriding this. When absent (e.g. in bindists),
+ `libDir` defaults to `topDir`, preserving relocatability.
+
+ The global package database path is now resolved relative to `libDir`
+ (which may differ from `topDir` when `LibDir` is set). This affects inplace
+ cross-compiler builds but is transparent to normal and bindist builds.
+}
=====================================
compiler/GHC/Driver/Config/Interpreter.hs
=====================================
@@ -17,8 +17,8 @@ import System.Directory
initInterpOpts :: DynFlags -> IO InterpOpts
initInterpOpts dflags = do
- wasm_dyld <- makeAbsolute $ topDir dflags </> "dyld.mjs"
- js_interp <- makeAbsolute $ topDir dflags </> "ghc-interp.js"
+ wasm_dyld <- makeAbsolute $ libDir dflags </> "dyld.mjs"
+ js_interp <- makeAbsolute $ libDir dflags </> "ghc-interp.js"
pure $ InterpOpts
{ interpExternal = gopt Opt_ExternalInterpreter dflags
, interpProg = pgm_i dflags
=====================================
compiler/GHC/Driver/DynFlags.hs
=====================================
@@ -61,7 +61,7 @@ module GHC.Driver.DynFlags (
-- ** System tool settings and locations
programName, projectVersion,
- ghcUsagePath, ghciUsagePath, topDir, toolDir,
+ ghcUsagePath, ghciUsagePath, topDir, libDir, toolDir,
versionedAppDir, versionedFilePath,
extraGccViaCFlags, globalPackageDatabasePath,
@@ -1516,6 +1516,8 @@ ghciUsagePath :: DynFlags -> FilePath
ghciUsagePath dflags = fileSettings_ghciUsagePath $ fileSettings dflags
topDir :: DynFlags -> FilePath
topDir dflags = fileSettings_topDir $ fileSettings dflags
+libDir :: DynFlags -> FilePath
+libDir dflags = fileSettings_libDir $ fileSettings dflags
toolDir :: DynFlags -> Maybe FilePath
toolDir dflags = fileSettings_toolDir $ fileSettings dflags
extraGccViaCFlags :: DynFlags -> [String]
=====================================
compiler/GHC/Driver/Session.hs
=====================================
@@ -3551,9 +3551,9 @@ compilerInfo dflags
("Project name", cProjectName)
-- Next come the settings, so anything else can be overridden
-- in the settings file (as "lookup" uses the first match for the
- -- key)
+ -- key). We filter out LibDir from rawSettings to avoid duplication.
: map (fmap expandDirectories)
- (rawSettings dflags)
+ (filter ((/= "LibDir") . fst) (rawSettings dflags))
++
[("C compiler command", queryCmd $ ccProgram . tgtCCompiler),
("C compiler flags", queryFlags $ ccProgram . tgtCCompiler),
@@ -3656,7 +3656,7 @@ compilerInfo dflags
-- Whether or not GHC was compiled using -prof
("GHC Profiled", showBool hostIsProfiled),
("Debug on", showBool debugIsOn),
- ("LibDir", topDir dflags),
+ ("LibDir", libDir dflags),
-- This is always an absolute path, unlike "Relative Global Package DB" which is
-- in the settings file.
("Global Package DB", globalPackageDatabasePath dflags)
=====================================
compiler/GHC/Settings.hs
=====================================
@@ -179,11 +179,24 @@ data ToolSettings = ToolSettings
-- | Paths to various files and directories used by GHC, including those that
-- provide more settings.
data FileSettings = FileSettings
- { fileSettings_ghcUsagePath :: FilePath -- ditto
- , fileSettings_ghciUsagePath :: FilePath -- ditto
- , fileSettings_toolDir :: Maybe FilePath -- ditto
- , fileSettings_topDir :: FilePath -- ditto
+ { fileSettings_ghcUsagePath :: FilePath
+ -- ^ Path to @ghc-usage.txt@, displayed by @ghc --help@
+ , fileSettings_ghciUsagePath :: FilePath
+ -- ^ Path to @ghci-usage.txt@, displayed by @ghci --help@
+ , fileSettings_toolDir :: Maybe FilePath
+ -- ^ Directory containing the mingw toolchain (Windows only);
+ -- see Note [tooldir: How GHC finds mingw on Windows] in `GHC.SysTools.BaseDir`
+ , fileSettings_topDir :: FilePath
+ -- ^ GHC's top directory: the root from which GHC locates its support files
+ -- (e.g. settings).
+ -- See Note [topdir: How GHC finds its files] in `GHC.SysTools.BaseDir`
, fileSettings_globalPackageDatabase :: FilePath
+ -- ^ Path to the global package database, relative to `libDir`
+ , fileSettings_libDir :: FilePath
+ -- ^ Directory containing GHC's library packages and the global package
+ -- database. Defaults to 'fileSettings_topDir' but can differ in inplace
+ -- builds used for cross-compilation testing (the "stage2 cross-compiler"
+ -- scenario).
}
=====================================
compiler/GHC/Settings/IO.hs
=====================================
@@ -28,6 +28,7 @@ import GHC.Toolchain.Program
import GHC.Toolchain
import GHC.Data.Maybe
import Data.Bifunctor (Bifunctor(second))
+import Data.Either (fromRight)
data SettingsError
= SettingsError_MissingData String
@@ -117,12 +118,6 @@ initSettings top_dir = do
then ["-fwrapv", "-fno-builtin"]
else []
- -- The package database is either a relative path to the location of the settings file
- -- OR an absolute path.
- -- In case the path is absolute then top_dir </> abs_path == abs_path
- -- the path is relative then top_dir </> rel_path == top_dir </> rel_path
- globalpkgdb_path <- installed <$> getSetting "Relative Global Package DB"
-
let ghc_usage_msg_path = installed "ghc-usage.txt"
ghci_usage_msg_path = installed "ghci-usage.txt"
@@ -148,6 +143,19 @@ initSettings top_dir = do
baseUnitId <- getSetting_raw "base unit-id"
+ -- LibDir is optional. If not set, derive it from topDir. This allows
+ -- bindists to work without explicitly setting LibDir, but gives us the
+ -- option to override it for inplace test compilers (the "stage2
+ -- cross-compiler" scenario). If LibDir is a relative path, it is
+ -- interpreted relative to topDir.
+ let lib_dir = installed $ fromRight "." $
+ getRawFilePathSetting top_dir settingsFile mySettings "LibDir"
+
+ -- The package database is either a relative path to lib_dir OR an absolute path.
+ -- In case the path is absolute then lib_dir </> abs_path == abs_path
+ -- the path is relative then lib_dir </> rel_path == lib_dir </> rel_path
+ globalpkgdb_path <- (lib_dir </>) <$> getSetting "Relative Global Package DB"
+
return $ Settings
{ sGhcNameVersion = GhcNameVersion
{ ghcNameVersion_programName = "ghc"
@@ -159,6 +167,7 @@ initSettings top_dir = do
, fileSettings_ghciUsagePath = ghci_usage_msg_path
, fileSettings_toolDir = mtool_dir
, fileSettings_topDir = top_dir
+ , fileSettings_libDir = lib_dir
, fileSettings_globalPackageDatabase = globalpkgdb_path
}
=====================================
hadrian/src/Rules/BinaryDist.hs
=====================================
@@ -14,6 +14,7 @@ import qualified System.Directory.Extra as IO
import Data.Either
import qualified Data.Set as Set
import Oracles.Flavour
+import Rules.Generate (generateSettings)
{-
Note [Binary distributions]
@@ -218,6 +219,17 @@ bindistRules = do
IO.createFileLink version_prog versioned_runhaskell_path
copyDirectory (ghcBuildDir -/- "lib") bindistFilesDir
+
+ -- Regenerate settings file without LibDir. For bindists, LibDir should
+ -- be derived from topdir at runtime such that the GHC binary is
+ -- relocatable. The package DB is always at "package.conf.d" relative to
+ -- the lib dir, matching the known bindist layout.
+ let bindistSettings = bindistFilesDir -/- "lib" -/- "settings"
+ bindistContext = vanillaContext Stage1 compiler
+ bindistSettingsContent <- interpretInContext bindistContext $
+ generateSettings bindistSettings False "package.conf.d"
+ writeFile' bindistSettings bindistSettingsContent
+
copyDirectory (rtsIncludeDir) bindistFilesDir
when windowsHost $ createGhcii (bindistFilesDir -/- "bin")
=====================================
hadrian/src/Rules/Generate.hs
=====================================
@@ -1,7 +1,7 @@
module Rules.Generate (
isGeneratedCmmFile, compilerDependencies, generatePackageCode,
generateRules, copyRules, generatedDependencies,
- templateRules
+ templateRules, generateSettings
) where
import Development.Shake.FilePath
@@ -256,8 +256,21 @@ generateRules = do
forM_ allStages $ \stage -> do
let prefix = root -/- stageString stage -/- "lib"
- go gen file = generate file (semiEmptyTarget (succStage stage)) gen
- (prefix -/- "settings") %> \out -> go (generateSettings out) out
+ -- Stage0 compiler builds Stage1, Stage1 -> Stage2, etc.
+ buildStage = succStage stage
+ go gen file = generate file (semiEmptyTarget buildStage) gen
+ (prefix -/- "settings") %> \out -> do
+ let get_pkg_db stg = packageDbPath (PackageDbLoc stg Final)
+ pkgDb <- case buildStage of
+ Stage0 {} -> error "Unable to generate settings for stage0. This should never be reached."
+ Stage1 -> get_pkg_db Stage1
+ Stage2 -> get_pkg_db Stage1
+ Stage3 -> get_pkg_db Stage2
+ -- addTrailingPathSeparator needed: makeRelativeNoSysLink uses
+ -- splitPath where "lib" and "lib/" are distinct components.
+ let lib_topDir = addTrailingPathSeparator prefix
+ relPkgDb = makeRelativeNoSysLink lib_topDir pkgDb
+ go (generateSettings out True relPkgDb) out
(prefix -/- "targets" -/- "default.target") %> \out -> go (show <$> expr getTargetTarget) out
where
@@ -460,19 +473,17 @@ ghcWrapper stage = do
return $ unwords $ map show $ [ ghcPath ]
++ [ "$@" ]
-generateSettings :: FilePath -> Expr String
-generateSettings settingsFile = do
+-- | Generate settings file, optionally including @LibDir@.
+--
+-- @rel_pkg_db@: package DB path relative to the lib dir (e.g.
+-- "package.conf.d"). Callers supply the correct relative path. For bindists
+-- the layout is known statically; for in-tree builds callers compute it. For
+-- bindists, we omit @LibDir@ so it defaults to @topDir@ at runtime.
+generateSettings :: FilePath -> Bool -> FilePath -> Expr String
+generateSettings settingsFile includeLibDir rel_pkg_db = do
ctx <- getContext
stage <- getStage
- package_db_path <- expr $ do
- let get_pkg_db stg = packageDbPath (PackageDbLoc stg Final)
- case stage of
- Stage0 {} -> error "Unable to generate settings for stage0"
- Stage1 -> get_pkg_db Stage1
- Stage2 -> get_pkg_db Stage1
- Stage3 -> get_pkg_db Stage2
-
-- The unit-id of the base package which is always linked against (#25382)
base_unit_id <- expr $ do
case stage of
@@ -481,15 +492,24 @@ generateSettings settingsFile = do
Stage2 -> pkgUnitId Stage1 base
Stage3 -> pkgUnitId Stage2 base
- let rel_pkg_db = makeRelativeNoSysLink (dropFileName settingsFile) package_db_path
+ let -- E.g. the Stage2 compiler lives in _build/stage1
+ -- So, we need to decrement the stage to get the correct directory
+ stage_dir_stage = predStage stage
+
+ -- addTrailingPathSeparator is needed because makeRelativeNoSysLink uses
+ -- splitPath internally, where "lib" and "lib/" are distinct components.
+ lib_topDir :: FilePath <- expr $ addTrailingPathSeparator <$> stageLibPath stage_dir_stage
+ let rel_lib_topDir = makeRelativeNoSysLink (dropFileName settingsFile) lib_topDir
settings <- traverse sequence $
- [ ("unlit command", ("$topdir/../bin/" <>) <$> expr (programName (ctx { Context.package = unlit })))
- , ("Use interpreter", expr $ yesNo <$> ghcWithInterpreter (predStage stage))
- , ("RTS ways", escapeArgs . map show . Set.toList <$> getRtsWays)
- , ("Relative Global Package DB", pure rel_pkg_db)
- , ("base unit-id", pure base_unit_id)
- ]
+ [ ("unlit command", ("$topdir/../bin/" <>) <$> expr (programName (ctx { Context.package = unlit })))
+ , ("Use interpreter", expr $ yesNo <$> ghcWithInterpreter (predStage stage))
+ , ("RTS ways", escapeArgs . map show . Set.toList <$> getRtsWays)
+ , ("Relative Global Package DB", pure rel_pkg_db)
+ , ("base unit-id", pure base_unit_id)
+ ]
+ ++ ([("LibDir", pure rel_lib_topDir) | includeLibDir])
+
let showTuple (k, v) = "(" ++ show k ++ ", " ++ show v ++ ")"
pure $ case settings of
[] -> "[]"
=====================================
libraries/ghc-boot/GHC/Settings/Utils.hs
=====================================
@@ -3,6 +3,7 @@ module GHC.Settings.Utils where
import Prelude -- See Note [Why do we import Prelude here?]
import Data.Char (isSpace)
+import Data.Either (fromRight)
import Data.Map (Map)
import qualified Data.Map as Map
@@ -45,7 +46,10 @@ getTargetArchOS target = tgtArchOs target
getGlobalPackageDb :: FilePath -> RawSettings -> Either String FilePath
getGlobalPackageDb settingsFile settings = do
rel_db <- getRawSetting settingsFile settings "Relative Global Package DB"
- return (dropFileName settingsFile </> rel_db)
+ let top_dir = dropFileName settingsFile
+ lib_dir = (top_dir </>) $ fromRight "." $
+ getRawFilePathSetting top_dir settingsFile settings "LibDir"
+ return (lib_dir </> rel_db)
--------------------------------------------------------------------------------
-- lib/settings
=====================================
testsuite/tests/ghc-api/settings/LibDir.hs
=====================================
@@ -0,0 +1,130 @@
+module Main where
+
+import Control.Monad (when)
+import Control.Monad.IO.Class (liftIO)
+import Data.List (intercalate)
+import GHC
+import GHC.Driver.DynFlags
+import GHC.Driver.Env (hsc_dflags)
+import GHC.Settings
+import System.Directory
+import System.Environment
+import System.Exit (ExitCode (ExitFailure), exitWith)
+import System.FilePath
+import System.IO (hPutStrLn, stderr)
+import System.Process (readProcess)
+import Unsafe.Coerce (unsafeCoerce)
+
+-- Verify that a LibDir setting in the settings file is respected:
+-- 1. fileSettings_libDir and fileSettings_globalPackageDatabase reflect the
+-- configured LibDir path (not topDir)
+-- 2. GHC can still compile with a LibDir that differs from topDir
+-- 3. --print-libdir and --print-global-package-db output the correct paths
+--
+-- We create a symlink to the real lib dir so that the package DB remains
+-- findable, but use a separate topDir so that topDir ≠ libDir, proving
+-- the LibDir setting is actually used.
+--
+-- Tested for both relative and absolute LibDir values.
+main :: IO ()
+main = do
+ libdir : ghcBin : _ <- getArgs
+
+ (rawSettingOpts, rawTargetOpts, realLibDir) <- runGhc (Just libdir) $ do
+ dflags <- hsc_dflags <$> getSession
+ pure (rawSettings dflags, rawTarget dflags, fileSettings_libDir (fileSettings dflags))
+
+ tmpDir <- getTemporaryDirectory
+ let topDir = tmpDir </> "T19174_top"
+ symlinkLib = tmpDir </> "T19174_lib"
+ -- Remove stale dirs from prior runs; createDirectoryLink fails if path exists.
+ removePathForcibly topDir
+ removePathForcibly symlinkLib
+ createDirectoryIfMissing True (topDir </> "targets")
+ createDirectoryLink realLibDir symlinkLib
+
+ let testWithLibDir libDirValue = do
+ writeTopDirFiles topDir rawSettingOpts rawTargetOpts libDirValue
+ runGhc (Just topDir) $ do
+ assertSettings topDir symlinkLib
+ compileAndRunTestExpr
+ assertGhcFlags ghcBin topDir symlinkLib
+
+ testWithLibDir (".." </> takeFileName symlinkLib)
+ testWithLibDir symlinkLib
+
+ putStrLn "OK"
+
+writeTopDirFiles ::
+ (Show a) =>
+ FilePath ->
+ [(String, String)] ->
+ a ->
+ String ->
+ IO ()
+writeTopDirFiles topDir rawSettingOpts rawTargetOpts libDirValue = do
+ let settings = filter ((/= "LibDir") . fst) rawSettingOpts ++ [("LibDir", libDirValue)]
+ writeFile (topDir </> "settings") $
+ "[" ++ intercalate "\n," (map show settings) ++ "]"
+ writeFile (topDir </> "targets" </> "default.target") $
+ show rawTargetOpts
+
+assertSettings :: FilePath -> FilePath -> Ghc ()
+assertSettings topDir expectedLib = do
+ dflags <- hsc_dflags <$> getSession
+ let fs = fileSettings dflags
+ actualLib = fileSettings_libDir fs
+ actualPkgDb = fileSettings_globalPackageDatabase fs
+ normActualLib <- liftIO $ canonicalizePath actualLib
+ normExpected <- liftIO $ canonicalizePath expectedLib
+ normTopDir <- liftIO $ canonicalizePath topDir
+ normActualPkgDb <- liftIO $ canonicalizePath actualPkgDb
+ normExpectedPkgDb <- liftIO $ canonicalizePath (expectedLib </> "package.conf.d")
+ liftIO $ do
+ when (normActualLib /= normExpected) $
+ die
+ [ "FAIL: libDir should be " ++ normExpected,
+ " got " ++ normActualLib
+ ]
+ when (normActualLib == normTopDir) $
+ die ["FAIL: libDir equals topDir — LibDir setting was ignored"]
+ when (normActualPkgDb /= normExpectedPkgDb) $
+ die
+ [ "FAIL: globalPackageDB should be " ++ normExpectedPkgDb,
+ " got " ++ normActualPkgDb
+ ]
+
+assertGhcFlags :: FilePath -> FilePath -> FilePath -> IO ()
+assertGhcFlags ghcBin topDir expectedLib = do
+ normExpectedLib <- canonicalizePath expectedLib
+ normExpectedPkgDb <- canonicalizePath (expectedLib </> "package.conf.d")
+
+ printedLibDir <- trim <$> readProcess ghcBin ["-B" ++ topDir, "--print-libdir"] ""
+ normPrintedLib <- canonicalizePath printedLibDir
+ when (normPrintedLib /= normExpectedLib) $
+ die
+ [ "FAIL: --print-libdir should be " ++ normExpectedLib,
+ " got " ++ normPrintedLib
+ ]
+
+ printedPkgDb <- trim <$> readProcess ghcBin ["-B" ++ topDir, "--print-global-package-db"] ""
+ normPrintedPkgDb <- canonicalizePath printedPkgDb
+ when (normPrintedPkgDb /= normExpectedPkgDb) $
+ die
+ [ "FAIL: --print-global-package-db should be " ++ normExpectedPkgDb,
+ " got " ++ normPrintedPkgDb
+ ]
+
+compileAndRunTestExpr :: Ghc ()
+compileAndRunTestExpr = do
+ dflags <- getSessionDynFlags
+ _ <- setSessionDynFlags dflags
+ setContext [IIDecl (simpleImportDecl (mkModuleName "Prelude"))]
+ result <- compileExpr "length [1,2,3 :: Int]"
+ liftIO $ print (unsafeCoerce result :: Int)
+
+trim :: String -> String
+trim = reverse . dropWhile (== '\n') . reverse
+
+die :: [String] -> IO ()
+die msgs = mapM_ (hPutStrLn stderr) msgs >> exitWith (ExitFailure 1)
=====================================
testsuite/tests/ghc-api/settings/LibDir.stdout
=====================================
@@ -0,0 +1,3 @@
+3
+3
+OK
=====================================
testsuite/tests/ghc-api/settings/all.T
=====================================
@@ -0,0 +1,12 @@
+test('LibDir',
+ [ extra_run_opts('"' + config.libdir + '" "' + config.compiler + '"')
+ , req_interp
+ # createDirectoryLink uses CreateSymbolicLink on Windows (requires developer
+ # mode or admin); also GHC searches for mingw relative to topDir, which our
+ # artificial topDir doesn't provide.
+ , when(opsys('mingw32'), skip)
+ # TODO: wasm CI image lacks permission to create symlinks in temp dir (`/tmp`).
+ , when(arch('wasm32'), skip)
+ ]
+ , compile_and_run
+ , ['-package ghc -package directory -package filepath'])
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/197c513567b547c1ca0b97d8a36a40…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/197c513567b547c1ca0b97d8a36a40…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc][wip/supersven/refactor-system-cxx-std-lib-hadrian-rules] 6 commits: Trim the continuation in mkDupableContWithDmds
by Sven Tennie (@supersven) 28 May '26
by Sven Tennie (@supersven) 28 May '26
28 May '26
Sven Tennie pushed to branch wip/supersven/refactor-system-cxx-std-lib-hadrian-rules at Glasgow Haskell Compiler / GHC
Commits:
4a645683 by Simon Peyton Jones at 2026-05-27T21:41:59-04:00
Trim the continuation in mkDupableContWithDmds
When there are no remaining argument demands, it means the application
is bottoming. In this case, we can trim the continuation to avoid the
panic that was observed in #27261.
See Note [Trimming the continuation for bottoming functions] in
GHC.Core.Opt.Simplify.Iteration.
- - - - -
8ab506ff by Cheng Shao at 2026-05-27T21:42:47-04:00
ghci: fix module name string lifetime in hs_hpc_module invocation
This patch makes hpcAddModule pass a properly malloced module name
string to hs_hpc_module, instead of using useAsCString which causes
use-after-free of module name string. Fixes #27297.
Co-authored-by: Codex <codex(a)openai.com>
- - - - -
b0233814 by sheaf at 2026-05-27T21:43:31-04:00
Relax acceptance threshold for T10421
As seen in #27289, the 1% acceptance threshold for this text was
overly narrow, resulting in spurious test failures. This commit widens
the acceptance threshold to 2%. Fixes #27289.
- - - - -
827de234 by Matthew Pickering at 2026-05-28T08:32:32+00:00
hadrian: Refactor system-cxx-std-lib rules
I noticed a few things wrong with the hadrian rules for
`system-cxx-std-lib` rules.
* For `text` there is an ad-hoc check to depend on `system-cxx-std-lib`
outside of `configurePackage`.
* The `system-cxx-std-lib` dependency is not read from cabal files.
* Recache is not called on the packge database after the `.conf` file is
generated, a more natural place for this rule is `registerRules`.
Treating this uniformly like other packages is complicated by it not
having any source code or a cabal file. However we can do a bit better
by reporting the dependency firstly in `PackageData` and then needing
the `.conf` file in the same place as every other package in
`configurePackage`.
Fixes #25303
Co-authored-by: Sven Tennie <sven.tennie(a)gmail.com>
- - - - -
a2bc3224 by Sven Tennie at 2026-05-28T08:32:32+00:00
Set shakeVersion
This leads to wiping Shake's database, providing backwards compatibility
to previous builds with different PackageData.
- - - - -
4f2742f2 by Sven Tennie at 2026-05-28T08:32:32+00:00
Remove superfluous rule
- - - - -
17 changed files:
- + changelog.d/T27261
- + changelog.d/hadrian-system-cxx-std-lib-25303
- compiler/GHC/Core/Opt/Simplify/Iteration.hs
- compiler/GHC/Core/Opt/Simplify/Utils.hs
- hadrian/src/Hadrian/Haskell/Cabal/Parse.hs
- hadrian/src/Hadrian/Haskell/Cabal/Type.hs
- hadrian/src/Main.hs
- hadrian/src/Rules/Generate.hs
- hadrian/src/Rules/Register.hs
- + libraries/ghc-boot/GHC/Data/ShortByteString.hs
- libraries/ghc-boot/ghc-boot.cabal.in
- libraries/ghci/GHCi/Coverage.hs
- libraries/ghci/GHCi/Run.hs
- testsuite/tests/perf/compiler/all.T
- + testsuite/tests/simplCore/should_compile/T27261.hs
- + testsuite/tests/simplCore/should_compile/T27261_aux.hs
- testsuite/tests/simplCore/should_compile/all.T
Changes:
=====================================
changelog.d/T27261
=====================================
@@ -0,0 +1,10 @@
+section: compiler
+issues: #27261
+mrs: !16084
+synopsis:
+ Avoid a crash in ``mkDupableContWithDmds`` when given empty demands
+description:
+ The case of an empty list of remaining argument demands is now explicitly
+ handled by trimming the simplifier continuation, to avoid a compiler crash
+ of the form ``Non-exhaustive patterns in dmd : cont_dmds`` or ``expectNonEmpty``
+ in ``mkDupableContWithDmds``.
=====================================
changelog.d/hadrian-system-cxx-std-lib-25303
=====================================
@@ -0,0 +1,18 @@
+section: packaging
+synopsis: Fix Hadrian rules for system-cxx-std-lib package dependency
+issues: #25303
+mrs: !16013
+description: {
+ Hadrian's handling of the ``system-cxx-std-lib`` virtual package has been
+ fixed and made more uniform.
+
+ Previously, ``text`` had an ad-hoc rule outside of ``configurePackage`` to
+ declare a dependency on ``system-cxx-std-lib``, the dependency was not
+ discovered from cabal files, and the package database was not recached after
+ the ``.conf`` file was generated.
+
+ The dependency is now read from cabal files via a new
+ ``dependsOnSystemCxxStdLib`` field in ``PackageData``, and the ``.conf`` file
+ is needed inside ``configurePackage`` alongside all other package
+ dependencies, consistent with how every other package is handled.
+}
=====================================
compiler/GHC/Core/Opt/Simplify/Iteration.hs
=====================================
@@ -62,6 +62,7 @@ import GHC.Types.Var ( isTyCoVar )
import GHC.Builtin.Types.Prim( realWorldStatePrimTy )
import GHC.Builtin.Names( runRWKey, seqHashKey )
+import qualified GHC.Data.List.Infinite as Inf
import GHC.Data.Maybe ( isNothing, orElse, mapMaybe )
import GHC.Data.FastString
import GHC.Unit.Module ( moduleName )
@@ -2444,24 +2445,9 @@ rebuildCall env arg_info _cont
---------- Bottoming applications --------------
rebuildCall env (ArgInfo { ai_fun = fun, ai_args = rev_args, ai_dmds = [] }) cont
- -- When we run out of strictness args, it means
- -- that the call is definitely bottom; see GHC.Core.Opt.Simplify.Utils.mkArgInfo
- -- Then we want to discard the entire strict continuation. E.g.
- -- * case (error "hello") of { ... }
- -- * (error "Hello") arg
- -- * f (error "Hello") where f is strict
- -- etc
- -- Then, especially in the first of these cases, we'd like to discard
- -- the continuation, leaving just the bottoming expression. But the
- -- type might not be right, so we may have to add a coerce.
- | not (contIsTrivial cont) -- Only do this if there is a non-trivial
- -- continuation to discard, else we do it
- -- again and again!
- = seqType cont_ty `seq` -- See Note [Avoiding space leaks in OutType]
- return (emptyFloats env, castBottomExpr res cont_ty)
- where
- res = argInfoExpr fun rev_args
- cont_ty = contResultType cont
+ -- When we run out of demands, it means that the call is definitely bottom.
+ -- See (TC2) in Note [Trimming the continuation for bottoming functions]
+ = rebuild env (argInfoExpr fun rev_args) (mkBottomCont cont)
---------- Simplify type applications --------------
rebuildCall env info (ApplyToTy { sc_arg_ty = arg_ty, sc_hole_ty = hole_ty, sc_cont = cont })
@@ -4045,6 +4031,41 @@ When we have
then we can just duplicate those alts because the A and C cases
will disappear immediately. This is more direct than creating
join points and inlining them away. See #4930.
+
+Note [Trimming the continuation for bottoming functions]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Suppose
+ f :: Int -> Int -> Int
+ f x = error "urk"
+
+ foo = f 3 4
+
+f's demand signature say "after one arg I return bottom". We can drop
+the remaining arguments, thus
+
+ foo = case f 3 of {}
+
+This trimming can also be done with other continuations:
+ * case (error "hello") of { ... }
+ * f (error "Hello") where f is strict
+ etc
+
+We implement the trimming in three parts:
+
+(TC1) In `mkArgInfo`, for a bottoming function, we make a list of `RemainingArgDmds`
+ with a finite list of elements (in the example above, just one).
+
+ For comparison, note that, for non-bottoming functions, the `RemainingArgDmds`
+ always finishes with an infinite list of `topDmd`.
+
+(TC2) In `rebuildCall`, when we run out of `RemainingArgDmds` we discard the
+ remaining continuation.
+
+ After discarding the continuation, the types might not match, in which case
+ we leave behind a (case <hole> of {}) wrapper. See the call to `mkBottomCont`.
+
+(TC3) In `mkDupableContWithDmds`, we similarly discard the continuation when
+ we run out of `RemainingArgDmds`.
-}
--------------------
@@ -4079,10 +4100,10 @@ mkDupableCont env cont
= mkDupableContWithDmds (zapSubstEnv env) (repeat topDmd) cont
mkDupableContWithDmds
- :: SimplEnvIS -> [Demand] -- Demands on arguments; always infinite
+ :: SimplEnvIS -> RemainingArgDmds
-> SimplCont -> SimplM ( SimplFloats, SimplCont)
-mkDupableContWithDmds env _ cont
+mkDupableContWithDmds env remaining_dmds cont
-- Check the invariant
| assertPpr (checkSimplEnvIS env) (pprBadSimplEnvIS env) False
= pprPanic "mkDupableContWithDmds" empty
@@ -4090,6 +4111,13 @@ mkDupableContWithDmds env _ cont
| contIsDupable cont
= return (emptyFloats env, cont)
+ -- No more demands => function is definitely bottom
+ -- => simply trim the continuation
+ -- c.f. the null-demands case in `rebuildCall`
+ -- See (TC3) in Note [Trimming the continuation for bottoming functions]
+ | null remaining_dmds
+ = return (emptyFloats env, mkBottomCont cont)
+
mkDupableContWithDmds _ _ (Stop {}) = panic "mkDupableCont" -- Handled by previous eqn
mkDupableContWithDmds env dmds (CastIt { sc_co = co, sc_opt = opt, sc_cont = cont })
@@ -4134,7 +4162,8 @@ mkDupableContWithDmds env _
, thumbsUpPlanA cont
= -- Use Plan A of Note [Duplicating StrictArg]
-- pprTrace "Using plan A" (ppr (ai_fun fun) $$ text "args" <+> ppr (ai_args fun) $$ text "cont" <+> ppr cont) $
- do { let _ :| dmds = expectNonEmpty $ ai_dmds fun
+ do { let _ :| dmds = expectNonEmpty (ai_dmds fun) -- See Invariant of StrictArg;
+ -- ai_dmds is never empty
; (floats1, cont') <- mkDupableContWithDmds env dmds cont
-- Use the demands from the function to add the right
-- demand info on any bindings we make for further args
@@ -4180,7 +4209,10 @@ mkDupableContWithDmds env dmds
-- let a = ...arg...
-- in [...hole...] a
-- NB: sc_dup /= OkToDup; that is caught earlier by contIsDupable
- do { let dmd:|cont_dmds = expectNonEmpty dmds
+ do { let dmd:|cont_dmds =
+ -- We took care to handle an empty demand list at the start,
+ -- ensuring this call to 'expectNonEmpty' does not panic (#27261).
+ expectNonEmpty dmds
; (floats1, cont') <- mkDupableContWithDmds env cont_dmds cont
; let env' = env `setInScopeFromF` floats1
; arg' <- simplArg env' Nothing hole_ty se arg arg_mco
@@ -4251,7 +4283,7 @@ mkDupableStrictBind env arg_bndr join_rhs res_ty
; let arg_info = ArgInfo { ai_fun = join_bndr
, ai_rules = [], ai_args = []
, ai_encl = False, ai_dmds = repeat topDmd
- , ai_discs = repeat 0 }
+ , ai_discs = Inf.repeat 0 }
; return ( addJoinFloats (emptyFloats env) $
unitJoinFloat $
NonRec join_bndr $
=====================================
compiler/GHC/Core/Opt/Simplify/Utils.hs
=====================================
@@ -25,13 +25,13 @@ module GHC.Core.Opt.Simplify.Utils (
StaticEnv(..),
isSimplified, contIsStop,
contIsDupable, contResultType, contHoleType, contHoleScaling,
- contIsTrivial, contArgs, contIsRhs,
+ contIsTrivial, contArgs, contIsRhs, mkBottomCont,
hasArgs, countArgs, contOutArgs, dropContArgs,
mkBoringStop, mkRhsStop, mkLazyArgStop,
interestingCallContext,
-- ArgInfo
- ArgInfo(..), ArgSpec(..), mkArgInfo,
+ ArgInfo(..), ArgSpec(..), RemainingArgDmds, mkArgInfo,
addValArgTo, addTyArgTo,
argInfoExpr, argSpecArg,
pushOutArgs, pushArgSpecs,
@@ -54,8 +54,10 @@ import GHC.Core.Opt.Stats ( Tick(..) )
import qualified GHC.Core.Subst
import GHC.Core.Ppr
import GHC.Core.TyCo.Ppr ( pprParendType )
+import GHC.Core.TyCo.Compare ( eqTypeIgnoringMultiplicity )
import GHC.Core.FVs
import GHC.Core.Utils
+import GHC.Core.Make( mkWildValBinder )
import GHC.Core.Opt.Arity
import GHC.Core.Unfold
import GHC.Core.Unfold.Make
@@ -75,6 +77,8 @@ import GHC.Types.Var.Set
import GHC.Types.Basic
import GHC.Types.Name.Env
+import GHC.Data.List.Infinite ( Infinite(..) )
+import qualified GHC.Data.List.Infinite as Inf
import GHC.Data.OrdList ( isNilOL )
import GHC.Data.FastString ( fsLit )
@@ -205,10 +209,10 @@ data SimplCont
| StrictArg -- (StrictArg (f e1 ..en) K)[e] = K[ f e1 .. en e ]
{ sc_dup :: DupFlag
- , sc_fun :: ArgInfo -- Specifies f, e1..en, Whether f has rules, etc
+ , sc_fun :: ArgInfo -- Specifies f, e1..en, whether f has rules, etc
-- plus demands and discount flags for *this* arg
-- and further args
- -- So ai_dmds and ai_discs are never empty
+ -- Invariant: ai_dmds and ai_discs are never empty
, sc_fun_ty :: OutType -- Type of the function (f e1 .. en),
-- presumably (arg_ty -> res_ty)
-- where res_ty is expected by sc_cont
@@ -348,32 +352,41 @@ doesn't matter because we'll never compute them all.
data ArgInfo
= ArgInfo {
- ai_fun :: OutId, -- The function
- ai_args :: [ArgSpec], -- ...applied to these args (which are in *reverse* order)
+ ai_fun :: OutId, -- ^ The function
+ ai_args :: [ArgSpec], -- ^ ...applied to these args (which are in *reverse* order)
-- NB: all these argumennts are already simplified
- ai_rules :: [CoreRule], -- Rules for this function
- ai_encl :: Bool, -- Flag saying whether this function
- -- or an enclosing one has rules (recursively)
- -- True => be keener to inline in all args
+ ai_rules :: [CoreRule], -- ^ Rules for this function
+ ai_encl :: Bool,
+ -- ^ Flag saying whether this function or an enclosing one has rules
+ -- (recursively)
+ --
+ -- @True@ means: be keener to inline in all args
- ai_dmds :: [Demand], -- Demands on remaining value arguments (beyond ai_args)
- -- Usually infinite, but if it is finite it guarantees
- -- that the function diverges after being given
- -- that number of args
+ ai_dmds :: RemainingArgDmds,
+ -- ^ Demands on remaining value arguments (beyond 'ai_args')
- ai_discs :: [Int] -- Discounts for remaining value arguments (beyond ai_args)
- -- non-zero => be keener to inline
- -- Always infinite
+ ai_discs :: Infinite Int
+ -- ^ Discounts for remaining value arguments (beyond 'ai_args')
+ --
+ -- A non-zero value means: be keener to inline
}
-data ArgSpec
- = ValArg { as_dmd :: Demand -- Demand placed on this argument
- , as_arg :: OutExpr -- Apply to this (coercion or value); c.f. ApplyToVal
- , as_hole_ty :: OutType } -- Type of the function (presumably t1 -> t2)
+-- | 'RemainingArgDmds' gives the demands on any remaining value arguments.
+--
+-- It is usually infinite (with 'topDmd's in the tail), but if it is finite it
+-- guarantees that the function diverges after being applied to that number
+-- of arguments.
+type RemainingArgDmds = [Demand]
- | TyArg { as_arg_ty :: OutType -- Apply to this type; c.f. ApplyToTy
- , as_hole_ty :: OutType } -- Type of the function (presumably forall a. blah)
+data ArgSpec
+ -- | A value argument
+ = ValArg { as_dmd :: Demand -- ^ Demand placed on this argument
+ , as_arg :: OutExpr -- ^ Apply to this (coercion or value); c.f. 'ApplyToVal'
+ , as_hole_ty :: OutType } -- ^ Type of the function (presumably @t1 -> t2@ for 'ValArg' or @forall a. blah@ for 'TyArg')
+ -- | A type argument
+ | TyArg { as_arg_ty :: OutType -- ^ Apply to this type; c.f. 'ApplyToTy'
+ , as_hole_ty :: OutType } -- ^ Type of the function (presumably @t1 -> t2@ for 'ValArg' or @forall a. blah@ for 'TyArg')
instance Outputable ArgInfo where
ppr (ArgInfo { ai_fun = fun, ai_args = args, ai_dmds = dmds, ai_rules = rules })
@@ -389,7 +402,7 @@ instance Outputable ArgSpec where
addValArgTo :: ArgInfo -> OutExpr -> OutType -> ArgInfo
addValArgTo ai arg hole_ty
- | ArgInfo { ai_dmds = dmd:dmds, ai_discs = _:discs } <- ai
+ | ArgInfo { ai_dmds = dmd:dmds, ai_discs = Inf _ discs } <- ai
-- Pop the top demand and and discounts off
, let arg_spec = ValArg { as_arg = arg, as_hole_ty = hole_ty, as_dmd = dmd }
= ai { ai_args = arg_spec : ai_args ai
@@ -492,12 +505,23 @@ contIsDupable (TickIt _ k) = contIsDupable k
contIsTrivial :: SimplCont -> Bool
contIsTrivial (Stop {}) = True
contIsTrivial (ApplyToTy { sc_cont = k }) = contIsTrivial k
--- This one doesn't look right. A value application is not trivial
--- contIsTrivial (ApplyToVal { sc_arg = Coercion _, sc_cont = k }) = contIsTrivial k
contIsTrivial (CastIt { sc_cont = k }) = contIsTrivial k
contIsTrivial _ = False
-------------------
+contStop :: SimplCont -> SimplCont
+-- ^ Get the 'Stop' at the tail of the continuation
+--
+-- Always returns a continuation of form @(Stop ...)@.
+contStop stop@(Stop {}) = stop
+contStop (CastIt { sc_cont = k }) = contStop k
+contStop (StrictBind { sc_cont = k }) = contStop k
+contStop (StrictArg { sc_cont = k }) = contStop k
+contStop (Select { sc_cont = k }) = contStop k
+contStop (ApplyToTy { sc_cont = k }) = contStop k
+contStop (ApplyToVal { sc_cont = k }) = contStop k
+contStop (TickIt _ k) = contStop k
+
contResultType :: SimplCont -> OutType
contResultType (Stop ty _ _) = ty
contResultType (CastIt { sc_cont = k }) = contResultType k
@@ -651,6 +675,35 @@ contEvalContext bndrs cont = go cont
-- Perhaps reconstruct the demand on the scrutinee by looking at field
-- and case binder dmds, see addCaseBndrDmd. No priority right now.
+-------------------
+mkBottomCont ::SimplCont -> SimplCont
+-- ^ Given a continuation `cont`, return a `cont` /of the same type/,
+-- looking like @(case \<hole\> of {})@.
+--
+-- This is used when we are going to fill in the @<hole>@ with bottom.
+-- See (TC2,3) in Note [Trimming the continuation for bottoming functions]
+--
+-- Don't bother to trim, making a @case <hole> of {}@, if we have only
+-- an essentially-trivial continuation; e.g. @(<hole> \@ty |> co)@.
+mkBottomCont cont = go cont
+ where
+ go k@(Stop {}) = k
+ go (TickIt t k') = TickIt t (go k')
+ go k@(CastIt { sc_cont = k' }) = k { sc_cont = go k' }
+ go k@(ApplyToTy { sc_cont = k' }) = k { sc_cont = go k' }
+ go k@(Select { sc_alts = [], sc_cont = Stop {} }) = k -- Optimisation only
+ go k | Stop res_ty _ _ <- stop_cont
+ , hole_ty `eqTypeIgnoringMultiplicity` res_ty
+ = stop_cont
+ | otherwise
+ = Select { sc_alts = []
+ , sc_bndr = mkWildValBinder OneTy hole_ty
+ , sc_env = Simplified OkDup
+ , sc_cont = stop_cont }
+ where
+ hole_ty = contHoleType k
+ stop_cont = contStop k
+
-------------------
mkArgInfo :: SimplEnv -> Id -> [CoreRule] -> SimplCont -> ArgInfo
mkArgInfo env fun rules_for_fun cont
@@ -672,16 +725,17 @@ mkArgInfo env fun rules_for_fun cont
fun_has_rules = not (null rules_for_fun)
- vanilla_discounts, arg_discounts :: [Int]
- vanilla_discounts = repeat 0
+ vanilla_discounts, arg_discounts :: Infinite Int
+ vanilla_discounts = Inf.repeat 0
arg_discounts = case idUnfolding fun of
CoreUnfolding {uf_guidance = UnfIfGoodArgs {ug_args = discounts}}
- -> discounts ++ vanilla_discounts
+ -> discounts Inf.++ vanilla_discounts
_ -> vanilla_discounts
- vanilla_dmds, arg_dmds :: [Demand]
+ vanilla_dmds :: RemainingArgDmds
vanilla_dmds = repeat topDmd
+ arg_dmds :: RemainingArgDmds
arg_dmds
| not (seInline env)
= vanilla_dmds -- See Note [Do not expose strictness if sm_inline=False]
@@ -689,26 +743,22 @@ mkArgInfo env fun rules_for_fun cont
= -- add_type_str fun_ty $
case splitDmdSig (idDmdSig fun) of
(demands, result_info)
- | not (demands `lengthExceeds` n_val_args)
- -> -- Enough args, use the strictness given.
- -- For bottoming functions we used to pretend that the arg
- -- is lazy, so that we don't treat the arg as an
- -- interesting context. This avoids substituting
- -- top-level bindings for (say) strings into
- -- calls to error. But now we are more careful about
- -- inlining lone variables, so its ok
- -- (see GHC.Core.Op.Simplify.Utils.analyseCont)
- if isDeadEndDiv result_info then
- demands -- Finite => result is bottom
- else
- demands ++ vanilla_dmds
+ | not (demands `lengthExceeds` n_val_args)
+ -> remaining_dmds -- Enough args, use the strictness given.
| otherwise
-> warnPprTrace True "More demands than arity" (ppr fun <+> ppr (idArity fun)
<+> ppr n_val_args <+> ppr demands) $
vanilla_dmds -- Not enough args, or no strictness
- add_type_strictness :: Type -> [Demand] -> [Demand]
- -- If the function arg types are strict, record that in the 'strictness bits'
+ where
+ remaining_dmds :: RemainingArgDmds
+ -- isDeadEndDiv: if remaining_dmds is finite, result is bottom
+ -- See (TC1) in Note [Trimming the continuation for bottoming functions]
+ remaining_dmds | isDeadEndDiv result_info = demands
+ | otherwise = demands ++ vanilla_dmds
+
+ add_type_strictness :: Type -> RemainingArgDmds -> RemainingArgDmds
+ -- If the function arg /types/ are strict, record that in the RemainingArgDmds
-- No need to instantiate because unboxed types (which dominate the strict
-- types) can't instantiate type variables.
-- add_type_strictness is done repeatedly (for each call);
@@ -915,16 +965,16 @@ the incentive to disappear when we inline `f`!
lazyArgContext :: ArgInfo -> CallCtxt
-- Use this for lazy arguments
lazyArgContext (ArgInfo { ai_encl = encl_rules, ai_discs = discs })
- | encl_rules = RuleArgCtxt
- | disc:_ <- discs, disc > 0 = DiscArgCtxt -- Be keener here
- | otherwise = BoringCtxt -- Nothing interesting
+ | encl_rules = RuleArgCtxt
+ | Inf disc _ <- discs, disc > 0 = DiscArgCtxt -- Be keener here
+ | otherwise = BoringCtxt -- Nothing interesting
strictArgContext :: ArgInfo -> CallCtxt
strictArgContext (ArgInfo { ai_encl = encl_rules, ai_discs = discs })
-- Use this for strict arguments
- | encl_rules = RuleArgCtxt
- | disc:_ <- discs, disc > 0 = DiscArgCtxt -- Be keener here
- | otherwise = RhsCtxt NonRecursive
+ | encl_rules = RuleArgCtxt
+ | Inf disc _ <- discs, disc > 0 = DiscArgCtxt -- Be keener here
+ | otherwise = RhsCtxt NonRecursive
-- Why RhsCtxt? if we see f (g x), and f is strict, we
-- want to be a bit more eager to inline g, because it may
-- expose an eval (on x perhaps) that can be eliminated or
=====================================
hadrian/src/Hadrian/Haskell/Cabal/Parse.hs
=====================================
@@ -81,10 +81,11 @@ parsePackageData pkg = do
sorted = sort [ C.unPackageName p | C.Dependency p _ _ <- allDeps ]
deps = nubOrd sorted \\ [name]
depPkgs = mapMaybe findPackageByName deps
+ cxxStdLib = elem "system-cxx-std-lib" deps
return $ PackageData name version
(C.fromShortText (C.synopsis pd))
(C.fromShortText (C.description pd))
- depPkgs gpd
+ depPkgs cxxStdLib gpd
where
-- Collect an overapproximation of dependencies by ignoring conditionals
collectDeps :: Maybe (C.CondTree v [C.Dependency] a) -> [C.Dependency]
@@ -138,7 +139,9 @@ configurePackage :: Context -> Action ()
configurePackage context@Context {..} = do
putProgressInfo $ "| Configure package " ++ quote (pkgName package)
gpd <- pkgGenericDescription package
- depPkgs <- packageDependencies <$> readPackageData package
+ pd <- readPackageData package
+ let depPkgs = packageDependencies pd
+ needSystemCxxStdLib = dependsOnSystemCxxStdLib pd
-- Stage packages are those we have in this stage.
stagePkgs <- stagePackages stage
@@ -157,7 +160,12 @@ configurePackage context@Context {..} = do
-- We'll need those packages in our package database.
deps <- sequence [ pkgConfFile (context { package = pkg, iplace = forceBaseAfterGhcInternal pkg })
| pkg <- depPkgs, pkg `elem` stagePkgs ]
- need $ extraPreConfigureDeps ++ deps
+ -- system-cxx-std-lib is magic.. it doesn't have a cabal file or source code, so we have
+ -- to treat it specially as `pkgConfFile` uses `readPackageData` to compute the version.
+ systemCxxStdLib <- sequence [ systemCxxStdLibConfPath (PackageDbLoc stage iplace) | needSystemCxxStdLib ]
+ need $ extraPreConfigureDeps
+ ++ deps
+ ++ systemCxxStdLib
-- Figure out what hooks we need.
let configureFile = replaceFileName (pkgCabalFile package) "configure"
=====================================
hadrian/src/Hadrian/Haskell/Cabal/Type.hs
=====================================
@@ -30,6 +30,7 @@ data PackageData = PackageData
, synopsis :: String
, description :: String
, packageDependencies :: [Package]
+ , dependsOnSystemCxxStdLib :: Bool
, genericPackageDescription :: GenericPackageDescription
} deriving (Eq, Generic, Show)
=====================================
hadrian/src/Main.hs
=====================================
@@ -63,7 +63,13 @@ main = do
shakeColor <- shouldUseColor
let options :: ShakeOptions
options = shakeOptions
- { shakeChange = ChangeModtimeAndDigest
+ { -- Bump shakeVersion whenever a type stored in the Shake oracle
+ -- changes its Binary representation (e.g. fields added/removed
+ -- from PackageData or other oracle value types). This forces
+ -- Shake to wipe the stale database instead of crashing on
+ -- deserialisation.
+ shakeVersion = "2"
+ , shakeChange = ChangeModtimeAndDigest
, shakeFiles = buildRoot -/- Base.shakeFilesDir
, shakeProgress = Progress.hadrianProgress cwd
, shakeRebuild = rebuild
=====================================
hadrian/src/Rules/Generate.hs
=====================================
@@ -242,9 +242,6 @@ copyRules = do
prefix -/- "html/**" <~ return "utils/haddock/haddock-api/resources"
prefix -/- "latex/**" <~ return "utils/haddock/haddock-api/resources"
- forM_ [Inplace, Final] $ \iplace ->
- root -/- relativePackageDbPath (PackageDbLoc stage iplace) -/- systemCxxStdLibConf %> \file -> do
- copyFile ("mk" -/- "system-cxx-std-lib-1.0.conf") file
generateRules :: Rules ()
generateRules = do
=====================================
hadrian/src/Rules/Register.hs
=====================================
@@ -6,7 +6,6 @@ module Rules.Register (
import Base
import Context
-import Flavour
import Oracles.Setting
import Hadrian.BuildPath
import Hadrian.Expression
@@ -48,14 +47,6 @@ configurePackageRules = do
isGmp <- (== "gmp") <$> interpretInContext ctx getBignumBackend
when isGmp $
need [buildP -/- "include/ghc-gmp.h"]
- when (pkg == text) $ do
- simdutf <- textWithSIMDUTF <$> flavour
- when simdutf $ do
- -- This is required, otherwise you get Error: hadrian:
- -- Encountered missing or private dependencies:
- -- system-cxx-std-lib ==1.0
- cxxStdLib <- systemCxxStdLibConfPath $ PackageDbLoc stage Inplace
- need [cxxStdLib]
Cabal.configurePackage ctx
root -/- "**/autogen/cabal_macros.h" %> \out -> do
@@ -105,6 +96,12 @@ registerPackageRules rs stage iplace = do
target (Context stage compiler vanilla iplace) (GhcPkg Recache stage) [] []
writeFileLines stamp []
+ -- Special rule for registering system-cxx-std-lib
+ root -/- relativePackageDbPath (PackageDbLoc stage iplace) -/- systemCxxStdLibConf %> \file -> do
+ copyFile ("mk" -/- "system-cxx-std-lib-1.0.conf") file
+ buildWithResources rs $
+ target (Context stage compiler vanilla iplace) (GhcPkg Recache stage) [] []
+
-- Register a package.
root -/- relativePackageDbPath (PackageDbLoc stage iplace) -/- "*.conf" %> \conf -> do
historyDisable
=====================================
libraries/ghc-boot/GHC/Data/ShortByteString.hs
=====================================
@@ -0,0 +1,17 @@
+module GHC.Data.ShortByteString
+ ( newCStringFromSBS
+ ) where
+
+import Prelude
+
+import qualified Data.ByteString.Short as SBS
+import Foreign
+import Foreign.C
+
+newCStringFromSBS :: SBS.ShortByteString -> IO CString
+newCStringFromSBS sbs =
+ SBS.useAsCStringLen sbs $ \(src, len) -> do
+ dst <- mallocBytes (len + 1)
+ copyBytes dst src len
+ pokeByteOff dst len (0 :: Word8)
+ pure dst
=====================================
libraries/ghc-boot/ghc-boot.cabal.in
=====================================
@@ -51,6 +51,7 @@ Library
exposed-modules:
GHC.BaseDir
+ GHC.Data.ShortByteString
GHC.Data.ShortText
GHC.Data.SizedSeq
GHC.Data.SmallArray
=====================================
libraries/ghci/GHCi/Coverage.hs
=====================================
@@ -9,9 +9,9 @@ import Prelude -- See note [Why do we import Prelude here?]
import Control.Exception
import Data.ByteString.Short (ShortByteString)
-import qualified Data.ByteString.Short as SBS
import Data.Word
import Foreign
+import GHC.Data.ShortByteString
import GHC.Foreign (CString)
import GHC.Utils.Encoding.UTF8 (utf8DecodeShortByteString)
import GHCi.ObjLink (lookupSymbol)
@@ -31,17 +31,19 @@ hpcAddModule ::
-- ^ Name of the ticks array found in the c-stub.
IO ()
hpcAddModule modlName ticks hash tickboxes = do
- SBS.useAsCString modlName $ \modlNameLiteral -> do
- -- we need to find the reference to the ticks array.
- lookupSymbol tickboxes >>= \ case
- Nothing -> do
- -- the symbol is not found, this is a bug!
- throwIO $ ErrorCall $ "hpcAddModule: failed to find symbol " <> utf8DecodeShortByteString tickboxes
- Just tickBoxRef -> do
- -- Calling 'hs_hpc_module' multiple times is safe, it will add the module only once.
- hpc_register_module modlNameLiteral (fromIntegral ticks) (fromIntegral hash) (castPtr tickBoxRef)
- -- calling 'hpc_startup' multiple times is safe, it will only be initialised once.
- hpc_startup
+ -- we need to find the reference to the ticks array.
+ lookupSymbol tickboxes >>= \ case
+ Nothing -> do
+ -- the symbol is not found, this is a bug!
+ throwIO $ ErrorCall $ "hpcAddModule: failed to find symbol " <> utf8DecodeShortByteString tickboxes
+ Just tickBoxRef -> do
+ -- hs_hpc_module stores the module name pointer in the RTS hash table
+ -- until exitHpc, so pass a malloced C string.
+ modlNameLiteral <- newCStringFromSBS modlName
+ -- Calling 'hs_hpc_module' multiple times is safe, it will add the module only once.
+ hpc_register_module modlNameLiteral (fromIntegral ticks) (fromIntegral hash) (castPtr tickBoxRef)
+ -- calling 'hpc_startup' multiple times is safe, it will only be initialised once.
+ hpc_startup
foreign import ccall unsafe "hs_hpc_module"
hpc_register_module :: CString -> Word32 -> Word32 -> Ptr Word64 -> IO ()
=====================================
libraries/ghci/GHCi/Run.hs
=====================================
@@ -37,6 +37,9 @@ import Control.Monad
import Data.ByteString (ByteString)
import qualified Data.ByteString.Short.Internal as BS
import qualified Data.ByteString.Unsafe as B
+#if defined(PROFILING)
+import GHC.Data.ShortByteString
+#endif
import GHC.Exts
import qualified GHC.Exts.Heap as Heap
import GHC.Stack
@@ -447,13 +450,6 @@ mkCostCentres mod ccs = do
c_srcspan <- newCStringFromSBS srcspan
toRemotePtr <$> c_mkCostCentre c_name c_module c_srcspan
- newCStringFromSBS sbs = do
- let len = BS.length sbs
- buf <- mallocBytes $ len + 1
- BS.copyToPtr sbs 0 buf (fromIntegral len)
- pokeByteOff buf len (0 :: Word8)
- pure buf
-
foreign import ccall unsafe "mkCostCentre"
c_mkCostCentre :: Ptr CChar -> Ptr CChar -> Ptr CChar -> IO (Ptr CostCentre)
#else
=====================================
testsuite/tests/perf/compiler/all.T
=====================================
@@ -678,7 +678,7 @@ test ('T18140',
['-v0 -O'])
test('T10421',
[ only_ways(['normal']),
- collect_compiler_runtime(1)
+ collect_compiler_runtime(2) # 1% tolerance was too small (#27289)
],
multimod_compile,
['T10421', '-v0 -O'])
=====================================
testsuite/tests/simplCore/should_compile/T27261.hs
=====================================
@@ -0,0 +1,17 @@
+{-# OPTIONS_GHC -fno-full-laziness #-}
+
+module T27261 (foo) where
+
+import T27261_aux (myError)
+
+foo :: [String] -> (() -> Int) -> Int
+foo cs =
+ \ k -> ( case bar of
+ Just str -> let cs2 = case cs of { [] -> cs; _ -> "stack entry" : cs }
+ in myError cs2 str
+ Nothing -> \ c -> c () )
+ ( \ _ -> k () )
+
+bar :: Maybe String
+bar = Nothing
+{-# NOINLINE bar #-}
=====================================
testsuite/tests/simplCore/should_compile/T27261_aux.hs
=====================================
@@ -0,0 +1,7 @@
+{-# LANGUAGE BangPatterns #-}
+
+module T27261_aux (myError) where
+
+myError :: [String] -> String -> a
+myError !_ _ = undefined
+{-# NOINLINE myError #-}
=====================================
testsuite/tests/simplCore/should_compile/all.T
=====================================
@@ -601,3 +601,4 @@ test('T25718a', normal, compile, ['-O -ddump-simpl -dsuppress-uniques -dsuppress
test('T25718b', normal, compile, ['-O -ddump-simpl -dsuppress-uniques -dsuppress-all -dno-typeable-binds'])
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'])
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/6b42a8f2ac8a68ca0f9dae5082957e…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/6b42a8f2ac8a68ca0f9dae5082957e…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc][wip/fendor/drop-preloadclosure-from-unitstate] Drop `preloadClosure` from `UnitState`
by Hannes Siebenhandl (@fendor) 28 May '26
by Hannes Siebenhandl (@fendor) 28 May '26
28 May '26
Hannes Siebenhandl pushed to branch wip/fendor/drop-preloadclosure-from-unitstate at Glasgow Haskell Compiler / GHC
Commits:
d07b7c61 by fendor at 2026-05-28T10:32:23+02:00
Drop `preloadClosure` from `UnitState`
It is always hard-coded to the same value.
Backpack Unit instantiation isn't using it any more.
Allows us to simplify the API and get rid of `improveUnit`.
- - - - -
7 changed files:
- + changelog.d/T27308
- compiler/GHC/Driver/Backpack.hs
- compiler/GHC/Iface/Load.hs
- compiler/GHC/Iface/Recomp.hs
- compiler/GHC/Unit.hs
- compiler/GHC/Unit/State.hs
- compiler/GHC/Unit/Types.hs
Changes:
=====================================
changelog.d/T27308
=====================================
@@ -0,0 +1,10 @@
+section: compiler
+synopsis: Drop `preloadClosure` from `UnitState`
+issues: #27308
+mrs: !16108
+
+description: {
+ Drop `preloadClosure` from `UnitState` as it is always set to the empty set.
+ This allows to simplify the `UnitState` and related functions.
+}
+
=====================================
compiler/GHC/Driver/Backpack.hs
=====================================
@@ -241,7 +241,6 @@ withBkpSession cid insts deps session_type do_this = do
-- Synthesize the flags
, packageFlags = packageFlags dflags ++ map (\(uid0, rn) ->
let uid = unwireUnit unit_state
- $ improveUnit unit_state
$ renameHoleUnit unit_state (listToUFM insts) uid0
in ExposePackage
(showSDoc dflags
@@ -310,19 +309,16 @@ buildUnit session cid insts lunit = do
-- The compilation dependencies are just the appropriately filled
-- in unit IDs which must be compiled before we can compile.
let hsubst = listToUFM insts
- deps0 = map (renameHoleUnit (hsc_units hsc_env) hsubst) raw_deps
+ deps = map (renameHoleUnit (hsc_units hsc_env) hsubst) raw_deps
-- Build dependencies OR make sure they make sense. BUT NOTE,
-- we can only check the ones that are fully filled; the rest
-- we have to defer until we've typechecked our local signature.
-- TODO: work this into GHC.Driver.Make!!
- forM_ (zip [1..] deps0) $ \(i, dep) ->
+ forM_ (zip [1..] deps) $ \(i, dep) ->
case session of
TcSession -> return ()
- _ -> compileInclude (length deps0) (i, dep)
-
- -- IMPROVE IT
- let deps = map (improveUnit (hsc_units hsc_env)) deps0
+ _ -> compileInclude (length deps) (i, dep)
mb_old_eps <- case session of
TcSession -> fmap Just getEpsGhc
=====================================
compiler/GHC/Iface/Load.hs
=====================================
@@ -914,13 +914,13 @@ findAndReadIface hsc_env doc_str mod wanted_mod hi_boot_file = do
&& not (isOneShot (ghcMode dflags))
then return (Failed (HomeModError mod loc))
else do
- r <- read_file hooks logger name_cache unit_state dflags wanted_mod (ml_hi_file loc)
+ r <- read_file hooks logger name_cache dflags wanted_mod (ml_hi_file loc)
case r of
Failed err
-> return (Failed $ BadIfaceFile err)
Succeeded (iface,_fp)
-> do
- r2 <- load_dynamic_too_maybe hooks logger name_cache unit_state
+ r2 <- load_dynamic_too_maybe hooks logger name_cache
(setDynamicNow dflags) wanted_mod
iface loc
case r2 of
@@ -936,20 +936,20 @@ findAndReadIface hsc_env doc_str mod wanted_mod hi_boot_file = do
err
-- | Check if we need to try the dynamic interface for -dynamic-too
-load_dynamic_too_maybe :: Hooks -> Logger -> NameCache -> UnitState -> DynFlags
+load_dynamic_too_maybe :: Hooks -> Logger -> NameCache -> DynFlags
-> Module -> ModIface -> ModLocation
-> IO (MaybeErr MissingInterfaceError ())
-load_dynamic_too_maybe hooks logger name_cache unit_state dflags wanted_mod iface loc
+load_dynamic_too_maybe hooks logger name_cache dflags wanted_mod iface loc
-- Indefinite interfaces are ALWAYS non-dynamic.
| not (moduleIsDefinite (mi_module iface)) = return (Succeeded ())
- | gopt Opt_BuildDynamicToo dflags = load_dynamic_too hooks logger name_cache unit_state dflags wanted_mod iface loc
+ | gopt Opt_BuildDynamicToo dflags = load_dynamic_too hooks logger name_cache dflags wanted_mod iface loc
| otherwise = return (Succeeded ())
-load_dynamic_too :: Hooks -> Logger -> NameCache -> UnitState -> DynFlags
+load_dynamic_too :: Hooks -> Logger -> NameCache -> DynFlags
-> Module -> ModIface -> ModLocation
-> IO (MaybeErr MissingInterfaceError ())
-load_dynamic_too hooks logger name_cache unit_state dflags wanted_mod iface loc = do
- read_file hooks logger name_cache unit_state dflags wanted_mod (ml_dyn_hi_file loc) >>= \case
+load_dynamic_too hooks logger name_cache dflags wanted_mod iface loc = do
+ read_file hooks logger name_cache dflags wanted_mod (ml_dyn_hi_file loc) >>= \case
Succeeded (dynIface, _)
| mi_mod_hash iface == mi_mod_hash dynIface
-> return (Succeeded ())
@@ -963,10 +963,10 @@ load_dynamic_too hooks logger name_cache unit_state dflags wanted_mod iface loc
-read_file :: Hooks -> Logger -> NameCache -> UnitState -> DynFlags
+read_file :: Hooks -> Logger -> NameCache -> DynFlags
-> Module -> FilePath
-> IO (MaybeErr ReadInterfaceError (ModIface, FilePath))
-read_file hooks logger name_cache unit_state dflags wanted_mod file_path = do
+read_file hooks logger name_cache dflags wanted_mod file_path = do
-- Figure out what is recorded in mi_module. If this is
-- a fully definite interface, it'll match exactly, but
@@ -975,7 +975,7 @@ read_file hooks logger name_cache unit_state dflags wanted_mod file_path = do
case getModuleInstantiation wanted_mod of
(_, Nothing) -> wanted_mod
(_, Just indef_mod) ->
- instModuleToModule unit_state
+ instModuleToModule
(uninstantiateInstantiatedModule indef_mod)
read_result <- readIface hooks logger dflags name_cache wanted_mod' file_path
case read_result of
=====================================
compiler/GHC/Iface/Recomp.hs
=====================================
@@ -620,7 +620,7 @@ checkMergedSignatures hsc_env mod_summary self_recomp = do
new_merged = case lookupUniqMap (requirementContext unit_state)
(ms_mod_name mod_summary) of
Nothing -> []
- Just r -> sort $ map (instModuleToModule unit_state) r
+ Just r -> sort $ map instModuleToModule r
if old_merged == new_merged
then up_to_date logger (text "signatures to merge in unchanged" $$ ppr new_merged)
else return $ needsRecompileBecause SigsMergeChanged
=====================================
compiler/GHC/Unit.hs
=====================================
@@ -314,32 +314,6 @@ field in the SDocContext to pretty-print.
(i.e. GHC doesn't correctly call `pprWithUnitState` before pretty-printing a
UnitId), that's what will be shown to the user so it's no big deal.
-
-Note [VirtUnit to RealUnit improvement]
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Over the course of instantiating VirtUnits on the fly while typechecking an
-indefinite library, we may end up with a fully instantiated VirtUnit. I.e.
-one that could be compiled and installed in the database. During
-type-checking we generate a virtual UnitId for it, say "abc".
-
-Now the question is: do we have a matching installed unit in the database?
-Suppose we have one with UnitId "xyz" (provided by Cabal so we don't know how
-to generate it). The trouble is that if both units end up being used in the
-same type-checking session, their names won't match (e.g. "abc:M.X" vs
-"xyz:M.X").
-
-As we want them to match we just replace the virtual unit with the installed
-one: for some reason this is called "improvement".
-
-There is one last niggle: improvement based on the unit database means
-that we might end up developing on a unit that is not transitively
-depended upon by the units the user specified directly via command line
-flags. This could lead to strange and difficult to understand bugs if those
-instantiations are out of date. The solution is to only improve a
-unit id if the new unit id is part of the 'preloadClosure'; i.e., the
-closure of all the units which were explicitly specified.
-
Note [Representation of module/name variables]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In our ICFP'16, we use <A> to represent module holes, and {A.T} to represent
=====================================
compiler/GHC/Unit/State.hs
=====================================
@@ -7,7 +7,6 @@ module GHC.Unit.State (
-- * Reading the package config, and processing cmdline args
UnitState(..),
- PreloadUnitClosure,
UnitDatabase (..),
UnitErr (..),
emptyUnitState,
@@ -29,7 +28,6 @@ module GHC.Unit.State (
lookupPackageName,
resolvePackageImport,
- improveUnit,
searchPackageId,
listVisibleModuleNames,
lookupModuleInAllUnits,
@@ -89,7 +87,6 @@ import GHC.Unit.Home
import GHC.Types.Unique.FM
import GHC.Types.Unique.DFM
-import GHC.Types.Unique.Set
import GHC.Types.Unique.DSet
import GHC.Types.Unique.Map
import GHC.Types.Unique
@@ -267,8 +264,6 @@ originEmpty :: ModuleOrigin -> Bool
originEmpty (ModOrigin Nothing [] [] False) = True
originEmpty _ = False
-type PreloadUnitClosure = UniqSet UnitId
-
-- | 'UniqFM' map from 'Unit' to a 'UnitVisibility'.
type VisibilityMap = UniqMap Unit UnitVisibility
@@ -431,13 +426,6 @@ data UnitState = UnitState {
-- may have the 'exposed' flag be 'False'.)
unitInfoMap :: UnitInfoMap,
- -- | The set of transitively reachable units according
- -- to the explicitly provided command line arguments.
- -- A fully instantiated VirtUnit may only be replaced by a RealUnit from
- -- this set.
- -- See Note [VirtUnit to RealUnit improvement]
- preloadClosure :: PreloadUnitClosure,
-
-- | A mapping of 'PackageName' to 'UnitId'. If several units have the same
-- package name (e.g. different instantiations), then we return one of them...
-- This is used when users refer to packages in Backpack includes.
@@ -490,7 +478,6 @@ data UnitState = UnitState {
emptyUnitState :: UnitState
emptyUnitState = UnitState {
unitInfoMap = emptyUniqMap,
- preloadClosure = emptyUniqSet,
packageNameMap = emptyUFM,
wireMap = emptyUniqMap,
unwireMap = emptyUniqMap,
@@ -516,7 +503,7 @@ type UnitInfoMap = UniqMap UnitId UnitInfo
-- | Find the unit we know about with the given unit, if any
lookupUnit :: UnitState -> Unit -> Maybe UnitInfo
-lookupUnit pkgs = lookupUnit' (allowVirtualUnits pkgs) (unitInfoMap pkgs) (preloadClosure pkgs)
+lookupUnit pkgs = lookupUnit' (allowVirtualUnits pkgs) (unitInfoMap pkgs)
-- | A more specialized interface, which doesn't require a 'UnitState' (so it
-- can be used while we're initializing 'DynFlags')
@@ -524,16 +511,15 @@ lookupUnit pkgs = lookupUnit' (allowVirtualUnits pkgs) (unitInfoMap pkgs) (prelo
-- Parameters:
-- * a boolean specifying whether or not to look for on-the-fly renamed interfaces
-- * a 'UnitInfoMap'
--- * a 'PreloadUnitClosure'
-lookupUnit' :: Bool -> UnitInfoMap -> PreloadUnitClosure -> Unit -> Maybe UnitInfo
-lookupUnit' allowOnTheFlyInst pkg_map closure u = case u of
+lookupUnit' :: Bool -> UnitInfoMap -> Unit -> Maybe UnitInfo
+lookupUnit' allowOnTheFlyInst pkg_map u = case u of
HoleUnit -> error "Hole unit"
RealUnit i -> lookupUniqMap pkg_map (unDefinite i)
VirtUnit i
| allowOnTheFlyInst
-> -- lookup UnitInfo of the indefinite unit to be instantiated and
-- instantiate it on-the-fly
- fmap (renameUnitInfo pkg_map closure (instUnitInsts i))
+ fmap (renameUnitInfo pkg_map (instUnitInsts i))
(lookupUniqMap pkg_map (instUnitInstanceOf i))
| otherwise
@@ -907,7 +893,6 @@ applyTrustFlag prec_map unusable pkgs flag =
applyPackageFlag
:: UnitPrecedenceMap
-> UnitInfoMap
- -> PreloadUnitClosure
-> UnusableUnits
-> Bool -- if False, if you expose a package, it implicitly hides
-- any previously exposed packages with the same name
@@ -916,10 +901,10 @@ applyPackageFlag
-> PackageFlag -- flag to apply
-> MaybeErr UnitErr VisibilityMap -- Now exposed
-applyPackageFlag prec_map pkg_map closure unusable no_hide_others pkgs vm flag =
+applyPackageFlag prec_map pkg_map unusable no_hide_others pkgs vm flag =
case flag of
ExposePackage _ arg (ModRenaming b rns) ->
- case findPackages prec_map pkg_map closure arg pkgs unusable of
+ case findPackages prec_map pkg_map arg pkgs unusable of
Left ps -> Failed (PackageFlagErr flag ps)
Right (p:_) -> Succeeded vm'
where
@@ -983,7 +968,7 @@ applyPackageFlag prec_map pkg_map closure unusable no_hide_others pkgs vm flag =
_ -> panic "applyPackageFlag"
HidePackage str ->
- case findPackages prec_map pkg_map closure (PackageArg str) pkgs unusable of
+ case findPackages prec_map pkg_map (PackageArg str) pkgs unusable of
Left ps -> Failed (PackageFlagErr flag ps)
Right ps -> Succeeded $ foldl' delFromUniqMap vm (map mkUnit ps)
@@ -992,12 +977,11 @@ applyPackageFlag prec_map pkg_map closure unusable no_hide_others pkgs vm flag =
-- if the 'UnitArg' has a renaming associated with it.
findPackages :: UnitPrecedenceMap
-> UnitInfoMap
- -> PreloadUnitClosure
-> PackageArg -> [UnitInfo]
-> UnusableUnits
-> Either [(UnitInfo, UnusableUnitReason)]
[UnitInfo]
-findPackages prec_map pkg_map closure arg pkgs unusable
+findPackages prec_map pkg_map arg pkgs unusable
= let ps = mapMaybe (finder arg) pkgs
in if null ps
then Left (mapMaybe (\(x,y) -> finder arg x >>= \x' -> return (x',y))
@@ -1015,7 +999,7 @@ findPackages prec_map pkg_map closure arg pkgs unusable
-> Just p
VirtUnit inst
| instUnitInstanceOf inst == unitId p
- -> Just (renameUnitInfo pkg_map closure (instUnitInsts inst) p)
+ -> Just (renameUnitInfo pkg_map (instUnitInsts inst) p)
_ -> Nothing
selectPackages :: UnitPrecedenceMap -> PackageArg -> [UnitInfo]
@@ -1030,10 +1014,10 @@ selectPackages prec_map arg pkgs unusable
else Right (sortByPreference prec_map ps, rest)
-- | Rename a 'UnitInfo' according to some module instantiation.
-renameUnitInfo :: UnitInfoMap -> PreloadUnitClosure -> [(ModuleName, Module)] -> UnitInfo -> UnitInfo
-renameUnitInfo pkg_map closure insts conf =
+renameUnitInfo :: UnitInfoMap -> [(ModuleName, Module)] -> UnitInfo -> UnitInfo
+renameUnitInfo pkg_map insts conf =
let hsubst = listToUFM insts
- smod = renameHoleModule' pkg_map closure hsubst
+ smod = renameHoleModule' pkg_map hsubst
new_insts = map (\(k,v) -> (k,smod v)) (unitInstantiations conf)
in conf {
unitInstantiations = new_insts,
@@ -1631,7 +1615,7 @@ mkUnitState logger cfg = do
-- user tries to enable an unusable package, we should let them know.
--
vis_map2 <- mayThrowUnitErr
- $ foldM (applyPackageFlag prec_map prelim_pkg_db emptyUniqSet unusable
+ $ foldM (applyPackageFlag prec_map prelim_pkg_db unusable
(unitConfigHideAll cfg) pkgs1)
vis_map1 other_flags
@@ -1660,7 +1644,7 @@ mkUnitState logger cfg = do
| otherwise = vis_map2
plugin_vis_map2
<- mayThrowUnitErr
- $ foldM (applyPackageFlag prec_map prelim_pkg_db emptyUniqSet unusable
+ $ foldM (applyPackageFlag prec_map prelim_pkg_db unusable
hide_plugin_pkgs pkgs1)
plugin_vis_map1
(reverse (unitConfigFlagsPlugins cfg))
@@ -1712,7 +1696,7 @@ mkUnitState logger cfg = do
$ closeUnitDeps pkg_db
$ zip (map toUnitId preload3) (repeat Nothing)
- let mod_map1 = mkModuleNameProvidersMap logger cfg pkg_db emptyUniqSet vis_map
+ let mod_map1 = mkModuleNameProvidersMap logger cfg pkg_db vis_map
mod_map2 = mkUnusableModuleNameProvidersMap unusable
mod_map = mod_map2 `plusUniqMap` mod_map1
@@ -1722,9 +1706,8 @@ mkUnitState logger cfg = do
, explicitUnits = explicit_pkgs
, homeUnitDepends = home_unit_deps
, unitInfoMap = pkg_db
- , preloadClosure = emptyUniqSet
, moduleNameProvidersMap = mod_map
- , pluginModuleNameProvidersMap = mkModuleNameProvidersMap logger cfg pkg_db emptyUniqSet plugin_vis_map
+ , pluginModuleNameProvidersMap = mkModuleNameProvidersMap logger cfg pkg_db plugin_vis_map
, packageNameMap = pkgname_map
, wireMap = wired_map
, unwireMap = listToUniqMap [ (v,k) | (k,v) <- nonDetUniqMapToList wired_map ]
@@ -1764,10 +1747,9 @@ mkModuleNameProvidersMap
:: Logger
-> UnitConfig
-> UnitInfoMap
- -> PreloadUnitClosure
-> VisibilityMap
-> ModuleNameProvidersMap
-mkModuleNameProvidersMap logger cfg pkg_map closure vis_map =
+mkModuleNameProvidersMap logger cfg pkg_map vis_map =
-- What should we fold on? Both situations are awkward:
--
-- * Folding on the visibility map means that we won't create
@@ -1839,7 +1821,7 @@ mkModuleNameProvidersMap logger cfg pkg_map closure vis_map =
hiddens = [(m, mkModMap pk m ModHidden) | m <- hidden_mods]
pk = mkUnit pkg
- unit_lookup uid = lookupUnit' (unitConfigAllowVirtual cfg) pkg_map closure uid
+ unit_lookup uid = lookupUnit' (unitConfigAllowVirtual cfg) pkg_map uid
`orElse` pprPanic "unit_lookup" (ppr uid)
exposed_mods = unitExposedModules pkg
@@ -2190,44 +2172,16 @@ fsPackageName info = fs
where
PackageName fs = unitPackageName info
-
--- | Given a fully instantiated 'InstantiatedUnit', improve it into a
--- 'RealUnit' if we can find it in the package database.
-improveUnit :: UnitState -> Unit -> Unit
-improveUnit state u = improveUnit' (unitInfoMap state) (preloadClosure state) u
-
--- | Given a fully instantiated 'InstantiatedUnit', improve it into a
--- 'RealUnit' if we can find it in the package database.
-improveUnit' :: UnitInfoMap -> PreloadUnitClosure -> Unit -> Unit
-improveUnit' _ _ uid@(RealUnit _) = uid -- short circuit
-improveUnit' pkg_map closure uid =
- -- Do NOT lookup indefinite ones, they won't be useful!
- case lookupUnit' False pkg_map closure uid of
- Nothing -> uid
- Just pkg ->
- -- Do NOT improve if the indefinite unit id is not
- -- part of the closure unique set. See
- -- Note [VirtUnit to RealUnit improvement]
- if unitId pkg `elementOfUniqSet` closure
- then mkUnit pkg
- else uid
-
--- | Check the database to see if we already have an installed unit that
--- corresponds to the given 'InstantiatedUnit'.
---
--- Return a `UnitId` which either wraps the `InstantiatedUnit` unchanged or
--- references a matching installed unit.
---
--- See Note [VirtUnit to RealUnit improvement]
-instUnitToUnit :: UnitState -> InstantiatedUnit -> Unit
-instUnitToUnit state iuid =
+-- | Return a `UnitId` which either wraps the `InstantiatedUnit` unchanged.
+instUnitToUnit :: InstantiatedUnit -> Unit
+instUnitToUnit iuid =
-- NB: suppose that we want to compare the instantiated
-- unit p[H=impl:H] against p+abcd (where p+abcd
-- happens to be the existing, installed version of
-- p[H=impl:H]. If we *only* wrap in p[H=impl:H]
-- VirtUnit, they won't compare equal; only
-- after improvement will the equality hold.
- improveUnit state $ VirtUnit iuid
+ VirtUnit iuid
-- | Substitution on module variables, mapping module names to module
@@ -2239,30 +2193,30 @@ type ShHoleSubst = ModuleNameEnv Module
-- @p[A=\<A>]:B@ maps to @p[A=q():A]:B@ with @A=q():A@;
-- similarly, @\<A>@ maps to @q():A@.
renameHoleModule :: UnitState -> ShHoleSubst -> Module -> Module
-renameHoleModule state = renameHoleModule' (unitInfoMap state) (preloadClosure state)
+renameHoleModule state = renameHoleModule' (unitInfoMap state)
-- | Substitutes holes in a 'Unit', suitable for renaming when
-- an include occurs; see Note [Representation of module/name variables].
--
-- @p[A=\<A>]@ maps to @p[A=\<B>]@ with @A=\<B>@.
renameHoleUnit :: UnitState -> ShHoleSubst -> Unit -> Unit
-renameHoleUnit state = renameHoleUnit' (unitInfoMap state) (preloadClosure state)
+renameHoleUnit state = renameHoleUnit' (unitInfoMap state)
--- | Like 'renameHoleModule', but requires only 'ClosureUnitInfoMap'
+-- | Like 'renameHoleModule', but requires only 'UnitInfoMap'
-- so it can be used by "GHC.Unit.State".
-renameHoleModule' :: UnitInfoMap -> PreloadUnitClosure -> ShHoleSubst -> Module -> Module
-renameHoleModule' pkg_map closure env m
+renameHoleModule' :: UnitInfoMap -> ShHoleSubst -> Module -> Module
+renameHoleModule' pkg_map env m
| not (isHoleModule m) =
- let uid = renameHoleUnit' pkg_map closure env (moduleUnit m)
+ let uid = renameHoleUnit' pkg_map env (moduleUnit m)
in mkModule uid (moduleName m)
| Just m' <- lookupUFM env (moduleName m) = m'
-- NB m = <Blah>, that's what's in scope.
| otherwise = m
--- | Like 'renameHoleUnit, but requires only 'ClosureUnitInfoMap'
+-- | Like 'renameHoleUnit', but requires only 'UnitInfoMap'
-- so it can be used by "GHC.Unit.State".
-renameHoleUnit' :: UnitInfoMap -> PreloadUnitClosure -> ShHoleSubst -> Unit -> Unit
-renameHoleUnit' pkg_map closure env uid =
+renameHoleUnit' :: UnitInfoMap -> ShHoleSubst -> Unit -> Unit
+renameHoleUnit' pkg_map env uid =
case uid of
(VirtUnit
InstantiatedUnit{ instUnitInstanceOf = cid
@@ -2270,20 +2224,15 @@ renameHoleUnit' pkg_map closure env uid =
, instUnitHoles = fh })
-> if isNullUFM (intersectUFM_C const (udfmToUfm (getUniqDSet fh)) env)
then uid
- -- Functorially apply the substitution to the instantiation,
- -- then check the 'ClosureUnitInfoMap' to see if there is
- -- a compiled version of this 'InstantiatedUnit' we can improve to.
- -- See Note [VirtUnit to RealUnit improvement]
- else improveUnit' pkg_map closure $
- mkVirtUnit cid
- (map (\(k,v) -> (k, renameHoleModule' pkg_map closure env v)) insts)
+ else mkVirtUnit cid
+ (map (\(k,v) -> (k, renameHoleModule' pkg_map env v)) insts)
_ -> uid
-- | Injects an 'InstantiatedModule' to 'Module' (see also
-- 'instUnitToUnit'.
-instModuleToModule :: UnitState -> InstantiatedModule -> Module
-instModuleToModule pkgstate (Module iuid mod_name) =
- mkModule (instUnitToUnit pkgstate iuid) mod_name
+instModuleToModule :: InstantiatedModule -> Module
+instModuleToModule (Module iuid mod_name) =
+ mkModule (instUnitToUnit iuid) mod_name
-- | Print unit-ids with UnitInfo found in the given UnitState
pprWithUnitState :: UnitState -> SDoc -> SDoc
=====================================
compiler/GHC/Unit/Types.hs
=====================================
@@ -250,9 +250,7 @@ data GenUnit uid
--
-- This unit may be indefinite or not (i.e. with remaining holes or not). If it
-- is definite, we don't know if it has already been compiled and installed in a
--- database. Nevertheless, we have a mechanism called "improvement" to try to
--- match a fully instantiated unit with existing compiled and installed units:
--- see Note [VirtUnit to RealUnit improvement].
+-- database.
--
-- An indefinite unit identifier pretty-prints to something like
-- @p[H=<H>,A=aimpl:A>]@ (@p@ is the 'UnitId', and the
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/d07b7c61362f2c19a2f6475496b645e…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/d07b7c61362f2c19a2f6475496b645e…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
[Git][ghc/ghc][wip/supersven/libDir-setting] 2 commits: Add optional config setting for LibDir (#19174)
by Sven Tennie (@supersven) 28 May '26
by Sven Tennie (@supersven) 28 May '26
28 May '26
Sven Tennie pushed to branch wip/supersven/libDir-setting at Glasgow Haskell Compiler / GHC
Commits:
8548be69 by Sven Tennie at 2026-05-28T10:10:47+02:00
Add optional config setting for LibDir (#19174)
Previously, the `libDir` was derived from `topDir`. This won't work for
inplace stage2 cross-compilers where binaries and libraries are in
different stage dirs (`_build/stage1/` for executables and
`_build/stage2` for libraries).
`LibDir` is set in the inplace `settings` files. For bindists, we
generate a new `settings` file with no `LibDir` entry. GHC then defaults
to use `topDir` as `libDir` again. This keeps the bindist relocatable.
If `LibDir` is a relative path, it is interpreted relatively to
`topDir`.
The global package db is part of the `lib/` folder. If we want to point
for inplace cross-compilers to the succeeding stage's folder, this is
done by setting `LibDir`. Thus, the global package db must be found
relative to `libDir`` (which may default to `topDir` or be set by
`LibDir`).
The complexity of settings becomes scary. So, add a test to ensure
`LibDir` works as expected.
- - - - -
197c5135 by Sven Tennie at 2026-05-28T10:18:25+02:00
Add Haddock to FileSettings
Helping to understand the fields' meanings without deeper analyses.
- - - - -
12 changed files:
- + changelog.d/libdir-setting
- compiler/GHC/Driver/Config/Interpreter.hs
- compiler/GHC/Driver/DynFlags.hs
- compiler/GHC/Driver/Session.hs
- compiler/GHC/Settings.hs
- compiler/GHC/Settings/IO.hs
- hadrian/src/Rules/BinaryDist.hs
- hadrian/src/Rules/Generate.hs
- libraries/ghc-boot/GHC/Settings/Utils.hs
- + testsuite/tests/ghc-api/settings/LibDir.hs
- + testsuite/tests/ghc-api/settings/LibDir.stdout
- + testsuite/tests/ghc-api/settings/all.T
Changes:
=====================================
changelog.d/libdir-setting
=====================================
@@ -0,0 +1,15 @@
+section: packaging
+synopsis: Added a new optional configuration setting for `LibDir` to support inplace
+ stage2 cross-compilers where binaries and libraries are in different stage
+ directories.
+issues: #19174
+mrs: !15716
+
+description: {
+ Previously, the `libDir` was always derived from `topDir`, which won't work
+ for inplace stage2 cross-compilers where executables are in `_build/stage1/`
+ and libraries are in `_build/stage2/`. Now, `LibDir` can be set, but is by
+ default derived from `topDir`. This facilitates the mentioned behaviour
+ while keeping the binary distribution code relocatable. This is a refactoring
+ step that does not change actual behaviour.
+}
=====================================
compiler/GHC/Driver/Config/Interpreter.hs
=====================================
@@ -17,8 +17,8 @@ import System.Directory
initInterpOpts :: DynFlags -> IO InterpOpts
initInterpOpts dflags = do
- wasm_dyld <- makeAbsolute $ topDir dflags </> "dyld.mjs"
- js_interp <- makeAbsolute $ topDir dflags </> "ghc-interp.js"
+ wasm_dyld <- makeAbsolute $ libDir dflags </> "dyld.mjs"
+ js_interp <- makeAbsolute $ libDir dflags </> "ghc-interp.js"
pure $ InterpOpts
{ interpExternal = gopt Opt_ExternalInterpreter dflags
, interpProg = pgm_i dflags
=====================================
compiler/GHC/Driver/DynFlags.hs
=====================================
@@ -61,7 +61,7 @@ module GHC.Driver.DynFlags (
-- ** System tool settings and locations
programName, projectVersion,
- ghcUsagePath, ghciUsagePath, topDir, toolDir,
+ ghcUsagePath, ghciUsagePath, topDir, libDir, toolDir,
versionedAppDir, versionedFilePath,
extraGccViaCFlags, globalPackageDatabasePath,
@@ -1516,6 +1516,8 @@ ghciUsagePath :: DynFlags -> FilePath
ghciUsagePath dflags = fileSettings_ghciUsagePath $ fileSettings dflags
topDir :: DynFlags -> FilePath
topDir dflags = fileSettings_topDir $ fileSettings dflags
+libDir :: DynFlags -> FilePath
+libDir dflags = fileSettings_libDir $ fileSettings dflags
toolDir :: DynFlags -> Maybe FilePath
toolDir dflags = fileSettings_toolDir $ fileSettings dflags
extraGccViaCFlags :: DynFlags -> [String]
=====================================
compiler/GHC/Driver/Session.hs
=====================================
@@ -3551,9 +3551,9 @@ compilerInfo dflags
("Project name", cProjectName)
-- Next come the settings, so anything else can be overridden
-- in the settings file (as "lookup" uses the first match for the
- -- key)
+ -- key). We filter out LibDir from rawSettings to avoid duplication.
: map (fmap expandDirectories)
- (rawSettings dflags)
+ (filter ((/= "LibDir") . fst) (rawSettings dflags))
++
[("C compiler command", queryCmd $ ccProgram . tgtCCompiler),
("C compiler flags", queryFlags $ ccProgram . tgtCCompiler),
@@ -3656,7 +3656,7 @@ compilerInfo dflags
-- Whether or not GHC was compiled using -prof
("GHC Profiled", showBool hostIsProfiled),
("Debug on", showBool debugIsOn),
- ("LibDir", topDir dflags),
+ ("LibDir", libDir dflags),
-- This is always an absolute path, unlike "Relative Global Package DB" which is
-- in the settings file.
("Global Package DB", globalPackageDatabasePath dflags)
=====================================
compiler/GHC/Settings.hs
=====================================
@@ -179,11 +179,24 @@ data ToolSettings = ToolSettings
-- | Paths to various files and directories used by GHC, including those that
-- provide more settings.
data FileSettings = FileSettings
- { fileSettings_ghcUsagePath :: FilePath -- ditto
- , fileSettings_ghciUsagePath :: FilePath -- ditto
- , fileSettings_toolDir :: Maybe FilePath -- ditto
- , fileSettings_topDir :: FilePath -- ditto
+ { fileSettings_ghcUsagePath :: FilePath
+ -- ^ Path to @ghc-usage.txt@, displayed by @ghc --help@
+ , fileSettings_ghciUsagePath :: FilePath
+ -- ^ Path to @ghci-usage.txt@, displayed by @ghci --help@
+ , fileSettings_toolDir :: Maybe FilePath
+ -- ^ Directory containing the mingw toolchain (Windows only);
+ -- see Note [tooldir: How GHC finds mingw on Windows] in `GHC.SysTools.BaseDir`
+ , fileSettings_topDir :: FilePath
+ -- ^ GHC's top directory: the root from which GHC locates its support files
+ -- (e.g. settings).
+ -- See Note [topdir: How GHC finds its files] in `GHC.SysTools.BaseDir`
, fileSettings_globalPackageDatabase :: FilePath
+ -- ^ Path to the global package database, relative to `libDir`
+ , fileSettings_libDir :: FilePath
+ -- ^ Directory containing GHC's library packages and the global package
+ -- database. Defaults to 'fileSettings_topDir' but can differ in inplace
+ -- builds used for cross-compilation testing (the "stage2 cross-compiler"
+ -- scenario).
}
=====================================
compiler/GHC/Settings/IO.hs
=====================================
@@ -28,6 +28,7 @@ import GHC.Toolchain.Program
import GHC.Toolchain
import GHC.Data.Maybe
import Data.Bifunctor (Bifunctor(second))
+import Data.Either (fromRight)
data SettingsError
= SettingsError_MissingData String
@@ -117,12 +118,6 @@ initSettings top_dir = do
then ["-fwrapv", "-fno-builtin"]
else []
- -- The package database is either a relative path to the location of the settings file
- -- OR an absolute path.
- -- In case the path is absolute then top_dir </> abs_path == abs_path
- -- the path is relative then top_dir </> rel_path == top_dir </> rel_path
- globalpkgdb_path <- installed <$> getSetting "Relative Global Package DB"
-
let ghc_usage_msg_path = installed "ghc-usage.txt"
ghci_usage_msg_path = installed "ghci-usage.txt"
@@ -148,6 +143,19 @@ initSettings top_dir = do
baseUnitId <- getSetting_raw "base unit-id"
+ -- LibDir is optional. If not set, derive it from topDir. This allows
+ -- bindists to work without explicitly setting LibDir, but gives us the
+ -- option to override it for inplace test compilers (the "stage2
+ -- cross-compiler" scenario). If LibDir is a relative path, it is
+ -- interpreted relative to topDir.
+ let lib_dir = installed $ fromRight "." $
+ getRawFilePathSetting top_dir settingsFile mySettings "LibDir"
+
+ -- The package database is either a relative path to lib_dir OR an absolute path.
+ -- In case the path is absolute then lib_dir </> abs_path == abs_path
+ -- the path is relative then lib_dir </> rel_path == lib_dir </> rel_path
+ globalpkgdb_path <- (lib_dir </>) <$> getSetting "Relative Global Package DB"
+
return $ Settings
{ sGhcNameVersion = GhcNameVersion
{ ghcNameVersion_programName = "ghc"
@@ -159,6 +167,7 @@ initSettings top_dir = do
, fileSettings_ghciUsagePath = ghci_usage_msg_path
, fileSettings_toolDir = mtool_dir
, fileSettings_topDir = top_dir
+ , fileSettings_libDir = lib_dir
, fileSettings_globalPackageDatabase = globalpkgdb_path
}
=====================================
hadrian/src/Rules/BinaryDist.hs
=====================================
@@ -14,6 +14,7 @@ import qualified System.Directory.Extra as IO
import Data.Either
import qualified Data.Set as Set
import Oracles.Flavour
+import Rules.Generate (generateSettings)
{-
Note [Binary distributions]
@@ -218,6 +219,17 @@ bindistRules = do
IO.createFileLink version_prog versioned_runhaskell_path
copyDirectory (ghcBuildDir -/- "lib") bindistFilesDir
+
+ -- Regenerate settings file without LibDir. For bindists, LibDir should
+ -- be derived from topdir at runtime such that the GHC binary is
+ -- relocatable. The package DB is always at "package.conf.d" relative to
+ -- the lib dir, matching the known bindist layout.
+ let bindistSettings = bindistFilesDir -/- "lib" -/- "settings"
+ bindistContext = vanillaContext Stage1 compiler
+ bindistSettingsContent <- interpretInContext bindistContext $
+ generateSettings bindistSettings False "package.conf.d"
+ writeFile' bindistSettings bindistSettingsContent
+
copyDirectory (rtsIncludeDir) bindistFilesDir
when windowsHost $ createGhcii (bindistFilesDir -/- "bin")
=====================================
hadrian/src/Rules/Generate.hs
=====================================
@@ -1,7 +1,7 @@
module Rules.Generate (
isGeneratedCmmFile, compilerDependencies, generatePackageCode,
generateRules, copyRules, generatedDependencies,
- templateRules
+ templateRules, generateSettings
) where
import Development.Shake.FilePath
@@ -256,8 +256,21 @@ generateRules = do
forM_ allStages $ \stage -> do
let prefix = root -/- stageString stage -/- "lib"
- go gen file = generate file (semiEmptyTarget (succStage stage)) gen
- (prefix -/- "settings") %> \out -> go (generateSettings out) out
+ -- Stage0 compiler builds Stage1, Stage1 -> Stage2, etc.
+ buildStage = succStage stage
+ go gen file = generate file (semiEmptyTarget buildStage) gen
+ (prefix -/- "settings") %> \out -> do
+ let get_pkg_db stg = packageDbPath (PackageDbLoc stg Final)
+ pkgDb <- case buildStage of
+ Stage0 {} -> error "Unable to generate settings for stage0. This should never be reached."
+ Stage1 -> get_pkg_db Stage1
+ Stage2 -> get_pkg_db Stage1
+ Stage3 -> get_pkg_db Stage2
+ -- addTrailingPathSeparator needed: makeRelativeNoSysLink uses
+ -- splitPath where "lib" and "lib/" are distinct components.
+ let lib_topDir = addTrailingPathSeparator prefix
+ relPkgDb = makeRelativeNoSysLink lib_topDir pkgDb
+ go (generateSettings out True relPkgDb) out
(prefix -/- "targets" -/- "default.target") %> \out -> go (show <$> expr getTargetTarget) out
where
@@ -460,19 +473,17 @@ ghcWrapper stage = do
return $ unwords $ map show $ [ ghcPath ]
++ [ "$@" ]
-generateSettings :: FilePath -> Expr String
-generateSettings settingsFile = do
+-- | Generate settings file, optionally including @LibDir@.
+--
+-- @rel_pkg_db@: package DB path relative to the lib dir (e.g.
+-- "package.conf.d"). Callers supply the correct relative path. For bindists
+-- the layout is known statically; for in-tree builds callers compute it. For
+-- bindists, we omit @LibDir@ so it defaults to @topDir@ at runtime.
+generateSettings :: FilePath -> Bool -> FilePath -> Expr String
+generateSettings settingsFile includeLibDir rel_pkg_db = do
ctx <- getContext
stage <- getStage
- package_db_path <- expr $ do
- let get_pkg_db stg = packageDbPath (PackageDbLoc stg Final)
- case stage of
- Stage0 {} -> error "Unable to generate settings for stage0"
- Stage1 -> get_pkg_db Stage1
- Stage2 -> get_pkg_db Stage1
- Stage3 -> get_pkg_db Stage2
-
-- The unit-id of the base package which is always linked against (#25382)
base_unit_id <- expr $ do
case stage of
@@ -481,15 +492,24 @@ generateSettings settingsFile = do
Stage2 -> pkgUnitId Stage1 base
Stage3 -> pkgUnitId Stage2 base
- let rel_pkg_db = makeRelativeNoSysLink (dropFileName settingsFile) package_db_path
+ let -- E.g. the Stage2 compiler lives in _build/stage1
+ -- So, we need to decrement the stage to get the correct directory
+ stage_dir_stage = predStage stage
+
+ -- addTrailingPathSeparator is needed because makeRelativeNoSysLink uses
+ -- splitPath internally, where "lib" and "lib/" are distinct components.
+ lib_topDir :: FilePath <- expr $ addTrailingPathSeparator <$> stageLibPath stage_dir_stage
+ let rel_lib_topDir = makeRelativeNoSysLink (dropFileName settingsFile) lib_topDir
settings <- traverse sequence $
- [ ("unlit command", ("$topdir/../bin/" <>) <$> expr (programName (ctx { Context.package = unlit })))
- , ("Use interpreter", expr $ yesNo <$> ghcWithInterpreter (predStage stage))
- , ("RTS ways", escapeArgs . map show . Set.toList <$> getRtsWays)
- , ("Relative Global Package DB", pure rel_pkg_db)
- , ("base unit-id", pure base_unit_id)
- ]
+ [ ("unlit command", ("$topdir/../bin/" <>) <$> expr (programName (ctx { Context.package = unlit })))
+ , ("Use interpreter", expr $ yesNo <$> ghcWithInterpreter (predStage stage))
+ , ("RTS ways", escapeArgs . map show . Set.toList <$> getRtsWays)
+ , ("Relative Global Package DB", pure rel_pkg_db)
+ , ("base unit-id", pure base_unit_id)
+ ]
+ ++ ([("LibDir", pure rel_lib_topDir) | includeLibDir])
+
let showTuple (k, v) = "(" ++ show k ++ ", " ++ show v ++ ")"
pure $ case settings of
[] -> "[]"
=====================================
libraries/ghc-boot/GHC/Settings/Utils.hs
=====================================
@@ -3,6 +3,7 @@ module GHC.Settings.Utils where
import Prelude -- See Note [Why do we import Prelude here?]
import Data.Char (isSpace)
+import Data.Either (fromRight)
import Data.Map (Map)
import qualified Data.Map as Map
@@ -45,7 +46,10 @@ getTargetArchOS target = tgtArchOs target
getGlobalPackageDb :: FilePath -> RawSettings -> Either String FilePath
getGlobalPackageDb settingsFile settings = do
rel_db <- getRawSetting settingsFile settings "Relative Global Package DB"
- return (dropFileName settingsFile </> rel_db)
+ let top_dir = dropFileName settingsFile
+ lib_dir = (top_dir </>) $ fromRight "." $
+ getRawFilePathSetting top_dir settingsFile settings "LibDir"
+ return (lib_dir </> rel_db)
--------------------------------------------------------------------------------
-- lib/settings
=====================================
testsuite/tests/ghc-api/settings/LibDir.hs
=====================================
@@ -0,0 +1,130 @@
+module Main where
+
+import Control.Monad (when)
+import Control.Monad.IO.Class (liftIO)
+import Data.List (intercalate)
+import GHC
+import GHC.Driver.DynFlags
+import GHC.Driver.Env (hsc_dflags)
+import GHC.Settings
+import System.Directory
+import System.Environment
+import System.Exit (ExitCode (ExitFailure), exitWith)
+import System.FilePath
+import System.IO (hPutStrLn, stderr)
+import System.Process (readProcess)
+import Unsafe.Coerce (unsafeCoerce)
+
+-- Verify that a LibDir setting in the settings file is respected:
+-- 1. fileSettings_libDir and fileSettings_globalPackageDatabase reflect the
+-- configured LibDir path (not topDir)
+-- 2. GHC can still compile with a LibDir that differs from topDir
+-- 3. --print-libdir and --print-global-package-db output the correct paths
+--
+-- We create a symlink to the real lib dir so that the package DB remains
+-- findable, but use a separate topDir so that topDir ≠ libDir, proving
+-- the LibDir setting is actually used.
+--
+-- Tested for both relative and absolute LibDir values.
+main :: IO ()
+main = do
+ libdir : ghcBin : _ <- getArgs
+
+ (rawSettingOpts, rawTargetOpts, realLibDir) <- runGhc (Just libdir) $ do
+ dflags <- hsc_dflags <$> getSession
+ pure (rawSettings dflags, rawTarget dflags, fileSettings_libDir (fileSettings dflags))
+
+ tmpDir <- getTemporaryDirectory
+ let topDir = tmpDir </> "T19174_top"
+ symlinkLib = tmpDir </> "T19174_lib"
+ -- Remove stale dirs from prior runs; createDirectoryLink fails if path exists.
+ removePathForcibly topDir
+ removePathForcibly symlinkLib
+ createDirectoryIfMissing True (topDir </> "targets")
+ createDirectoryLink realLibDir symlinkLib
+
+ let testWithLibDir libDirValue = do
+ writeTopDirFiles topDir rawSettingOpts rawTargetOpts libDirValue
+ runGhc (Just topDir) $ do
+ assertSettings topDir symlinkLib
+ compileAndRunTestExpr
+ assertGhcFlags ghcBin topDir symlinkLib
+
+ testWithLibDir (".." </> takeFileName symlinkLib)
+ testWithLibDir symlinkLib
+
+ putStrLn "OK"
+
+writeTopDirFiles ::
+ (Show a) =>
+ FilePath ->
+ [(String, String)] ->
+ a ->
+ String ->
+ IO ()
+writeTopDirFiles topDir rawSettingOpts rawTargetOpts libDirValue = do
+ let settings = filter ((/= "LibDir") . fst) rawSettingOpts ++ [("LibDir", libDirValue)]
+ writeFile (topDir </> "settings") $
+ "[" ++ intercalate "\n," (map show settings) ++ "]"
+ writeFile (topDir </> "targets" </> "default.target") $
+ show rawTargetOpts
+
+assertSettings :: FilePath -> FilePath -> Ghc ()
+assertSettings topDir expectedLib = do
+ dflags <- hsc_dflags <$> getSession
+ let fs = fileSettings dflags
+ actualLib = fileSettings_libDir fs
+ actualPkgDb = fileSettings_globalPackageDatabase fs
+ normActualLib <- liftIO $ canonicalizePath actualLib
+ normExpected <- liftIO $ canonicalizePath expectedLib
+ normTopDir <- liftIO $ canonicalizePath topDir
+ normActualPkgDb <- liftIO $ canonicalizePath actualPkgDb
+ normExpectedPkgDb <- liftIO $ canonicalizePath (expectedLib </> "package.conf.d")
+ liftIO $ do
+ when (normActualLib /= normExpected) $
+ die
+ [ "FAIL: libDir should be " ++ normExpected,
+ " got " ++ normActualLib
+ ]
+ when (normActualLib == normTopDir) $
+ die ["FAIL: libDir equals topDir — LibDir setting was ignored"]
+ when (normActualPkgDb /= normExpectedPkgDb) $
+ die
+ [ "FAIL: globalPackageDB should be " ++ normExpectedPkgDb,
+ " got " ++ normActualPkgDb
+ ]
+
+assertGhcFlags :: FilePath -> FilePath -> FilePath -> IO ()
+assertGhcFlags ghcBin topDir expectedLib = do
+ normExpectedLib <- canonicalizePath expectedLib
+ normExpectedPkgDb <- canonicalizePath (expectedLib </> "package.conf.d")
+
+ printedLibDir <- trim <$> readProcess ghcBin ["-B" ++ topDir, "--print-libdir"] ""
+ normPrintedLib <- canonicalizePath printedLibDir
+ when (normPrintedLib /= normExpectedLib) $
+ die
+ [ "FAIL: --print-libdir should be " ++ normExpectedLib,
+ " got " ++ normPrintedLib
+ ]
+
+ printedPkgDb <- trim <$> readProcess ghcBin ["-B" ++ topDir, "--print-global-package-db"] ""
+ normPrintedPkgDb <- canonicalizePath printedPkgDb
+ when (normPrintedPkgDb /= normExpectedPkgDb) $
+ die
+ [ "FAIL: --print-global-package-db should be " ++ normExpectedPkgDb,
+ " got " ++ normPrintedPkgDb
+ ]
+
+compileAndRunTestExpr :: Ghc ()
+compileAndRunTestExpr = do
+ dflags <- getSessionDynFlags
+ _ <- setSessionDynFlags dflags
+ setContext [IIDecl (simpleImportDecl (mkModuleName "Prelude"))]
+ result <- compileExpr "length [1,2,3 :: Int]"
+ liftIO $ print (unsafeCoerce result :: Int)
+
+trim :: String -> String
+trim = reverse . dropWhile (== '\n') . reverse
+
+die :: [String] -> IO ()
+die msgs = mapM_ (hPutStrLn stderr) msgs >> exitWith (ExitFailure 1)
=====================================
testsuite/tests/ghc-api/settings/LibDir.stdout
=====================================
@@ -0,0 +1,3 @@
+3
+3
+OK
=====================================
testsuite/tests/ghc-api/settings/all.T
=====================================
@@ -0,0 +1,12 @@
+test('LibDir',
+ [ extra_run_opts('"' + config.libdir + '" "' + config.compiler + '"')
+ , req_interp
+ # createDirectoryLink uses CreateSymbolicLink on Windows (requires developer
+ # mode or admin); also GHC searches for mingw relative to topDir, which our
+ # artificial topDir doesn't provide.
+ , when(opsys('mingw32'), skip)
+ # TODO: wasm CI image lacks permission to create symlinks in temp dir (`/tmp`).
+ , when(arch('wasm32'), skip)
+ ]
+ , compile_and_run
+ , ['-package ghc -package directory -package filepath'])
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/f99d858cc211ca2d6577d8bf6a53e7…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/f99d858cc211ca2d6577d8bf6a53e7…
You're receiving this email because of your account on gitlab.haskell.org.
1
0
28 May '26
Cheng Shao deleted branch wip/fix-hpc-bytecode-modname at Glasgow Haskell Compiler / GHC
--
You're receiving this email because of your account on gitlab.haskell.org.
1
0