[Git][ghc/ghc][wip/mangoiv/9.12.5-rc4] 2 commits: An abstract TyCon may hide a unary class
by Magnus (@MangoIV) 04 Sep '26
by Magnus (@MangoIV) 04 Sep '26
04 Sep '26
Magnus pushed to branch wip/mangoiv/9.12.5-rc4 at Glasgow Haskell Compiler / GHC
Commits:
1b66cc1e by Zubin Duggal at 2026-09-04T14:07:07+02:00
An abstract TyCon may hide a unary class
A class declared in an hs-boot file is an AbstractTyCon inside the
module loop, and compiling the real declaration may reveal it to be a
UnaryClassTyCon.
- isTerminatingType returned True for such AbstractTyCons
- IfaceToCore set the unary flag to False in the DFunId
So we could end up speculating bottom dictionaries because inside a module
loop we see an UnaryClassTyCon as an AbstractTyCon
Use isTerminatingTyCon, which returns False for an abstract TyCon.
The Bool in DFunId is now a cache for isTerminatingTyCon, set in
mkDFunIdDetails.
Fixes #27704
Note by the backporter (mangoiv): we're backporting by the fact that
a tycon is a unary type class con IFF it is a type class con and a
newtype prior to 9bd7fcc518111a1549c98720c222cdbabd32ed46.
(cherry picked from commit db1e0bda5c3209d75133fa9e2560d684f35f950b)
- - - - -
e7b125d6 by Zubin Duggal at 2026-09-04T15:53:18+02:00
Specialise: don't replace dead args with absent fillers
specHeader decides an argument is dead by calling isDeadBinder on a binder of
the /optimised RHS/, then applies the filler to the /stable unfolding/
template instead. The two may differ, so the argument can be dead in
the RHS and not in the template.
The specialised function's unfolding then has an absent filler, and any call
site that inlines it evaluates the error thunk.
Dropping dead args in the specialiser is rarely worth it, to quote Simon,
"The later worker/wrapper pass will pick up the dead arg later if it is really dead. Keeps the specialiser simpler."
So instead of trying to check if the arg really is dead in the stable unfolding,
just drop the logic for dropping dead args in the specialiser altogeher.
Fixes #27703
(cherry picked from commit 2b3cdef64bbc535bd72e3dfb512b4cfd10a72ca2)
- - - - -
30 changed files:
- + changelog.d/27703
- + changelog.d/27704
- compiler/GHC/Core.hs
- compiler/GHC/Core/Opt/Specialise.hs
- compiler/GHC/Core/TyCon.hs
- compiler/GHC/Core/Type.hs
- compiler/GHC/Core/Utils.hs
- compiler/GHC/IfaceToCore.hs
- compiler/GHC/Types/Id/Info.hs
- compiler/GHC/Types/Id/Make.hs
- + testsuite/tests/core-to-stg/T27704/Callee.hs
- + testsuite/tests/core-to-stg/T27704/Callee.hs-boot
- + testsuite/tests/core-to-stg/T27704/Main.hs
- + testsuite/tests/core-to-stg/T27704/Mid.hs
- + testsuite/tests/core-to-stg/T27704/T27704.stdout
- + testsuite/tests/core-to-stg/T27704/all.T
- + testsuite/tests/core-to-stg/T27704a/Callee.hs
- + testsuite/tests/core-to-stg/T27704a/Callee.hs-boot
- + testsuite/tests/core-to-stg/T27704a/Main.hs
- + testsuite/tests/core-to-stg/T27704a/Mid.hs
- + testsuite/tests/core-to-stg/T27704a/T27704a.stdout
- + testsuite/tests/core-to-stg/T27704a/all.T
- testsuite/tests/simplCore/should_compile/T17966.stderr
- testsuite/tests/simplCore/should_compile/T7785.stderr
- testsuite/tests/simplCore/should_compile/spec004.hs
- testsuite/tests/simplCore/should_compile/spec004.stderr
- + testsuite/tests/simplCore/should_run/T27703/Lib.hs
- + testsuite/tests/simplCore/should_run/T27703/Main.hs
- + testsuite/tests/simplCore/should_run/T27703/T27703.stdout
- + testsuite/tests/simplCore/should_run/T27703/all.T
Changes:
=====================================
changelog.d/27703
=====================================
@@ -0,0 +1,5 @@
+section: compiler
+synopsis: Fix a bug where the specialiser could drop an argument that the
+ function's stable unfolding still used, resulting in a runtime crash.
+mrs: !16519
+issues: #27703
=====================================
changelog.d/27704
=====================================
@@ -0,0 +1,6 @@
+section: compiler
+synopsis: Fix a bug where a class declared abstractly in an hs-boot file was
+ assumed not to be unary, so GHC speculatively evaluated a dictionary that
+ could be bottom, crashing the program.
+mrs: !16519
+issues: #27704
=====================================
compiler/GHC/Core.hs
=====================================
@@ -574,6 +574,17 @@ Wrinkle (NBD1)
say that /all/ unary-class constraints might diverge. Remember, almost all
classes are non-unary, and thus definitely non-bottom.
+Wrinkle (NBD2)
+ A class declared in an hs-boot file is an AbstractTyCon inside the module
+ loop:
+ module Callee where { class UC (a :: Type) } -- Callee.hs-boot
+ module Callee where { class UC a where { ucm :: a -> a } } -- Callee.hs
+ isUnaryClassTyCon returns False for it, but compiling the real declaration
+ may reveal a UnaryClassTyCon, whose dictionary is `ucm` and can be bottom.
+
+ Speculating a superclass selection from such a dictionary crashes (#27704).
+ So we must treat an AbstractTyCon as possibly unary.
+
Note [Case expression invariants]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Case expressions are one of the more complicated elements of the Core
=====================================
compiler/GHC/Core/Opt/Specialise.hs
=====================================
@@ -20,13 +20,12 @@ import GHC.Core.Multiplicity
import GHC.Core.SimpleOpt( defaultSimpleOpts, simpleOptExprWith )
import GHC.Core.Predicate
import GHC.Core.Coercion( Coercion )
-import GHC.Core.DataCon ( StrictnessMark (..) )
-import GHC.Core.Opt.Monad
+import GHC.Core.DataCon (dataConTyCon)
import qualified GHC.Core.Subst as Core
import GHC.Core.Unfold.Make
import GHC.Core
-import GHC.Core.Opt.WorkWrap.Utils ( mkAbsentFiller )
+import GHC.Core.Make ( wrapFloats )
import GHC.Core.Unify ( tcMatchTy )
import GHC.Core.Rules
import GHC.Core.Utils ( exprIsTrivial, exprIsTopLevelBindable
@@ -70,6 +69,7 @@ import GHC.Core.Unfold
import Data.List( partition )
import Data.List.NonEmpty ( NonEmpty (..) )
import GHC.Core.Subst (substTickish)
+import GHC.Core.Opt.Monad
{-
************************************************************************
@@ -1713,7 +1713,7 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
; ( useful, rhs_env2, leftover_bndrs
, rule_bndrs, rule_lhs_args
- , spec_bndrs1, dx_binds, spec_args) <- specHeader this_mod env rhs_bndrs all_call_args
+ , spec_bndrs1, dx_binds, spec_args) <- specHeader env rhs_bndrs all_call_args
-- ; pprTrace "spec_call" (vcat
-- [ text "fun: " <+> ppr fn
@@ -1794,7 +1794,7 @@ specCalls spec_imp env existing_rules calls_for_me fn rhs
spec_fn_details
= case idDetails fn of
JoinId join_arity _ -> JoinId (join_arity - join_arity_decr) Nothing
- DFunId is_nt -> DFunId is_nt
+ DFunId terminating -> DFunId terminating
_ -> VanillaId
; spec_fn <- newSpecIdSM (idName fn) spec_fn_ty spec_fn_details spec_fn_info
@@ -2014,49 +2014,22 @@ In the rule, d1 and d2 are just wildcards, not used in the RHS. Note
additionally that 'x' isn't captured by this rule --- we bind only
enough etas in order to capture all of the *specialised* arguments.
-Note [Drop dead args from specialisations]
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-When specialising a function, it’s possible some of the arguments may
-actually be dead. For example, consider:
+Note [Do not drop dead args from specialisations]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+When specialising a function, some of its arguments may be dead. For example
f :: forall a. () -> Show a => a -> String
f x y = show y ++ "!"
-We might generate the following CallInfo for `f @Int`:
-
- [SpecType Int, UnspecArg, SpecDict $dShowInt, UnspecArg]
-
-Normally we’d include both the x and y arguments in the
-specialisation, since we’re not specialising on either of them. But
-that’s silly, since x is actually unused! So we might as well drop it
-in the specialisation:
-
- $sf :: Int -> String
- $sf y = show y ++ "!"
-
- {-# RULE "SPEC f @Int" forall x. f @Int x $dShow = $sf #-}
-
-This doesn’t save us much, since the arg would be removed later by
-worker/wrapper, anyway, but it’s easy to do.
-
-Wrinkles
-
-* Note that we only drop dead arguments if:
- 1. We don’t specialise on them.
- 2. They come before an argument we do specialise on.
- Doing the latter would require eta-expanding the RULE, which could
- make it match less often, so it’s not worth it. Doing the former could
- be more useful --- it would stop us from generating pointless
- specialisations --- but it’s more involved to implement and unclear if
- it actually provides much benefit in practice.
-
-* If the function has a stable unfolding, specHeader has to come up with
- arguments to pass to that stable unfolding, when building the stable
- unfolding of the specialised function: this is the last field in specHeader's
- big result tuple.
+gives the CallInfo [SpecType Int, UnspecArg, SpecDict $dShowInt, UnspecArg]
+for `f @Int`, in which `x` is dead. We used to drop such an argument from the
+specialisation and pass an absent filler in its place.
- The right thing to do is to produce a LitRubbish; it should rapidly
- disappear. Rather like GHC.Core.Opt.WorkWrap.Utils.mk_absent_let.
+We no longer do so, because the filler is applied to the function's stable
+unfolding template rather than to its optimised RHS, and the two may differ:
+the argument can be dead in the RHS and not in the template. The specialised
+function's unfolding then contains the filler, and any call site that inlines
+it evaluates the error thunk. See #27703 for an instance of how this goes wrong.
Note [Specialisation modulo dictionary selectors]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2121,8 +2094,7 @@ since we’ll generate a RULE like
RULE "SPEC f @Int" forall x [Occ=Dead].
f @Int x $dShow = $sf
-and Core Lint complains, even though x only appears on the LHS (due to
-Note [Drop dead args from specialisations]).
+and Core Lint complains, even though x only appears on the LHS.
Why is that a Lint error? Because the arguments on the LHS of a rule
are syntactically expressions, not patterns, so Lint treats the
@@ -2564,8 +2536,7 @@ isSpecDict _ = False
-- , [T1, T2, c, i, dEqT1, dShow1]
-- )
specHeader
- :: Module -- The module being compiled, for mkAbsentFiller
- -> SpecEnv
+ :: SpecEnv
-> [InBndr] -- The binders from the original function 'f'
-> [SpecArg] -- From the CallInfo
-> SpecM ( Bool -- True <=> some useful specialisation happened
@@ -2591,7 +2562,7 @@ specHeader
-- We want to specialise on type 'T1', and so we must construct a substitution
-- 'a->T1', as well as a LHS argument for the resulting RULE and unfolding
-- details.
-specHeader mod env (bndr : bndrs) (SpecType ty : args)
+specHeader env (bndr : bndrs) (SpecType ty : args)
= do { -- Find qvars, the type variables to add to the binders for the rule
-- Namely those free in `ty` that aren't in scope
-- See (MP2) in Note [Specialising polymorphic dictionaries]
@@ -2603,7 +2574,7 @@ specHeader mod env (bndr : bndrs) (SpecType ty : args)
ty' = substTy env1 ty
env2 = extendTvSubst env1 bndr ty'
; (useful, env3, leftover_bndrs, rule_bs, rule_es, bs', dx, spec_args)
- <- specHeader mod env2 bndrs args
+ <- specHeader env2 bndrs args
; pure ( useful
, env3
, leftover_bndrs
@@ -2619,10 +2590,10 @@ specHeader mod env (bndr : bndrs) (SpecType ty : args)
-- a substitution on it (in case the type refers to 'a'). Additionally, we need
-- to produce a binder, LHS argument and RHS argument for the resulting rule,
-- /and/ a binder for the specialised body.
-specHeader mod env (bndr : bndrs) (UnspecType : args)
+specHeader env (bndr : bndrs) (UnspecType : args)
= do { let (env', bndr') = substBndr env bndr
; (useful, env'', leftover_bndrs, rule_bs, rule_es, bs', dx, spec_args)
- <- specHeader mod env' bndrs args
+ <- specHeader env' bndrs args
; pure ( useful
, env''
, leftover_bndrs
@@ -2636,14 +2607,14 @@ specHeader mod env (bndr : bndrs) (UnspecType : args)
-- Next we want to specialise the 'Eq a' dict away. We need to construct
-- a wildcard binder to match the dictionary (See Note [Specialising Calls] for
-- the nitty-gritty), as a LHS rule and unfolding details.
-specHeader mod env (bndr : bndrs) (SpecDict d : args)
+specHeader env (bndr : bndrs) (SpecDict d : args)
| not (isDeadBinder bndr)
, allVarSet (`elemInScopeSet` in_scope) (exprFreeVars d)
-- See Note [Weird special case for SpecDict]
= do { (env1, bndr') <- newDictBndr env bndr -- See Note [Zap occ info in rule binders]
; let (env2, dx_bind, spec_dict) = bindAuxiliaryDict env1 bndr bndr' d
; (_, env3, leftover_bndrs, rule_bs, rule_es, bs', dx, spec_args)
- <- specHeader mod env2 bndrs args
+ <- specHeader env2 bndrs args
; pure ( True -- Ha! A useful specialisation!
, env3
, leftover_bndrs
@@ -2668,27 +2639,14 @@ specHeader mod env (bndr : bndrs) (SpecDict d : args)
-- why 'i' doesn't appear in our RULE above. But we have no guarantee that
-- there aren't 'UnspecArg's which come /before/ all of the dictionaries, so
-- this case must be here.
-specHeader mod env (bndr : bndrs) (_ : args)
+specHeader env (bndr : bndrs) (_ : args)
-- The "_" can be UnSpecArg, or SpecDict where the bndr is dead
= do { -- see Note [Zap occ info in rule binders]
let (env', bndr') = substBndr env (zapIdOccInfo bndr)
; (useful, env'', leftover_bndrs, rule_bs, rule_es, bs', dx, spec_args)
- <- specHeader mod env' bndrs args
-
- ; let -- See Note [Drop dead args from specialisations]
- -- C.f. GHC.Core.Opt.WorkWrap.Utils.mk_absent_let
- (mb_spec_bndr, spec_arg)
- | isDeadBinder bndr
- , Just filler <- mkAbsentFiller mod bndr' NotMarkedStrict
- -- NB: mkAbsentFiller returns Nothing for a terminating type (e.g. a
- -- dictionary), so this guard fails and we fall through, keeping the
- -- argument instead of dropping it.
- -- See Note [Don't make fillers for terminating types]
- -- in GHC.Core.Opt.WorkWrap.Utils
- = (Nothing, filler)
- | otherwise
- = (Just bndr', varToCoreExpr bndr')
+ <- specHeader env' bndrs args
+ ; let (mb_spec_bndr, spec_arg) = (Just bndr', varToCoreExpr bndr')
; pure ( useful
, env''
, leftover_bndrs
@@ -2704,12 +2662,12 @@ specHeader mod env (bndr : bndrs) (_ : args)
-- If we run out of binders, stop immediately
-- See Note [Specialisation Must Preserve Sharing]
-specHeader _ env [] _ = pure (False, env, [], [], [], [], [], [])
+specHeader env [] _ = pure (False, env, [], [], [], [], [], [])
-- Return all remaining binders from the original function. These have the
-- invariant that they should all correspond to unspecialised arguments, so
-- it's safe to stop processing at this point.
-specHeader _ env bndrs []
+specHeader env bndrs []
= pure (False, env', bndrs', [], [], [], [], [])
where
(env', bndrs') = substBndrs env bndrs
=====================================
compiler/GHC/Core/TyCon.hs
=====================================
@@ -46,8 +46,9 @@ module GHC.Core.TyCon(
noTcTyConScopedTyVars,
-- ** Predicates on TyCons
- isAlgTyCon, isVanillaAlgTyCon,
- isClassTyCon, isFamInstTyCon,
+ isAlgTyCon, isVanillaAlgTyCon, isClassTyCon,
+ isUnaryClassTyCon, isUnaryClassTyCon_maybe, isTerminatingTyCon,
+ isFamInstTyCon,
isPrimTyCon,
isTupleTyCon, isUnboxedTupleTyCon, isBoxedTupleTyCon,
isUnboxedSumTyCon, isPromotedTupleTyCon,
@@ -2721,6 +2722,27 @@ famTyConFlav_maybe (TyCon { tyConDetails = details })
| FamilyTyCon {famTcFlav = flav} <- details = Just flav
| otherwise = Nothing
+isUnaryClassTyCon :: TyCon -> Bool
+isUnaryClassTyCon tc = isClassTyCon tc && isNewTyCon tc
+
+isTerminatingTyCon :: TyCon -> Bool
+-- ^ True <=> a value of type (T t1..tn), where `T` is this TyCon, cannot be bottom.
+-- The only TyCons that satisfy `isTerminatingTyCon` are non-unary class TyCons.
+-- See (UCM3) in Note [Unary class magic], and
+-- Note [NON-BOTTOM-DICTS invariant] in GHC.Core.
+-- An AbstractTyCon returns False: it comes from an hs-boot file, and the real
+-- declaration may turn out to be a unary class. See (NBD2) in that Note.
+isTerminatingTyCon tc = isClassTyCon tc && not (isUnaryClassTyCon tc)
+ && not (isAbstractTyCon tc)
+
+isUnaryClassTyCon_maybe :: TyCon -> Maybe (Class, DataCon)
+isUnaryClassTyCon_maybe (TyCon { tyConDetails = details })
+ | AlgTyCon { algTcFlavour = ClassTyCon cls _
+ , algTcRhs = NewTyCon {data_con = dc} } <- details
+ = Just (cls, dc)
+ | otherwise
+ = Nothing
+
-- | Is this 'TyCon' that for a class instance?
isClassTyCon :: TyCon -> Bool
isClassTyCon (TyCon { tyConDetails = details })
=====================================
compiler/GHC/Core/Type.hs
=====================================
@@ -2479,7 +2479,7 @@ isTerminatingType :: HasDebugCallStack => Type -> Bool
-- NB: unlifted types are not terminating types!
-- e.g. you can write a term (loop 1)::Int# that diverges.
isTerminatingType ty = case tyConAppTyCon_maybe ty of
- Just tc -> isClassTyCon tc && not (isNewTyCon tc)
+ Just tc -> isTerminatingTyCon tc
_ -> False
isPrimitiveType :: Type -> Bool
=====================================
compiler/GHC/Core/Utils.hs
=====================================
@@ -1934,7 +1934,7 @@ app_ok fun_ok primop_ok fun args
| otherwise
= case idDetails fun of
- DFunId new_type -> not new_type
+ DFunId terminating -> terminating
-- DFuns terminate, unless the dict is implemented
-- with a newtype in which case they may not
=====================================
compiler/GHC/IfaceToCore.hs
=====================================
@@ -1749,8 +1749,7 @@ tcIfaceDataAlt mult con inst_tys arg_strs rhs
tcIdDetails :: Name -> Type -> IfaceIdDetails -> IfL IdDetails
tcIdDetails _ _ IfVanillaId = return VanillaId
tcIdDetails _ _ (IfWorkerLikeId dmds) = return $ WorkerLikeId dmds
-tcIdDetails _ ty IfDFunId
- = return (DFunId (isNewTyCon (classTyCon cls)))
+tcIdDetails _ ty IfDFunId = return (mkDFunIdDetails cls)
where
(_, _, cls, _) = tcSplitDFunTy ty
=====================================
compiler/GHC/Types/Id/Info.hs
=====================================
@@ -20,6 +20,7 @@ Haskell. [WDP 94/11])
module GHC.Types.Id.Info (
-- * The IdDetails type
IdDetails(..), pprIdDetails, coVarDetails, isCoVarDetails,
+ mkDFunIdDetails,
JoinArity, isJoinIdDetails_maybe,
RecSelParent(..), recSelParentName, recSelFirstConName,
recSelParentCons, idDetailsConcreteTvs,
@@ -201,9 +202,8 @@ data IdDetails
| TickBoxOpId TickBoxOp -- ^ The 'Id' is for a HPC tick box (both traditional and binary)
| DFunId Bool -- ^ A dictionary function.
- -- Bool = True <=> the class has only one method, so may be
- -- implemented with a newtype, so it might be bad
- -- to be strict on this dictionary
+ -- Bool = True <=> a dictionary of this class cannot be bottom
+ -- See Note [NON-BOTTOM-DICTS invariant] in GHC.Core
| CoVarId -- ^ A coercion variable
-- This only covers /un-lifted/ coercions, of type
@@ -372,6 +372,9 @@ isJoinIdDetails_maybe :: IdDetails -> Maybe (JoinArity, (Maybe [CbvMark]))
isJoinIdDetails_maybe (JoinId join_arity marks) = Just (join_arity, marks)
isJoinIdDetails_maybe _ = Nothing
+mkDFunIdDetails :: Class -> IdDetails
+mkDFunIdDetails cls = DFunId (isTerminatingTyCon (classTyCon cls))
+
instance Outputable IdDetails where
ppr = pprIdDetails
@@ -388,7 +391,8 @@ pprIdDetails other = brackets (pp other)
pp (PrimOpId {}) = text "PrimOp"
pp (FCallId _) = text "ForeignCall"
pp (TickBoxOpId _) = text "TickBoxOp"
- pp (DFunId nt) = text "DFunId" <> ppWhen nt (text "(nt)")
+ pp (DFunId terminating) = text "DFunId"
+ <> ppWhen (not terminating) (text "(may-be-bottom)")
pp (RecSelId { sel_naughty = is_naughty })
= brackets $ text "RecSel" <>
ppWhen is_naughty (text "(naughty)")
=====================================
compiler/GHC/Types/Id/Make.hs
=====================================
@@ -1849,11 +1849,10 @@ mkDictFunId :: Name -- Name to use for the dict fun;
-- See Note [Dict funs and default methods]
mkDictFunId dfun_name tvs theta clas tys
- = mkExportedLocalId (DFunId is_nt)
+ = mkExportedLocalId (mkDFunIdDetails clas)
dfun_name
dfun_ty
where
- is_nt = isNewTyCon (classTyCon clas)
dfun_ty = TcType.tcMkDFunSigmaTy tvs theta (mkClassPred clas tys)
{-
=====================================
testsuite/tests/core-to-stg/T27704/Callee.hs
=====================================
@@ -0,0 +1,14 @@
+module Callee where
+
+import Mid
+
+-- The real UC is a *unary* class: one method, no superclasses. So a (UC a)
+-- dictionary *is* the function ucm, and it can be bottom.
+class UC a where
+ ucm :: a -> a
+
+instance UC Int where
+ ucm = undefined
+
+instance MC Int where
+ mcm _ = 7
=====================================
testsuite/tests/core-to-stg/T27704/Callee.hs-boot
=====================================
@@ -0,0 +1,8 @@
+{-# LANGUAGE KindSignatures #-}
+module Callee where
+
+import Data.Kind (Type)
+
+-- Abstract class declaration: no context, no methods, no associated types,
+-- so GHC.Tc.TyCl builds an AbstractTyCon rather than a UnaryClassTyCon.
+class UC (a :: Type)
=====================================
testsuite/tests/core-to-stg/T27704/Main.hs
=====================================
@@ -0,0 +1,6 @@
+module Main where
+import Mid
+import Callee ()
+
+main :: IO ()
+main = print (f (1 :: Int))
=====================================
testsuite/tests/core-to-stg/T27704/Mid.hs
=====================================
@@ -0,0 +1,18 @@
+{-# LANGUAGE GADTs, ConstraintKinds, ScopedTypeVariables #-}
+module Mid where
+
+import {-# SOURCE #-} Callee
+
+class UC a => MC a where
+ mcm :: a -> Int
+
+data Dict c where
+ Dict :: c => Dict c
+
+{-# NOINLINE keep #-}
+keep :: [Dict c] -> Int
+keep xs = length xs + 41
+
+{-# NOINLINE f #-}
+f :: forall a. MC a => a -> Int
+f _ = keep [Dict :: Dict (UC a)]
=====================================
testsuite/tests/core-to-stg/T27704/T27704.stdout
=====================================
@@ -0,0 +1 @@
+42
=====================================
testsuite/tests/core-to-stg/T27704/all.T
=====================================
@@ -0,0 +1,4 @@
+test('T27704',
+ [extra_files(['Main.hs', 'Mid.hs', 'Callee.hs', 'Callee.hs-boot'])],
+ multimod_compile_and_run,
+ ['Main', '-O'])
=====================================
testsuite/tests/core-to-stg/T27704a/Callee.hs
=====================================
@@ -0,0 +1,13 @@
+module Callee where
+
+import Mid ()
+
+-- Really unary: the dictionary /is/ ucm, so it can be bottom.
+class UC a where
+ ucm :: a -> a
+
+instance UC Int where
+ ucm = id
+
+instance UC a => UC [a] where
+ ucm = error "ucm[] forced"
=====================================
testsuite/tests/core-to-stg/T27704a/Callee.hs-boot
=====================================
@@ -0,0 +1,10 @@
+{-# LANGUAGE KindSignatures #-}
+module Callee where
+
+import Data.Kind (Type)
+
+-- No context, no methods, no associated types, so buildAbstractClass makes an
+-- AbstractTyCon rather than a UnaryClassTyCon: Mid cannot see that UC is unary.
+class UC (a :: Type)
+
+instance UC a => UC [a]
=====================================
testsuite/tests/core-to-stg/T27704a/Main.hs
=====================================
@@ -0,0 +1,6 @@
+module Main where
+import Mid
+import Callee ()
+
+main :: IO ()
+main = print (f (1 :: Int))
=====================================
testsuite/tests/core-to-stg/T27704a/Mid.hs
=====================================
@@ -0,0 +1,18 @@
+{-# LANGUAGE GADTs, ScopedTypeVariables #-}
+module Mid where
+
+import {-# SOURCE #-} Callee
+
+data Dict c where
+ Dict :: c => Dict c
+
+{-# NOINLINE keep #-}
+keep :: [Dict c] -> Int
+keep xs = length xs + 41
+
+-- Builds a (UC [a]) dictionary but never forces it. The dfun comes from the
+-- hs-boot file, where UC looks non-unary, so its DFunId must not claim to
+-- terminate: with -fspec-eval-dictfun, CorePrep would speculate it.
+{-# NOINLINE f #-}
+f :: forall a. UC a => a -> Int
+f _ = keep [Dict :: Dict (UC [a])]
=====================================
testsuite/tests/core-to-stg/T27704a/T27704a.stdout
=====================================
@@ -0,0 +1 @@
+42
=====================================
testsuite/tests/core-to-stg/T27704a/all.T
=====================================
@@ -0,0 +1,4 @@
+test('T27704a',
+ [extra_files(['Main.hs', 'Mid.hs', 'Callee.hs', 'Callee.hs-boot'])],
+ multimod_compile_and_run,
+ ['Main', '-O -fspec-eval-dictfun'])
=====================================
testsuite/tests/simplCore/should_compile/T17966.stderr
=====================================
@@ -113,19 +113,15 @@ $cm_aHo
(show @c_aHr $dShow_aHs c_aBh))
end Rec }
--- RHS size: {terms: 1, types: 0, coercions: 10, joins: 0/0}
-T17966.$fCBoolb [InlPrag=INLINE (sat-args=0)]
- :: forall b. Show b => C Bool b
-[LclIdX[DFunId(nt)],
- Arity=5,
- Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
- WorkFree=True, Expandable=True,
- Guidance=ALWAYS_IF(arity=1,unsat_ok=False,boring_ok=True)
- Tmpl= $cm_aHo
- `cast` (forall (b :: <*>_N).
- <Show b>_R %<'Many>_N ->_R Sym (T17966.N:C[0] <Bool>_N <b>_N)
- :: (forall {b} c. (Show b, Show c) => Bool -> b -> c -> String)
- ~R# (forall {b}. Show b => C Bool b))}]
+-- RHS size: {terms: 5, types: 6, coercions: 0, joins: 0/0}
+T17966.$fCBoolb [InlPrag=CONLIKE] :: forall b. Show b => C Bool b
+[LclIdX[DFunId(may-be-bottom)],
+ Arity=1,
+ Unf=Unf{Src=StableSystem, TopLvl=True,
+ Value=True, ConLike=True, WorkFree=True, Expandable=True,
+ Guidance=ALWAYS_IF(arity=1,unsat_ok=True,boring_ok=False)
+ Tmpl= \ (@b_anX) (v_B1 [Occ=Once1] :: Show b_anX) ->
+ T17966.C:C @Bool @b_anX ($cm_aJp @b_anX v_B1)}]
T17966.$fCBoolb
= $cm_aHo
`cast` (forall (b :: <*>_N).
=====================================
testsuite/tests/simplCore/should_compile/T7785.stderr
=====================================
@@ -37,7 +37,7 @@ $cmyfmap_aG7
-- RHS size: {terms: 1, types: 0, coercions: 3, joins: 0/0}
Foo.$fMyFunctor[] [InlPrag=INLINE (sat-args=0)] :: MyFunctor []
-[LclIdX[DFunId(nt)],
+[LclIdX[DFunId(may-be-bottom)],
Arity=4,
Str=<A><A><U><SU>,
Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
=====================================
testsuite/tests/simplCore/should_compile/spec004.hs
=====================================
@@ -1,6 +1,7 @@
{-# LANGUAGE RankNTypes #-}
--- Dead arguments should be dropped in specialisations. See !2913.
+-- Dead arguments are kept in specialisations.
+-- See Note [Do not drop dead args from specialisations] in GHC.Core.Opt.Specialise
module ShouldCompile where
=====================================
testsuite/tests/simplCore/should_compile/spec004.stderr
=====================================
@@ -1,22 +1,22 @@
==================== Specialise ====================
Result size of Specialise
- = {terms: 52, types: 41, coercions: 0, joins: 0/0}
+ = {terms: 39, types: 38, coercions: 0, joins: 0/0}
--- RHS size: {terms: 13, types: 10, coercions: 0, joins: 0/0}
-$sfoo [InlPrag=NOINLINE[0]] :: Int -> [Char]
-[LclId, Arity=1]
+-- RHS size: {terms: 14, types: 11, coercions: 0, joins: 0/0}
+$sfoo [InlPrag=NOINLINE[0]] :: () -> Int -> [Char]
+[LclId, Arity=2]
$sfoo
- = \ (y :: Int) ->
- GHC.Base.build
+ = \ (ds :: ()) (y :: Int) ->
+ GHC.Internal.Base.build
@Char
(\ (@b) (c [OS=OneShot] :: Char -> b -> b) (n [OS=OneShot] :: b) ->
GHC.Base.foldr
@Char
@b
c
- (GHC.CString.unpackFoldrCString# @b "!"# c n)
- (GHC.Show.$fShowInt_$cshow y))
+ (GHC.Internal.Types.unpackFoldrCString# @b "!"# c n)
+ (GHC.Internal.Show.$fShowInt_$cshow y))
-- RHS size: {terms: 17, types: 15, coercions: 0, joins: 0/0}
foo [InlPrag=NOINLINE[0]] :: forall a. () -> Show a => a -> String
@@ -25,7 +25,9 @@ foo [InlPrag=NOINLINE[0]] :: forall a. () -> Show a => a -> String
Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
WorkFree=True, Expandable=True, Guidance=IF_ARGS [0 30 0] 150 40},
RULES: "SPEC foo @Int" [0]
- forall (ds :: ()) ($dShow :: Show Int). foo @Int ds $dShow = $sfoo]
+ forall (ds [Occ=Once1] :: ()) ($dShow [Occ=Dead] :: Show Int).
+ foo @Int ds $dShow
+ = $sfoo ds]
foo
= \ (@a) _ [Occ=Dead] ($dShow :: Show a) (y :: a) ->
GHC.Base.build
@@ -35,44 +37,9 @@ foo
@Char
@b
c
- (GHC.CString.unpackFoldrCString# @b "!"# c n)
+ (GHC.Internal.Types.unpackFoldrCString# @b "!"# c n)
(show @a $dShow y))
--- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
-$trModule :: GHC.Prim.Addr#
-[LclId,
- Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
- WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 20 0}]
-$trModule = "main"#
-
--- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
-$trModule :: GHC.Types.TrName
-[LclId,
- Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
- WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
-$trModule = GHC.Types.TrNameS $trModule
-
--- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
-$trModule :: GHC.Prim.Addr#
-[LclId,
- Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
- WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 50 0}]
-$trModule = "ShouldCompile"#
-
--- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
-$trModule :: GHC.Types.TrName
-[LclId,
- Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
- WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
-$trModule = GHC.Types.TrNameS $trModule
-
--- RHS size: {terms: 3, types: 0, coercions: 0, joins: 0/0}
-ShouldCompile.$trModule :: GHC.Types.Module
-[LclIdX,
- Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
- WorkFree=True, Expandable=True, Guidance=IF_ARGS [] 10 10}]
-ShouldCompile.$trModule = GHC.Types.Module $trModule $trModule
-
-- RHS size: {terms: 5, types: 1, coercions: 0, joins: 0/0}
bar :: String
[LclIdX,
=====================================
testsuite/tests/simplCore/should_run/T27703/Lib.hs
=====================================
@@ -0,0 +1,18 @@
+{-# LANGUAGE RankNTypes #-}
+module Lib (g) where
+
+{-# NOINLINE consume #-}
+consume :: Int -> Int
+consume n = n `seq` 0
+
+{-# RULES "consume/drop" [~1] forall n. consume n = 0 #-}
+
+-- A dead value argument (n) that comes *before* the dictionary we
+-- specialise on. No implicit parameters involved.
+{-# INLINE [1] f #-}
+f :: forall a. Int -> Show a => a -> String
+f n y = show y ++ replicate (consume n) '!'
+
+{-# NOINLINE g #-}
+g :: Bool -> String
+g b = f 7 b ++ "."
=====================================
testsuite/tests/simplCore/should_run/T27703/Main.hs
=====================================
@@ -0,0 +1,5 @@
+module Main where
+import Lib
+
+main :: IO ()
+main = putStrLn (g True)
=====================================
testsuite/tests/simplCore/should_run/T27703/T27703.stdout
=====================================
@@ -0,0 +1 @@
+True.
=====================================
testsuite/tests/simplCore/should_run/T27703/all.T
=====================================
@@ -0,0 +1,4 @@
+test('T27703',
+ [extra_files(['Main.hs', 'Lib.hs'])],
+ multimod_compile_and_run,
+ ['Main', '-O'])
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/feec438d628b953c892fdbcc35bc2c…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/feec438d628b953c892fdbcc35bc2c…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][wip/T27731] 5 commits: Add -Wimplicit-field-strictness (#16836)
by Simon Peyton Jones (@simonpj) 04 Sep '26
by Simon Peyton Jones (@simonpj) 04 Sep '26
04 Sep '26
Simon Peyton Jones pushed to branch wip/T27731 at Glasgow Haskell Compiler / GHC
Commits:
9e7739ab by Simon Jakobi at 2026-09-03T13:45:43-04:00
Add -Wimplicit-field-strictness (#16836)
This opt-in warning fires when a data constructor field lacks an
explicit strictness annotation (`!` or `~`). It complements the
LazyFieldAnnotations extension (4762a8bf30f) from GHC proposal 752,
which makes `~` annotations available for this purpose.
Deciding which fields to report requires their levity, so the check
runs after typechecking. To keep the noise down, the diagnostic is
emitted once per data declaration, grouped by constructor.
Closes #16836.
Assisted-by: Claude Fable 5
- - - - -
6a44d591 by mangoiv at 2026-09-03T13:46:22-04:00
simplifier: remove a bogus `assert` in `rebuild_app'` in `GHC.CoreToStg.Prep.cpeApp`.
Prior to this commit
commit 08bc245be70d95801bc1138804ed1de9474fbdc0
Author: sheaf <sam.derbyshire(a)gmail.com>
Date: Sat Feb 28 16:30:43 2026 +0100
Clean up join points, casts & ticks
This commit shores up the logic dealing with casts and ticks occurring
in between a join point binding and a jump
any `PlaceRuntime` ticks we observed were profiling ticks, even though that
isn't necessary.
The more liberal rules in the above commit allow e.g. breakpoint ticks
(which are valid PlaceRuntime ticks) to legitimately appear in an argument
position.
Fixes #27556
- - - - -
eaa95a6f by Andrei Borzenkov at 2026-09-04T08:08:40-04:00
Parentheses in prefix GADT constructors (#27423)
Updated `splitLHsGadtTy` to allow looking
through the parentheses for inner binders. General example
of a code pattern that's allowed now:
data S a where
MkS :: (forall a. S a)
That should work now with any combination of nested
foralls and parentheses.
We don't perform parenthesis unwrapping for record
GADT constructors in accordance with GHC Proposal #402.
To this end `con_inner_bndrs` no longer stores plain forall
telescopes: `[HsForAllTelescope pass]` is replaced with
`[LHsGadtTelescope pass]`, a new `HsArg`-style type whose
`HsGadtForAll` holds an inner telescope and whose `HsGadtPar`
holds a pair of parentheses. The parentheses carry no meaning
for renaming or type checking; the only reason to record them
is exact-printing.
Updated `pprConDecl` to improve the `parse == parse . ppr . parse`
property of GADT pretty-printing.
The pretty printer can now output code that's similar to this:
data T a where
MkT1 :: (forall a. T a)
MkT2 :: forall . forall a. T a
These are special cases of inner forall binders
for prefix GADT constructors, when we have either
implicit or zero explicit outer binders.
- - - - -
639456a8 by Evgeny Malyshev at 2026-09-04T08:09:25-04:00
HsPat: Add spacing to unary tuple patterns
Use hsep when printing unary boxed TuplePat applications so that the
MkSolo constructor is separated from its argument. The old hcat rendered
wildcard and parenthesized arguments as MkSolo_ and MkSolo().
Add T27034 to exercise the HsPat splice-dump path with wildcard and
variable arguments, and update existing affected golden output.
Fixes #27034
Assisted-By: OpenAI Codex
- - - - -
144e99f8 by Simon Peyton Jones at 2026-09-04T14:31:50+01:00
Simplify the error-suppression mechanism in GHC.Tc.Errors
In GHC.Tc.Errors.reportWanteds we suppress some errors in favour of
others. But the mechanism had grown crufty, and #27331 exposed a bug.
This MR makes it simpler and more uniform, by doing everything via
the `ei_suppress` field of the ErrorItem.
Claude then found another closely-related bug (see the review linked
on !16564). That bug is immortalised in new test T18851d.
I'm still not happy with `ignoreConstraint` but we can worry about
that another day; I have not touched it.
- - - - -
70 changed files:
- + changelog.d/27556
- + changelog.d/T27731
- + changelog.d/allow-gadt-prefix-con-parens
- + changelog.d/implicit-field-strictness-warning
- changelog.d/lazy-field-annotations
- compiler/GHC/CoreToStg/Prep.hs
- compiler/GHC/Driver/Flags.hs
- compiler/GHC/Driver/Session.hs
- compiler/GHC/Hs/Decls.hs
- compiler/GHC/Hs/Instances.hs
- compiler/GHC/Hs/Pat.hs
- compiler/GHC/Hs/Type.hs
- compiler/GHC/HsToCore/Quote.hs
- compiler/GHC/Iface/Ext/Ast.hs
- compiler/GHC/Rename/HsType.hs
- compiler/GHC/Rename/Module.hs
- compiler/GHC/Tc/Errors.hs
- compiler/GHC/Tc/Errors/Ppr.hs
- compiler/GHC/Tc/Errors/Types.hs
- compiler/GHC/Tc/Gen/HsType.hs
- compiler/GHC/Tc/TyCl.hs
- compiler/GHC/Tc/Types/Constraint.hs
- compiler/GHC/Types/Error/Codes.hs
- compiler/GHC/Types/Hint.hs
- compiler/GHC/Types/Hint/Ppr.hs
- compiler/GHC/Types/Tickish.hs
- compiler/Language/Haskell/Syntax/Decls.hs
- compiler/Language/Haskell/Syntax/Type.hs
- docs/users_guide/exts/gadt_syntax.rst
- docs/users_guide/exts/strict.rst
- docs/users_guide/using-warnings.rst
- − testsuite/tests/gadt/T14320.stderr
- testsuite/tests/gadt/T18191.hs
- testsuite/tests/gadt/T18191.stderr
- + testsuite/tests/gadt/T27423a.hs
- + testsuite/tests/gadt/T27423b.hs
- + testsuite/tests/gadt/T27423b.stderr
- testsuite/tests/gadt/all.T
- testsuite/tests/printer/Makefile
- + testsuite/tests/printer/T27423c.hs
- testsuite/tests/printer/all.T
- + testsuite/tests/simplCore/should_compile/T27556.hs
- + testsuite/tests/simplCore/should_compile/T27556.script
- testsuite/tests/simplCore/should_compile/all.T
- testsuite/tests/th/T17380.stderr
- testsuite/tests/th/T18612.stderr
- + testsuite/tests/th/T27034.hs
- + testsuite/tests/th/T27034.stderr
- testsuite/tests/th/T8761.stderr
- testsuite/tests/th/all.T
- testsuite/tests/typecheck/should_fail/FunDepOrigin1b.hs
- + testsuite/tests/typecheck/should_fail/T18851e.hs
- + testsuite/tests/typecheck/should_fail/T18851e.stderr
- + testsuite/tests/typecheck/should_fail/T27731.hs
- + testsuite/tests/typecheck/should_fail/T27731.stderr
- testsuite/tests/typecheck/should_fail/all.T
- + testsuite/tests/warnings/should_compile/T16836a.hs
- + testsuite/tests/warnings/should_compile/T16836a.stderr
- + testsuite/tests/warnings/should_compile/T16836b.hs
- + testsuite/tests/warnings/should_compile/T16836c.hs
- + testsuite/tests/warnings/should_compile/T16836c.stderr
- + testsuite/tests/warnings/should_compile/T16836d.hs
- + testsuite/tests/warnings/should_compile/T16836d.stderr
- testsuite/tests/warnings/should_compile/all.T
- utils/check-exact/ExactPrint.hs
- utils/haddock/haddock-api/src/Haddock/Backends/Hoogle.hs
- utils/haddock/haddock-api/src/Haddock/Convert.hs
- utils/haddock/haddock-api/src/Haddock/GhcUtils.hs
- utils/haddock/haddock-api/src/Haddock/Interface/Rename.hs
- utils/haddock/haddock-api/src/Haddock/Types.hs
The diff was not included because it is too large.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/be4acc52911ebb0dd9c2060e148fab…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/be4acc52911ebb0dd9c2060e148fab…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][wip/marge_bot_batch_merge_job] 4 commits: Parentheses in prefix GADT constructors (#27423)
by Marge Bot (@marge-bot) 04 Sep '26
by Marge Bot (@marge-bot) 04 Sep '26
04 Sep '26
Marge Bot pushed to branch wip/marge_bot_batch_merge_job at Glasgow Haskell Compiler / GHC
Commits:
eaa95a6f by Andrei Borzenkov at 2026-09-04T08:08:40-04:00
Parentheses in prefix GADT constructors (#27423)
Updated `splitLHsGadtTy` to allow looking
through the parentheses for inner binders. General example
of a code pattern that's allowed now:
data S a where
MkS :: (forall a. S a)
That should work now with any combination of nested
foralls and parentheses.
We don't perform parenthesis unwrapping for record
GADT constructors in accordance with GHC Proposal #402.
To this end `con_inner_bndrs` no longer stores plain forall
telescopes: `[HsForAllTelescope pass]` is replaced with
`[LHsGadtTelescope pass]`, a new `HsArg`-style type whose
`HsGadtForAll` holds an inner telescope and whose `HsGadtPar`
holds a pair of parentheses. The parentheses carry no meaning
for renaming or type checking; the only reason to record them
is exact-printing.
Updated `pprConDecl` to improve the `parse == parse . ppr . parse`
property of GADT pretty-printing.
The pretty printer can now output code that's similar to this:
data T a where
MkT1 :: (forall a. T a)
MkT2 :: forall . forall a. T a
These are special cases of inner forall binders
for prefix GADT constructors, when we have either
implicit or zero explicit outer binders.
- - - - -
639456a8 by Evgeny Malyshev at 2026-09-04T08:09:25-04:00
HsPat: Add spacing to unary tuple patterns
Use hsep when printing unary boxed TuplePat applications so that the
MkSolo constructor is separated from its argument. The old hcat rendered
wildcard and parenthesized arguments as MkSolo_ and MkSolo().
Add T27034 to exercise the HsPat splice-dump path with wildcard and
variable arguments, and update existing affected golden output.
Fixes #27034
Assisted-By: OpenAI Codex
- - - - -
8d47396e by Simon Jakobi at 2026-09-04T08:41:13-04:00
X86 NCG: use btr/bts/btc for single-bit operations
Previously the Cmm patterns
x & ~(1 << i)
x | (1 << i)
x ^ (1 << i)
compiled to mov/shl/not/and-style sequences of 3-4 instructions. Now
they compile to a single btr, bts or btc, matching what C compilers
produce.
When the bit index is a literal, constant folding has already collapsed
these patterns into ones with a literal mask, such as
x & 0xfffffeffffffffff for x & ~(1 << 40). Such masks are now also
compiled to a bit-test instruction when they don't fit in an imm32 and
would otherwise have to be loaded into a register first.
For a variable bit index, this applies only when the shift is unchecked
(uncheckedShiftL#, Data.Bits.unsafeShiftL): the bounds-checked shiftL
used by e.g. the default clearBit/setBit/complementBit implementations
wraps the shift in a bounds mask that this optimisation does not see
through. With a literal index, the bounds mask is constant-folded away,
so the checked operations benefit too.
See Note [Bit-test instructions] in GHC.CmmToAsm.X86.CodeGen.
Fixes #25233.
Assisted-by: Claude Fable 5
- - - - -
c396d8cb by Simon Peyton Jones at 2026-09-04T08:41:19-04:00
Move HsStatic free-var test to typechecker
A `static` form should have no free *term* variables, but it
can have free *type* variables. Alas, the renamer does not really know what
is a term variable and what is a type variable, because of required type
arguments. This patch moves the test to the typechecker, which does know.
Addresses #27664
- - - - -
54 changed files:
- + changelog.d/T27764
- + changelog.d/allow-gadt-prefix-con-parens
- + changelog.d/ncg-x86-bit-test-instructions
- compiler/GHC/CmmToAsm/X86/CodeGen.hs
- compiler/GHC/CmmToAsm/X86/Instr.hs
- compiler/GHC/CmmToAsm/X86/Ppr.hs
- compiler/GHC/Hs/Decls.hs
- compiler/GHC/Hs/Expr.hs
- compiler/GHC/Hs/Instances.hs
- compiler/GHC/Hs/Pat.hs
- compiler/GHC/Hs/Type.hs
- compiler/GHC/HsToCore/Quote.hs
- compiler/GHC/Iface/Ext/Ast.hs
- compiler/GHC/Rename/Expr.hs
- compiler/GHC/Rename/HsType.hs
- compiler/GHC/Rename/Module.hs
- compiler/GHC/Tc/Gen/Expr.hs
- compiler/GHC/Tc/Gen/HsType.hs
- compiler/GHC/Tc/TyCl.hs
- compiler/Language/Haskell/Syntax/Decls.hs
- compiler/Language/Haskell/Syntax/Type.hs
- docs/users_guide/exts/gadt_syntax.rst
- + testsuite/tests/codeGen/should_gen_asm/T25233.asm
- + testsuite/tests/codeGen/should_gen_asm/T25233.hs
- + testsuite/tests/codeGen/should_gen_asm/T25233b.asm
- + testsuite/tests/codeGen/should_gen_asm/T25233b.cmm
- testsuite/tests/codeGen/should_gen_asm/all.T
- − testsuite/tests/gadt/T14320.stderr
- testsuite/tests/gadt/T18191.hs
- testsuite/tests/gadt/T18191.stderr
- + testsuite/tests/gadt/T27423a.hs
- + testsuite/tests/gadt/T27423b.hs
- + testsuite/tests/gadt/T27423b.stderr
- testsuite/tests/gadt/all.T
- testsuite/tests/printer/Makefile
- + testsuite/tests/printer/T27423c.hs
- testsuite/tests/printer/all.T
- testsuite/tests/rename/should_fail/RnStaticPointersFail01.stderr
- testsuite/tests/rename/should_fail/RnStaticPointersFail03.stderr
- testsuite/tests/rename/should_fail/T26545.stderr
- testsuite/tests/th/T17380.stderr
- testsuite/tests/th/T18612.stderr
- + testsuite/tests/th/T27034.hs
- + testsuite/tests/th/T27034.stderr
- testsuite/tests/th/T8761.stderr
- testsuite/tests/th/all.T
- + testsuite/tests/typecheck/should_compile/T27664.hs
- testsuite/tests/typecheck/should_compile/all.T
- utils/check-exact/ExactPrint.hs
- utils/haddock/haddock-api/src/Haddock/Backends/Hoogle.hs
- utils/haddock/haddock-api/src/Haddock/Convert.hs
- utils/haddock/haddock-api/src/Haddock/GhcUtils.hs
- utils/haddock/haddock-api/src/Haddock/Interface/Rename.hs
- utils/haddock/haddock-api/src/Haddock/Types.hs
The diff was not included because it is too large.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/4da8f180b9d82b0169fee4100a611b…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/4da8f180b9d82b0169fee4100a611b…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][master] HsPat: Add spacing to unary tuple patterns
by Marge Bot (@marge-bot) 04 Sep '26
by Marge Bot (@marge-bot) 04 Sep '26
04 Sep '26
Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC
Commits:
639456a8 by Evgeny Malyshev at 2026-09-04T08:09:25-04:00
HsPat: Add spacing to unary tuple patterns
Use hsep when printing unary boxed TuplePat applications so that the
MkSolo constructor is separated from its argument. The old hcat rendered
wildcard and parenthesized arguments as MkSolo_ and MkSolo().
Add T27034 to exercise the HsPat splice-dump path with wildcard and
variable arguments, and update existing affected golden output.
Fixes #27034
Assisted-By: OpenAI Codex
- - - - -
7 changed files:
- compiler/GHC/Hs/Pat.hs
- testsuite/tests/th/T17380.stderr
- testsuite/tests/th/T18612.stderr
- + testsuite/tests/th/T27034.hs
- + testsuite/tests/th/T27034.stderr
- testsuite/tests/th/T8761.stderr
- testsuite/tests/th/all.T
Changes:
=====================================
compiler/GHC/Hs/Pat.hs
=====================================
@@ -443,7 +443,7 @@ pprPat (TuplePat _ pats bx)
-- `MkSolo x`, not `(x)`
| [pat] <- pats
, Boxed <- bx
- = hcat [text (mkTupleStr Boxed dataName 1), pprParendLPat appPrec pat]
+ = hsep [text (mkTupleStr Boxed dataName 1), pprParendLPat appPrec pat]
| otherwise
= tupleParens (boxityTupleSort bx) (pprWithCommas ppr pats)
pprPat (SumPat _ pat alt arity) = sumParens (pprAlternative ppr pat alt arity)
=====================================
testsuite/tests/th/T17380.stderr
=====================================
@@ -19,8 +19,8 @@ T17380.hs:15:6: error: [GHC-83865]
T17380.hs:18:7: error: [GHC-83865]
• Couldn't match expected type: Maybe String
with actual type: Solo (Maybe String)
- • In the pattern: MkSolo(Just "wat")
- In an equation for ‘quux’: quux (MkSolo(Just "wat")) = Just "frerf"
+ • In the pattern: MkSolo (Just "wat")
+ In an equation for ‘quux’: quux (MkSolo (Just "wat")) = Just "frerf"
T17380.hs:21:8: error: [GHC-83865]
• Couldn't match type: Maybe String
=====================================
testsuite/tests/th/T18612.stderr
=====================================
@@ -8,6 +8,6 @@ T18612.hs:(10,7)-(11,75): Splicing type
======>
Identity (Solo ()) -> Identity (Solo ())
T18612.hs:12:4-36: Splicing pattern
- conP 'Identity [tupP [tupP []]] ======> Identity (MkSolo())
+ conP 'Identity [tupP [tupP []]] ======> Identity (MkSolo ())
T18612.hs:12:41-78: Splicing expression
conE 'Identity `appE` tupE [tupE []] ======> Identity (MkSolo ())
=====================================
testsuite/tests/th/T27034.hs
=====================================
@@ -0,0 +1,7 @@
+{-# LANGUAGE TemplateHaskell #-}
+module T27034 where
+
+import Language.Haskell.TH
+
+f $(tupP [wildP]) = ()
+g $(tupP [varP (mkName "x")]) = x
=====================================
testsuite/tests/th/T27034.stderr
=====================================
@@ -0,0 +1,4 @@
+T27034.hs:6:4-17: Splicing pattern
+ tupP [wildP] ======> MkSolo _
+T27034.hs:7:4-29: Splicing pattern
+ tupP [varP (mkName "x")] ======> MkSolo x
=====================================
testsuite/tests/th/T8761.stderr
=====================================
@@ -28,7 +28,7 @@ T8761.hs:(16,1)-(39,13): Splicing declarations
return pats
======>
pattern Q1 x1 x2 x3 <- ((x1, x2), [x3], _, _)
- pattern x1 `Q2` x2 = MkSolo(x1, x2)
+ pattern x1 `Q2` x2 = MkSolo (x1, x2)
pattern Q3{qx3, qy3, qz3} <- ((qx3, qy3), [qz3]) where
Q3 qx3 qy3 qz3 = ((qx3, qy3), [qz3])
T8761.hs:(42,1)-(46,29): Splicing declarations
=====================================
testsuite/tests/th/all.T
=====================================
@@ -655,6 +655,7 @@ test('T26099', normal, compile_fail, [''])
test('T8306_th', only_ways(['ghci']), ghci_script, ['T8306_th.script'])
test('T26862_th', only_ways(['ghci']), ghci_script, ['T26862_th.script'])
test('T27022', normal, compile_and_run, [''])
+test('T27034', normal, compile, ['-v0 -ddump-splices -dsuppress-uniques'])
test('T20902', normal, compile, [''])
test('TH_cvt_DefaultDataInstDecl', normal, compile_fail, ['-v0'])
test('TH_cvt_IllegalDeclaration', normal, compile_fail, ['-v0'])
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/639456a83d19137677e9a2dd59ef9b4…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/639456a83d19137677e9a2dd59ef9b4…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][master] Parentheses in prefix GADT constructors (#27423)
by Marge Bot (@marge-bot) 04 Sep '26
by Marge Bot (@marge-bot) 04 Sep '26
04 Sep '26
Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC
Commits:
eaa95a6f by Andrei Borzenkov at 2026-09-04T08:08:40-04:00
Parentheses in prefix GADT constructors (#27423)
Updated `splitLHsGadtTy` to allow looking
through the parentheses for inner binders. General example
of a code pattern that's allowed now:
data S a where
MkS :: (forall a. S a)
That should work now with any combination of nested
foralls and parentheses.
We don't perform parenthesis unwrapping for record
GADT constructors in accordance with GHC Proposal #402.
To this end `con_inner_bndrs` no longer stores plain forall
telescopes: `[HsForAllTelescope pass]` is replaced with
`[LHsGadtTelescope pass]`, a new `HsArg`-style type whose
`HsGadtForAll` holds an inner telescope and whose `HsGadtPar`
holds a pair of parentheses. The parentheses carry no meaning
for renaming or type checking; the only reason to record them
is exact-printing.
Updated `pprConDecl` to improve the `parse == parse . ppr . parse`
property of GADT pretty-printing.
The pretty printer can now output code that's similar to this:
data T a where
MkT1 :: (forall a. T a)
MkT2 :: forall . forall a. T a
These are special cases of inner forall binders
for prefix GADT constructors, when we have either
implicit or zero explicit outer binders.
- - - - -
29 changed files:
- + changelog.d/allow-gadt-prefix-con-parens
- compiler/GHC/Hs/Decls.hs
- compiler/GHC/Hs/Instances.hs
- compiler/GHC/Hs/Type.hs
- compiler/GHC/HsToCore/Quote.hs
- compiler/GHC/Iface/Ext/Ast.hs
- compiler/GHC/Rename/HsType.hs
- compiler/GHC/Rename/Module.hs
- compiler/GHC/Tc/Gen/HsType.hs
- compiler/GHC/Tc/TyCl.hs
- compiler/Language/Haskell/Syntax/Decls.hs
- compiler/Language/Haskell/Syntax/Type.hs
- docs/users_guide/exts/gadt_syntax.rst
- − testsuite/tests/gadt/T14320.stderr
- testsuite/tests/gadt/T18191.hs
- testsuite/tests/gadt/T18191.stderr
- + testsuite/tests/gadt/T27423a.hs
- + testsuite/tests/gadt/T27423b.hs
- + testsuite/tests/gadt/T27423b.stderr
- testsuite/tests/gadt/all.T
- testsuite/tests/printer/Makefile
- + testsuite/tests/printer/T27423c.hs
- testsuite/tests/printer/all.T
- utils/check-exact/ExactPrint.hs
- utils/haddock/haddock-api/src/Haddock/Backends/Hoogle.hs
- utils/haddock/haddock-api/src/Haddock/Convert.hs
- utils/haddock/haddock-api/src/Haddock/GhcUtils.hs
- utils/haddock/haddock-api/src/Haddock/Interface/Rename.hs
- utils/haddock/haddock-api/src/Haddock/Types.hs
Changes:
=====================================
changelog.d/allow-gadt-prefix-con-parens
=====================================
@@ -0,0 +1,15 @@
+section: language
+synopsis: Allow parentheses in prefix GADT constructor declarations, as specified
+ by GHC Proposal #402 "Stable GADT constructor syntax".
+issues: #27423
+mrs: !16321
+
+description:
+ Parenthesized types are now accepted in prefix GADT constructor declarations,
+ even when they contain explicit ``forall`` quantifiers. For example:
+
+ data T where
+ MkT :: (forall a. a -> b -> T)
+
+ This is equivalent to ``MkT :: forall {b}. (forall a. a -> b -> T)``, so the
+ forall-or-nothing rule continues to be respected.
=====================================
compiler/GHC/Hs/Decls.hs
=====================================
@@ -974,14 +974,37 @@ pprConDecl (ConDeclGADT { con_names = cons
, con_mb_cxt = mcxt, con_g_args = args
, con_res_ty = res_ty, con_modifiers = mods, con_doc = doc })
= pprMaybeWithDoc doc $ pprLHsModifiers mods <+> ppr_con_names (toList cons) <+> dcolon
- <+> (sep [pprHsOuterSigTyVarBndrs outer_bndrs
- <+> hsep (map pprHsForAllTelescope inner_bndrs)
- <+> pprLHsContext mcxt,
- sep (ppr_args args ++ [ppr res_ty]) ])
+ <+> sep [ppr_outer_bndrs, ppr_inner_bndrs (
+ sep [ pprLHsContext mcxt,
+ sep (ppr_args args ++ [ppr res_ty])])]
where
ppr_args (PrefixConGADT _ args) = map (pprHsConDeclFieldWith (\arr tyDoc -> tyDoc <+> pprHsModifiedFunArr arr)) args
ppr_args (RecConGADT _ fields) = [pprHsConDeclRecFields (unLoc fields) <+> arrow]
+ -- pprint all parentheses and foralls, so parse == parse . ppr . parse
+ ppr_inner_bndrs :: SDoc -> SDoc
+ ppr_inner_bndrs tyDoc = foldr ppr_inner_bndr (tyDoc <> close_parens) inner_bndrs
+
+ ppr_inner_bndr (L _ HsGadtPar{}) rest = lparen <> rest
+ ppr_inner_bndr (L _ (HsGadtForAll _ tele)) rest
+ | HsForAllInvis {hsf_invis_bndrs=[]} <- tele = empty_forall <+> rest
+ | otherwise = pprHsForAllTelescope tele <+> rest
+
+ -- for each open paren generate a closed one
+ close_parens = hcat [ rparen | L _ HsGadtPar{} <- inner_bndrs ]
+
+ -- pprint empty explicit outer forall as `forall.` if there are inner binders, because otherwise
+ -- `forall. forall a. ...` would become `forall a. ...` and that would parse into
+ -- different AST, thus breaking parse == parse . ppr . parse property
+ ppr_outer_bndrs
+ | HsOuterExplicit{hso_bndrs = []} <- outer_bndrs
+ , not (null inner_bndrs)
+ = empty_forall
+ | otherwise
+ = pprHsOuterSigTyVarBndrs outer_bndrs
+
+ empty_forall = forAllLit <> dot
+
ppr_con_names :: (OutputableBndr a) => [GenLocated l a] -> SDoc
ppr_con_names = pprWithCommas (pprPrefixOcc . unLoc)
=====================================
compiler/GHC/Hs/Instances.hs
=====================================
@@ -623,6 +623,11 @@ deriving instance Data (HsForAllTelescope GhcPs)
deriving instance Data (HsForAllTelescope GhcRn)
deriving instance Data (HsForAllTelescope GhcTc)
+-- deriving instance (DataIdLR p p) => Data (HsGadtTelescope p)
+deriving instance Data (HsGadtTelescope GhcPs)
+deriving instance Data (HsGadtTelescope GhcRn)
+deriving instance Data (HsGadtTelescope GhcTc)
+
-- deriving instance (DataIdLR p p) => Data (HsTyVarBndr p)
deriving instance (Data flag) => Data (HsTyVarBndr flag GhcPs)
deriving instance (Data flag) => Data (HsTyVarBndr flag GhcRn)
=====================================
compiler/GHC/Hs/Type.hs
=====================================
@@ -39,6 +39,7 @@ module GHC.Hs.Type (
HsLit(..),
HsIPName(..), hsIPNameFS,
HsArg(..), numVisibleArgs, pprHsArgsApp,
+ HsGadtTelescope(..),
LHsTypeArg, lhsTypeArgSrcSpan,
OutputableBndrFlag,
@@ -71,6 +72,7 @@ module GHC.Hs.Type (
hsLTyVarName, hsLTyVarNames,
hsForAllTelescopeBndrs,
hsForAllTelescopeNames,
+ gadtArgTelescopes, gadtTelescopeBndrs, mkHsGadtForAlls,
hsLTyVarLocName, hsExplicitLTyVarNames,
splitLHsInstDeclTy, getLHsInstDeclHead, getLHsInstDeclClass_maybe,
splitLHsPatSynTy,
@@ -621,6 +623,15 @@ hsForAllTelescopeNames :: HsForAllTelescope (GhcPass p) -> [IdP (GhcPass p)]
hsForAllTelescopeNames (HsForAllVis _ bndrs) = hsLTyVarNames bndrs
hsForAllTelescopeNames (HsForAllInvis _ bndrs) = hsLTyVarNames bndrs
+gadtArgTelescopes :: [LHsGadtTelescope (GhcPass p)] -> [HsForAllTelescope (GhcPass p)]
+gadtArgTelescopes args = [ tele | L _ (HsGadtForAll _ tele) <- args ]
+
+gadtTelescopeBndrs :: [LHsGadtTelescope (GhcPass p)] -> [LHsTyVarBndr ForAllTyFlag (GhcPass p)]
+gadtTelescopeBndrs = concatMap hsForAllTelescopeBndrs . gadtArgTelescopes
+
+mkHsGadtForAlls :: [HsForAllTelescope (GhcPass p)] -> [HsGadtTelescope (GhcPass p)]
+mkHsGadtForAlls = map (HsGadtForAll noExtField)
+
hsExplicitLTyVarNames :: LHsQTyVars (GhcPass p) -> [IdP (GhcPass p)]
-- Explicit variables only
hsExplicitLTyVarNames qtvs = hsLTyVarNames (hsQTvExplicit qtvs)
@@ -750,6 +761,14 @@ type instance XArgPar (GhcPass _) = SrcSpan
type instance XXArg (GhcPass _) = DataConCantHappen
+type instance XGadtForAll (GhcPass _) = NoExtField
+
+type instance XGadtPar GhcPs = (EpToken "(", EpToken ")")
+type instance XGadtPar GhcRn = NoExtField
+type instance XGadtPar GhcTc = NoExtField
+
+type instance XXGadtArg (GhcPass _) = DataConCantHappen
+
type instance XPrefixCon (GhcPass p) = NoExtField
type instance XInfixCon (GhcPass p) = NoExtField
type instance XRecCon (GhcPass p) = (EpToken "{", EpToken "}")
@@ -877,44 +896,55 @@ splitLHsSigmaTyInvis ty
= (tvs, ctxt, ty2)
-- | Decompose a GADT type into its constituent parts.
--- Returns @(outer_bndrs, mb_ctxt, body)@, where:
+-- Returns @(outer_bndrs, inner_bndrs, mb_ctxt, body)@, where:
--
-- * @outer_bndrs@ are 'HsOuterExplicit' if the type has explicit, outermost
-- type variable binders. Otherwise, they are 'HsOuterImplicit'.
--
+-- * @inner_bndrs@ are the remaining @forall@ telescopes, interleaved with the
+-- parentheses that enclose them.
+--
-- * @mb_ctxt@ is @Just@ the context, if it is provided.
-- Otherwise, it is @Nothing@.
--
-- * @body@ is the body of the type after the optional @forall@s and context.
--
--- This function is careful not to look through parentheses.
+-- This function does look through parentheses, but it does not discard them:
+-- they are syntactically significant, so they are recorded in @inner_bndrs@.
-- See @Note [GADT abstract syntax] (Wrinkle: No nested foralls or contexts)@
--- "GHC.Hs.Decls" for why this is important.
+-- in "GHC.Hs.Decls" for why this is important.
splitLHsGadtTy ::
LHsSigType GhcPs
- -> (HsOuterSigTyVarBndrs GhcPs, [HsForAllTelescope GhcPs], Maybe (LHsContext GhcPs), LHsType GhcPs)
+ -> (HsOuterSigTyVarBndrs GhcPs, [LHsGadtTelescope GhcPs], Maybe (LHsContext GhcPs), LHsType GhcPs)
splitLHsGadtTy (L _ sig_ty)
| (outer_bndrs, sigma_ty) <- split_outer_bndrs sig_ty
, (inner_bndrs, phi_ty) <- split_inner_bndrs sigma_ty
, (mb_ctxt, rho_ty) <- splitLHsQualTy_KP phi_ty
- = case rho_ty of
- L _ (HsFunTy _ _ (L _ (XHsType HsRecTy{})) _) | not (null inner_bndrs)
+ = if is_gadt_rec_ty rho_ty && not (null inner_bndrs)
-- Bad! Record GADTs are not allowed to have inner_bndrs,
-- undo the split to get a proper error message later
- -> (outer_bndrs, [], Nothing, sigma_ty)
- _ -> (outer_bndrs, inner_bndrs, mb_ctxt, rho_ty)
+ then (outer_bndrs, [], Nothing, sigma_ty)
+ else (outer_bndrs, inner_bndrs, mb_ctxt, rho_ty)
where
split_outer_bndrs :: HsSigType GhcPs -> (HsOuterSigTyVarBndrs GhcPs, LHsType GhcPs)
split_outer_bndrs (HsSig{sig_bndrs = outer_bndrs, sig_body = body_ty}) =
(outer_bndrs, body_ty)
- split_inner_bndrs :: LHsType GhcPs -> ([HsForAllTelescope GhcPs], LHsType GhcPs)
- split_inner_bndrs (L _ HsForAllTy { hst_tele = tele
+ split_inner_bndrs ::
+ LHsType GhcPs -> ([LHsGadtTelescope GhcPs], LHsType GhcPs)
+ split_inner_bndrs (L l HsForAllTy { hst_tele = tele
, hst_body = body })
- = let ~(teles, t) = split_inner_bndrs body
- in (tele:teles, t)
+ = let ~(args, t) = split_inner_bndrs body
+ in (L l (HsGadtForAll noExtField tele) : args, t)
+ split_inner_bndrs (L l (HsParTy toks ty))
+ = let ~(args, t) = split_inner_bndrs ty
+ in (L l (HsGadtPar toks) : args, t)
split_inner_bndrs t = ([], t)
+ -- type of form {fld :: ty, ...} -> ResTy
+ is_gadt_rec_ty (L _ (HsFunTy _ _ (L _ (XHsType HsRecTy{})) _)) = True
+ is_gadt_rec_ty _ = False
+
-- | Decompose a type of the form @forall <tvs>. body@ into its constituent
-- parts. Only splits type variable binders that
-- were quantified invisibly (e.g., @forall a.@, with a dot).
@@ -1283,6 +1313,11 @@ instance OutputableBndrId p
ppr (HsForAllInvis { hsf_invis_bndrs = bndrs }) =
text "HsForAllInvis:" <+> ppr bndrs
+instance OutputableBndrId p
+ => Outputable (HsGadtTelescope (GhcPass p)) where
+ ppr (HsGadtForAll _ tele) = text "HsGadtForAll" <+> ppr tele
+ ppr (HsGadtPar _) = text "HsGadtPar"
+
instance (OutputableBndrId p, OutputableBndrFlag flag p)
=> Outputable (HsTyVarBndr flag (GhcPass p)) where
ppr = pprTyVarBndr
@@ -1611,3 +1646,5 @@ type instance Anno (HsConDeclRecField (GhcPass p)) = SrcSpanAnnA
type instance Anno (FieldOcc (GhcPass p)) = SrcSpanAnnA
type instance Anno (HsModifierOf ty (GhcPass p)) = SrcSpanAnnA
+
+type instance Anno (HsGadtTelescope (GhcPass p)) = SrcSpanAnnA
=====================================
compiler/GHC/HsToCore/Quote.hs
=====================================
@@ -924,11 +924,13 @@ repC (L l (ConDeclGADT { con_names = cons
= notHandledL (locA l) ThDataConVisibleForall
where
- no_explicit_forall = nullOuterExplicit outer_bndrs && null inner_bndrs
+ inner_teles = gadtArgTelescopes inner_bndrs
+
+ no_explicit_forall = nullOuterExplicit outer_bndrs && null inner_teles
no_context = isNothing mcxt
m_invis_inner_bndrs :: Maybe [[LHsTyVarBndr Specificity GhcRn]]
- m_invis_inner_bndrs = traverse get_invis_bndrs inner_bndrs
+ m_invis_inner_bndrs = traverse get_invis_bndrs inner_teles
get_invis_bndrs :: HsForAllTelescope GhcRn -> Maybe [LHsTyVarBndr Specificity GhcRn]
get_invis_bndrs HsForAllVis{} = Nothing
=====================================
compiler/GHC/Iface/Ext/Ast.hs
=====================================
@@ -1789,7 +1789,7 @@ instance ToHie (LocatedA (ConDecl GhcRn)) where
HsOuterExplicit{} -> []
exp_bndrs =
[ L l (updateHsTyVarBndrFlag Invisible b) | L l b <- hsOuterExplicitBndrs outer_bndrs ]
- ++ concatMap hsForAllTelescopeBndrs inner_bndrs
+ ++ concatMap hsForAllTelescopeBndrs (gadtArgTelescopes inner_bndrs)
ConDeclH98 { con_name = name, con_ex_tvs = qvars
, con_mb_cxt = ctx, con_args = dets
, con_doc = doc} ->
=====================================
compiler/GHC/Rename/HsType.hs
=====================================
@@ -28,8 +28,7 @@ module GHC.Rename.HsType (
checkPrecMatch, checkSectionPrec,
-- Binding related stuff
- bindHsOuterTyVarBndrs, bindHsForAllTelescope,
- bindHsForAllTelescopes,
+ bindHsOuterTyVarBndrs, bindHsForAllTelescope, bindHsGadtTelescopes,
bindLHsTyVarBndr, bindLHsTyVarBndrs, WarnUnusedForalls(..),
rnImplicitTvOccs, bindSigTyVarsFV, bindHsQTyVars,
FreeKiTyVars, filterInScopeM,
@@ -37,7 +36,7 @@ module GHC.Rename.HsType (
extractHsTysRdrTyVars, extractRdrKindSigVars,
extractConDeclGADTDetailsTyVars, extractDataDefnKindVars,
extractHsOuterTvBndrs, extractHsTyArgRdrKiTyVars,
- extractHsForAllTelescopes,
+ extractHsGadtTelescopes,
nubL, nubN,
-- Error helpers
@@ -1249,16 +1248,19 @@ bindHsForAllTelescope doc tele thing_inside =
checkForAllTelescopeWildcardBndrs doc bndrs'
thing_inside $ mkHsForAllInvisTele noAnn bndrs'
-bindHsForAllTelescopes :: HsDocContext
- -> [HsForAllTelescope GhcPs]
- -> ([HsForAllTelescope GhcRn] -> RnM (a, FreeNames))
- -> RnM (a, FreeNames)
-bindHsForAllTelescopes _ [] thing_inside =
+bindHsGadtTelescopes :: HsDocContext
+ -> [LHsGadtTelescope GhcPs]
+ -> ([LHsGadtTelescope GhcRn] -> RnM (a, FreeNames))
+ -> RnM (a, FreeNames)
+bindHsGadtTelescopes _ [] thing_inside =
thing_inside []
-bindHsForAllTelescopes doc (tele:teles) thing_inside =
- bindHsForAllTelescope doc tele $ \tele' ->
- bindHsForAllTelescopes doc teles $ \teles' ->
- thing_inside (tele':teles')
+bindHsGadtTelescopes doc (L l HsGadtPar{} : args) thing_inside =
+ bindHsGadtTelescopes doc args $ \args' ->
+ thing_inside (L l (HsGadtPar noExtField) : args')
+bindHsGadtTelescopes doc (L l (HsGadtForAll _ tele) : args) thing_inside =
+ bindHsForAllTelescope doc tele $ \tele' ->
+ bindHsGadtTelescopes doc args $ \args' ->
+ thing_inside (L l (HsGadtForAll noExtField tele') : args')
-- See Note [Wildcard binders in disallowed contexts] in GHC.Hs.Type
checkForAllTelescopeWildcardBndrs :: HsDocContext
@@ -2286,13 +2288,15 @@ extractHsOuterTvBndrs outer_bndrs body_fvs =
HsOuterImplicit{} -> body_fvs
HsOuterExplicit{hso_bndrs = bndrs} -> extract_hs_tv_bndrs bndrs [] body_fvs
-extractHsForAllTelescopes :: [HsForAllTelescope GhcPs]
- -> FreeKiTyVars -- Free in body
- -> FreeKiTyVars -- Free in result
-extractHsForAllTelescopes [] body_fvs = body_fvs
-extractHsForAllTelescopes (tele:teles) body_fvs =
+extractHsGadtTelescopes :: [LHsGadtTelescope GhcPs]
+ -> FreeKiTyVars -- Free in body
+ -> FreeKiTyVars -- Free in result
+extractHsGadtTelescopes [] body_fvs = body_fvs
+extractHsGadtTelescopes (L _ HsGadtPar{} : args) body_fvs =
+ extractHsGadtTelescopes args body_fvs
+extractHsGadtTelescopes (L _ (HsGadtForAll _ tele) : args) body_fvs =
extract_hs_for_all_telescope tele [] $
- extractHsForAllTelescopes teles body_fvs
+ extractHsGadtTelescopes args body_fvs
extract_hs_tv_bndrs :: [LHsTyVarBndr flag GhcPs]
-> FreeKiTyVars -- Accumulator
=====================================
compiler/GHC/Rename/Module.hs
=====================================
@@ -2644,7 +2644,7 @@ rnConDecl (ConDeclGADT { con_names = names
-- See #14808.
implicit_bndrs =
extractHsOuterTvBndrs outer_bndrs $
- extractHsForAllTelescopes inner_bndrs $
+ extractHsGadtTelescopes inner_bndrs $
extractHsTysRdrTyVars (hsConDeclTheta mcxt) $
extractConDeclGADTDetailsTyVars args $
extractHsTysRdrTyVars [res_ty] []
@@ -2652,7 +2652,7 @@ rnConDecl (ConDeclGADT { con_names = names
; let ctxt = ConDeclCtx (toList new_names)
; bindHsOuterTyVarBndrs ctxt Nothing implicit_bndrs outer_bndrs $ \outer_bndrs' ->
- bindHsForAllTelescopes ctxt inner_bndrs $ \inner_bndrs' ->
+ bindHsGadtTelescopes ctxt inner_bndrs $ \inner_bndrs' ->
do { (new_cxt, fvs1) <- rnMbContext ctxt mcxt
; (new_args, fvs2) <- rnConDeclGADTDetails (unLoc (head new_names)) ctxt args
; (new_res_ty, fvs3) <- rnLHsType ctxt res_ty
=====================================
compiler/GHC/Tc/Gen/HsType.hs
=====================================
@@ -3549,12 +3549,12 @@ tcOuterTKBndrsX skol_mode skol_info outer_bndrs thing_inside
---------------
tcGadtConTyVarBndrs :: SkolemInfo
-> HsOuterSigTyVarBndrs GhcRn
- -> [HsForAllTelescope GhcRn]
+ -> [LHsGadtTelescope GhcRn]
-> TcM a -> TcM ([TcTyVarBinder], a)
tcGadtConTyVarBndrs skol_info outer inner thing_inside
= do { (outer_bndrs, (inner_tvbs, a)) <-
tcOuterTKBndrs skol_info outer $
- tcExplicitTKBndrs skol_info (concatMap hsForAllTelescopeBndrs inner) $
+ tcExplicitTKBndrs skol_info (gadtTelescopeBndrs inner) $
thing_inside
; outer_bndrs <- scopedSortOuter outer_bndrs
; let outer_tvbs = tyVarSpecToBinders (outerTyVarBndrs outer_bndrs)
=====================================
compiler/GHC/Tc/TyCl.hs
=====================================
@@ -2287,7 +2287,7 @@ kcConDecl new_or_data _tc_res_kind
bind_con_tvbs outer_bndrs inner_bndrs thing_inside
-- Why "_Tv"? See Note [Using TyVarTvs for kind-checking GADTs]
= discardResult $ bindOuterSigTKBndrs_Tv outer_bndrs $
- bindExplicitTKBndrs_Tv (concatMap hsForAllTelescopeBndrs inner_bndrs) $
+ bindExplicitTKBndrs_Tv (gadtTelescopeBndrs inner_bndrs) $
thing_inside
{- Note [kcConDecls: kind-checking data type decls]
=====================================
compiler/Language/Haskell/Syntax/Decls.hs
=====================================
@@ -961,8 +961,9 @@ data ConDecl pass
-- cf. HsSigType that also stores the outermost sig_bndrs separately
-- from the forall telescopes in sig_body.
-- See Note [Representing type signatures] in Language.Haskell.Syntax.Type
- , con_inner_bndrs :: [HsForAllTelescope pass]
- -- ^ The forall telescopes other than the outermost invisible forall.
+ , con_inner_bndrs :: [LHsGadtTelescope pass]
+ -- ^ The forall telescopes other than the outermost invisible forall,
+ -- interleaved with the parentheses that enclose them.
, con_mb_cxt :: Maybe (LHsContext pass) -- ^ User-written context (if any)
, con_g_args :: HsConDeclGADTDetails pass -- ^ Arguments; never infix
, con_res_ty :: LHsType pass -- ^ Result type
@@ -1074,22 +1075,21 @@ the GADT type, in precisely that order. For instance:
MkT5 :: forall a. Int -> Eq a => a -> T
-- Rejected, `Eq a` is nested
MkT6 :: (forall a. a -> T)
- -- Rejected, `forall a` is nested due to the surrounding parentheses
- MkT7 :: (Eq a => a -> t)
- -- Rejected, `Eq a` is nested due to the surrounding parentheses
+ -- OK, the parentheses are recorded in con_inner_bndrs.
+ MkT7 :: (Eq a => a -> T)
+ -- OK, ditto
For the full details, see the "Formal syntax for GADTs" section of the GHC
User's Guide. GHC enforces that GADT constructors do not have nested `forall`s
-or contexts in two parts:
+or contexts in a single place:
-1. GHC, in the process of splitting apart a GADT's type,
- extracts out the leading `forall` and context (if they are provided). To
- accomplish this splitting, the renamer uses the
- GHC.Hs.Type.splitLHsGADTPrefixTy function, which is careful not to remove
- parentheses surrounding the leading `forall` or context (as these
- parentheses can be syntactically significant). If the third result returned
- by splitLHsGADTPrefixTy contains any `forall`s or contexts, then they must
- be nested, so they will be rejected.
+ GHC, in the process of splitting apart a GADT's type,
+ extracts out the leading `forall`s and context (if they are provided). To
+ accomplish this splitting, the parser uses the GHC.Hs.Type.splitLHsGadtTy
+ function, which records the parentheses that surround the leading `forall`s
+ in con_inner_bndrs (as these parentheses are syntactically significant).
+ If the body returned by splitLHsGadtTy still contains any `forall`s or
+ contexts, then they must be nested, so they will be rejected.
Note that this step applies to both prefix and record GADTs alike, as they
both have syntax which permits `forall`s and contexts. The difference is
@@ -1098,11 +1098,6 @@ or contexts in two parts:
* For prefix GADTs, this happens in the renamer (in rnConDecl), as we cannot
split until after the type operator fixities have been resolved.
* For record GADTs, this happens in the parser (in mkGadtDecl).
-2. If the GADT type is prefix, the renamer (in the ConDeclGADTPrefixPs case of
- rnConDecl) will then check for nested `forall`s/contexts in the body of a
- prefix GADT type, after it has determined what all of the argument types are.
- This step is necessary to catch examples like MkT4 above, where the nested
- quantification occurs after a visible argument type.
-}
-- | The arguments in a Haskell98-style data constructor.
=====================================
compiler/Language/Haskell/Syntax/Type.hs
=====================================
@@ -21,6 +21,7 @@ module Language.Haskell.Syntax.Type (
isHsBndrInvisible,
isHsBndrWildCard,
HsForAllTelescope(..),
+ HsGadtTelescope(..), LHsGadtTelescope, XGadtForAll, XGadtPar, XXGadtArg,
HsTyVarBndr(..), LHsTyVarBndr,
LHsQTyVars(..),
HsOuterTyVarBndrs(..), HsOuterFamEqnTyVarBndrs, HsOuterSigTyVarBndrs,
@@ -392,6 +393,42 @@ data HsForAllTelescope pass
}
| XHsForAllTelescope !(XXHsForAllTelescope pass)
+-- | A type for interleaved GADT foralls and parentheses, inspired by HsArg.
+--
+-- Here's an example:
+--
+-- data D where
+-- MkD :: forall x y. -- these go to the `con_outer_bndrs` field
+-- forall a b. ( forall c. forall d. ( forall. ...
+-- ↑ ↑ ↑ ↑ ↑ ↑
+-- 1 2 3 4 5 6
+--
+-- That would correspond to a list
+--
+-- 1 → [ HsGadtForAll
+-- 2 → , HsGadtPar
+-- 3 → , HsGadtForAll
+-- 4 → , HsGadtForAll
+-- 5 → , HsGadtPar
+-- 6 → , HsGadtForAll
+-- , ...]
+data HsGadtTelescope pass
+ = HsGadtForAll !(XGadtForAll pass) (HsForAllTelescope pass)
+ | HsGadtPar !(XGadtPar pass)
+ -- ^ `HsGadtPar` is only usefull for pretty-printing/exact-printing for recovering
+ -- parenthisis interleaved with foralls.
+ --
+ -- This approach differs from `HsPar`, which wraps the inner expression as if
+ -- surrounding it with parentheses. We can ditch the `HsPar` approach because
+ -- we know that all parentheses will be closed after the return type.
+ | XHsGadtTelescope !(XXGadtArg pass)
+
+type LHsGadtTelescope pass = XRec pass (HsGadtTelescope pass)
+
+type family XGadtForAll pass
+type family XGadtPar pass
+type family XXGadtArg pass
+
-- | Located Haskell Type Variable Binder
type LHsTyVarBndr flag pass = XRec pass (HsTyVarBndr flag pass)
-- See Note [HsType binders]
=====================================
docs/users_guide/exts/gadt_syntax.rst
=====================================
@@ -201,12 +201,12 @@ syntactically allowed. Some further various observations about this grammar:
something like ``MkS :: Int -> (forall a. a) -> S`` is allowed, since
parentheses separate the ``forall`` from the ``->``.)
-- Furthermore, GADT constructors do not permit outermost parentheses that
- surround the ``foralls`` or ``opt_ctxt``, if at least one of them are
- used. For example, ``MkU :: (forall a. a -> U)`` would be rejected, since
- it would treat the ``forall`` as being nested.
+- GADT constructors permit outermost parentheses that surround the ``foralls``
+ or ``opt_ctxt``, as well as interleaved parentheses between multiple
+ ``foralls``. For example, ``MkU :: (forall a. a -> U)`` is accepted, as is
+ ``MkW :: forall a. (forall b. a -> b -> W)``.
- Note that it is acceptable to use parentheses in a ``prefix_gadt_body``.
+ Note that it is also acceptable to use parentheses in a ``prefix_gadt_body``.
For instance, ``MkV1 :: forall a. (a) -> (V1)`` is acceptable, as is
``MkV2 :: forall a. (a -> V2)``.
=====================================
testsuite/tests/gadt/T14320.stderr deleted
=====================================
@@ -1,4 +0,0 @@
-
-T14320.hs:17:14: error: [GHC-71492]
- GADT constructor type signature cannot contain nested ‘forall’s or contexts
- In the definition of data constructor ‘TEBad’
=====================================
testsuite/tests/gadt/T18191.hs
=====================================
@@ -2,15 +2,6 @@
{-# LANGUAGE RankNTypes #-}
module T18191 where
-data T where
- MkT :: (forall a. a -> b -> T)
-
-data S a where
- MkS :: (forall a. S a)
-
-data U a where
- MkU :: (Show a => U a)
-
data Z a where
MkZ1 :: forall a. forall b. { unZ1 :: (a, b) } -> Z (a, b)
MkZ2 :: Eq a => Eq b => { unZ1 :: (a, b) } -> Z (a, b)
=====================================
testsuite/tests/gadt/T18191.stderr
=====================================
@@ -1,28 +1,16 @@
-
-T18191.hs:6:11: error: [GHC-71492]
- • GADT constructor type signature cannot contain nested ‘forall’s or contexts
- • In the definition of data constructor ‘MkT’
-
-T18191.hs:9:11: error: [GHC-71492]
- • GADT constructor type signature cannot contain nested ‘forall’s or contexts
- • In the definition of data constructor ‘MkS’
-
-T18191.hs:12:11: error: [GHC-71492]
- • GADT constructor type signature cannot contain nested ‘forall’s or contexts
- • In the definition of data constructor ‘MkU’
-
-T18191.hs:15:21: error: [GHC-71492]
+T18191.hs:6:21: error: [GHC-71492]
• GADT constructor type signature cannot contain nested ‘forall’s or contexts
• In the definition of data constructor ‘MkZ1’
-T18191.hs:15:31: error: [GHC-89246]
+T18191.hs:6:31: error: [GHC-89246]
• Record syntax is illegal here: {unZ1 :: (a, b)}
• In the definition of data constructor ‘MkZ1’
-T18191.hs:16:19: error: [GHC-71492]
+T18191.hs:7:19: error: [GHC-71492]
• GADT constructor type signature cannot contain nested ‘forall’s or contexts
• In the definition of data constructor ‘MkZ2’
-T18191.hs:16:27: error: [GHC-89246]
+T18191.hs:7:27: error: [GHC-89246]
• Record syntax is illegal here: {unZ1 :: (a, b)}
• In the definition of data constructor ‘MkZ2’
+
=====================================
testsuite/tests/gadt/T27423a.hs
=====================================
@@ -0,0 +1,46 @@
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE RequiredTypeArguments #-}
+module T27423a where
+
+data G a where
+ MkG1 :: a -> G a
+ MkG2 :: (a -> G a)
+ MkG3 :: forall a. a -> G a
+ MkG4 :: forall a. (a -> G a)
+
+-- this is equivalent to `forall {b}. (forall a. a -> b -> T)`.
+data T where
+ MkT2 :: (forall a. a -> b -> T)
+
+-- this is equivalent to `forall. (forall a. S a)`.
+data S a where
+ MkS :: (forall a. S a)
+
+-- An explicit, empty outer forall combined with a parenthesised inner one.
+data W a where
+ MkW :: forall. (forall a. W a)
+
+-- A forall and a context combined inside the same parentheses, with no
+-- outer forall at all.
+data Y a where
+ MkY :: (forall a. Show a => a -> Y a)
+
+-- Multiple, redundant nested parentheses around the whole type should
+-- be accepted.
+data H a where
+ MkH :: ((a -> H a))
+
+-- An unparenthesised outer forall together with an independent,
+-- parenthesised inner forall.
+data I a where
+ MkI :: forall a. (forall b. I (b, a))
+
+-- Visible foralls, parenthesised and not.
+data V a b where
+ MkV1 :: forall a -> forall b. (Int -> V a b)
+ MkV2 :: forall a. (forall b -> (Bool -> V a b))
+
+data P a where
+ MkP1 :: Show a => (a -> P a)
+ MkP2 :: forall a. Int -> (a -> P a)
+ MkP3 :: (forall a. Show a => (Int -> (a -> P a)))
=====================================
testsuite/tests/gadt/T27423b.hs
=====================================
@@ -0,0 +1,21 @@
+{-# LANGUAGE GADTs #-}
+module T27423b where
+
+-- Record-style GADT constructors must remain unparenthesisable, per
+-- GHC Proposal #402: this is out of scope for #27423 and should
+-- continue to be rejected.
+data T1 a where
+ MkT1 :: ({ fld :: a } -> T1 a)
+
+data T2 a where
+ MkT2 :: (forall a. { fld :: a } -> T2 a)
+
+-- Without parentheses, forall-or-nothing applies to the whole type, so
+-- `b` is not implicitly quantified and this must be rejected.
+data T3 where
+ MkT3 :: forall a. a -> b -> T3
+
+-- That should gone soon, but let's check that we didn't implement it
+-- earlier than we need to
+data T4 a where
+ MkT4 :: Show a => (forall b. a -> b -> T4 a)
=====================================
testsuite/tests/gadt/T27423b.stderr
=====================================
@@ -0,0 +1,19 @@
+T27423b.hs:8:12: error: [GHC-89246]
+ • Record syntax is illegal here: {fld :: a}
+ • In the definition of data constructor ‘MkT1’
+
+T27423b.hs:11:12: error: [GHC-71492]
+ • GADT constructor type signature cannot contain nested ‘forall’s or contexts
+ • In the definition of data constructor ‘MkT2’
+
+T27423b.hs:11:22: error: [GHC-89246]
+ • Record syntax is illegal here: {fld :: a}
+ • In the definition of data constructor ‘MkT2’
+
+T27423b.hs:16:26: error: [GHC-76037]
+ Not in scope: type variable ‘b’
+
+T27423b.hs:21:22: error: [GHC-71492]
+ • GADT constructor type signature cannot contain nested ‘forall’s or contexts
+ • In the definition of data constructor ‘MkT4’
+
=====================================
testsuite/tests/gadt/all.T
=====================================
@@ -114,7 +114,7 @@ test('T7558', normal, compile, [''])
test('T9380', normal, compile_and_run, [''])
test('T12087', normal, compile_fail, [''])
test('T12468', normal, compile_fail, [''])
-test('T14320', normal, compile_fail, [''])
+test('T14320', normal, compile, [''])
test('T14719', normal, compile_fail, ['-fdiagnostics-show-caret'])
test('T14808', normal, compile, [''])
test('T15009', normal, compile, [''])
@@ -132,3 +132,6 @@ test('T19847b', normal, compile, [''])
test('T23022', normal, compile, ['-dcore-lint'])
test('T23023', normal, compile_fail, ['-O -dcore-lint']) # todo: move this test?
test('T23298', normal, compile_fail, [''])
+
+test('T27423a', normal, compile, [''])
+test('T27423b', normal, compile_fail, [''])
=====================================
testsuite/tests/printer/Makefile
=====================================
@@ -937,3 +937,8 @@ PprQualifiedStrings:
Haddock1:
# $(CHECK_PPR) $(LIBDIR) Haddock1.hs
$(CHECK_EXACT) $(LIBDIR) Haddock1.hs
+
+.PHONY: T27423c
+T27423c:
+ $(CHECK_PPR) $(LIBDIR) T27423c.hs
+ $(CHECK_EXACT) $(LIBDIR) T27423c.hs
=====================================
testsuite/tests/printer/T27423c.hs
=====================================
@@ -0,0 +1,46 @@
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE RequiredTypeArguments #-}
+module T27423c where
+
+-- Exact-printing regression test
+-- Not every declaration there would pass renamer without errors
+data G a where
+ MkG1 :: a -> G a
+ MkG2 :: (a -> G a)
+ MkG3 :: forall a. a -> G a
+ MkG4 :: forall a. (a -> G a)
+
+data T where
+ MkT1 :: forall a. a -> b -> T
+ MkT2 :: (forall a. a -> b -> T)
+
+data S a where
+ MkS :: (forall a. S a)
+ MkS2 :: forall. (forall a. S a)
+ MkS3 :: forall. forall a. S a
+ MkS4 :: forall a. forall. forall b. forall. forall. forall c. S a
+
+data U a where
+ MkU :: (Show a => U a)
+
+data V a where
+ MkV1 :: ((a -> V a))
+ MkV2 :: forall a. (forall b. V (b, a))
+ MkV3 :: (forall a. Show a => a -> V a)
+ MkV4 :: forall a. ((forall b. (Show a => a -> (b -> V a))))
+
+data W a b where
+ MkW1 :: forall a -> forall b. (Int -> W a b)
+ MkW2 :: forall a. (forall b -> (Bool -> W a b))
+
+data P a where
+ MkP1 :: Show a => (a -> P a)
+ MkP2 :: forall a. Int -> (a -> P a)
+
+-- Comments in and around the parentheses
+data C a where
+ MkC1 :: -- comment before the parenthesis
+ (forall a. C a)
+ MkC2 :: ( -- comment after the parenthesis
+ forall a. C a)
+ MkC3 :: (forall a. {- inline comment -} C a)
=====================================
testsuite/tests/printer/all.T
=====================================
@@ -224,3 +224,4 @@ test('TestNamedDefaults', [ignore_stderr, req_ppr_deps], makefile_test, ['TestNa
test('PprModifiers', [ignore_stderr,req_ppr_deps], makefile_test, ['PprModifiers'])
test('PprQualifiedStrings', [ignore_stderr,req_ppr_deps], makefile_test, ['PprQualifiedStrings'])
test('Haddock1', [ignore_stderr,req_ppr_deps], makefile_test, ['Haddock1'])
+test('T27423c', [ignore_stderr,req_ppr_deps], makefile_test, ['T27423c'])
=====================================
utils/check-exact/ExactPrint.hs
=====================================
@@ -370,6 +370,10 @@ cua CanUpdateAnchor f = f
cua CanUpdateAnchorOnly _ = return []
cua NoCanUpdateAnchor _ = return []
+enterAnn :: (Monad m, Monoid w, ExactPrint a) => Entry -> a -> EP w m a
+enterAnn = enterAnnWith exact setAnnotationAnchor
+
+{-# INLINE enterAnnWith #-}
-- | "Enter" an annotation, by using the associated 'anchor' field as
-- the new reference point for calculating all DeltaPos positions.
-- This is the heart of the exact printing process.
@@ -377,14 +381,17 @@ cua NoCanUpdateAnchor _ = return []
-- This is combination of the ghc=exactprint Delta.withAST and
-- Print.exactPC functions and effectively does the delta processing
-- immediately followed by the print processing. JIT ghc-exactprint.
-enterAnn :: (Monad m, Monoid w, ExactPrint a) => Entry -> a -> EP w m a
-enterAnn NoEntryVal a = do
+enterAnnWith :: (Monad m, Monoid w, Typeable a, Typeable b) =>
+ (a -> EP w m b) -> -- exact
+ (b -> EpaLocation -> [TrailingAnn] -> EpAnnComments -> b) -> -- setAnnotationAnchor
+ Entry -> a -> EP w m b
+enterAnnWith exactVia _ NoEntryVal a = do
p <- getPosP
debugM $ "enterAnn:starting:NO ANN:(p,a) =" ++ show (p, astId a)
- r <- exact a
+ r <- exactVia a
debugM $ "enterAnn:done:NO ANN:p =" ++ show (p, astId a)
return r
-enterAnn !(Entry anchor' trailing_anns cs flush canUpdateAnchor) a = do
+enterAnnWith exactVia setAnnAnchor !(Entry anchor' trailing_anns cs flush canUpdateAnchor) a = do
acceptSpan <- getAcceptSpan
setAcceptSpan False
case anchor' of
@@ -495,7 +502,7 @@ enterAnn !(Entry anchor' trailing_anns cs flush canUpdateAnchor) a = do
advance edp
debugM $ "enterAnn:exact a starting:" ++ show (showAst anchor')
- a' <- exact a
+ a' <- exactVia a
debugM $ "enterAnn:exact a done:" ++ show (showAst anchor')
-- Core recursive exactprint done, start end of Entry processing
@@ -549,8 +556,8 @@ enterAnn !(Entry anchor' trailing_anns cs flush canUpdateAnchor) a = do
EpaSpan s -> EpaDelta s edp []
_ -> EpaDelta noSrcSpan edp []
let r = case canUpdateAnchor of
- CanUpdateAnchor -> setAnnotationAnchor a' newAnchor trailing' (mkEpaComments priorCs postCs)
- CanUpdateAnchorOnly -> setAnnotationAnchor a' newAnchor [] emptyComments
+ CanUpdateAnchor -> setAnnAnchor a' newAnchor trailing' (mkEpaComments priorCs postCs)
+ CanUpdateAnchorOnly -> setAnnAnchor a' newAnchor [] emptyComments
NoCanUpdateAnchor -> a'
return r
@@ -4262,21 +4269,22 @@ instance ExactPrint (ConDecl GhcPs) where
L _ (HsOuterImplicit _) -> return outer_bndrs
_ -> markAnnotated outer_bndrs
- inner_bndrs' <- mapM markAnnotated inner_bndrs
+ (inner_bndrs', (mcxt', args', res_ty')) <- markGadtArgs inner_bndrs $ do
+ mcxt' <- markAnnotated mcxt
+ args' <-
+ case args of
+ (PrefixConGADT x args0) -> do
+ args0' <- mapM markAnnotated args0
+ return (PrefixConGADT x args0')
+ (RecConGADT (oc,cc,rarr) fields) -> do
+ oc' <- markEpToken oc
+ fields' <- markAnnotated fields
+ cc' <- markEpToken cc
+ rarr' <- markEpUniToken rarr
+ return (RecConGADT (oc',cc',rarr') fields')
+ res_ty' <- markAnnotated res_ty
+ return (mcxt', args', res_ty')
- mcxt' <- markAnnotated mcxt
- args' <-
- case args of
- (PrefixConGADT x args0) -> do
- args0' <- mapM markAnnotated args0
- return (PrefixConGADT x args0')
- (RecConGADT (oc,cc,rarr) fields) -> do
- oc' <- markEpToken oc
- fields' <- markAnnotated fields
- cc' <- markEpToken cc
- rarr' <- markEpUniToken rarr
- return (RecConGADT (oc',cc',rarr') fields')
- res_ty' <- markAnnotated res_ty
return (ConDeclGADT { con_g_ext = AnnConDeclGADT [] [] dcol'
, con_names = cons'
, con_outer_bndrs = outer_bndrs'
@@ -4285,6 +4293,44 @@ instance ExactPrint (ConDecl GhcPs) where
, con_modifiers = mods'
, con_res_ty = res_ty', con_doc = doc })
+-- | Exact print the inner binders of a GADT signature.
+--
+-- It's that complicated because we need to mark comments/trailing anns
+-- stored inside `L` and mark closing parenthesis _after_ we mark inner
+-- type:
+--
+-- data T a b where
+-- MkT ::
+-- forall a. ( -- mark inside `markGadtArgs`
+-- forall b. some type -> T a b -- mark everything there
+-- ) -- mark inside `markGadtArgs`
+--
+-- We don't have the same problem for `HsArgPar` because we ignore it
+-- during exact-print, "Does not appear in original source"
+markGadtArgs :: (Monad m, Monoid w, Typeable a)
+ => [LHsGadtTelescope GhcPs] -> EP w m a
+ -> EP w m ([LHsGadtTelescope GhcPs], a)
+markGadtArgs args inner_action = go args
+ where
+ go [] = do
+ r <- inner_action
+ return ([], r)
+ go (arg:xs) = enterAnnWith (exact_arg xs) setAnchor (entryFromLocatedA arg) arg
+
+ exact_arg xs (L l (HsGadtForAll _ tele)) = do
+ tele' <- markAnnotated tele
+ (xs', r) <- go xs
+ return (L l (HsGadtForAll noExtField tele') : xs', r)
+ exact_arg xs (L l (HsGadtPar (lp, rp))) = do
+ lp' <- markEpToken lp
+ (xs', r) <- go xs
+ rp' <- markEpToken rp
+ return (L l (HsGadtPar (lp',rp')) : xs', r)
+
+ -- The binder just entered is the head of the returned list
+ setAnchor (arg':xs', r) anc ts cs = (setAnchorAn arg' anc ts cs : xs', r)
+ setAnchor ([], r) _ _ _ = ([], r)
+
-- ---------------------------------------------------------------------
instance ExactPrint Void where
=====================================
utils/haddock/haddock-api/src/Haddock/Backends/Hoogle.hs
=====================================
@@ -350,7 +350,7 @@ ppCtor
typeSig = operator name ++ " :: " ++ outHsSigType sDocContext con_sig_ty
name = out sDocContext $ unL <$> names
con_sig_ty = HsSig noExtField outer_bndrs $
- mkForallTys inner_bndrs phi_ty
+ mkGadtArgTys inner_bndrs phi_ty
where
phi_ty = case mcxt of
Just theta -> mkQualTy theta tau_ty
@@ -367,13 +367,14 @@ ppCtor
noLocA (HsQualTy{ hst_xqual = noExtField
, hst_ctxt = ctxt, hst_body = body})
- mkForallTy :: HsForAllTelescope GhcRn -> LHsType GhcRn -> LHsType GhcRn
- mkForallTy tele body =
- noLocA (HsForAllTy { hst_xforall = noExtField
+ mkGadtArgTy :: LHsGadtTelescope GhcRn -> LHsType GhcRn -> LHsType GhcRn
+ mkGadtArgTy (L l (HsGadtForAll _ tele)) body =
+ L l (HsForAllTy { hst_xforall = noExtField
, hst_tele = tele, hst_body = body })
+ mkGadtArgTy (L l HsGadtPar{}) body = L l (HsParTy noAnn body)
- mkForallTys :: [HsForAllTelescope GhcRn] -> LHsType GhcRn -> LHsType GhcRn
- mkForallTys = flip (foldr mkForallTy)
+ mkGadtArgTys :: [LHsGadtTelescope GhcRn] -> LHsType GhcRn -> LHsType GhcRn
+ mkGadtArgTys = flip (foldr mkGadtArgTy)
ppFixity :: SDocContext -> (Name, Fixity) -> [String]
ppFixity sDocContext (name, fixity) = [out sDocContext ((FixitySig noExtField (NoNamespaceSpecifier noExtField) [noLocA name] fixity) :: FixitySig GhcRn)]
=====================================
utils/haddock/haddock-api/src/Haddock/Convert.hs
=====================================
@@ -567,7 +567,7 @@ synifyDataCon use_gadt_syntax dc =
, hso_bndrs = map synifyTyVarBndr outer_tvbs
}
- inner_bndrs = mk_telescopes inner_tvbs
+ inner_bndrs = map noLocA $ mkHsGadtForAlls (mk_telescopes inner_tvbs)
mk_telescopes bs
| (invis, other) <- split_invis_tvbs bs, not (null invis)
=====================================
utils/haddock/haddock-api/src/Haddock/GhcUtils.hs
=====================================
@@ -229,7 +229,7 @@ getGADTConType
( HsSig
{ sig_ext = noExtField
, sig_bndrs = unLoc outer_bndrs
- , sig_body = mkForallTys inner_bndrs phi_ty
+ , sig_body = mkGadtArgTys inner_bndrs phi_ty
}
)
where
@@ -251,13 +251,14 @@ getGADTConType
noLocA (HsQualTy{ hst_xqual = noAnn
, hst_ctxt = ctxt, hst_body = body})
- mkForallTy :: HsForAllTelescope DocNameI -> LHsType DocNameI -> LHsType DocNameI
- mkForallTy tele body =
- noLocA (HsForAllTy { hst_xforall = noAnn
+ mkGadtArgTy :: LHsGadtTelescope DocNameI -> LHsType DocNameI -> LHsType DocNameI
+ mkGadtArgTy (L l (HsGadtForAll _ tele)) body =
+ L l (HsForAllTy { hst_xforall = noAnn
, hst_tele = tele, hst_body = body })
+ mkGadtArgTy (L l HsGadtPar{}) body = L l (HsParTy noAnn body)
- mkForallTys :: [HsForAllTelescope DocNameI] -> LHsType DocNameI -> LHsType DocNameI
- mkForallTys = flip (foldr mkForallTy)
+ mkGadtArgTys :: [LHsGadtTelescope DocNameI] -> LHsType DocNameI -> LHsType DocNameI
+ mkGadtArgTys = flip (foldr mkGadtArgTy)
getGADTConType (ConDeclH98{}) = panic "getGADTConType"
=====================================
utils/haddock/haddock-api/src/Haddock/Interface/Rename.hs
=====================================
@@ -476,6 +476,11 @@ renameHsBndrVis :: HsBndrVis GhcRn -> RnM (HsBndrVis DocNameI)
renameHsBndrVis (HsBndrRequired _) = return (HsBndrRequired noExtField)
renameHsBndrVis (HsBndrInvisible at) = return (HsBndrInvisible at)
+renameHsGadtTelescope :: LHsGadtTelescope GhcRn -> RnM (LHsGadtTelescope DocNameI)
+renameHsGadtTelescope (L l HsGadtPar{}) = pure $ L l $ HsGadtPar noExtField
+renameHsGadtTelescope (L l (HsGadtForAll _ tele)) =
+ L l . HsGadtForAll noExtField <$> renameHsForAllTelescope tele
+
renameHsForAllTelescope :: HsForAllTelescope GhcRn -> RnM (HsForAllTelescope DocNameI)
renameHsForAllTelescope tele = case tele of
HsForAllVis _ bndrs -> do
@@ -761,7 +766,7 @@ renameCon
} = do
lnames' <- mapM renameNameL lnames
outer_bndrs' <- mapM renameOuterTyVarBndrs outer_bndrs
- inner_bndrs' <- mapM renameHsForAllTelescope inner_bndrs
+ inner_bndrs' <- mapM renameHsGadtTelescope inner_bndrs
lcontext' <- traverse renameLContext lcontext
details' <- renameGADTDetails details
res_ty' <- renameLType res_ty
=====================================
utils/haddock/haddock-api/src/Haddock/Types.hs
=====================================
@@ -840,6 +840,7 @@ type instance Anno (CType DocNameI) = SrcSpanAnnA
type instance Anno (Header DocNameI) = SrcSpanAnnA
type instance Anno (HsModifierOf (LocatedA (HsType DocNameI)) DocNameI) = SrcSpanAnnA
type instance Anno (HsContextDetails DocNameI a) = SrcSpanAnnA
+type instance Anno (HsGadtTelescope DocNameI) = SrcSpanAnnA
type XRecCond a =
( XParTy a ~ (EpToken "(", EpToken ")")
@@ -904,6 +905,10 @@ type instance XHsForAllVis DocNameI = NoExtField
type instance XHsForAllInvis DocNameI = NoExtField
type instance XXHsForAllTelescope DocNameI = DataConCantHappen
+type instance XGadtForAll DocNameI = NoExtField
+type instance XGadtPar DocNameI = NoExtField
+type instance XXGadtArg DocNameI = DataConCantHappen
+
type instance XTyVarBndr DocNameI = NoExtField
type instance XXTyVarBndr DocNameI = DataConCantHappen
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/eaa95a6f1136a9379cad2d334708355…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/eaa95a6f1136a9379cad2d334708355…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][wip/sjakobi/T25450-march-native] Implement -march=native for x86/x86_64
by Simon Jakobi (@sjakobi) 04 Sep '26
by Simon Jakobi (@sjakobi) 04 Sep '26
04 Sep '26
Simon Jakobi pushed to branch wip/sjakobi/T25450-march-native at Glasgow Haskell Compiler / GHC
Commits:
f2058e46 by Simon Jakobi at 2026-09-04T13:33:36+02:00
Implement -march=native for x86/x86_64
Add a -march=native flag that probes the host CPU and enables the
matching CPU-feature DynFlags, so it affects both the NCG and the LLVM
backend. Detected features are folded additively into the existing
flags, so explicit -m... options are never disabled.
The probe is a small C shim over GCC/Clang's __builtin_cpu_supports.
The deviations that remain are documented in
compiler/cbits/cpu_features_x86.c.
The RTS exports the runtime symbols behind the builtin (__cpu_model &
co.) so GHCi's runtime linker can load the ghc library where GHCi links
statically, e.g. on Windows or musl.
The flag is registered in expected-undocumented-flags.txt due to #27321.
Thanks to @aratamizuki for pointing out the macOS AVX-512 XSAVE quirk
and for testing!
Closes #25450
Assisted-by: Claude Fable 5
- - - - -
17 changed files:
- + changelog.d/march-native
- changelog.d/print-enabled-cpu-features
- + compiler/GHC/Driver/CpuFeatures.hs
- compiler/GHC/Driver/DynFlags.hs
- compiler/GHC/Driver/Session.hs
- + compiler/cbits/cpu_features_x86.c
- compiler/ghc.cabal.in
- docs/users_guide/expected-undocumented-flags.txt
- docs/users_guide/using.rst
- rts/RtsSymbols.c
- testsuite/tests/codeGen/should_gen_asm/all.T
- + testsuite/tests/codeGen/should_gen_asm/march-native-enables-popcnt.asm
- + testsuite/tests/codeGen/should_gen_asm/march-native-enables-popcnt.hs
- testsuite/tests/driver/all.T
- + testsuite/tests/driver/march_native.stdout
- + testsuite/tests/driver/march_native_additive.stdout
- + testsuite/tests/driver/march_native_unsupported_arch.stderr
Changes:
=====================================
changelog.d/march-native
=====================================
@@ -0,0 +1,12 @@
+section: compiler
+synopsis: Add -march=native flag
+issues: #25450
+mrs: !16126
+
+description:
+ GHC now supports ``-march=native`` on x86 and x86_64. It probes the CPU of the
+ machine running GHC and enables all of the corresponding ``-m...`` CPU-feature
+ options automatically (such as ``-msse4.2``, ``-mavx2``, ``-mbmi2`` and
+ ``-mfma``), for both the native code generator and the LLVM backend. The
+ detected features are enabled in addition to any explicitly requested feature
+ flags. The flag is rejected for non-x86 targets and when cross-compiling.
=====================================
changelog.d/print-enabled-cpu-features
=====================================
@@ -8,9 +8,11 @@ description:
prints a JSON object describing the CPU features currently enabled for code
generation, together with a set of ``-m...`` flags that reproduce the
effective feature set for the current target.
- Dynamic options such as ``-mavx2`` and ``-mbmi2`` are respected. ::
+ Dynamic options such as ``-mavx2`` and ``-mbmi2`` are respected, so the flag
+ can also be used to inspect which features :ghc-flag:`-march=native` detected
+ and enabled. ::
- $ ghc -mavx2 --print-enabled-cpu-features
+ $ ghc -march=native --print-enabled-cpu-features
{"tag":"enabled-cpu-features","version":1,"target":"x86_64-linux-gnu",
- "features":["SSE","SSE2","SSE3","SSSE3","SSE4.1","SSE4.2","AVX","AVX2"],
- "as_m_flags":["-mavx2"]}
+ "features":["SSE","SSE2","SSE3","SSSE3","SSE4.1","SSE4.2","AVX","AVX2","BMI1","BMI2","FMA"],
+ "as_m_flags":["-mavx2","-mbmi2","-mfma"]}
=====================================
compiler/GHC/Driver/CpuFeatures.hs
=====================================
@@ -0,0 +1,64 @@
+{-# LANGUAGE CPP #-}
+
+module GHC.Driver.CpuFeatures
+ ( X86CpuFeature(..)
+ , cachedX86CpuFeatures
+ ) where
+
+import GHC.Prelude
+
+import Data.Word (Word64)
+import System.IO.Unsafe (unsafePerformIO)
+
+-- | x86 CPU features understood by GHC's native CPU feature probe.
+data X86CpuFeature
+ = SSE2
+ | SSE3
+ | SSSE3
+ | SSE4_1
+ | SSE4_2
+ | AVX
+ | AVX2
+ | AVX512F
+ | AVX512BW
+ | AVX512CD
+ | AVX512DQ
+ | AVX512VL
+ | BMI1
+ | BMI2
+ | FMA
+ | GFNI
+ deriving (Eq, Ord, Show, Enum, Bounded)
+
+-- | Decode the bitmask returned by 'ghc_detect_x86_cpu_features'.
+--
+-- Bit positions are constructor order, which must match the enum in
+-- @compiler/cbits/cpu_features_x86.c@.
+decodeX86CpuFeatureMask :: Word64 -> [X86CpuFeature]
+decodeX86CpuFeatureMask mask =
+ [ feat
+ | feat <- [minBound .. maxBound]
+ , testBit mask (fromEnum feat)
+ ]
+
+-- | Low-level FFI access to the C probe.
+detectX86CpuFeatureMask :: IO Word64
+#if defined(javascript_HOST_ARCH)
+detectX86CpuFeatureMask = pure 0
+#else
+detectX86CpuFeatureMask = c_ghc_detect_x86_cpu_features
+#endif
+
+-- | Probe host x86 CPU features and decode them into an ordered feature list.
+detectX86CpuFeatures :: IO [X86CpuFeature]
+detectX86CpuFeatures = decodeX86CpuFeatureMask <$> detectX86CpuFeatureMask
+
+-- | The host's x86 CPU features, probed once and memoized.
+cachedX86CpuFeatures :: [X86CpuFeature]
+cachedX86CpuFeatures = unsafePerformIO detectX86CpuFeatures
+{-# NOINLINE cachedX86CpuFeatures #-}
+
+#if !defined(javascript_HOST_ARCH)
+foreign import ccall unsafe "ghc_detect_x86_cpu_features"
+ c_ghc_detect_x86_cpu_features :: IO Word64
+#endif
=====================================
compiler/GHC/Driver/DynFlags.hs
=====================================
@@ -473,6 +473,8 @@ data DynFlags = DynFlags {
fma :: Bool, -- ^ Enable FMA instructions.
gfni :: Bool, -- ^ Enable GFNI Instructions.
la664 :: Bool, -- ^ Enable LA664 instructions
+ marchNative :: Bool, -- ^ @-march=native@ was requested; the host
+ -- CPU features are applied during flag parsing.
-- Constants used to control the amount of optimization done.
@@ -765,6 +767,7 @@ defaultDynFlags mySettings =
gfni = False,
-- For LoongArch, la464 is used by default.
la664 = False,
+ marchNative = False,
maxInlineAllocSize = 128,
maxInlineMemcpyInsns = 32,
=====================================
compiler/GHC/Driver/Session.hs
=====================================
@@ -245,6 +245,8 @@ import GHC.Platform
import GHC.Platform.Ways
import GHC.Platform.Profile
import GHC.Platform.ArchOS
+import GHC.Platform.Host (hostPlatformArch)
+import qualified GHC.Driver.CpuFeatures as Cpu
import GHC.Unit.Types
import GHC.Unit.Parser
@@ -907,8 +909,10 @@ parseDynamicFlagsFull activeFlags cmdline logger dflags0 args = do
unless (null errs) $ liftIO $ throwGhcExceptionIO $ errorsToGhcException $
map ((rdr . ppr . getLoc &&& unLoc) . errMsg) $ errs
+ dflags1' <- applyMarchNative dflags1
+
-- check for disabled flags in safe haskell
- let (dflags2, sh_warns) = safeFlagCheck cmdline dflags1
+ let (dflags2, sh_warns) = safeFlagCheck cmdline dflags1'
theWays = ways dflags2
unless (allowed_combination theWays) $ liftIO $
@@ -1743,6 +1747,7 @@ dynamic_flags_deps = [
, make_ord_flag defGhcFlag "mavx512vl" (noArg (\d -> d { avx512vl = True }))
, make_ord_flag defGhcFlag "mfma" (noArg (\d -> d { fma = True }))
, make_ord_flag defGhcFlag "mgfni" (noArg (\d -> d { gfni = True }))
+ , make_ord_flag defGhcFlag "march=native" (noArg (\d -> d { marchNative = True }))
, make_ord_flag defGhcFlag "mla664" (noArg (\d -> d { la664 = True }))
@@ -3826,6 +3831,53 @@ x86AsMFlags dflags =
gfniFlags = [ "-mgfni" | gfni dflags ]
+-- | Apply a requested @-march=native@ by probing the host CPU and enabling the
+-- matching CPU-feature flags.
+applyMarchNative :: MonadIO m => DynFlags -> m DynFlags
+applyMarchNative dflags
+ | not (marchNative dflags) = return dflags
+ | otherwise = do
+ let arch = platformArch (targetPlatform dflags)
+ unless (arch == ArchX86 || arch == ArchX86_64) $ liftIO $
+ throwGhcExceptionIO $ CmdLineError
+ "-march=native is only supported on x86 and x86_64 targets"
+ unless (arch == hostPlatformArch) $ liftIO $
+ throwGhcExceptionIO $ CmdLineError
+ "-march=native is not supported when cross-compiling"
+ return (applyX86CpuFeatures Cpu.cachedX86CpuFeatures dflags)
+
+-- | Enable the 'DynFlags' CPU-feature fields corresponding to a probed set of
+-- host x86 features.
+applyX86CpuFeatures :: [Cpu.X86CpuFeature] -> DynFlags -> DynFlags
+applyX86CpuFeatures feats dflags = dflags
+ { sseAvxVersion = foldr (max . Just) (sseAvxVersion dflags) sseLevels
+ , bmiVersion = foldr (max . Just) (bmiVersion dflags) bmiLevels
+ , avx512f = avx512f dflags || has Cpu.AVX512F
+ , avx512bw = avx512bw dflags || has Cpu.AVX512BW
+ , avx512cd = avx512cd dflags || has Cpu.AVX512CD
+ , avx512dq = avx512dq dflags || has Cpu.AVX512DQ
+ , avx512vl = avx512vl dflags || has Cpu.AVX512VL
+ , fma = fma dflags || has Cpu.FMA
+ , gfni = gfni dflags || has Cpu.GFNI
+ }
+ where
+ has feat = feat `elem` feats
+ sseLevels = [ lvl | feat <- feats, Just lvl <- [sseLevelOf feat] ]
+ bmiLevels = [ lvl | feat <- feats, Just lvl <- [bmiLevelOf feat] ]
+ sseLevelOf feat = case feat of
+ Cpu.SSE2 -> Just SSE2
+ Cpu.SSE3 -> Just SSE3
+ Cpu.SSSE3 -> Just SSSE3
+ Cpu.SSE4_1 -> Just SSE4
+ Cpu.SSE4_2 -> Just SSE42
+ Cpu.AVX -> Just AVX1
+ Cpu.AVX2 -> Just AVX2
+ _ -> Nothing
+ bmiLevelOf feat = case feat of
+ Cpu.BMI1 -> Just BMI1
+ Cpu.BMI2 -> Just BMI2
+ _ -> Nothing
+
-- | Query if the target RTS has the given 'Ways'. It's computed from
-- the @"RTS ways"@ field in the settings file.
targetHasRTSWays :: DynFlags -> Ways -> Bool
=====================================
compiler/cbits/cpu_features_x86.c
=====================================
@@ -0,0 +1,97 @@
+#include <HsFFI.h>
+
+/* Runtime x86 CPU feature detection backing GHC.Driver.CpuFeatures
+ (-march=native).
+
+ The bit positions in the returned mask are GHC's own; the enum below
+ must match the constructor order of X86CpuFeature in
+ GHC.Driver.CpuFeatures.
+
+ The __builtin_cpu_* calls below compile to references to libgcc/compiler-rt
+ symbols (__cpu_model & co.), which GHCi's runtime linker can only resolve
+ because the RTS exports them: see RTS_X86_CPU_MODEL_SYMBOLS in
+ rts/RtsSymbols.c and keep its guards in sync with this file's. */
+
+enum {
+ GHC_X86_FEAT_SSE2 = 0,
+ GHC_X86_FEAT_SSE3,
+ GHC_X86_FEAT_SSSE3,
+ GHC_X86_FEAT_SSE4_1,
+ GHC_X86_FEAT_SSE4_2,
+ GHC_X86_FEAT_AVX,
+ GHC_X86_FEAT_AVX2,
+ GHC_X86_FEAT_AVX512F,
+ GHC_X86_FEAT_AVX512BW,
+ GHC_X86_FEAT_AVX512CD,
+ GHC_X86_FEAT_AVX512DQ,
+ GHC_X86_FEAT_AVX512VL,
+ GHC_X86_FEAT_BMI1,
+ GHC_X86_FEAT_BMI2,
+ GHC_X86_FEAT_FMA,
+ GHC_X86_FEAT_GFNI
+};
+
+#if (defined(__i386__) || defined(__x86_64__)) \
+ && (defined(__GNUC__) || defined(__clang__))
+
+/* GFNI joined the __builtin_cpu_supports feature lists later than the
+ other features here (GCC 11; compiler-rt's cpu_model in LLVM 12).
+ Under an older bootstrap compiler we conservatively report no GFNI. */
+#if (defined(__clang_major__) && __clang_major__ >= 12) \
+ || (!defined(__clang__) && defined(__GNUC__) && __GNUC__ >= 11)
+#define GHC_HAS_CPU_SUPPORTS_GFNI 1
+#else
+#define GHC_HAS_CPU_SUPPORTS_GFNI 0
+#endif
+
+HsWord64 ghc_detect_x86_cpu_features(void)
+{
+ __builtin_cpu_init();
+
+ HsWord64 feats = 0;
+
+ /* __builtin_cpu_supports returns the feature's (nonzero) mask bit,
+ not 0/1, so it must be normalized before shifting. */
+#define GHC_CPU_FEATURE(name, bit) \
+ if (__builtin_cpu_supports(name)) { feats |= ((HsWord64)1 << (bit)); }
+
+ GHC_CPU_FEATURE("sse2", GHC_X86_FEAT_SSE2)
+ GHC_CPU_FEATURE("sse3", GHC_X86_FEAT_SSE3)
+ GHC_CPU_FEATURE("ssse3", GHC_X86_FEAT_SSSE3)
+ GHC_CPU_FEATURE("sse4.1", GHC_X86_FEAT_SSE4_1)
+ GHC_CPU_FEATURE("sse4.2", GHC_X86_FEAT_SSE4_2)
+ GHC_CPU_FEATURE("avx", GHC_X86_FEAT_AVX)
+ GHC_CPU_FEATURE("avx2", GHC_X86_FEAT_AVX2)
+ GHC_CPU_FEATURE("avx512f", GHC_X86_FEAT_AVX512F)
+ GHC_CPU_FEATURE("avx512bw", GHC_X86_FEAT_AVX512BW)
+ GHC_CPU_FEATURE("avx512cd", GHC_X86_FEAT_AVX512CD)
+ GHC_CPU_FEATURE("avx512dq", GHC_X86_FEAT_AVX512DQ)
+ GHC_CPU_FEATURE("avx512vl", GHC_X86_FEAT_AVX512VL)
+ GHC_CPU_FEATURE("bmi", GHC_X86_FEAT_BMI1)
+ GHC_CPU_FEATURE("bmi2", GHC_X86_FEAT_BMI2)
+
+ /* FMA is usable only when the OS saves the AVX register state. However
+ compiler-rt's cpu_model/x86.c reports "fma" from the raw CPUID bit. */
+ if (__builtin_cpu_supports("fma") && __builtin_cpu_supports("avx")) {
+ feats |= ((HsWord64)1 << GHC_X86_FEAT_FMA);
+ }
+
+#if GHC_HAS_CPU_SUPPORTS_GFNI
+ GHC_CPU_FEATURE("gfni", GHC_X86_FEAT_GFNI)
+#endif
+
+#undef GHC_CPU_FEATURE
+
+ return feats;
+}
+
+#else
+
+/* Non-x86 host, or a compiler without __builtin_cpu_supports (e.g. MSVC):
+ report no features. */
+HsWord64 ghc_detect_x86_cpu_features(void)
+{
+ return 0;
+}
+
+#endif
=====================================
compiler/ghc.cabal.in
=====================================
@@ -187,6 +187,7 @@ Library
else
c-sources:
cbits/cutils.c
+ cbits/cpu_features_x86.c
cbits/genSym.c
cbits/keepCAFsForGHCi.c
@@ -518,6 +519,7 @@ Library
GHC.Driver.Config.StgToCmm
GHC.Driver.Config.Tidy
GHC.Driver.Config.StgToJS
+ GHC.Driver.CpuFeatures
GHC.Driver.DynFlags
GHC.Driver.IncludeSpecs
GHC.Driver.Downsweep
=====================================
docs/users_guide/expected-undocumented-flags.txt
=====================================
@@ -74,6 +74,7 @@
-instantiated-with
-keep-hi-file
-keep-o-file
+-march=native
-n
-no-keep-hi-file
-no-keep-o-file
=====================================
docs/users_guide/using.rst
=====================================
@@ -539,7 +539,9 @@ The available mode flags are:
Print a JSON object describing the CPU features currently enabled for code
generation, together with a set of ``-m...`` flags that reproduce the
effective feature set for the current target.
- Dynamic options such as ``-mavx2`` and ``-mbmi2`` are respected.
+ Dynamic options such as ``-mavx2`` and ``-mbmi2`` are respected, so this flag
+ can also be used to inspect which features :ghc-flag:`-march=native` detected
+ and enabled.
.. ghc-flag:: --print-debug-on
:shortdesc: print whether GHC was built with ``-DDEBUG``
@@ -1897,6 +1899,34 @@ Some flags only make sense for particular target platforms.
so this flag has no effect when used with the :ref:`native code generator <native-code-gen>`
or the :ref:`LLVM backend <llvm-code-gen>`.
+.. ghc-flag:: -march=native
+ :shortdesc: (x86 only) Enable all CPU features supported by the host
+ :type: dynamic
+ :category: platform-options
+
+ (x86/x86_64 only) Probe the CPU of the machine running GHC and enable all of
+ the corresponding ``-m...`` CPU-feature options automatically (for example
+ ``-msse4.2``, ``-mavx2``, ``-mbmi2``, ``-mfma``). The detected features apply
+ to both the :ref:`native code generator <native-code-gen>` and the
+ :ref:`LLVM backend <llvm-code-gen>`.
+
+ The detected features are enabled *in addition* to any CPU-feature flags you
+ pass explicitly, regardless of their order on the command line; ``-march=native``
+ never disables a feature.
+
+ The features that were detected and enabled can be inspected with
+ :ghc-flag:`--print-enabled-cpu-features`.
+
+ .. warning::
+
+ Code compiled with ``-march=native`` may use instructions that are not
+ available on other CPUs, and is therefore not portable to a different
+ machine.
+
+ Only x86 and x86_64 targets are supported so far; the flag is rejected on
+ other targets. It is also rejected when cross-compiling, since the host CPU
+ is then unrelated to the target.
+
Haddock
-------
=====================================
rts/RtsSymbols.c
=====================================
@@ -1039,6 +1039,30 @@ extern char **environ;
#define RTS_ARM_OUTLINE_ATOMIC_SYMBOLS
#endif
+// libgcc/compiler-rt symbols behind __builtin_cpu_supports/__builtin_cpu_init,
+// referenced by compiler/cbits/cpu_features_x86.c (-march=native).
+// Without them the runtime linker cannot load the ghc library's object code
+// (e.g. for plugins in a statically linked GHCi). Keep the guards in sync
+// with cpu_features_x86.c.
+#if (defined(i386_HOST_ARCH) || defined(x86_64_HOST_ARCH)) \
+ && (defined(__GNUC__) || defined(__clang__))
+// __cpu_features2 holds the feature words beyond the first 32 (e.g. GFNI)
+// and only exists in the runtimes of GCC >= 11 resp. LLVM >= 12.
+#if (defined(__clang_major__) && __clang_major__ >= 12) \
+ || (!defined(__clang__) && defined(__GNUC__) && __GNUC__ >= 11)
+#define RTS_X86_CPU_FEATURES2_SYMBOLS \
+ SymI_NeedsDataProto(__cpu_features2)
+#else
+#define RTS_X86_CPU_FEATURES2_SYMBOLS
+#endif
+#define RTS_X86_CPU_MODEL_SYMBOLS \
+ SymI_NeedsProto(__cpu_indicator_init) \
+ SymI_NeedsDataProto(__cpu_model) \
+ RTS_X86_CPU_FEATURES2_SYMBOLS
+#else
+#define RTS_X86_CPU_MODEL_SYMBOLS
+#endif
+
// Symbols defined by libc
#define RTS_LIBC_SYMBOLS \
SymI_HasProto_redirect(atexit, atexit, STRENGTH_STRONG, SYM_TYPE_CODE) /* See Note [Strong symbols] */ \
@@ -1199,6 +1223,7 @@ RTS_OPENBSD_ONLY_SYMBOLS
RTS_LIBC_SYMBOLS
RTS_LIBGCC_SYMBOLS
RTS_ARCH_LIBGCC_SYMBOLS
+RTS_X86_CPU_MODEL_SYMBOLS
RTS_FINI_ARRAY_SYMBOLS
RTS_LIBFFI_SYMBOLS
RTS_ARM_OUTLINE_ATOMIC_SYMBOLS
@@ -1254,6 +1279,7 @@ void initLinkerRtsSyms (StrHashTable *symhash) {
RTS_OPENBSD_ONLY_SYMBOLS
RTS_LIBGCC_SYMBOLS
RTS_ARCH_LIBGCC_SYMBOLS
+ RTS_X86_CPU_MODEL_SYMBOLS
RTS_FINI_ARRAY_SYMBOLS
RTS_LIBFFI_SYMBOLS
RTS_ARM_OUTLINE_ATOMIC_SYMBOLS
=====================================
testsuite/tests/codeGen/should_gen_asm/all.T
=====================================
@@ -17,6 +17,11 @@ test('msse-option-order', [unless(arch('x86_64') or arch('i386'), skip),
when(unregisterised(), skip)], compile_grep_asm, ['hs', False, '-msse4.2 -msse2'])
test('mavx-should-enable-popcnt', [unless(arch('x86_64') or arch('i386'), skip),
when(unregisterised(), skip)], compile_grep_asm, ['hs', False, '-mavx'])
+# -march=native probes the host CPU, so gate on the host actually having SSE4.2
+# (have_cpu_feature reports nothing under cross, skipping the test there too).
+test('march-native-enables-popcnt',
+ [unless((arch('x86_64') or arch('i386')) and have_cpu_feature('sse4_2'), skip),
+ when(unregisterised(), skip)], compile_grep_asm, ['hs', False, '-march=native'])
test('avx512-int64-mul', [unless(arch('x86_64'), skip),
when(unregisterised(), skip)], compile_grep_asm, ['hs', True, '-mavx512dq -mavx512vl'])
test('avx512-int64-minmax', [unless(arch('x86_64'), skip),
=====================================
testsuite/tests/codeGen/should_gen_asm/march-native-enables-popcnt.asm
=====================================
@@ -0,0 +1 @@
+popcnt(?![0-9])
\ No newline at end of file
=====================================
testsuite/tests/codeGen/should_gen_asm/march-native-enables-popcnt.hs
=====================================
@@ -0,0 +1,11 @@
+-- `-march=native` enables the host's CPU features. On a host with SSE4.2
+-- (gated in all.T via have_cpu_feature) this makes popCount compile to a
+-- `popcnt` instruction rather than the SSE2-baseline software fallback.
+import Data.Bits
+
+{-# NOINLINE foo #-}
+foo :: Int -> Int
+foo x = 1 + popCount x
+
+main :: IO ()
+main = print (foo 42)
=====================================
testsuite/tests/driver/all.T
=====================================
@@ -7,6 +7,12 @@ def normalise_unknown_flag(msg):
m = re.search(r'unrecognised flag: \S+', msg)
return m.group(0) + '\n' if m else msg
+def normalise_march_native_error(msg):
+ # Keep only the stable '-march=native ...' diagnostic; the program-name
+ # prefix and any usage trailer vary across configurations.
+ m = re.search(r'-march=native is [^\n]+', msg)
+ return m.group(0) + '\n' if m else msg
+
test('driver011', [extra_files(['A011.hs'])], makefile_test, ['test011'])
test('driver012', [extra_files(['A012.hs'])], makefile_test, ['test012'])
@@ -265,6 +271,45 @@ test('print_enabled_cpu_features_unknown_flag',
run_command,
['{compiler} -mavx22 --print-enabled-cpu-features'])
+# -march=native enables at least the x86_64 baseline (SSE2). The full feature
+# set is host-dependent, so we only assert the always-present baseline.
+test('march_native',
+ [unless(arch('x86_64') or arch('i386'), skip)],
+ run_command,
+ ['{compiler} -march=native --print-enabled-cpu-features | grep -o SSE2'])
+
+# On non-x86 targets -march=native must be rejected.
+test('march_native_unsupported_arch',
+ [when(arch('x86_64') or arch('i386'), skip),
+ normalise_errmsg_fun(normalise_march_native_error), exit_code(1)],
+ run_command,
+ ['{compiler} -march=native --print-enabled-cpu-features'])
+
+# -march=native is additive: its feature set is a superset of the default set.
+# We extract the "features" arrays with and without the flag and assert that no
+# baseline feature is dropped: 'grep -vxF -f native.txt base.txt' prints any
+# baseline feature absent from the -march=native set, of which we expect none.
+# (grep exits 1 when it prints nothing, so '|| true' keeps the success case from
+# failing the test; the empty-stdout check is what enforces the assertion.)
+# This avoids hard-coding the host-specific feature set.
+test('march_native_superset',
+ [unless(arch('x86_64') or arch('i386'), skip)],
+ run_command,
+ ['{compiler} --print-enabled-cpu-features | '
+ 'sed \'s/.*"features":\\[//;s/].*//;s/"//g\' | tr \',\' \'\\n\' > base.txt && '
+ '{compiler} -march=native --print-enabled-cpu-features | '
+ 'sed \'s/.*"features":\\[//;s/].*//;s/"//g\' | tr \',\' \'\\n\' > native.txt && '
+ '(grep -vxF -f native.txt base.txt || true)'])
+
+# -march=native is additive with explicit -m flags, regardless of order: an
+# explicitly requested feature (here AVX2, forced on independent of the host) is
+# still present whether the flag comes before or after -march=native.
+test('march_native_additive',
+ [unless(arch('x86_64') or arch('i386'), skip)],
+ run_command,
+ ['{compiler} -mavx2 -march=native --print-enabled-cpu-features | grep -o AVX2 && '
+ '{compiler} -march=native -mavx2 --print-enabled-cpu-features | grep -o AVX2'])
+
test('T10219', normal, run_command,
# `-x hspp` in make mode should work.
# Note: need to specify `-x hspp` before the filename.
=====================================
testsuite/tests/driver/march_native.stdout
=====================================
@@ -0,0 +1 @@
+SSE2
=====================================
testsuite/tests/driver/march_native_additive.stdout
=====================================
@@ -0,0 +1,2 @@
+AVX2
+AVX2
=====================================
testsuite/tests/driver/march_native_unsupported_arch.stderr
=====================================
@@ -0,0 +1 @@
+-march=native is only supported on x86 and x86_64 targets
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/f2058e46be0967e93119a4d0e3140fe…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/f2058e46be0967e93119a4d0e3140fe…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][wip/davide/io_uring] 2 commits: WIP Implement native io_uring IO manager
by David Eichmann (@DavidEichmann) 04 Sep '26
by David Eichmann (@DavidEichmann) 04 Sep '26
04 Sep '26
David Eichmann pushed to branch wip/davide/io_uring at Glasgow Haskell Compiler / GHC
Commits:
5c5d8bc5 by David Eichmann at 2026-08-26T11:17:22+01:00
WIP Implement native io_uring IO manager
- - - - -
622a28fe by David Eichmann at 2026-09-04T12:02:39+01:00
io_uring: integrate Duncan's prototype code. It Compiles
- - - - -
12 changed files:
- docs/users_guide/runtime_control.rst
- rts/IOManager.c
- rts/IOManager.h
- rts/IOManagerInternals.h
- rts/configure.ac
- rts/include/rts/Constants.h
- rts/include/rts/Flags.h
- rts/include/rts/storage/Closures.h
- + rts/posix/IOURing.c
- + rts/posix/IOURing.h
- rts/posix/Timeout.c
- rts/rts.cabal
Changes:
=====================================
docs/users_guide/runtime_control.rst
=====================================
@@ -1445,6 +1445,7 @@ Currently the available I/O managers are:
================ ========= ============
``select`` Posix Non-threaded
``poll`` Posix Non-threaded
+``io-uring`` Linux Non-threaded
``mio`` All Threaded
``win32-legacy`` Windows Non-threaded
``winio`` Windows Both
@@ -1512,6 +1513,11 @@ limit can be adjusted using OS facilities (e.g. the ``ulimit`` command).
Exceeding this limit will cause the RTS (and thus typically the process) to
terminate.
+The ``io_uring`` I/O manager
+~~~~~~~~~~~~~~~~~~~~~~~
+
+TODO
+
The ``mio`` I/O manager
~~~~~~~~~~~~~~~~~~~~~~~
This I/O manager is based on several platform-specific APIs. It supports
=====================================
rts/IOManager.c
=====================================
@@ -16,8 +16,9 @@
* -------------------------------------------------------------------------*/
#include "Rts.h"
+#include "ClosureTable.h"
#include "rts/IOInterface.h" // exported
-#include "IOManager.h" // RTS internal
+#include "IOManager.h" // RTS internal
#include "Capability.h"
#include "Schedule.h"
#include "Prelude.h"
@@ -37,14 +38,19 @@
#include "posix/Timeout.h"
#endif
+#if defined(IOMGR_ENABLED_IO_URING)
+#include "posix/IOURing.h"
+#include "posix/Timeout.h"
+#endif
+
#if defined(IOMGR_ENABLED_MIO_POSIX)
-#include "posix/MIO.h"
#include "Prelude.h"
+#include "posix/MIO.h"
#endif
#if defined(IOMGR_ENABLED_MIO_WIN32)
-#include "win32/ThrIOManager.h"
#include "win32/AsyncMIO.h"
+#include "win32/ThrIOManager.h"
#endif
#if defined(IOMGR_ENABLED_WIN32_LEGACY)
@@ -55,8 +61,8 @@
#endif
#if defined(IOMGR_ENABLED_WINIO)
-#include "win32/ThrIOManager.h"
#include "win32/AsyncWinIO.h"
+#include "win32/ThrIOManager.h"
#if !defined(THREADED_RTS)
#include "win32/AwaitEvent.h"
#endif
@@ -64,7 +70,6 @@
#include <string.h>
-
/* We have lots of functions below with conditional implementations for
* different I/O managers. Some functions, for some I/O managers, naturally
* have implementations that do nothing or barf. When only one such I/O
@@ -90,11 +95,11 @@
* We put this check here since it has to live somewhere, and the main users
* of StgAsyncIOOp are I/O managers.
*/
-GHC_STATIC_ASSERT(sizeof(StgAsyncIOOp)
- == sizeof(StgHeader)
- + sizeof(StgPtr) * stg_ASYNCIOOP_NUM_PTRS
- + sizeof(StgWord) * stg_ASYNCIOOP_NUM_NONPTRS,
- "sizeof(StgAsyncIOOp) does not match expected size");
+GHC_STATIC_ASSERT(sizeof(StgAsyncIOOp) ==
+ sizeof(StgHeader) +
+ sizeof(StgPtr) * stg_ASYNCIOOP_NUM_PTRS +
+ sizeof(StgWord) * stg_ASYNCIOOP_NUM_NONPTRS,
+ "sizeof(StgAsyncIOOp) does not match expected size");
/* Global var to tell us which I/O manager impl we are using */
IOManagerType iomgr_type;
@@ -107,95 +112,94 @@ IOManagerType iomgr_type;
bool rts_IOManagerIsWin32Native = false;
#endif
-enum IOManagerAvailability
-parseIOManagerFlag(const char *iomgrstr, IO_MANAGER_FLAG *flag)
-{
- if (strcmp("select", iomgrstr) == 0) {
+enum IOManagerAvailability parseIOManagerFlag(const char *iomgrstr,
+ IO_MANAGER_FLAG *flag) {
+ if (strcmp("select", iomgrstr) == 0) {
#if defined(IOMGR_ENABLED_SELECT)
- *flag = IO_MNGR_FLAG_SELECT;
- return IOManagerAvailable;
+ *flag = IO_MNGR_FLAG_SELECT;
+ return IOManagerAvailable;
#else
- return IOManagerUnavailable;
+ return IOManagerUnavailable;
#endif
- }
- else if (strcmp("poll", iomgrstr) == 0) {
+ } else if (strcmp("poll", iomgrstr) == 0) {
#if defined(IOMGR_ENABLED_POLL)
- *flag = IO_MNGR_FLAG_POLL;
- return IOManagerAvailable;
+ *flag = IO_MNGR_FLAG_POLL;
+ return IOManagerAvailable;
#else
- return IOManagerUnavailable;
+ return IOManagerUnavailable;
#endif
- }
- else if (strcmp("mio", iomgrstr) == 0) {
+ } else if (strcmp("io-uring", iomgrstr) == 0) {
+#if defined(IOMGR_ENABLED_IO_URING)
+ *flag = IO_MNGR_FLAG_IO_URING;
+ return IOManagerAvailable;
+#else
+ return IOManagerUnavailable;
+#endif
+ } else if (strcmp("mio", iomgrstr) == 0) {
#if defined(IOMGR_ENABLED_MIO_POSIX) || defined(IOMGR_ENABLED_MIO_WIN32)
- *flag = IO_MNGR_FLAG_MIO;
- return IOManagerAvailable;
+ *flag = IO_MNGR_FLAG_MIO;
+ return IOManagerAvailable;
#else
- return IOManagerUnavailable;
+ return IOManagerUnavailable;
#endif
- }
- else if (strcmp("winio", iomgrstr) == 0) {
+ } else if (strcmp("winio", iomgrstr) == 0) {
#if defined(IOMGR_ENABLED_WINIO)
- *flag = IO_MNGR_FLAG_WINIO;
- return IOManagerAvailable;
+ *flag = IO_MNGR_FLAG_WINIO;
+ return IOManagerAvailable;
#else
- return IOManagerUnavailable;
+ return IOManagerUnavailable;
#endif
- }
- else if (strcmp("win32-legacy", iomgrstr) == 0) {
+ } else if (strcmp("win32-legacy", iomgrstr) == 0) {
#if defined(IOMGR_ENABLED_WIN32_LEGACY)
- *flag = IO_MNGR_FLAG_WIN32_LEGACY;
- return IOManagerAvailable;
+ *flag = IO_MNGR_FLAG_WIN32_LEGACY;
+ return IOManagerAvailable;
#else
- return IOManagerUnavailable;
-#endif
- }
- else if (strcmp("auto", iomgrstr) == 0) {
- *flag = IO_MNGR_FLAG_AUTO;
- return IOManagerAvailable;
- }
- /* Two deprecated aliases. These aliases only had any effect on Windows,
- * but were available as RTS flags on all platforms. The "native" flag
- * refers to the newer Windows WinIO IO manager (threaded or non-threaded),
- * while (somewhat confusingly) the "posix" flag refers to the older
- * Windows I/O managers (win32-legacy and mio). On non-Windows, we now make
- * these flags equivalent to IO_MNGR_FLAG_AUTO.
- */
- else if (strcmp("native", iomgrstr) == 0) {
+ return IOManagerUnavailable;
+#endif
+ } else if (strcmp("auto", iomgrstr) == 0) {
+ *flag = IO_MNGR_FLAG_AUTO;
+ return IOManagerAvailable;
+ }
+ /* Two deprecated aliases. These aliases only had any effect on Windows,
+ * but were available as RTS flags on all platforms. The "native" flag
+ * refers to the newer Windows WinIO IO manager (threaded or non-threaded),
+ * while (somewhat confusingly) the "posix" flag refers to the older
+ * Windows I/O managers (win32-legacy and mio). On non-Windows, we now make
+ * these flags equivalent to IO_MNGR_FLAG_AUTO.
+ */
+ else if (strcmp("native", iomgrstr) == 0) {
#if defined(mingw32_HOST_OS)
/* On windows "native" is now an alias for IO_MNGR_FLAG_WINIO */
#if defined(IOMGR_ENABLED_WINIO)
- *flag = IO_MNGR_FLAG_WINIO;
- return IOManagerAvailable;
+ *flag = IO_MNGR_FLAG_WINIO;
+ return IOManagerAvailable;
#else
- return IOManagerUnavailable;
+ return IOManagerUnavailable;
#endif
#else // !defined(mingw32_HOST_OS)
- *flag = IO_MNGR_FLAG_AUTO;
- return IOManagerAvailable;
+ *flag = IO_MNGR_FLAG_AUTO;
+ return IOManagerAvailable;
#endif
- }
- else if (strcmp("posix", iomgrstr) == 0) {
+ } else if (strcmp("posix", iomgrstr) == 0) {
#if defined(mingw32_HOST_OS)
- /* On Windows "posix" is now an alias for either IO_MNGR_FLAG_MIO or
- * IO_MNGR_FLAG_WIN32_LEGACY */
+ /* On Windows "posix" is now an alias for either IO_MNGR_FLAG_MIO or
+ * IO_MNGR_FLAG_WIN32_LEGACY */
#if defined(IOMGR_ENABLED_MIO_WIN32)
- *flag = IO_MNGR_FLAG_MIO;
- return IOManagerAvailable;
+ *flag = IO_MNGR_FLAG_MIO;
+ return IOManagerAvailable;
#elif defined(IOMGR_ENABLED_WIN32_LEGACY)
- *flag = IO_MNGR_FLAG_WIN32_LEGACY;
- return IOManagerAvailable;
+ *flag = IO_MNGR_FLAG_WIN32_LEGACY;
+ return IOManagerAvailable;
#else
- return IOManagerUnavailable;
+ return IOManagerUnavailable;
#endif
#else // !defined(mingw32_HOST_OS)
- *flag = IO_MNGR_FLAG_AUTO;
- return IOManagerAvailable;
+ *flag = IO_MNGR_FLAG_AUTO;
+ return IOManagerAvailable;
#endif
- }
- else {
- return IOManagerUnrecognised;
- }
+ } else {
+ return IOManagerUnrecognised;
+ }
}
/* Based on the I/O manager RTS flag, select an I/O manager to use.
@@ -206,127 +210,136 @@ parseIOManagerFlag(const char *iomgrstr, IO_MANAGER_FLAG *flag)
* Called early in the RTS initialisation, after the RTS flags have been
* processed.
*/
-void selectIOManager(void)
-{
- switch (RtsFlags.MiscFlags.ioManager) {
- case IO_MNGR_FLAG_AUTO:
+void selectIOManager(void) {
+ switch (RtsFlags.MiscFlags.ioManager) {
+ case IO_MNGR_FLAG_AUTO:
#if defined(THREADED_RTS)
-#if defined(IOMGR_DEFAULT_THREADED_MIO)
+#if defined(IOMGR_DEFAULT_THREADED_MIO)
#if defined(mingw32_HOST_OS)
- iomgr_type = IO_MANAGER_MIO_WIN32;
+ iomgr_type = IO_MANAGER_MIO_WIN32;
#else
- iomgr_type = IO_MANAGER_MIO_POSIX;
+ iomgr_type = IO_MANAGER_MIO_POSIX;
#endif
#elif defined(IOMGR_DEFAULT_THREADED_WINIO)
- iomgr_type = IO_MANAGER_WINIO;
+ iomgr_type = IO_MANAGER_WINIO;
+#elif defined(IOMGR_DEFAULT_THREADED_IO_URING)
+ iomgr_type = IO_MANAGER_IO_URING;
#else
#error No I/O default manager. See IOMGR_DEFAULT_THREADED_ flags
#endif
#else // !defined(THREADED_RTS)
-#if defined(IOMGR_DEFAULT_NON_THREADED_SELECT)
- iomgr_type = IO_MANAGER_SELECT;
+#if defined(IOMGR_DEFAULT_NON_THREADED_SELECT)
+ iomgr_type = IO_MANAGER_SELECT;
#elif defined(IOMGR_DEFAULT_NON_THREADED_POLL)
- iomgr_type = IO_MANAGER_POLL;
+ iomgr_type = IO_MANAGER_POLL;
#elif defined(IOMGR_DEFAULT_NON_THREADED_WINIO)
- iomgr_type = IO_MANAGER_WINIO;
+ iomgr_type = IO_MANAGER_WINIO;
#elif defined(IOMGR_DEFAULT_NON_THREADED_WIN32_LEGACY)
- iomgr_type = IO_MANAGER_WIN32_LEGACY;
+ iomgr_type = IO_MANAGER_WIN32_LEGACY;
+#elif defined(IOMGR_DEFAULT_NON_THREADED_IO_URING)
+ iomgr_type = IO_MANAGER_IO_URING;
#else
#error No I/O default manager. See IOMGR_DEFAULT_NON_THREADED_ flags
#endif
#endif
- break;
+ break;
#if defined(IOMGR_ENABLED_SELECT)
- case IO_MNGR_FLAG_SELECT:
- iomgr_type = IO_MANAGER_SELECT;
- break;
+ case IO_MNGR_FLAG_SELECT:
+ iomgr_type = IO_MANAGER_SELECT;
+ break;
+#endif
+
+#if defined(IOMGR_ENABLED_IO_URING)
+ case IO_MNGR_FLAG_IO_URING:
+ iomgr_type = IO_MANAGER_IO_URING;
+ break;
#endif
#if defined(IOMGR_ENABLED_POLL)
- case IO_MNGR_FLAG_POLL:
- iomgr_type = IO_MANAGER_POLL;
- break;
+ case IO_MNGR_FLAG_POLL:
+ iomgr_type = IO_MANAGER_POLL;
+ break;
#endif
#if defined(IOMGR_ENABLED_MIO_POSIX)
- case IO_MNGR_FLAG_MIO:
- iomgr_type = IO_MANAGER_MIO_POSIX;
- break;
+ case IO_MNGR_FLAG_MIO:
+ iomgr_type = IO_MANAGER_MIO_POSIX;
+ break;
#endif
#if defined(IOMGR_ENABLED_MIO_WIN32)
- case IO_MNGR_FLAG_MIO:
- iomgr_type = IO_MANAGER_MIO_WIN32;
- break;
+ case IO_MNGR_FLAG_MIO:
+ iomgr_type = IO_MANAGER_MIO_WIN32;
+ break;
#endif
#if defined(IOMGR_ENABLED_WINIO)
- case IO_MNGR_FLAG_WINIO:
- iomgr_type = IO_MANAGER_WINIO;
- break;
+ case IO_MNGR_FLAG_WINIO:
+ iomgr_type = IO_MANAGER_WINIO;
+ break;
#endif
#if defined(IOMGR_ENABLED_WIN32_LEGACY)
- case IO_MNGR_FLAG_WIN32_LEGACY:
- iomgr_type = IO_MANAGER_WIN32_LEGACY;
- break;
+ case IO_MNGR_FLAG_WIN32_LEGACY:
+ iomgr_type = IO_MANAGER_WIN32_LEGACY;
+ break;
#endif
- default:
- barf("selectIOManager: %d", RtsFlags.MiscFlags.ioManager);
- }
+ default:
+ barf("selectIOManager: %d", RtsFlags.MiscFlags.ioManager);
+ }
#if defined(mingw32_HOST_OS)
- rts_IOManagerIsWin32Native = iomgr_type == IO_MANAGER_WINIO;
+ rts_IOManagerIsWin32Native = iomgr_type == IO_MANAGER_WINIO;
#endif
}
-
-char * showIOManager(void)
-{
- switch (iomgr_type) {
+char *showIOManager(void) {
+ switch (iomgr_type) {
#if defined(IOMGR_ENABLED_SELECT)
- case IO_MANAGER_SELECT:
- return "select";
+ case IO_MANAGER_SELECT:
+ return "select";
#endif
#if defined(IOMGR_ENABLED_POLL)
- case IO_MANAGER_POLL:
- return "poll";
+ case IO_MANAGER_POLL:
+ return "poll";
+#endif
+#if defined(IOMGR_ENABLED_IO_URING)
+ case IO_MANAGER_IO_URING:
+ return "io-uring";
#endif
#if defined(IOMGR_ENABLED_MIO_POSIX)
- case IO_MANAGER_MIO_POSIX:
- return "mio";
+ case IO_MANAGER_MIO_POSIX:
+ return "mio";
#endif
#if defined(IOMGR_ENABLED_MIO_WIN32)
- case IO_MANAGER_MIO_WIN32:
- return "mio";
+ case IO_MANAGER_MIO_WIN32:
+ return "mio";
#endif
#if defined(IOMGR_ENABLED_WINIO)
- case IO_MANAGER_WINIO:
- return "winio";
+ case IO_MANAGER_WINIO:
+ return "winio";
#endif
#if defined(IOMGR_ENABLED_WIN32_LEGACY)
- case IO_MANAGER_WIN32_LEGACY:
- return "win32-legacy";
+ case IO_MANAGER_WIN32_LEGACY:
+ return "win32-legacy";
#endif
- default:
- barf("showIOManager: %d", iomgr_type);
- }
+ default:
+ barf("showIOManager: %d", iomgr_type);
+ }
}
/* Allocate a CapIOManager for a given Capability. Having this helps us keep
* struct CapIOManager opaque from most of the rest of the RTS.
*/
-CapIOManager *allocCapabilityIOManager(Capability *cap)
-{
- CapIOManager *iomgr = stgMallocBytes(sizeof(CapIOManager),
- "allocCapabilityIOManager");
- iomgr->cap = cap; /* link back */
- return iomgr;
+CapIOManager *allocCapabilityIOManager(Capability *cap) {
+ CapIOManager *iomgr =
+ stgMallocBytes(sizeof(CapIOManager), "allocCapabilityIOManager");
+ iomgr->cap = cap; /* link back */
+ return iomgr;
}
-
/* Initialise the per-capability CapIOManager that lives in each Capability.
* Called from initCapability(), which is done in the RTS startup in
* initCapabilities(), and later at runtime via setNumCapabilities().
@@ -334,539 +347,555 @@ CapIOManager *allocCapabilityIOManager(Capability *cap)
* Note that during RTS startup this is called _before_ the storage manager
* is initialised, so this is not allowed to allocate on the GC heap.
*/
-void initCapabilityIOManager(CapIOManager *iomgr)
-{
- debugTrace(DEBUG_iomanager, "initialising I/O manager %s for cap %d",
- showIOManager(), iomgr->cap->no);
+void initCapabilityIOManager(CapIOManager *iomgr) {
+ debugTrace(DEBUG_iomanager, "initialising I/O manager %s for cap %d",
+ showIOManager(), iomgr->cap->no);
- switch (iomgr_type) {
+ switch (iomgr_type) {
#if defined(IOMGR_ENABLED_SELECT)
- case IO_MANAGER_SELECT:
- initCapabilityIOManagerSelect(iomgr);
- break;
+ case IO_MANAGER_SELECT:
+ initCapabilityIOManagerSelect(iomgr);
+ break;
#endif
#if defined(IOMGR_ENABLED_POLL)
- case IO_MANAGER_POLL:
- initCapabilityIOManagerPoll(iomgr);
- break;
+ case IO_MANAGER_POLL:
+ initCapabilityIOManagerPoll(iomgr);
+ break;
+#endif
+
+#if defined(IOMGR_ENABLED_IO_URING)
+ case IO_MANAGER_IO_URING:
+ initCapabilityIOManagerIOURing(iomgr);
+ break;
#endif
#if defined(IOMGR_ENABLED_WIN32_LEGACY)
- case IO_MANAGER_WIN32_LEGACY:
- iomgr->blocked_queue_hd = END_TSO_QUEUE;
- iomgr->blocked_queue_tl = END_TSO_QUEUE;
- break;
+ case IO_MANAGER_WIN32_LEGACY:
+ iomgr->blocked_queue_hd = END_TSO_QUEUE;
+ iomgr->blocked_queue_tl = END_TSO_QUEUE;
+ break;
#endif
#if defined(IOMGR_ENABLED_MIO_POSIX)
- case IO_MANAGER_MIO_POSIX:
- iomgr->control_fd = -1;
- break;
+ case IO_MANAGER_MIO_POSIX:
+ iomgr->control_fd = -1;
+ break;
#endif
- default:
- break;
- }
+ default:
+ break;
+ }
}
-
-void freeCapabilityIOManager(CapIOManager *iomgr)
-{
- switch (iomgr_type) {
+void freeCapabilityIOManager(CapIOManager *iomgr) {
+ switch (iomgr_type) {
#if defined(IOMGR_ENABLED_SELECT)
- case IO_MANAGER_SELECT:
- freeCapabilityIOManagerSelect(iomgr);
- break;
+ case IO_MANAGER_SELECT:
+ freeCapabilityIOManagerSelect(iomgr);
+ break;
#endif
#if defined(IOMGR_ENABLED_POLL)
- case IO_MANAGER_POLL:
- freeCapabilityIOManagerPoll(iomgr);
- break;
+ case IO_MANAGER_POLL:
+ freeCapabilityIOManagerPoll(iomgr);
+ break;
#endif
- default:
- break;
- }
-}
+#if defined(IOMGR_ENABLED_IO_URING)
+ case IO_MANAGER_IO_URING:
+ freeCapabilityIOManagerIOURing(iomgr);
+ break;
+#endif
+ default:
+ break;
+ }
+}
/* Called late in the RTS initialisation
*/
-void startIOManager(void)
-{
- debugTrace(DEBUG_iomanager, "starting %s I/O manager", showIOManager());
+void startIOManager(void) {
+ debugTrace(DEBUG_iomanager, "starting %s I/O manager", showIOManager());
- switch (iomgr_type) {
+ switch (iomgr_type) {
#if defined(IOMGR_ENABLED_SELECT) || defined(IOMGR_ENABLED_POLL)
#if defined(IOMGR_ENABLED_SELECT)
- case IO_MANAGER_SELECT:
+ case IO_MANAGER_SELECT:
#endif
#if defined(IOMGR_ENABLED_POLL)
- case IO_MANAGER_POLL:
+ case IO_MANAGER_POLL:
#endif
- /* Make the exception CAF a GC root. See initBuiltinGcRoots for
- * similar examples. We throw this exception if a thread tries to
- * wait on an invalid FD.
- */
- getStablePtr((StgPtr)blockedOnBadFD_closure);
- break;
+ /* Make the exception CAF a GC root. See initBuiltinGcRoots for
+ * similar examples. We throw this exception if a thread tries to
+ * wait on an invalid FD.
+ */
+ getStablePtr((StgPtr)blockedOnBadFD_closure);
+ break;
#endif
#if defined(IOMGR_ENABLED_MIO_POSIX)
- case IO_MANAGER_MIO_POSIX:
- /* Posix implementation in posix/Signals.c
- * TODO: rename ioManagerStart to be less generic, impl specific
- */
- ioManagerStart();
- break;
+ case IO_MANAGER_MIO_POSIX:
+ /* Posix implementation in posix/Signals.c
+ * TODO: rename ioManagerStart to be less generic, impl specific
+ */
+ ioManagerStart();
+ break;
#endif
#if defined(IOMGR_ENABLED_MIO_WIN32)
- case IO_MANAGER_MIO_WIN32:
- /* Win32 implementation in win32/ThrIOManager.c
- * TODO: rename ioManagerStart to be less generic, impl specific
- */
- ioManagerStart();
- break;
+ case IO_MANAGER_MIO_WIN32:
+ /* Win32 implementation in win32/ThrIOManager.c
+ * TODO: rename ioManagerStart to be less generic, impl specific
+ */
+ ioManagerStart();
+ break;
#endif
#if defined(IOMGR_ENABLED_WINIO)
- case IO_MANAGER_WINIO:
- /* The WinIO I/O manager sadly has two implementations of this
- * startup, depending on the threading mode.
- * TODO: rationalise this into one entry point, that internally
- * can do different things in the two cases.
- */
+ case IO_MANAGER_WINIO:
+ /* The WinIO I/O manager sadly has two implementations of this
+ * startup, depending on the threading mode.
+ * TODO: rationalise this into one entry point, that internally
+ * can do different things in the two cases.
+ */
#if defined(THREADED_RTS)
- /* Win32 implementation in win32/ThrIOManager.c
- */
- ioManagerStart();
+ /* Win32 implementation in win32/ThrIOManager.c
+ */
+ ioManagerStart();
#else
- startupAsyncWinIO();
+ startupAsyncWinIO();
#endif
- break;
+ break;
#endif
#if defined(IOMGR_ENABLED_WIN32_LEGACY)
- case IO_MANAGER_WIN32_LEGACY:
- startupAsyncIO();
- break;
+ case IO_MANAGER_WIN32_LEGACY:
+ startupAsyncIO();
+ break;
#endif
- default:
- break;
- }
+ default:
+ break;
+ }
}
/* Called from forkProcess in the child process on the surviving capability.
*/
-void
-restartIOManager(CapIOManager *iomgr, Capability **pcap)
-{
+void restartIOManager(CapIOManager *iomgr, Capability **pcap) {
- switch (iomgr_type) {
+ switch (iomgr_type) {
#if defined(IOMGR_ENABLED_MIO_POSIX)
- case IO_MANAGER_MIO_POSIX:
- /* Posix implementation in posix/Signals.c
- *
- * TODO: figure out if it is necessary for the threaded MIO case
- * for the starting of the IO manager threads to be synchronous.
- * It would be simpler if it could start them asynchronously and
- * thus not have to have the pcap as an inout param, that could be
- * modified. In practice it cannot be modified anyway since in the
- * contexts where it is called (forkProcess), there is only a
- * single cap available.
- */
- ioManagerStartCap(pcap);
- break;
-#endif
- /* The IO_MANAGER_SELECT needs no initialisation */
- /* The IO_MANAGER_POLL needs no initialisation */
-
- /* No impl for any of the Windows I/O managers, since no forking. */
- default:
- break;
- }
-}
+ case IO_MANAGER_MIO_POSIX:
+ /* Posix implementation in posix/Signals.c
+ *
+ * TODO: figure out if it is necessary for the threaded MIO case
+ * for the starting of the IO manager threads to be synchronous.
+ * It would be simpler if it could start them asynchronously and
+ * thus not have to have the pcap as an inout param, that could be
+ * modified. In practice it cannot be modified anyway since in the
+ * contexts where it is called (forkProcess), there is only a
+ * single cap available.
+ */
+ ioManagerStartCap(pcap);
+ break;
+#endif
+ /* The IO_MANAGER_SELECT needs no initialisation */
+ /* The IO_MANAGER_POLL needs no initialisation */
+ /* No impl for any of the Windows I/O managers, since no forking. */
+ default:
+ break;
+ }
+}
/* Called from setNumCapabilities.
*/
-void notifyIOManagerCapabilitiesChanged(CapIOManager *iomgr, Capability **pcap)
-{
- switch (iomgr_type) {
+void notifyIOManagerCapabilitiesChanged(CapIOManager *iomgr,
+ Capability **pcap) {
+ switch (iomgr_type) {
#if defined(IOMGR_ENABLED_MIO_POSIX)
- case IO_MANAGER_MIO_POSIX:
- rts_evalIO(pcap, ioManagerCapabilitiesChanged_closure, NULL);
- break;
+ case IO_MANAGER_MIO_POSIX:
+ rts_evalIO(pcap, ioManagerCapabilitiesChanged_closure, NULL);
+ break;
#endif
- default:
- break;
- }
+ default:
+ break;
+ }
}
-
/* Called in the RTS shutdown before the scheduler exits
*/
-void
-stopIOManager(void)
-{
- switch (iomgr_type) {
+void stopIOManager(void) {
+ switch (iomgr_type) {
#if defined(IOMGR_ENABLED_MIO_POSIX)
- case IO_MANAGER_MIO_POSIX:
- /* Posix implementation in posix/Signals.c
- * TODO: rename ioManagerDie to be less generic, impl specific
- */
- ioManagerDie();
- break;
+ case IO_MANAGER_MIO_POSIX:
+ /* Posix implementation in posix/Signals.c
+ * TODO: rename ioManagerDie to be less generic, impl specific
+ */
+ ioManagerDie();
+ break;
#endif
#if defined(IOMGR_ENABLED_MIO_WIN32) || defined(IOMGR_ENABLED_WINIO)
#if defined(IOMGR_ENABLED_MIO_WIN32)
- case IO_MANAGER_MIO_WIN32:
+ case IO_MANAGER_MIO_WIN32:
#endif
#if defined(IOMGR_ENABLED_WINIO)
- case IO_MANAGER_WINIO:
+ case IO_MANAGER_WINIO:
#endif
- /* Win32 implementation in win32/ThrIOManager.c
- * This impl is currently shared by both MIO and WinIO I/O managers
- * TODO: rename ioManagerDie to be less generic, impl specific
- */
- ioManagerDie();
- break;
+ /* Win32 implementation in win32/ThrIOManager.c
+ * This impl is currently shared by both MIO and WinIO I/O managers
+ * TODO: rename ioManagerDie to be less generic, impl specific
+ */
+ ioManagerDie();
+ break;
#endif
- default:
- break;
- }
+ default:
+ break;
+ }
}
-
/* Called in the RTS shutdown after the scheduler exits
*/
-void
-exitIOManager(bool wait_threads)
-{
- switch (iomgr_type) {
+void exitIOManager(bool wait_threads) {
+ switch (iomgr_type) {
#if defined(IOMGR_ENABLED_WINIO)
- case IO_MANAGER_WINIO:
- shutdownAsyncWinIO(wait_threads);
- break;
+ case IO_MANAGER_WINIO:
+ shutdownAsyncWinIO(wait_threads);
+ break;
#endif
#if defined(IOMGR_ENABLED_WIN32_LEGACY)
- case IO_MANAGER_WIN32_LEGACY:
- shutdownAsyncIO(wait_threads);
- break;
+ case IO_MANAGER_WIN32_LEGACY:
+ shutdownAsyncIO(wait_threads);
+ break;
#endif
- default:
- break;
- }
+ default:
+ break;
+ }
}
-void markCapabilityIOManager(evac_fn evac, void *user, CapIOManager *iomgr)
-{
- switch (iomgr_type) {
+void markCapabilityIOManager(evac_fn evac, void *user, CapIOManager *iomgr) {
+ switch (iomgr_type) {
#if defined(IOMGR_ENABLED_SELECT)
- case IO_MANAGER_SELECT:
- evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_hd);
- evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_tl);
- evac(user, (StgClosure **)(void *)&iomgr->sleeping_queue);
- break;
+ case IO_MANAGER_SELECT:
+ evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_hd);
+ evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_tl);
+ evac(user, (StgClosure **)(void *)&iomgr->sleeping_queue);
+ break;
#endif
#if defined(IOMGR_ENABLED_POLL)
- case IO_MANAGER_POLL:
- markClosureTable(evac, user, &iomgr->aiop_table);
- evac(user, (StgClosure **)(void *)&iomgr->timeout_queue);
- break;
+ case IO_MANAGER_POLL:
+ markClosureTable(evac, user, &iomgr->aiop_table);
+ evac(user, (StgClosure **)(void *)&iomgr->timeout_queue);
+ break;
#endif
-#if defined(IOMGR_ENABLED_WIN32_LEGACY)
- case IO_MANAGER_WIN32_LEGACY:
- evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_hd);
- evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_tl);
- break;
+#if defined(IOMGR_ENABLED_IO_URING)
+ case IO_MANAGER_IO_URING:
+ markClosureTable(evac, user, &iomgr->aiop_table);
+ break;
#endif
- default:
- break;
- }
-}
+#if defined(IOMGR_ENABLED_WIN32_LEGACY)
+ case IO_MANAGER_WIN32_LEGACY:
+ evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_hd);
+ evac(user, (StgClosure **)(void *)&iomgr->blocked_queue_tl);
+ break;
+#endif
+ default:
+ break;
+ }
+}
/* Declared in rts/IOInterface.h. Used only by the MIO threaded I/O manager on
* Unix platforms.
*/
#if !defined(mingw32_HOST_OS)
-void
-setIOManagerControlFd(uint32_t cap_no, int fd) {
+void setIOManagerControlFd(uint32_t cap_no, int fd) {
#if defined(THREADED_RTS)
- if (cap_no < getNumCapabilities()) {
- RELAXED_STORE(&getCapability(cap_no)->iomgr->control_fd, fd);
- } else {
- errorBelch("warning: setIOManagerControlFd called with illegal capability number.");
- }
+ if (cap_no < getNumCapabilities()) {
+ RELAXED_STORE(&getCapability(cap_no)->iomgr->control_fd, fd);
+ } else {
+ errorBelch("warning: setIOManagerControlFd called with illegal capability "
+ "number.");
+ }
#endif
}
#endif
-
-void pollCompletedTimeoutsOrIO(CapIOManager *iomgr)
-{
- debugTrace(DEBUG_iomanager, "polling for completed IO or timeouts");
- switch (iomgr_type) {
+void pollCompletedTimeoutsOrIO(CapIOManager *iomgr) {
+ debugTrace(DEBUG_iomanager, "polling for completed IO or timeouts");
+ switch (iomgr_type) {
#if defined(IOMGR_ENABLED_SELECT)
- case IO_MANAGER_SELECT:
- awaitCompletedTimeoutsOrIOSelect(iomgr, false);
- break;
+ case IO_MANAGER_SELECT:
+ awaitCompletedTimeoutsOrIOSelect(iomgr, false);
+ break;
#endif
#if defined(IOMGR_ENABLED_POLL)
- case IO_MANAGER_POLL:
- pollCompletedTimeoutsOrIOPoll(iomgr);
- break;
+ case IO_MANAGER_POLL:
+ pollCompletedTimeoutsOrIOPoll(iomgr);
+ break;
#endif
-#if defined(IOMGR_ENABLED_WIN32_LEGACY) || \
- (defined(IOMGR_ENABLED_WINIO) && !defined(THREADED_RTS))
+#if defined(IOMGR_ENABLED_IO_URING)
+ case IO_MANAGER_IO_URING:
+ pollCompletedTimeoutsOrIOIOURing(iomgr);
+ break;
+#endif
+
+#if defined(IOMGR_ENABLED_WIN32_LEGACY) || \
+ (defined(IOMGR_ENABLED_WINIO) && !defined(THREADED_RTS))
#if defined(IOMGR_ENABLED_WIN32_LEGACY)
- case IO_MANAGER_WIN32_LEGACY:
+ case IO_MANAGER_WIN32_LEGACY:
#endif
#if defined(IOMGR_ENABLED_WINIO)
- case IO_MANAGER_WINIO:
+ case IO_MANAGER_WINIO:
#endif
- awaitCompletedTimeoutsOrIOWin32(iomgr->cap, false);
- break;
+ awaitCompletedTimeoutsOrIOWin32(iomgr->cap, false);
+ break;
#endif
- default:
- barf("pollCompletedTimeoutsOrIO not implemented");
- }
+ default:
+ barf("pollCompletedTimeoutsOrIO not implemented");
+ }
}
-
-bool awaitCompletedTimeoutsOrIO(CapIOManager *iomgr)
-{
- debugTrace(DEBUG_iomanager, "waiting for completed IO or timeouts");
- bool completed = true; // wait completed or interrupted?
- switch (iomgr_type) {
+bool awaitCompletedTimeoutsOrIO(CapIOManager *iomgr) {
+ debugTrace(DEBUG_iomanager, "waiting for completed IO or timeouts");
+ bool completed = true; // wait completed or interrupted?
+ switch (iomgr_type) {
#if defined(IOMGR_ENABLED_SELECT)
- case IO_MANAGER_SELECT:
- completed = awaitCompletedTimeoutsOrIOSelect(iomgr, true);
- break;
+ case IO_MANAGER_SELECT:
+ completed = awaitCompletedTimeoutsOrIOSelect(iomgr, true);
+ break;
#endif
#if defined(IOMGR_ENABLED_POLL)
- case IO_MANAGER_POLL:
- completed = awaitCompletedTimeoutsOrIOPoll(iomgr);
- break;
+ case IO_MANAGER_POLL:
+ completed = awaitCompletedTimeoutsOrIOPoll(iomgr);
+ break;
+#endif
+
+#if defined(IOMGR_ENABLED_IO_URING)
+ case IO_MANAGER_IO_URING:
+ completed = awaitCompletedTimeoutsOrIOIOURing(iomgr);
+ break;
#endif
-#if defined(IOMGR_ENABLED_WIN32_LEGACY) || \
- (defined(IOMGR_ENABLED_WINIO) && !defined(THREADED_RTS))
+#if defined(IOMGR_ENABLED_WIN32_LEGACY) || \
+ (defined(IOMGR_ENABLED_WINIO) && !defined(THREADED_RTS))
#if defined(IOMGR_ENABLED_WIN32_LEGACY)
- case IO_MANAGER_WIN32_LEGACY:
+ case IO_MANAGER_WIN32_LEGACY:
#endif
#if defined(IOMGR_ENABLED_WINIO)
- case IO_MANAGER_WINIO:
+ case IO_MANAGER_WINIO:
#endif
- completed = awaitCompletedTimeoutsOrIOWin32(iomgr->cap, true);
- break;
+ completed = awaitCompletedTimeoutsOrIOWin32(iomgr->cap, true);
+ break;
#endif
- default:
- barf("awaitCompletedTimeoutsOrIO not implemented");
- }
- ASSERT(!emptyRunQueue(iomgr->cap) ||
- getSchedState() != SCHED_RUNNING ||
- !completed);
- return completed;
+ default:
+ barf("awaitCompletedTimeoutsOrIO not implemented");
+ }
+ ASSERT(!emptyRunQueue(iomgr->cap) || getSchedState() != SCHED_RUNNING ||
+ !completed);
+ return completed;
}
-
/* Interrupt the I/O manager if it is blocked in awaitCompletedTimeoutsOrIO,
* causing it to return early and return false.
*/
-void interruptIOManager(CapIOManager *iomgr)
-{
- debugTrace(DEBUG_iomanager, "Interrupting the I/O manager...");
- switch (iomgr_type) {
+void interruptIOManager(CapIOManager *iomgr) {
+ debugTrace(DEBUG_iomanager, "Interrupting the I/O manager...");
+ switch (iomgr_type) {
#if defined(IOMGR_ENABLED_SELECT)
- case IO_MANAGER_SELECT:
- interruptIOManagerSelect(iomgr);
- break;
+ case IO_MANAGER_SELECT:
+ interruptIOManagerSelect(iomgr);
+ break;
+#endif
+
+#if defined(IOMGR_ENABLED_IO_URING)
+ case IO_MANAGER_IO_URING:
+ interruptIOManagerIOURing(iomgr);
+ break;
#endif
#if defined(IOMGR_ENABLED_POLL)
- case IO_MANAGER_POLL:
- interruptIOManagerPoll(iomgr);
- break;
+ case IO_MANAGER_POLL:
+ interruptIOManagerPoll(iomgr);
+ break;
#endif
#if defined(IOMGR_ENABLED_WIN32_LEGACY)
- case IO_MANAGER_WIN32_LEGACY:
- abandonRequestWait();
- break;
+ case IO_MANAGER_WIN32_LEGACY:
+ abandonRequestWait();
+ break;
#endif
#if defined(IOMGR_ENABLED_WINIO)
- case IO_MANAGER_WINIO:
- /* FIXME: no support yet for interrupting in WinIO I/O manager
- * See issue #27403
- */
- break;
+ case IO_MANAGER_WINIO:
+ /* FIXME: no support yet for interrupting in WinIO I/O manager
+ * See issue #27403
+ */
+ break;
#endif
- default:
- break;
- }
+ default:
+ break;
+ }
}
-
/* CMM primop. Result is true on success, or false on allocation failure. */
-bool syncIOWaitReady(CapIOManager *iomgr,
- StgTSO *tso,
- IOReadOrWrite rw,
- HsInt fd)
-{
- debugTrace(DEBUG_iomanager,
- "thread %ld waiting for %s I/O readiness on fd %d",
- (long) tso->id, rw == IORead ? "read" : "write", (int) fd);
- ASSERT(tso->why_blocked == NotBlocked);
- switch (iomgr_type) {
+bool syncIOWaitReady(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw,
+ HsInt fd) {
+ debugTrace(DEBUG_iomanager,
+ "thread %ld waiting for %s I/O readiness on fd %d", (long)tso->id,
+ rw == IORead ? "read" : "write", (int)fd);
+ ASSERT(tso->why_blocked == NotBlocked);
+ switch (iomgr_type) {
#if defined(IOMGR_ENABLED_SELECT)
- case IO_MANAGER_SELECT:
- {
- StgThreadWhyBlocked why_blocked = (rw == IORead ? BlockedOnRead
- : BlockedOnWrite)
- | BlockInfoForceNonClosure;
- tso->block_info.fd = fd;
- appendToIOBlockedQueue(iomgr, tso);
- RELEASE_STORE(&tso->why_blocked, why_blocked);
- return true;
- }
+ case IO_MANAGER_SELECT: {
+ StgThreadWhyBlocked why_blocked =
+ (rw == IORead ? BlockedOnRead : BlockedOnWrite) |
+ BlockInfoForceNonClosure;
+ tso->block_info.fd = fd;
+ appendToIOBlockedQueue(iomgr, tso);
+ RELEASE_STORE(&tso->why_blocked, why_blocked);
+ return true;
+ }
#endif
#if defined(IOMGR_ENABLED_POLL)
- case IO_MANAGER_POLL:
- return syncIOWaitReadyPoll(iomgr, tso, rw, fd);
+ case IO_MANAGER_POLL:
+ return syncIOWaitReadyPoll(iomgr, tso, rw, fd);
#endif
- default:
- barf("waitRead# / waitWrite# not available for current I/O manager");
- }
+#if defined(IOMGR_ENABLED_IO_URING)
+ case IO_MANAGER_IO_URING:
+ return syncIOWaitReadyIOURing(iomgr, tso, rw, fd);
+#endif
+ default:
+ barf("waitRead# / waitWrite# not available for current I/O manager");
+ }
}
-
-void syncIOCancel(CapIOManager *iomgr, StgTSO *tso)
-{
- debugTrace(DEBUG_iomanager, "cancelling I/O for thread %ld", (long) tso->id);
- switch (iomgr_type) {
+void syncIOCancel(CapIOManager *iomgr, StgTSO *tso) {
+ debugTrace(DEBUG_iomanager, "cancelling I/O for thread %ld", (long)tso->id);
+ switch (iomgr_type) {
#if defined(IOMGR_ENABLED_SELECT)
- case IO_MANAGER_SELECT:
- removeThreadFromDeQueue(iomgr->cap,
- &iomgr->blocked_queue_hd,
- &iomgr->blocked_queue_tl,
- tso);
- break;
+ case IO_MANAGER_SELECT:
+ removeThreadFromDeQueue(iomgr->cap, &iomgr->blocked_queue_hd,
+ &iomgr->blocked_queue_tl, tso);
+ break;
#endif
#if defined(IOMGR_ENABLED_POLL)
- case IO_MANAGER_POLL:
- syncIOCancelPoll(iomgr, tso);
- break;
+ case IO_MANAGER_POLL:
+ syncIOCancelPoll(iomgr, tso);
+ break;
+#endif
+#if defined(IOMGR_ENABLED_IO_URING)
+ case IO_MANAGER_IO_URING:
+ syncIOCancelIOURing(iomgr, tso);
+ break;
#endif
#if defined(IOMGR_ENABLED_WIN32_LEGACY)
- case IO_MANAGER_WIN32_LEGACY:
- removeThreadFromDeQueue(iomgr->cap,
- &iomgr->blocked_queue_hd,
- &iomgr->blocked_queue_tl,
- tso);
- abandonWorkRequest(tso->block_info.async_reqID);
- break;
-#endif
- default:
- barf("syncIOCancel not supported for I/O manager %d", iomgr_type);
- }
+ case IO_MANAGER_WIN32_LEGACY:
+ removeThreadFromDeQueue(iomgr->cap, &iomgr->blocked_queue_hd,
+ &iomgr->blocked_queue_tl, tso);
+ abandonWorkRequest(tso->block_info.async_reqID);
+ break;
+#endif
+ default:
+ barf("syncIOCancel not supported for I/O manager %d", iomgr_type);
+ }
}
-
#if defined(IOMGR_ENABLED_SELECT)
-static void insertIntoSleepingQueue(CapIOManager *iomgr, StgTSO *tso, LowResTime target);
+static void insertIntoSleepingQueue(CapIOManager *iomgr, StgTSO *tso,
+ LowResTime target);
#endif
-
/* CMM primop. Result is true on success, or false on allocation failure. */
-bool syncDelay(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay)
-{
- debugTrace(DEBUG_iomanager, "thread %ld waiting for %lld us", tso->id, us_delay);
- ASSERT(tso->why_blocked == NotBlocked);
- switch (iomgr_type) {
+bool syncDelay(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay) {
+ debugTrace(DEBUG_iomanager, "thread %ld waiting for %lld us", tso->id,
+ us_delay);
+ ASSERT(tso->why_blocked == NotBlocked);
+ switch (iomgr_type) {
#if defined(IOMGR_ENABLED_SELECT)
- case IO_MANAGER_SELECT:
- {
- LowResTime target = getDelayTarget(us_delay);
- tso->block_info.target = target;
- insertIntoSleepingQueue(iomgr, tso, target);
- RELEASE_STORE(&tso->why_blocked, BlockedOnDelay | BlockInfoForceNonClosure);
- return true;
- }
+ case IO_MANAGER_SELECT: {
+ LowResTime target = getDelayTarget(us_delay);
+ tso->block_info.target = target;
+ insertIntoSleepingQueue(iomgr, tso, target);
+ RELEASE_STORE(&tso->why_blocked, BlockedOnDelay | BlockInfoForceNonClosure);
+ return true;
+ }
#endif
+#if defined(IOMGR_ENABLED_POLL) || defined(IOMGR_ENABLED_IO_URING)
+
#if defined(IOMGR_ENABLED_POLL)
- case IO_MANAGER_POLL:
- return syncDelayTimeout(iomgr, tso, us_delay);
+ case IO_MANAGER_POLL:
+#endif
+#if defined(IOMGR_ENABLED_IO_URING)
+ case IO_MANAGER_IO_URING:
+#endif
+ return syncDelayTimeout(iomgr, tso, us_delay);
#endif
#if defined(IOMGR_ENABLED_WIN32_LEGACY)
- case IO_MANAGER_WIN32_LEGACY:
- /* It would be nice to allocate this on the heap instead as it
- * would make the primops more consistent.
- */
- {
- tso->block_info.async_reqID = addDelayRequest(us_delay);
-
- /* Having all async-blocked threads reside on the blocked_queue
- * simplifies matters, so set the status to OnDoProc and put the
- * delayed thread on the blocked_queue.
- */
- appendToIOBlockedQueue(iomgr, tso);
- RELEASE_STORE(&tso->why_blocked, BlockedOnDoProc);
- return true;
- }
-#endif
- default:
- barf("syncDelay not supported for I/O manager %d", iomgr_type);
+ case IO_MANAGER_WIN32_LEGACY:
+ /* It would be nice to allocate this on the heap instead as it
+ * would make the primops more consistent.
+ */
+ {
+ tso->block_info.async_reqID = addDelayRequest(us_delay);
+
+ /* Having all async-blocked threads reside on the blocked_queue
+ * simplifies matters, so set the status to OnDoProc and put the
+ * delayed thread on the blocked_queue.
+ */
+ appendToIOBlockedQueue(iomgr, tso);
+ RELEASE_STORE(&tso->why_blocked, BlockedOnDoProc);
+ return true;
}
+#endif
+ default:
+ barf("syncDelay not supported for I/O manager %d", iomgr_type);
+ }
}
-
-void syncDelayCancel(CapIOManager *iomgr, StgTSO *tso)
-{
- debugTrace(DEBUG_iomanager, "cancelling delay for thread %ld", (long) tso->id);
- switch (iomgr_type) {
+void syncDelayCancel(CapIOManager *iomgr, StgTSO *tso) {
+ debugTrace(DEBUG_iomanager, "cancelling delay for thread %ld", (long)tso->id);
+ switch (iomgr_type) {
#if defined(IOMGR_ENABLED_SELECT)
- case IO_MANAGER_SELECT:
- ASSERT(tso->why_blocked == (BlockedOnDelay | BlockInfoForceNonClosure));
- removeThreadFromQueue(iomgr->cap, &iomgr->sleeping_queue, tso);
- break;
+ case IO_MANAGER_SELECT:
+ ASSERT(tso->why_blocked == (BlockedOnDelay | BlockInfoForceNonClosure));
+ removeThreadFromQueue(iomgr->cap, &iomgr->sleeping_queue, tso);
+ break;
#endif
#if defined(IOMGR_ENABLED_POLL)
- case IO_MANAGER_POLL:
- syncDelayCancelTimeout(iomgr, tso);
- break;
-#endif
- /* Note: no case for IO_MANAGER_WIN32_LEGACY despite it having a case
- * for syncDelay above. This is because the win32 legacy I/O manager
- * treats delay as an I/O operation, using the BlockedOnDoProc blocking
- * reason, rather than the BlockedOnDelay reason. As a consequence,
- * cancellation goes via syncIOCancel instead. Yes, it's a bit weird.
- */
-
- default:
- barf("syncDelayCancel not supported for I/O manager %d", iomgr_type);
- }
-}
+ case IO_MANAGER_POLL:
+ syncDelayCancelTimeout(iomgr, tso);
+ break;
+#endif
+#if defined(IOMGR_ENABLED_IO_URING)
+ case IO_MANAGER_IO_URING:
+ syncIOCancelIOURing(iomgr, tso);
+ break;
+#endif
+ /* Note: no case for IO_MANAGER_WIN32_LEGACY despite it having a case
+ * for syncDelay above. This is because the win32 legacy I/O manager
+ * treats delay as an I/O operation, using the BlockedOnDoProc blocking
+ * reason, rather than the BlockedOnDelay reason. As a consequence,
+ * cancellation goes via syncIOCancel instead. Yes, it's a bit weird.
+ */
+ default:
+ barf("syncDelayCancel not supported for I/O manager %d", iomgr_type);
+ }
+}
#if defined(IOMGR_ENABLED_SELECT) || defined(IOMGR_ENABLED_WIN32_LEGACY)
-void appendToIOBlockedQueue(CapIOManager *iomgr, StgTSO *tso)
-{
- ASSERT(tso->_link == END_TSO_QUEUE);
- if (iomgr->blocked_queue_hd == END_TSO_QUEUE) {
- iomgr->blocked_queue_hd = tso;
- } else {
- setTSOLink(iomgr->cap, iomgr->blocked_queue_tl, tso);
- }
- iomgr->blocked_queue_tl = tso;
+void appendToIOBlockedQueue(CapIOManager *iomgr, StgTSO *tso) {
+ ASSERT(tso->_link == END_TSO_QUEUE);
+ if (iomgr->blocked_queue_hd == END_TSO_QUEUE) {
+ iomgr->blocked_queue_hd = tso;
+ } else {
+ setTSOLink(iomgr->cap, iomgr->blocked_queue_tl, tso);
+ }
+ iomgr->blocked_queue_tl = tso;
}
#endif
@@ -879,39 +908,37 @@ void appendToIOBlockedQueue(CapIOManager *iomgr, StgTSO *tso)
* used. This is a wart that should be excised.
*/
// TODO: move to Select.c and rename
-static void insertIntoSleepingQueue(CapIOManager *iomgr, StgTSO *tso, LowResTime target)
-{
- StgTSO *prev = NULL;
- StgTSO *t = iomgr->sleeping_queue;
- while (t != END_TSO_QUEUE && t->block_info.target < target) {
- prev = t;
- t = t->_link;
- }
-
- tso->_link = t;
- if (prev == NULL) {
- iomgr->sleeping_queue = tso;
- } else {
- setTSOLink(iomgr->cap, prev, tso);
- }
+static void insertIntoSleepingQueue(CapIOManager *iomgr, StgTSO *tso,
+ LowResTime target) {
+ StgTSO *prev = NULL;
+ StgTSO *t = iomgr->sleeping_queue;
+ while (t != END_TSO_QUEUE && t->block_info.target < target) {
+ prev = t;
+ t = t->_link;
+ }
+
+ tso->_link = t;
+ if (prev == NULL) {
+ iomgr->sleeping_queue = tso;
+ } else {
+ setTSOLink(iomgr->cap, prev, tso);
+ }
}
#endif
/* Temporary compat helper function used in the Win32 I/O managers.
* TODO: replace by consulting the iomgr_type global instead.
*/
-bool is_io_mng_native_p (void)
-{
- switch (iomgr_type) {
+bool is_io_mng_native_p(void) {
+ switch (iomgr_type) {
#if defined(IOMGR_ENABLED_WINIO)
- case IO_MANAGER_WINIO:
- return true;
+ case IO_MANAGER_WINIO:
+ return true;
#endif
- default:
- return false;
- }
+ default:
+ return false;
+ }
}
-
/* See comment above with the #pragma GCC diagnostic push */
#pragma GCC diagnostic pop
=====================================
rts/IOManager.h
=====================================
@@ -61,6 +61,9 @@ extern bool rts_IOManagerIsWin32Native;
#if defined(IOMGR_BUILD_POLL) && !defined(THREADED_RTS)
#define IOMGR_ENABLED_POLL
#endif
+#if defined(IOMGR_BUILD_IO_URING)
+ #define IOMGR_ENABLED_IO_URING
+#endif
#if defined(IOMGR_BUILD_MIO) && defined(THREADED_RTS)
/* For MIO, it is really two separate I/O manager implementations: one for
* Windows and one for non-Windows. This is clear from both the C code on the
@@ -94,6 +97,8 @@ extern bool rts_IOManagerIsWin32Native;
#define IOMGR_DEFAULT_STR "mio"
#elif defined(IOMGR_DEFAULT_THREADED_WINIO)
#define IOMGR_DEFAULT_STR "winio"
+#elif defined(IOMGR_DEFAULT_THREADED_IO_URING)
+ #define IOMGR_DEFAULT_STR "io_uring"
#else
#error No I/O default manager. See IOMGR_DEFAULT_THREADED_ flags
#endif
@@ -106,6 +111,8 @@ extern bool rts_IOManagerIsWin32Native;
#define IOMGR_DEFAULT_STR "winio"
#elif defined(IOMGR_DEFAULT_NON_THREADED_WIN32_LEGACY)
#define IOMGR_DEFAULT_STR "win32-legacy"
+#elif defined(IOMGR_DEFAULT_NON_THREADED_IO_URING)
+ #define IOMGR_DEFAULT_STR "io_uring"
#else
#error No I/O default manager. See IOMGR_DEFAULT_NON_THREADED_ flags
#endif
@@ -125,6 +132,11 @@ extern bool rts_IOManagerIsWin32Native;
#else
#define IOMGR_ENABLED_STR_POLL ""
#endif
+#if defined(IOMGR_ENABLED_IO_URING)
+ #define IOMGR_ENABLED_STR_IO_URING " io_uring"
+#else
+ #define IOMGR_ENABLED_STR_IO_URING ""
+#endif
#if defined(IOMGR_ENABLED_MIO_POSIX) || defined(IOMGR_ENABLED_MIO_WIN32)
#define IOMGR_ENABLED_STR_MIO " mio"
#else
@@ -143,6 +155,7 @@ extern bool rts_IOManagerIsWin32Native;
#define IOMGRS_ENABLED_STR \
IOMGR_ENABLED_STR_SELECT \
IOMGR_ENABLED_STR_POLL \
+ IOMGR_ENABLED_STR_IO_URING \
IOMGR_ENABLED_STR_MIO \
IOMGR_ENABLED_STR_WINIO \
IOMGR_ENABLED_STR_WIN32_LEGACY
@@ -158,6 +171,9 @@ typedef enum {
#if defined(IOMGR_ENABLED_POLL)
IO_MANAGER_POLL,
#endif
+#if defined(IOMGR_ENABLED_IO_URING)
+ IO_MANAGER_IO_URING,
+#endif
#if defined(IOMGR_ENABLED_MIO_POSIX)
IO_MANAGER_MIO_POSIX,
#endif
=====================================
rts/IOManagerInternals.h
=====================================
@@ -20,6 +20,11 @@
#include "TimeoutQueue.h"
#endif
+#if defined(IOMGR_ENABLED_IO_URING)
+#include <liburing.h>
+#include "ClosureTable.h"
+#endif
+
#include "BeginPrivate.h"
/* The per-capability data structures belonging to the I/O manager.
@@ -53,7 +58,7 @@ struct _CapIOManager {
#endif
#endif
-#if defined(IOMGR_ENABLED_POLL)
+#if defined(IOMGR_ENABLED_POLL) || defined(IOMGR_ENABLED_IO_URING)
/* AIOP and timeout collections shared by several I/O manager impls */
ClosureTable aiop_table;
StgTimeoutQueue *timeout_queue;
@@ -67,6 +72,54 @@ struct _CapIOManager {
struct pollfd *aiop_poll_table, *full_poll_table;
#endif
+#if defined(IOMGR_ENABLED_IO_URING)
+ /* io_uring library structure */
+ struct io_uring *uring;
+
+ /* The number of operations submitted (by Haskell threads to the I/O
+ manager) and not yet notified of completion. */
+ int n_submitted_b; /* for blocking operations */
+ int n_submitted_nb; /* for non-blocking operations */
+
+ /* The number of operations pending in the submission queue, but not yet
+ submitted to the kernel (so not in-flight). */
+ int n_prepared_b; /* for blocking operations */
+ int n_prepared_nb; /* for non-blocking operations */
+
+ /* The number of operations submitted to the kernel but where the
+ corresponding completion has not yet been processed. */
+ int n_inflight_b; /* for blocking operations */
+ int n_inflight_nb; /* for non-blocking operations */
+
+ /* The limit on the number of operations we allow to be in-flight */
+ int limit_inflight_b; /* for blocking operations */
+ int limit_inflight_nb; /* for non-blocking operations */
+
+ /* The number of operations pending in the overflow queue (so not in the
+ submission queue or in flight) */
+ /* no overflow for blocking operations */
+ int n_overflow_nb; /* for non-blocking operations */
+
+ /* Invariants:
+ n_submitted_b = n_prepared_b + n_inflight_b
+ n_submitted_nb = n_prepared_nb + n_inflight_nb + n_overflow_nb
+ n_prepared_b + n_prepared_nb <= size of submission queue
+ */
+
+ /* A queue of threads blocked on I/O submission and a parallel queue of
+ * their corresponding SQEs. This is only used when there are more pending
+ * (non-blocking) I/O operations than the inflight limit.
+ */
+ StgTSO *overflow_tso_q_hd, *overflow_tso_q_tl;
+ struct overflow_sqe_q_t {
+ struct io_uring_sqe *sqe;
+ struct overflow_sqe_q_t *next;
+#if defined(DEBUG)
+ StgThreadID tid;
+#endif
+ } *overflow_sqe_q_hd, *overflow_sqe_q_tl;
+#endif
+
#if defined(IOMGR_ENABLED_WIN32_LEGACY)
/* Thread queue for threads blocked on I/O completion. */
StgTSO *blocked_queue_hd;
=====================================
rts/configure.ac
=====================================
@@ -381,12 +381,16 @@ GHC_IOMANAGER_ENABLE([poll], [EnableIOManagerPoll], [IOMGR_BUILD_POLL],
#include <poll.h>])
if test "$HostOS" = "darwin" -a "$EnableIOManagerPoll" = "YES"; then
dnl Sadly poll is broken on macOS. See the macOS man page:
- dnl it doesn't work for device files!
+ dnl it doesn\'t work for device files!
AC_MSG_WARN([macOS poll() does not work properly. Disabling.])
EnableIOManagerPoll=NO
fi
fi])
+GHC_IOMANAGER_ENABLE([io-uring], [EnableIOManagerIOURing], [IOMGR_BUILD_IO_URING],
+ [EnableIOManagerIOURing=YES]
+)
+
GHC_IOMANAGER_ENABLE([mio], [EnableIOManagerMIO], [IOMGR_BUILD_MIO],
[EnableIOManagerMIO=YES])
@@ -407,6 +411,7 @@ if test "$HostOS" = "mingw32"; then
else
GHC_IOMANAGER_DEFAULT_SELECT([IOManagerNonThreadedDefault], [select], [EnableIOManagerSelect])
GHC_IOMANAGER_DEFAULT_SELECT([IOManagerNonThreadedDefault], [poll], [EnableIOManagerPoll])
+ GHC_IOMANAGER_DEFAULT_SELECT([IOManagerNonThreadedDefault], [io-uring], [EnableIOManagerIOURing])
GHC_IOMANAGER_DEFAULT_SELECT([IOManagerThreadedDefault], [mio], [EnableIOManagerMIO])
fi
GHC_IOMANAGER_DEFAULT_CHECK_NOT_EMPTY([IOManagerNonThreadedDefault],[non-threaded])
@@ -428,12 +433,18 @@ GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded],
GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded],
[win32-legacy], [IOMGR_DEFAULT_NON_THREADED_WIN32_LEGACY])
+GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded],
+ [io-uring], [IOMGR_DEFAULT_NON_THREADED_IO_URING])
+
GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerThreadedDefault], [threaded],
[mio], [IOMGR_DEFAULT_THREADED_MIO])
GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerThreadedDefault], [threaded],
[winio], [IOMGR_DEFAULT_THREADED_WINIO])
+GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerThreadedDefault], [threaded],
+ [io-uring], [IOMGR_DEFAULT_THREADED_IO_URING])
+
dnl ** FFI
dnl --------------------------------------------------------------
=====================================
rts/include/rts/Constants.h
=====================================
@@ -315,6 +315,8 @@
#define ThreadMigrating 11 /* Uses block_info.unused */
#define BlockedOnDoProc 12 /* Uses block_info.async_reqID */
+#define BlockedOnIOSubmission 15
+
/* Reserved values, not values that why_blocked currently use. They
* are used in primop stg_threadStatuszh and must not overlap with
* other why_blocked status values. They could be changed, if the
=====================================
rts/include/rts/Flags.h
=====================================
@@ -259,6 +259,7 @@ typedef enum _IO_MANAGER_FLAG {
/* All other choices pick only the requested one, with no fallback. */
IO_MNGR_FLAG_SELECT, /* Unix only, non-threaded RTS only */
IO_MNGR_FLAG_POLL, /* Unix only, non-threaded RTS only */
+ IO_MNGR_FLAG_IO_URING, /* Linux only, */
IO_MNGR_FLAG_MIO, /* cross-platform, threaded RTS only */
IO_MNGR_FLAG_WINIO, /* Windows only */
IO_MNGR_FLAG_WIN32_LEGACY, /* Windows only, non-threaded RTS only */
@@ -285,6 +286,8 @@ typedef struct _MISC_FLAGS {
* for the linker, NULL ==> off */
IO_MANAGER_FLAG ioManager; /* The I/O manager to use. */
uint32_t numIoWorkerThreads; /* Number of I/O worker threads to use. */
+ uint32_t io_uring_sq_entries; /* io_uring submission queue size */
+ uint32_t io_uring_cq_entries; /* io_uring completion queue size */
} MISC_FLAGS;
/* See Note [Synchronization of flags and base APIs] */
=====================================
rts/include/rts/storage/Closures.h
=====================================
@@ -728,7 +728,12 @@ union NotifyCompletion {
enum NotifyCompletionType {
NotifyTSO = 0, // thread-synchronous I/O
NotifyMVar = 1, // async I/O with MVar notification
- NotifyTVar = 2 // async I/O with TVar notification
+ NotifyTVar = 2, // async I/O with TVar notification
+ // If a TSO receives an async exception while it's waiting on I/O then
+ // the TSO stops waiting but the I/O may still be outstanding, and we
+ // need the corresponding StgAsyncIOOp until the I/O completes. We use
+ // NotifyNone in this case to avoid disturbing the original TSO.
+ NotifyNone = 3
};
/* A node in the leftist heap. */
=====================================
rts/posix/IOURing.c
=====================================
@@ -0,0 +1,1222 @@
+/* -----------------------------------------------------------------------------
+ *
+ * (c) The GHC Team 2021-2023
+ *
+ * An I/O manager based on the Linux io_uring API.
+ *
+ * ---------------------------------------------------------------------------*/
+
+#include "rts/PosixSource.h"
+#include "Rts.h"
+#include "RtsAPI.h"
+
+#include "IOManager.h" // defines IOMGR_ENABLED_IO_URING
+
+#if defined(IOMGR_ENABLED_IO_URING)
+
+#include "Capability.h"
+#include "Prelude.h"
+#include "RaiseAsync.h"
+#include "RtsUtils.h"
+#include "Schedule.h"
+#include "Threads.h"
+#include "rts/Time.h"
+
+#include "IOURing.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <liburing.h>
+#include <limits.h>
+#include <poll.h> // for poll() flags POLLIN POLLOUT
+
+#include "IOManagerInternals.h"
+#include "Timeout.h"
+#include "TimeoutQueue.h"
+
+/******************************************************************************
+
+This I/O manager is based on the Linux io_uring API. We rely on the liburing
+library, rather than using the system calls directly.
+
+Introduction
+============
+
+The io_uring API is an _almost_ generic mechanism for performing Linux syscalls
+asynchronously. It supports a range of I/O related operations, including
+ordinary file read and write, and waiting for I/O readiness. It works using a
+queue to submit I/O operations, and another queue to receive I/O completions.
+The io_uring documentation calls these the "submission queue", abbreviated SQ,
+and "completion queue", abbreviated CQ. The corresponding queue entries are
+abbreviated as SQEs and CQEs.
+
+There is a single system call to both submit operations and/or wait for I/O
+completion (or a timeout). It is also possible to poll for new entries in the
+completion queue without using a system call at all. This fits the RTS
+scheduler design quite well. Every time round the scheduler loop we have to
+do a non-blocking check for I/O completion, and it is only when there are no
+runnable threads that we want to block and wait for I/O (or timers). So in busy
+applications there are a lot more non-blocking than blocking checks for I/O
+completion. So being able to do the non-blocking poll without needing a system
+call should save significantly on system calls, compared to other APIs.
+
+We use the liburing C library, rather than the system calls directly. This
+provides a degree of convenience and portability across kernel versions.
+
+Synchronous I/O
+---------------
+
+Classically, asynchronous I/O APIs are slower than ordinary synchronous I/O
+APIs for the case of buffered I/O reads where the requested data is already in
+the OS page cache (if they support buffered I/O at all). This requires
+applications that use asynchronous I/O (and thus care about performance) to use
+it only in some cases, and use synchronous I/O when that is expected to be
+faster. This is complex, and thus relatively few applications use asynchronous
+I/O. Furthermore, knowing whether data is in the page cache is something that
+only the kernel knows reliably. User space can only make educated guesses.
+
+A major selling point of io_uring is that it (mostly) solves this problem. It
+is designed work for buffered I/O (as well as direct I/O). Operations are
+submitted by putting entries (SQEs) into the submission queue (SQ) and then
+entering the kernel (io_uring_enter). The kernel starts work on all the
+operations. If any of them can complete synchronously then the kernel can place
+the completion entries (CQEs) into the completion queue (CQ). Thus the
+synchronous operations complete synchronously. Furthermore, the decision about
+whether it can complete synchronously is made dynamically by the kernel (e.g.
+based on whether the data is available in the page cache). The result is that a
+simple file read where the data is available in the page cache can be as quick
+as a normal synchronous file read system call.
+
+Overall, this allows the same API to be used for all I/O, without having to
+guess about synchronous vs asynchronous completions. Furthermore, io_uring
+supports both disk I/O and sockets I/O, whereas older APIs only supported one
+or the other (see e.g. epoll for sockets and Linux/Posix AIO for disk files).
+Overall this allows a less complex solution, by using the same relatively
+uniform API for everything (even through the API itself is somewhat more
+complex than other APIs).
+
+General strategy
+----------------
+
+Our I/O strategy with io_uring is to prepare but not submit I/O operations in
+the I/O primops, and then submit the operations in the scheduler. That is, in
+the I/O primops we put the I/O operations (SQEs) into the submission queue (SQ),
+but we don't yet call into the kernel to inform it of the new operations.
+Instead we do that in the scheduler. At this point in the scheduler, we can
+both submit any pending I/O operations and handle any completions. This lets us
+submit and collect I/O with a single system call. Handling I/O completions
+typically results in waking up threads, which the scheduler can then deal with.
+
+Consider the important example of simple disk reads that complete synchronously.
+In this case the completion is available immediately after io_uring_enter
+returns and the scheduler can reschedule the thread that submitted the I/O. So
+the end result _should_ be as fast as a normal synchronous blocking read()
+system call (but benchmarks are needed to verify this).
+
+Important fast-path cases
+-------------------------
+
+There are a few important special fast-path cases to keep in mind:
+
+1. Common case: as above, a Haskell thread has pending I/O, so we submit
+ it. This requires a system call. Afterwards we also process any
+ completions. This means any I/O operations that complete synchronously
+ get handled immediately and with a single system call. This is crucial
+ for performance of ordinary buffered I/O in the common case that the data
+ is available in the page cache.
+
+2. Common case: no pending I/O to submit, but there is outstanding I/O
+ that we need to see if it has completed. This does not require a
+ system call. We just need to look at the completion ring.
+
+3. Rare case: we have pending I/O to submit but the kernel refuses to
+ accept more I/O because the completion queue is full. In this case
+ we process the completion queue first, and then retry. This ends up
+ using two system calls.
+
+I/O submission overload
+-----------------------
+
+Asynchronous I/O APIs enable I/O to be submitted without waiting and thus they
+must deal with the problem of having too much I/O in flight at once for the
+resources available.
+
+For example, epoll_ctl will return ENOMEM or ENOSPC if it cannot allocate the
+necessary memory or hits a resource limit. The epoll backend for MIO turns
+these failures into Haskell exceptions. This works ok in practice for epoll
+because epoll only supports one kind of async I/O operation: waiting for I/O
+readiness. This has relatively low resource use and so systems generally hit
+other resource limits first, e.g. file descriptors.
+
+On the other hand io_uring supports a variety of I/O operations with different
+behaviour and resource use. They can roughly be divided into two categories:
+1. cheap blocking operations; and
+2. expensive non-blocking operations.
+
+The cheap blocking operations include waiting for I/O readiness on pipes and
+sockets, as with epoll, but it also includes blocking reads/write/send/recv on
+pipes and sockets. These operations tend to have very low resource use and
+correspondingly the kernel can support very many outstanding operations. This
+is needed for some large scale networking use cases where there can be very
+many sockets in use at once.
+
+The comparatively expensive non-blocking operations include read and write on
+disk files and various file and file system operations. These operations tend
+to have higher resource use and so the kernel cannot support too many of them
+in progress at once. Furthermore, for non-blocking operations there isn't much
+need to have huge numbers in progress at once: the performance benefit is
+limited by the hardware concurrency (CPUs, SSD queue depth etc).
+
+Unfortunately, the limits on the number of concurrent operations of each kind
+is not known, or at least not reported by the kernel in advance. When trying
+to submit new operations, the kernel can report EAGAIN if it is out of
+resources. This reporting mechanism is awkward because of the two classes of
+operations. The way we would like to respond to hitting a limit is also
+different for blocking vs non-blocking operations.
+
+For non-blocking operations, we can simply wait for some operations to complete
+and then submit more. This works because non-blocking operations will
+eventually complete, and indeed typically complete fairly promptly.
+
+For blocking operations on the other hand, there's not a lot we can do if we
+hit a resource limit. Waiting may not help. Blocking operations can block
+indefinitely. It is also possible to deadlock if not all I/O readiness
+notifications are active simultaneously. So if we hit a resource limit for
+these operations then we can't do much better than throw exceptions to the
+Haskell threads submitting the operations. This is of course what the MIO epoll
+backend does.
+
+We will make the assumption that the kernel does _not_ have separate limits for
+these two classes of operations, but assume that it is a common limit based on
+the common resource of kernel memory. As noted above, there is no great benefit
+to having excessive concurrency of non-blocking operations. We can limit the
+concurrency (using a queue) with no change in semantics of the application. On
+the other hand an application that wants to wait on zillions of blocking
+operations cannot have that concurrency reduced without it having a semantic
+effect on the application. We either have to support it or (hopefully
+gracefully) fail as resources run out.
+
+So we take the approach of trying to limit the concurrency and thus resource
+use of the non-blocking operations so that the remaining kernel memory can
+maximise the number of blocking operations we can support. We do that by
+imposing a "reasonable" limit on the number of concurrent non-blocking
+operations. We also impose a separate larger limit on the number of concurrent
+blocking operations. The high level idea is that we treat the non-blocking
+limit like a semaphore (with that concurrency limit) on the threads submitting
+non-blocking I/O operations. This means we can handle overload transparently.
+On the other hand the limit on blocking operations is a hard limit and if we
+hit that then we have to throw exceptions (to the threads submitting the I/O).
+
+In the typical application use cases this strategy should avoid encountering
+EAGAIN in the first place, however it can still happen and we must handle it
+somehow.
+
+Before considering EAGAIN, consider a more normal scenario with lots of
+concurrent I/O -- both blocking and non-blocking operations -- where we do hit
+the limit on non-blocking operations. We track the number of blocking and
+non-blocking operations that are currently "in flight" using the counters
+iomgr->uring_inflight_{non}blocking_aiops. These are incremented by the number
+of prepared operations when the submission queue is successfully flushed, and
+decremented for each completion processed. Hitting the limit means that when a
+thread submits a new non-blocking operation we find the count of inflight
+non-blocking operations is already at the limit, and thus a new operation would
+be over the limit.
+
+Normally when a thread uses a primop to submit an I/O operation we would grab
+an existing SQE from the submission queue (SQ) and fill in the SQE. If however
+we would go over the inflight limit, then instead we allocate a fresh SQE (on
+the C heap using malloc) and then fill in the SQE as normal. Then we put the
+SQE and the TSO for the thread that submitted it onto (the end of) a pair of
+overflow queues: one for the SQEs and one for the corresponding threads. We use
+a pair of queues rather than a queue of pairs because the TSOs are allocated on
+the GC heap but the SQEs are on the C heap and there is existing infrastructure
+for handling TSO queues, including tracing them for GC. In the scheduler, when
+we process the completion of a blocking operation, if there are entries on the
+overflow queues then we can dequeue an SQE and corresponding TSO and copy the
+SQE into the SQ and reschedule the TSO. This means the SQE is ready to go and
+the TSO is unblocked. If we are in this overflow situation and the completions
+for several non-blocking operations are processed in one go then will will add
+several SQEs to be added to the SQ in one batch. This should naturally lead to
+batching in the overflow situation and amortise the syscall overheads.
+
+While all of this is going on, other threads submitting _blocking_ operations
+can proceed as normal, preparing their operations into the submission queue.
+
+Now if we do encounter EAGAIN, we assume that it's the blocking operations
+that are at fault.
+
+Approaches to batching
+----------------------
+
+We currently do no batching, but this section discusses plausible approaches.
+
+With this design we have the opportunity to try to accumulate multiple pending
+I/O operations and then submit them in one batch, which could improve
+performance in I/O intensive applications by reducing the number of system
+calls. Ordinary synchronous I/O primops like threadWaitRead# block the Haskell
+thread and return to the scheduler, which means we only accumulate a single
+pending I/O operation. There are a couple ways we could achieve batching.
+
+One approach is to allow individual Haskell threads to prepare multiple I/O
+operations by providing asynchronous I/O primops. This could be quite effective
+at generating a lot of I/O, but it would likely see relatively little use
+because it requires changing application designs.
+
+Another approach is by having the scheduler not always submit pending I/O, but
+instead let it run other threads in the hope that they will produce more
+pending I/O. It would wait until either a time limit or a pending count limit
+before submitting the I/O. Such an approach would increase I/O latency:
+consider the scheduler running a thread that creates a pending I/O operation,
+followed by running a CPU-bound thread for a whole 20 millisecond timeslice,
+after which the scheduler submits the I/O. Thus this approach could only be
+used for low priority I/O, which itself would require introducing a notion of
+I/O priority.
+
+One can imagine variations on this design such as a more sophisticated
+scheduler predicting if the next thread to run is likely to be I/O or CPU bound
+and using that to decide whether to flush pending I/O or to speculate on
+accumulating more. Or the scheduler could set a timer to interrupt CPU bound
+threads sooner if it's speculating on gathering more I/O. This could reduce
+the cost on average, and bound latency, but there would still be a latency vs
+throughput tradeoff, which would almost certainly require some notion of I/O
+priority.
+
+
+io_uring features we use
+------------------------
+
+IORING_FEAT_NODROP
+
+io_uring features we could use but don't (yet)
+----------------------------------------------
+
+io_uring features we cannot use
+-------------------------------
+
+NOTES: cannot use registered ring fd, due to multiple worker threads, even
+though it's protected by the lock. Same for registered/direct fds.
+
+
+Tracking counters
+-----------------
+
+We track the number of operations submitted (by Haskell threads to the I/O
+manager) and not yet notified of completion:
+
+> int n_submitted_b;
+> int n_submitted_nb;
+
+These are incremented when the primop submits I/O to the I/O manager, and are
+decremented when the I/O completion is processed. We track blocking and
+non-blocking operations separately.
+
+We track the number of operations that are prepared in the submission queue,
+but not yet submitted to the kernel.
+
+> int n_prepared_b;
+> int n_prepared_nb;
+
+These are incremented when an operation is prepared in the submission queue
+and decremented when operations are submitted to the kernel.
+
+We track the number of operations submitted to the kernel and where the
+completion has not yet been processed. We call these "in-flight" operations.
+
+> int n_inflight_b;
+> int n_inflight_nb;
+
+These are incremented when operations are submitted to the kernel, and
+decremented when I/O completions are processed.
+
+We track the limit on the number of operations the I/O manager will allow to be
+in-flight with the kernel.
+
+> int limit_inflight_b;
+> int limit_inflight_nb;
+
+These are typically set on RTS startup and then rarely changed. The
+limit_inflight_b can be reduced dynamically if the I/O manager encounters
+EAGAIN when submitting operations.
+
+We track the number of non-blocking operations that are in the overflow queue.
+These are operations that have been submitted, and could not be put into the
+submission queue (because it would exceed the limit_inflight_nb), and so go
+into the overflow queue instead.
+
+> int n_overflow_nb;
+
+This is incremented (instead of n_prepared_nb) if the number of n_inflight_nb
+plus n_prepared_nb is at or above the limit_inflight_nb, in which case the
+operation (and submitting thread) is put into the overflow queue. It is
+decremented when there is space available within the limit_inflight_nb and the
+operation can be put into the submission queue (and thus also incrementing the
+n_prepared_nb).
+
+We maintain two invariants:
+
+1. n_submitted_b = n_prepared_b + n_inflight_b
+2. n_submitted_nb = n_prepared_nb + n_overflow_nb + n_inflight_nb
+
+The implication is that set of prepared, overflow and inflight operations are
+distinct from each other and their union is equal to the submitted set. Another
+way to look at it is that there distinct states for submitted operations:
+prepared, overflow (for non-blocking) and inflight.
+
+******************************************************************************/
+
+/* Forward declarations */
+static bool enlargeTables(CapIOManager *iomgr);
+static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop);
+static void enqueueOverflowQueue(CapIOManager *iomgr,
+ StgTSO *tso, struct io_uring_sqe *sqe);
+static void dequeueOverflowQueue(CapIOManager *iomgr, StgTSO **ptso,
+ struct io_uring_sqe **psqe);
+
+/* Constants */
+
+/* A couple tags we add to the sqe->user_data to tell us about this operation
+ * when we process the completion. Currently we distinguish:
+ * 1. non-blocking vs blocking operations, just so we can update our counters
+ * which count these separately.
+ * 2. cancellation operations
+ */
+const uint64_t AIOP_TAG_CANCEL = 0x80lu << 56; /* bit 63 */
+const uint64_t AIOP_TAG_OP_NB = 0x40lu << 56; /* bit 62 */
+const uint64_t AIOP_TAG_MASK = 0xc0lu << 56; /* bit 62 | 63 */
+
+void initCapabilityIOManagerIOURing(CapIOManager *iomgr) {
+ initClosureTable(&iomgr->aiop_table, ClosureTableCompact);
+ iomgr->timeout_queue = emptyTimeoutQueue();
+
+ int sq_entries = RtsFlags.MiscFlags.io_uring_sq_entries;
+
+ iomgr->n_submitted_b = 0;
+ iomgr->n_submitted_nb = 0;
+ iomgr->n_prepared_b = 0;
+ iomgr->n_prepared_nb = 0;
+ iomgr->n_inflight_b = 0;
+ iomgr->n_inflight_nb = 0;
+ iomgr->limit_inflight_b = INT_MAX;
+ iomgr->limit_inflight_nb = 4 * sq_entries;
+ iomgr->n_overflow_nb = 0;
+ iomgr->overflow_tso_q_hd = END_TSO_QUEUE;
+ iomgr->overflow_tso_q_tl = END_TSO_QUEUE;
+ iomgr->overflow_sqe_q_hd = NULL;
+ iomgr->overflow_sqe_q_tl = NULL;
+
+ /* Set the uring params: we want to use independent sizes of submission
+ * and completion queues. We typically want a bigger completion queue than
+ * a submission queue.
+ */
+ struct io_uring_params params = {
+ .flags =
+ IORING_SETUP_CQSIZE | IORING_SETUP_CLAMP | IORING_SETUP_SUBMIT_ALL,
+ .cq_entries = RtsFlags.MiscFlags.io_uring_cq_entries};
+ // TODO: what happens if we use flags that are not recognised by the kernel
+ // version, e.g. IORING_SETUP_SUBMIT_ALL prior to 5.18?
+
+ /* TODO: see if we want to support IORING_SETUP_SQPOLL. With
+ IORING_FEAT_NATIVE_WORKERS, it doesn't need any priviledges. */
+
+ /* Share the same kernel work-queue between the urings for each capability.
+ * Do this by using the IORING_SETUP_ATTACH_WQ flag for capabilities > 0,
+ * and pass the uring fd for cap 0 (the main capability).
+ *
+ * TODO why?
+ */
+ if (iomgr->cap->no > 0) {
+ params.flags |= IORING_SETUP_ATTACH_WQ;
+ params.wq_fd = MainCapability.iomgr->uring->ring_fd;
+ }
+
+ /* Try to initialise the uring */
+ struct io_uring *uring =
+ stgMallocBytes(sizeof(struct io_uring), "initCapabilityIOManagerUring");
+ int res = io_uring_queue_init_params(sq_entries, uring, ¶ms);
+ if (res < 0)
+ goto fail;
+
+ /* Check for features we require. */
+ unsigned required =
+ /* Needed for simple handling of ring sizes and limits.
+ * TODO: we might be able to support kernels without this
+ * by setting the limits to be the same as the ring sizes. */
+ IORING_FEAT_NODROP
+
+ /* TODO
+ *
+ * Early kernel versions required that the SQE struct data pointers
+ * remains valid until completion of the IO request. With
+ * IORING_FEAT_SUBMIT_STABLE such structs need only be valid untill
+ * submission into the SQ. Man pages for io_uring_prep_* functions should
+ * have a note if they are affected. The __kernel_timespec struct passed
+ * to io_uring_prep_timeout() is affected. io_uring_prep_accept() is also
+ * affected. Neither is used by this IO Manager. TODO perhaps we want this
+ * feature because it is widely supported (assuming io_uring is supported)
+ * and we may add affected IO operations in the future?
+ * See https://www.man7.org/linux/man-pages/man3/io_uring_submit.3.html
+ */
+ | IORING_FEAT_SUBMIT_STABLE
+
+ /* Needed for read/write that updates the file pos. */
+ | IORING_FEAT_RW_CUR_POS
+
+ /* Needed for corner cases like reading from /proc/self,
+ * or signalfd */
+ | IORING_FEAT_NATIVE_WORKERS;
+
+ if ((uring->features & required) != required)
+ goto fail;
+
+ /* Arrange for the uring (fd and mmap'ed queues) not to be inherited. */
+ res = fcntl(uring->ring_fd, F_SETFD, FD_CLOEXEC);
+ if (res < 0)
+ goto fail;
+ res = io_uring_ring_dontfork(uring);
+ if (res < 0)
+ goto fail;
+
+ /* Success. Save what we need. */
+ iomgr->uring = uring;
+ return;
+
+ /* Failure. Clean up. */
+fail:
+ stgFree(uring);
+ barf("uring iomgr: initialisation failed");
+ // TODO: we should add support to fail and use a fallback I/O manager
+}
+
+void freeCapabilityIOManagerIOURing(CapIOManager *iomgr) {
+ io_uring_queue_exit(iomgr->uring);
+}
+
+/******************************************************************************
+ * Common prologues and epilogues for primops for I/O operations.
+ *
+ * There are different common prologues/epilogues depending on:
+ * - synchronous or asynchronous primops
+ * - blocking or non-blocking I/O operations
+ */
+
+/* The common prologue for for all synchronous primops for both blocking and
+ * non-blocking I/O operations.
+ */
+static int prologueSyncIOOp(CapIOManager *iomgr, StgTSO *tso, int why_blocked,
+ StgAsyncIOOp **paiop) {
+ StgAsyncIOOp *aiop;
+ aiop = (StgAsyncIOOp *)allocateMightFail(iomgr->cap, sizeofW(StgAsyncIOOp));
+ if (RTS_UNLIKELY(aiop == NULL)) {
+ return (sizeof(StgAsyncIOOp));
+ }
+ SET_HDR(aiop, &stg_ASYNCIOOP_info, CCS_SYSTEM); // TODO: get CCCS
+ aiop->notify_type = NotifyTSO;
+ aiop->notify.tso = tso;
+ tso->why_blocked = why_blocked;
+ tso->block_info.aiop = aiop;
+ *paiop = aiop;
+ return 0;
+}
+
+/* The common prologue for all non-blocking I/O operations.
+ *
+ * Allocate a table index
+ * Fill in some of the aiop fields
+ * Allocate an SQE, either on the ring or on the heap if we're in overflow.
+ */
+static int prologueNonBlockingIOOp(CapIOManager *iomgr, StgTSO *tso,
+ StgAsyncIOOp *aiop,
+ struct io_uring_sqe **psqe,
+ bool *tso_block) {
+ Capability* cap = iomgr->cap;
+ if (RTS_UNLIKELY(isFullClosureTable(&iomgr->aiop_table))) {
+ int fail = enlargeTables(iomgr);
+ if (RTS_UNLIKELY(fail))
+ return fail;
+ }
+
+ int index = insertClosureTable(cap, &iomgr->aiop_table, aiop);
+
+ aiop->capno = cap->no;
+ aiop->index = index;
+
+ struct io_uring_sqe *sqe;
+
+ if (iomgr->n_inflight_nb + iomgr->n_prepared_nb < iomgr->limit_inflight_nb) {
+ /* The typical case. Allocate an SQE from the ring */
+ sqe = io_uring_get_sqe(iomgr->uring);
+ ASSERT(sqe); /* Otherwise we counted wrong */
+ iomgr->n_submitted_nb++;
+ iomgr->n_prepared_nb++;
+ *tso_block = false;
+ } else {
+ /* We're going to have to block the submitting thread.
+ * Allocate an SQE on the heap and suspend the calling TSO.
+ */
+ sqe = stgMallocBytes(sizeof(struct io_uring_sqe), "uring iomgr");
+ enqueueOverflowQueue(iomgr, tso, sqe);
+ iomgr->n_submitted_nb++;
+ iomgr->n_overflow_nb++;
+ *tso_block = true;
+ }
+ io_uring_sqe_set_data64(sqe, index);
+ *psqe = sqe;
+ return 0;
+}
+
+/* The common prologue for all blocking I/O operations.
+ *
+ * Allocate a table index
+ * Fill in some of the aiop fields
+ * Allocate an SQE, or return failure if we're at the limit.
+ */
+static bool prologueBlockingIOOp(CapIOManager *iomgr, StgTSO *tso,
+ StgAsyncIOOp *aiop,
+ struct io_uring_sqe **psqe) {
+ Capability *cap = iomgr->cap;
+
+ if (iomgr->n_inflight_b + iomgr->n_prepared_b >= iomgr->limit_inflight_b) {
+ /* If we reach the limit we fail and throw an exception */
+ raiseAsync(cap, tso,
+ (StgClosure *)blockedOnBadFD_closure
+ /*TODO: use ioopResourcesExhausted_closure */,
+ false, NULL);
+ return false;
+ // TODO: review this
+ // TODO: current return value is for memory alloc failure, not for
+ // other failures. Need error result separate from alloc.
+ }
+
+ if (RTS_UNLIKELY(isFullClosureTable(&iomgr->aiop_table))) {
+ bool ok = enlargeTables(iomgr);
+ if (RTS_UNLIKELY(!ok))
+ return false;
+ }
+
+ int index = insertClosureTable(cap, &iomgr->aiop_table, aiop);
+
+ aiop->capno = cap->no;
+ aiop->index = index;
+
+ /* Allocate an SQE on the ring */
+ struct io_uring_sqe *sqe = io_uring_get_sqe(iomgr->uring);
+ ASSERT(sqe); /* Otherwise we counted wrong */
+ iomgr->n_submitted_b++;
+ iomgr->n_prepared_b++;
+ io_uring_sqe_set_data64(sqe, index);
+ *psqe = sqe;
+ return 0;
+}
+
+/* The common epilogue for all async non-blocking I/O operations.
+ */
+static int epilogueAsyncNonBlockingIOOp(CapIOManager *iomgr, StgTSO *tso,
+ StgAsyncIOOp *aiop, bool tso_block) {
+ if (tso_block) {
+ tso->why_blocked = BlockedOnIOSubmission;
+ tso->block_info.aiop = aiop;
+ return -1;
+ } else if (io_uring_sq_space_left(iomgr->uring) == 0) {
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+/* Common epilogue for all async blocking I/O operations.
+ */
+static int epilogueAsyncBlockingIOOp(CapIOManager *iomgr) {
+ return io_uring_sq_space_left(iomgr->uring) == 0 ? -1 : 0;
+}
+
+/******************************************************************************
+ * Non-blocking I/O operations. This includes read/write on files (not sockets).
+ *
+ * The code is organised as common I/O preparation functions and then individual
+ * primops (sync and async). They also rely on the common prologue and epilogue
+ * functions above.
+ */
+
+static int prepareIOReadWrite(CapIOManager *iomgr, StgTSO *tso, bool *tso_block,
+ StgAsyncIOOp *aiop, IOReadOrWrite rw, int fd,
+ StgClosure *live, void *buf, size_t len,
+ off_t off) {
+ struct io_uring_sqe *sqe;
+ int fail = prologueNonBlockingIOOp(iomgr, tso, aiop, &sqe, tso_block);
+ if (RTS_UNLIKELY(fail))
+ return fail;
+
+ aiop->live = live;
+ if (rw == IORead) {
+ io_uring_prep_read(sqe, fd, buf, len, off);
+ } else {
+ io_uring_prep_write(sqe, fd, buf, len, off);
+ }
+ return 0;
+}
+
+int asyncIOReadWriteIOURing(CapIOManager *iomgr, StgTSO *tso, StgAsyncIOOp *aiop,
+ IOReadOrWrite rw, int fd, StgClosure *live,
+ void *buf, size_t len, off_t off) {
+ bool tso_block;
+ int fail = prepareIOReadWrite(iomgr, tso, &tso_block, aiop, rw, fd, live, buf,
+ len, off);
+ if (RTS_UNLIKELY(fail))
+ return fail;
+
+ return epilogueAsyncNonBlockingIOOp(iomgr, tso, aiop, tso_block);
+}
+
+int syncIOReadWriteIOURing(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw,
+ int fd, StgClosure *live, void *buf, size_t len,
+ off_t off) {
+ StgAsyncIOOp *aiop;
+ int why_blocked = rw == IORead ? BlockedOnRead : BlockedOnWrite;
+ int fail = prologueSyncIOOp(iomgr, tso, why_blocked, &aiop);
+ if (RTS_UNLIKELY(fail))
+ return fail;
+
+ bool unused;
+ fail =
+ prepareIOReadWrite(iomgr, tso, &unused, aiop, rw, fd, live, buf, len, off);
+ if (RTS_UNLIKELY(fail))
+ return fail;
+ return 0;
+}
+
+/******************************************************************************
+ * Blocking I/O operations. This includes waiting for I/O readiness on sockets,
+ * pipes etc.
+ *
+ * The code is organised as common I/O preparation functions and then individual
+ * primops (sync and async). They also rely on the common prologue and epilogue
+ * functions above.
+ */
+
+static void prepareIOWaitReady(StgAsyncIOOp *aiop, struct io_uring_sqe *sqe,
+ IOReadOrWrite rw, int fd) {
+ aiop->live = &stg_ASYNCIO_LIVE0_closure;
+ io_uring_prep_poll_add(sqe, fd, rw == IORead ? POLLIN : POLLOUT);
+}
+
+bool asyncIOWaitReadyIOURing(CapIOManager *iomgr, StgTSO *tso, StgAsyncIOOp *aiop,
+ IOReadOrWrite rw, int fd) {
+ struct io_uring_sqe *sqe;
+ bool ok = prologueBlockingIOOp(iomgr, tso, aiop, &sqe);
+ if (RTS_UNLIKELY(!ok))
+ return false;
+
+ prepareIOWaitReady(aiop, sqe, rw, fd);
+
+ return epilogueAsyncBlockingIOOp(iomgr);
+}
+
+bool syncIOWaitReadyIOURing(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw,
+ HsInt fd) {
+ StgAsyncIOOp *aiop;
+ int why_blocked = rw == IORead ? BlockedOnRead : BlockedOnWrite;
+ bool ok = prologueSyncIOOp(iomgr, tso, why_blocked, &aiop);
+ if (RTS_UNLIKELY(!ok))
+ return false;
+
+ struct io_uring_sqe *sqe;
+ ok = prologueBlockingIOOp(iomgr, tso, aiop, &sqe);
+ if (RTS_UNLIKELY(!ok))
+ return false;
+
+ prepareIOWaitReady(aiop, sqe, rw, fd);
+
+ return 0;
+}
+
+/******************************************************************************
+ * Actions to cancel outstanding I/O operations. Also support cancelling any
+ * outstanding I/O on an fd prior to it being closed.
+ *
+ * This covers both synchronous and asynchronous operations.
+ */
+
+static void ioCancel(CapIOManager *iomgr, StgAsyncIOOp *aiop);
+
+void syncIOCancelIOURing(CapIOManager *iomgr, StgTSO *tso) {
+ StgAsyncIOOp *aiop = tso->block_info.aiop;
+ ASSERT(aiop->notify_type == NotifyTSO);
+ ASSERT(indexClosureTable(&iomgr->aiop_table, aiop->index) == aiop);
+ ioCancel(iomgr, aiop);
+ /* We cannot use the normal notifyIOCompletion here. We are in the context
+ * of throwTo, interrupting a thread blocked on IO via an async exception.
+ * We don't put the TSO back on the run queue or change the why_blocked
+ * status, as that is done by removeFromQueues (in the throwTo* functions).
+ */
+ tso->block_info.closure = (StgClosure *)END_TSO_QUEUE;
+
+ // TODO: Synchronous cancellation from throwTo seems to be pretty keen for
+ // the thread to be unblocked immediately so it can start unwinding the
+ // stack. Perhaps it is ok to continue the cancellation in the background.
+ // But if so then we will need to adjust the notify type to be none /
+ // cancelled!
+ /* Cancelling thread-synchronous I/O happens from throwTo, which is very
+ * keen for the thread to be unblocked immediately so it can unwind the
+ * stack and schedule the thread to run an exception handler. This demand
+ * to be synchronous is a bit tricky to arrange because cancelling the I/O
+ * operation the thread is blocked on is potentially asynchronous.
+ */
+ aiop->notify_type = NotifyNone;
+ aiop->notify.tso = END_TSO_QUEUE;
+}
+
+void asyncIOCancelIOURing(CapIOManager *iomgr, StgAsyncIOOp *aiop) {
+ /* We can reliably determine if the aiop is still in progress by checking
+ * if the aiop_table still points to this aiop object. This is reliable
+ * because each aiop is GC heap allocated, so cannot be recycled until it
+ * is no longer retained by the application.
+ */
+ ASSERT(aiop->notify_type != NotifyTSO);
+ if (indexClosureTable(&iomgr->aiop_table, aiop->index) == aiop) {
+ ioCancel(iomgr, aiop);
+ notifyIOCompletion(iomgr, aiop);
+ }
+}
+
+static void ioCancel(CapIOManager *iomgr, StgAsyncIOOp *aiop) {
+ barf("URing.c:ioCancel:TODO");
+
+ int ix = aiop->index;
+ struct io_uring_sqe *sqe = io_uring_get_sqe(iomgr->uring);
+ // TODO: return status to indicate if we need to return to the scheduler
+ // to flush a full submission queue
+
+ /* io_uring lets us include one word into submission queue entries (SQEs),
+ * which come back in the corresponding completion queue entry (CQE). We
+ * use this feature to identify the aiop so that we will be able to
+ * process the completion properly, e.g. waking up the right thread.
+ * We can't use a direct pointer to the aiop because the aiops are heap
+ * allocated and GC pointers are not stable. We use the index in the
+ * ClosureTable, because this is stable. Indeed the raison d'être of the
+ * ClosureTable is to provide stable pointers for thus purpose.
+ //TODO: the above is a helpful comment but it belongs elsewhere, e.g.
+ //in the intro.
+ */
+ uint64_t sqe_data = ix;
+
+ /* Although IORING_OP_POLL_ADD has a special separate cancellation using
+ * IORING_OP_POLL_REMOVE, apparently it can also be cancelled using the
+ * generic IORING_OP_ASYNC_CANCEL, which is good so we don't need to
+ * distinguish.
+ */
+ io_uring_prep_cancel64(sqe, sqe_data, 0 /*flags*/);
+ // TODO: if we use other tags in the sqe_data we'll need to reconstruct
+ // them here so we can find the right item. e.g. if we use AIOP_TAG_OP_NB
+ // we'd need to distinguish in the aiop->flags for example.
+
+ /* Cancelling is itself a new uring I/O operation which will have a
+ * corresponding completion. Set a high bit to mark this as a cancel
+ * operation, but still knowing the index of the original operation.
+ */
+ io_uring_sqe_set_data64(sqe, sqe_data | AIOP_TAG_CANCEL);
+
+ // TODO: verify for IORING_OP_POLL_ADD, if we do a successful
+ // IORING_OP_POLL_REMOVE then which completions do we get?
+ // Do we get a completion for the IORING_OP_POLL_ADD, and if so with
+ // what result? Or does a successful IORING_OP_POLL_REMOVE mean we
+ // only get a completion for the remove and not the add?
+
+ // TODO: verify similar for normal I/O cancel, e.g. a read on a pipe.
+ // Which completions do we get if we cancel?
+
+ // TODO: is it ok cancel I/O asynchronously here? Do we need to submit the
+ // cancel op? The cancel op will complete synchronously but the cancellation
+ // may only complete later. This might confuse resource cleanup, e.g. because
+ // a file will not get closed until the cancel finishes.
+}
+
+/* TODO: need to add support for closing properly.
+ * Unfortunately, uring's behaviour for closing a fd when there are outstanding
+ * poll (or other async I/O) operations on that fd is unhelpful. The poll
+ * operation itself keeps a reference to the file open. Thus the close will
+ * not in fact interrupt and cancel the poll.
+ * So the I/O manager needs to be notified of fd close, so that we can do
+ * something. Fortunately we can use io_uring_prep_cancel_fd to cancel all
+ * operations on an fd.
+ *
+ * I think cancelled ops _do_ generate CQEs. So we should be able to do the
+ * appropriate notifications by waiting for the original CQEs. We should
+ * probably issue the cancellation
+ *
+ */
+
+/******************************************************************************
+ * The functions called from the scheduler to poll or wait for pending I/O,
+ * and process any I/O completions.
+ */
+
+bool anyPendingTimeoutsOrIOIOURing(CapIOManager *iomgr) {
+ return !isEmptyTimeoutQueue(iomgr->timeout_queue) ||
+ !isEmptyClosureTable(&iomgr->aiop_table);
+}
+
+static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop) {
+ switch (aiop->notify_type) {
+ case NotifyTSO: {
+ if (aiop->outcome == IOOpOutcomeFailed && aiop->error == EBADF) {
+ /* The fd is invalid: raise an IOError exception in the blocked
+ * thread. (See bug #4934 for what happens without this.)
+ */
+ StgTSO *tso = aiop->notify.tso;
+ debugTrace(DEBUG_iomanager,
+ "Raising exception in thread %" FMT_StgThreadID
+ " blocked on an invalid fd",
+ tso->id);
+ raiseAsync(iomgr->cap, tso, (StgClosure *)blockedOnBadFD_closure, false, NULL);
+ break;
+ } else {
+ /* We should be guaranteed that the tso is still on the same
+ * cap because the tso was not on the run queue of any cap and
+ * so is not subject to thread migration.
+ */
+ StgTSO *tso = aiop->notify.tso;
+ tso->why_blocked = NotBlocked;
+ tso->_link = END_TSO_QUEUE;
+ pushOnRunQueue(iomgr->cap, tso);
+ }
+ break;
+ }
+ case NotifyMVar:
+ performTryPutMVar(iomgr->cap, aiop->notify.mvar, Unit_closure);
+ break;
+
+ case NotifyTVar:
+ barf("uring iomgr: TVar notification not yet supported");
+ break;
+ }
+}
+
+/* Process all the I/O completions that are currently available without
+ * blocking.
+ *
+ * This will correctly deal with completion queue overflow: if the completions
+ * queue is empty but has the overflow bit set then io_uring_peek_batch_cqe
+ * will do another non-blocking uring enter to refill the completion queue.
+ */
+static void processIOCompletions(CapIOManager *iomgr) {
+ struct io_uring *uring = iomgr->uring;
+ while (1) {
+ unsigned head, count = 0;
+ struct io_uring_cqe *cqe;
+ io_uring_for_each_cqe(uring, head, cqe) {
+ uint64_t cqe_data = io_uring_cqe_get_data64(cqe);
+ if (RTS_UNLIKELY(cqe_data & AIOP_TAG_CANCEL)) {
+ // This is a CQE for a cancellation. After posting a cancellation SQE
+ // then we expect to get _both_ a cancellation CQE and a CQE for the
+ // original operation that was the target of cancellation. This means
+ // that (provided it's not an error) we can ignore the cancellation SQE
+ // and just process the normal SQE for the target operation.
+ //
+ // The target operation will either be cancelled successfully
+ // immediately (in which case the cancellation cqe->res == 0) or if the
+ // target operation is in progress and cannot be cancelled, then we'll
+ // get cqe->res == -EALREADY for the cancellation the result of the
+ // operation (which may still be an interrupted outcome. If the
+ // cancellation fails, we
+ if (cqe->res != 0 && cqe->res != -EALREADY)
+ sysErrorBelch("uring:processIOCompletions: unknown cancel CQE result");
+ } else {
+ int ix = cqe_data;
+ StgAsyncIOOp *aiop = indexClosureTable(&iomgr->aiop_table, ix);
+ removeClosureTable(iomgr->cap, &iomgr->aiop_table, ix);
+ aiop->result = cqe->res;
+ // TODO: if we use these tags in the sqe_data we'll need to reconstruct
+ // them for cancel so we can find the right item. e.g. if we use
+ // AIOP_TAG_OP_NB we'd need to distinguish in the aiop->flags for
+ // example.
+ if (cqe_data & AIOP_TAG_OP_NB) {
+ iomgr->n_inflight_nb--;
+ iomgr->n_submitted_nb--;
+ } else {
+ iomgr->n_inflight_b--;
+ iomgr->n_submitted_b--;
+ }
+ notifyIOCompletion(iomgr, aiop);
+ }
+ count++;
+ }
+ io_uring_cq_advance(uring, count);
+ if (RTS_UNLIKELY(io_uring_cq_has_overflow(uring))) {
+ if (io_uring_get_events(uring) < 0) {
+ sysErrorBelch("io_uring_enter");
+ stg_exit(EXIT_FAILURE);
+ }
+ continue;
+ } else {
+ break;
+ }
+ }
+}
+
+/* Check invariants that must hold on entry to and exit from the scheduler.
+ * Used before/after {poll,await}CompletedTimeoutsOrIOURing which are called
+ * from the scheduler.
+ */
+static void assertURingSchedulerInvariants(CapIOManager *iomgr) {
+ struct io_uring *uring = iomgr->uring;
+
+ // That our tracking counters are consistent.
+ ASSERT(iomgr->n_submitted_b == iomgr->n_prepared_b + iomgr->n_inflight_b);
+ ASSERT(iomgr->n_submitted_nb ==
+ iomgr->n_prepared_nb + iomgr->n_overflow_nb + iomgr->n_inflight_nb);
+
+ // That we are within limits
+ ASSERT(iomgr->n_inflight_b <= iomgr->limit_inflight_b);
+ ASSERT(iomgr->n_inflight_nb <= iomgr->limit_inflight_nb);
+
+ // That we correctly track the submission queue size.
+ ASSERT((int)io_uring_sq_ready(uring) ==
+ iomgr->n_prepared_b + iomgr->n_prepared_nb);
+
+ // That our overflow queue is consistent with the overflow counter.
+ ASSERT(iomgr->n_overflow_nb > 0
+ ? iomgr->overflow_sqe_q_hd == NULL &&
+ iomgr->overflow_sqe_q_tl == NULL &&
+ iomgr->overflow_tso_q_hd == END_TSO_QUEUE &&
+ iomgr->overflow_tso_q_tl == END_TSO_QUEUE
+ : iomgr->n_overflow_nb == 0 && iomgr->overflow_sqe_q_hd != NULL &&
+ iomgr->overflow_sqe_q_tl != NULL &&
+ iomgr->overflow_tso_q_hd != END_TSO_QUEUE &&
+ iomgr->overflow_tso_q_tl != END_TSO_QUEUE);
+}
+
+/* If there are any completed I/O operations or expired timers, process the
+ * completions as appropriate. If there are none, return without waiting.
+ *
+ * This is the non-blocking variant. See awaitCompletedTimeoutsOrIOURing
+ * for the potentially-blocking variant.
+ */
+void pollCompletedTimeoutsOrIOIOURing(CapIOManager *iomgr) {
+ struct io_uring *uring = iomgr->uring;
+
+ assertURingSchedulerInvariants(iomgr);
+
+ /* Process timeouts, if any, but don't immediately return to the scheduler,
+ * since we should submit I/O and reap any completions too.
+ */
+ if (!isEmptyTimeoutQueue(iomgr->timeout_queue)) {
+ Time now = getProcessElapsedTime();
+ processTimeoutCompletions(iomgr, now);
+ }
+
+ /* Submit I/O if needed */
+ if (io_uring_sq_ready(uring)) {
+ int res = io_uring_submit_and_get_events(uring);
+
+ if (RTS_UNLIKELY(res < 0)) {
+ if (res == -EBUSY) {
+ /* This is an odd one. According to the doc:
+ * If the IORING_FEAT_NODROP feature flag is set, then EBUSY
+ * will be returned if there were overflow entries,
+ * IORING_ENTER_GETEVENTS flag is set and not all of the
+ * overflow entries were able to be flushed to the CQ ring.
+ *
+ * So it's not really an error at all. It just means we will
+ * have to do multiple iterations in processIOCompletions()
+ * to collect all the completions.
+ *
+ * Thus EBUSY should imply that there are entries in the CQ.
+ */
+ ASSERT(io_uring_cq_ready(uring) > 0);
+ }
+ } else {
+ ASSERT(res == iomgr->n_prepared_b + iomgr->n_prepared_nb);
+ /* We're using IORING_SETUP_SUBMIT_ALL so we should expect to have
+ * all of them submitted, or an error.
+ * https://github.com/axboe/liburing/issues/186
+ * Alternatively, we could loop and submit the remainder.
+ */
+ iomgr->n_inflight_b += iomgr->n_prepared_b;
+ iomgr->n_inflight_nb += iomgr->n_prepared_nb;
+ iomgr->n_prepared_b = 0;
+ iomgr->n_prepared_nb = 0;
+ }
+ }
+
+ if (io_uring_cq_ready(uring)) {
+ processIOCompletions(iomgr);
+ }
+ // TODO: now we need to check if we have any items in our overflow queue
+ // and if so, we need to copy some of those into the SQ and submit them.
+ // copy in up to either the SQ limit or in-flight limit.
+
+ assertURingSchedulerInvariants(iomgr);
+}
+
+/* If there are any completed I/O operations or expired timers, process the
+ * completions as appropriate. If there are none, wait until I/O or a timer
+ * does complete (or we get a signal with a handler) and process the
+ * completions as appropriate.
+ *
+ * This is the potentially-blocking variant. See pollCompletedTimeoutsOrIOURing
+ * for the non-blocking variant.
+ */
+bool awaitCompletedTimeoutsOrIOIOURing(CapIOManager *iomgr) {
+ struct io_uring *uring = iomgr->uring;
+
+ assertURingSchedulerInvariants(iomgr);
+
+ do {
+
+ /* We're being asked (by the scheduler) to block if there's no
+ * immediate timer or I/O completions. So there had better be
+ * some pending I/O or pending timers, or we'd deadlock.
+ */
+ ASSERT(!isEmptyTimeoutQueue(iomgr->timeout_queue) ||
+ !isEmptyClosureTable(&iomgr->aiop_table));
+
+ Time now = getProcessElapsedTime();
+ processTimeoutCompletions(iomgr, now);
+
+ /* If we didn't wake any threads due to expiring timeouts, then we need
+ * to wait on I/O. Or to put it another way, even if we did wake some
+ * threads, we'll still poll (but not wait) for I/O. This is to ensure
+ * we avoid starving threads blocked on I/O.
+ */
+ bool wait = emptyRunQueue(iomgr->cap);
+
+ /* There are four possible cases:
+ * 1. non-blocking check for I/O completion with I/O submission
+ * 2. non-blocking check for I/O completion with no I/O submission
+ * 3. blocking wait for I/O completion with a timeout
+ * 4. blocking wait for I/O completion without a timeout
+ *
+ * If we woke any threads due to timeouts we're in the first or second
+ * case.
+ *
+ * Otherwise we're in one of the blocking cases. We will use a timeout
+ * if the timeout queue is non-empty.
+ */
+
+ int res;
+ if (!wait && io_uring_sq_ready(uring)) {
+ /* Case 1 (as above) */
+ res = io_uring_submit_and_get_events(uring);
+ } else if (!wait) {
+ /* Case 2 (as above) */
+ res = io_uring_get_events(uring);
+ } else {
+ struct timespec tv;
+ if (timeoutInNanoseconds(iomgr, true, now, &tv)) {
+ /* Case 3 (as above) */
+ /* struct timespec and struct __kernel_timespec are compatible
+ * but not exactly the same. Sigh. */
+ struct __kernel_timespec ts = {.tv_sec = tv.tv_sec,
+ .tv_nsec = tv.tv_nsec};
+ struct io_uring_cqe *cqe_unused;
+ res =
+ io_uring_submit_and_wait_timeout(uring, &cqe_unused, 1, &ts, NULL);
+ } else {
+ /* Case 4 (as above) */
+ res = io_uring_submit_and_wait(uring, 1);
+ }
+ }
+
+ if (res >= 0) {
+ processIOCompletions(iomgr);
+ } else if (errno == EINTR) {
+
+ } else if (errno == EBUSY || errno == EAGAIN) {
+
+ } else {
+ sysErrorBelch("io_uring_enter");
+ stg_exit(EXIT_FAILURE);
+ }
+ } while (emptyRunQueue(iomgr->cap) &&
+ (!isEmptyClosureTable(&iomgr->aiop_table) ||
+ !isEmptyTimeoutQueue(iomgr->timeout_queue)) &&
+ getSchedState() == SCHED_RUNNING);
+
+ assertURingSchedulerInvariants(iomgr);
+
+ return true; // TODO return false if interrupted
+}
+
+void interruptIOManagerIOURing(CapIOManager *iomgr) {
+ barf("interruptIOManagerIOURing:TODO");
+
+ // Here are 2 possible ways to implement interrupt:
+ //
+ // * On initialization, create a new FD with newFdWakeup() and submit a
+ // recurring POLL SQE. Then on interrupt, call sendFdWakeup() to foce a CQE
+ // to be generated.
+ // * Submit a NOP (see io_uring_prep_nop()) SQE. This should produce a trivial
+ // NOP CQE. The issue with this is that the SQE may already be full, which
+ // requires some solution.
+}
+
+/******************************************************************************
+ * Local helper utilities
+ */
+
+static bool enlargeTables(CapIOManager *iomgr) {
+ int oldcapacity = capacityClosureTable(&iomgr->aiop_table);
+ int newcapacity = (oldcapacity == 0) ? 1 : (oldcapacity * 2);
+ return enlargeClosureTable(iomgr->cap, &iomgr->aiop_table, newcapacity);
+}
+
+/*
+ */
+static void enqueueOverflowQueue(CapIOManager *iomgr,
+ StgTSO *tso, struct io_uring_sqe *sqe) {
+ /* Append the TSO to the tail of the overflow queue of TSOs. */
+ ASSERT(tso->_link == END_TSO_QUEUE);
+ if (iomgr->overflow_tso_q_hd == END_TSO_QUEUE) {
+ iomgr->overflow_tso_q_hd = tso;
+ } else {
+ setTSOLink(iomgr->cap, iomgr->overflow_tso_q_tl, tso);
+ }
+ iomgr->overflow_tso_q_tl = tso;
+
+ /* And append the SQE to the tail of the overflow queue of SQEs. */
+ struct overflow_sqe_q_t *entry;
+ entry = stgMallocBytes(sizeof(struct overflow_sqe_q_t), "uring iomgr");
+ *entry = (struct overflow_sqe_q_t){.sqe = sqe,
+ .next = NULL,
+#if defined(DEBUG)
+ .tid = tso->id
+#endif
+ };
+ if (iomgr->overflow_sqe_q_hd == NULL) {
+ iomgr->overflow_sqe_q_hd = entry;
+ } else {
+ iomgr->overflow_sqe_q_tl->next = entry;
+ }
+ iomgr->overflow_sqe_q_tl = entry;
+}
+
+static void dequeueOverflowQueue(CapIOManager *iomgr, StgTSO **ptso,
+ struct io_uring_sqe **psqe) {
+ /* Remove the TSO and SQE from the head of their respective queues */
+ StgTSO *tso = iomgr->overflow_tso_q_hd;
+ struct overflow_sqe_q_t *entry = iomgr->overflow_sqe_q_hd;
+
+ if (tso == END_TSO_QUEUE) {
+ // TODO: decide if we need this or if we should assume the queue is
+ // non-empty
+ ASSERT(entry == NULL);
+ *ptso = END_TSO_QUEUE;
+ *psqe = NULL;
+ } else {
+ iomgr->overflow_tso_q_hd = tso->_link;
+ RELAXED_STORE(&tso->_link, END_TSO_QUEUE);
+ if (iomgr->overflow_tso_q_hd == END_TSO_QUEUE) {
+ iomgr->overflow_tso_q_tl = END_TSO_QUEUE;
+ }
+
+ iomgr->overflow_sqe_q_hd = entry->next;
+ if (iomgr->overflow_sqe_q_hd == NULL) {
+ iomgr->overflow_sqe_q_tl = NULL;
+ }
+
+ *ptso = tso;
+ *psqe = entry->sqe;
+ }
+}
+
+#endif /* IOMGR_ENABLED_IO_URING */
=====================================
rts/posix/IOURing.h
=====================================
@@ -0,0 +1,38 @@
+/* -----------------------------------------------------------------------------
+ *
+ * (c) The GHC Team 2026
+ *
+ * An I/O manager based on io_uring
+ *
+ * Prototypes for functions in IOURing.c
+ *
+ * -------------------------------------------------------------------------*/
+
+#pragma once
+
+#include "IOManager.h"
+#include "Rts.h"
+
+#include "BeginPrivate.h"
+
+#if defined(IOMGR_ENABLED_IO_URING)
+
+void initCapabilityIOManagerIOURing(CapIOManager *iomgr);
+void freeCapabilityIOManagerIOURing(CapIOManager *iomgr);
+
+/* Synchronous I/O and timer operations */
+bool syncIOWaitReadyIOURing(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw,
+ HsInt fd);
+void syncIOCancelIOURing(CapIOManager *iomgr, StgTSO *tso);
+
+bool syncDelayTimeoutIOURing(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay);
+void syncDelayCancelTimeoutIOURing(CapIOManager *iomgr, StgTSO *tso);
+
+/* Scheduler operations */
+void pollCompletedTimeoutsOrIOIOURing(CapIOManager *iomgr);
+bool awaitCompletedTimeoutsOrIOIOURing(CapIOManager *iomgr);
+void interruptIOManagerIOURing(CapIOManager *iomgr);
+
+#endif /* IOMGR_ENABLED_IO_MANAGER */
+
+#include "EndPrivate.h"
\ No newline at end of file
=====================================
rts/posix/Timeout.c
=====================================
@@ -24,7 +24,7 @@
/* Currently only used by the poll I/O manager, but in future may be used by
several in-RTS I/O managers.
*/
-#if defined(IOMGR_ENABLED_POLL)
+#if defined(IOMGR_ENABLED_POLL) || defined(IOMGR_ENABLED_IO_URING)
bool syncDelayTimeout(CapIOManager *iomgr, StgTSO *tso, HsInt us_delay)
{
=====================================
rts/rts.cabal
=====================================
@@ -210,7 +210,8 @@ library
-- implicitly added by `cc`, we must explicitly add it here to ensure
-- that it is ordered correctly with libpthread, since ghc-internal.cabal
-- also explicitly lists libc. See #19029.
- extra-libraries: c
+ -- TODO we only want to use liburing if the io manager is enabled
+ extra-libraries: c uring
extra-libraries-static: c
if flag(librt)
extra-libraries: rt
@@ -582,6 +583,7 @@ library
posix/FdWakeup.c
posix/MIO.c
posix/Poll.c
+ posix/IOURing.c
posix/Select.c
posix/Signals.c
posix/Timeout.c
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/f173286a74cb67b50bc3e776276258…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/f173286a74cb67b50bc3e776276258…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][wip/dcoutts/printf-warnings] 17 commits: Never make an absent filler for a constraint type
by Duncan Coutts (@dcoutts) 04 Sep '26
by Duncan Coutts (@dcoutts) 04 Sep '26
04 Sep '26
Duncan Coutts pushed to branch wip/dcoutts/printf-warnings at Glasgow Haskell Compiler / GHC
Commits:
7bf546fc by Simon Peyton Jones at 2026-08-31T23:48:53-04:00
Never make an absent filler for a constraint type
mkAbsentFiller used isTerminatingType to decide, but that is not enough.
Consider
class Eq a => UC a where {}
let u :: UC Int -- UC Int is a "non-terminating type"
u = error "Absent"
let e :: Eq Int -- Eq Int is a "terminating type"
e = $p1UC u
We clearly must not make a filler for `e`, because we speculatively
evaluate it. But speculatively evaluating `e` forces `u`, so we must not
make one for `u` either.
Asking isDictTy instead is not enough either, because it does not catch a
constraint hidden behind an unreduced type family application:
type family F a :: Constraint
type instance F W = TC W
a :: F W => Int -> Int -- (F W) argument is absent
Oops! Entered absent arg Arg: irred
Type: F W
So play safe and use isPredTy: never make an absent filler for any
constraint-kinded type.
Fixes #27627
- - - - -
5f474953 by Zubin Duggal at 2026-08-31T23:48:53-04:00
Add tests for absent fillers at dictionary types
T27627 a unary class whose superclass is a non-unary class
T27627a ...whose superclass is a Constraint-kinded type family
T27627b ...whose superclass is a quantified constraint
T27627c a unary class applied to itself, (UC (UC (TC a)))
T27627e a (forall b. P b) dictionary that loops
- - - - -
cd5c6bcc by Zubin Duggal at 2026-08-31T23:48:53-04:00
An abstract TyCon may hide a unary class
A class declared in an hs-boot file is an AbstractTyCon inside the
module loop, and compiling the real declaration may reveal it to be a
UnaryClassTyCon.
- isTerminatingType returned True for such AbstractTyCons
- IfaceToCore set the unary flag to False in the DFunId
So we could end up speculating bottom dictionaries because inside a module
loop we see an UnaryClassTyCon as an AbstractTyCon
Use isTerminatingTyCon, which returns False for an abstract TyCon.
The Bool in DFunId is now a cache for isTerminatingTyCon, set in
mkDFunIdDetails.
Fixes #27704
- - - - -
abfc224a by Zubin Duggal at 2026-08-31T23:48:53-04:00
Specialise: don't replace dead args with absent fillers
specHeader decides an argument is dead by calling isDeadBinder on a binder of
the /optimised RHS/, then applies the filler to the /stable unfolding/
template instead. The two may differ, so the argument can be dead in
the RHS and not in the template.
The specialised function's unfolding then has an absent filler, and any call
site that inlines it evaluates the error thunk.
Dropping dead args in the specialiser is rarely worth it, to quote Simon,
"The later worker/wrapper pass will pick up the dead arg later if it is really dead. Keeps the specialiser simpler."
So instead of trying to check if the arg really is dead in the stable unfolding,
just drop the logic for dropping dead args in the specialiser altogeher.
Fixes #27703
- - - - -
1557fd1c by Zubin Duggal at 2026-08-31T23:48:53-04:00
CorePrep: don't speculate a call across an hs-boot edge
We take care not to evaluate things that might be bottom, like a
looping dictionary group, but our analysis is defeated by boot files.
We only track recursion within a module, so two dictionaries that
depend on each other across a module loop each look non-recursive, and
we might speculate them.
Any recursion we cannot see must cross an hs-boot edge, so refuse to
speculate calls that cross one.
Fixes #27717
- - - - -
4117e5ae by Wolfgang Jeltsch at 2026-08-31T23:49:35-04:00
Incorporate the `rethrowSTM` reexport into the `stm` submodule
- - - - -
4bfbf5c8 by ARATA Mizuki at 2026-09-01T18:55:08-04:00
testsuite: Fix out-of-bounds access in T3586
unsafeRead and unsafeWrite use 0-based index.
Looking at #3586, the expected output seems to be 2.8e8.
Addresses #27596
- - - - -
3f9db6d4 by ARATA Mizuki at 2026-09-01T18:55:08-04:00
testsuite: Fix out-of-bounds access in T21305
writeInt64Array# takes an index measured in units of Int64 elements.
Fixes #27596
- - - - -
44d7788f by Simon Peyton Jones at 2026-09-01T18:55:53-04:00
Fix buglet in INLINE-arity calculation for pattern synonyms
This fixes #27744.
The buglet was accidentally introduced by
commit 3a0f9a51c1dacc474c7fd128082edd8bf4081256
Author: Simon Peyton Jones <simon.peytonjones(a)gmail.com>
Date: Sat Aug 1 00:13:02 2026 +0100
Fix three bugs related to required type args and INLINE pragmas
I failed to find all the calls to `addInlinePragArity`!
- - - - -
df058f1d by Simon Jakobi at 2026-09-02T07:18:06-04:00
testsuite: Migrate perf tests off collect_compiler_stats('all')
The 'all' metric argument applies a single tolerance to bytes
allocated, max_bytes_used and peak_megabytes_allocated, although their
noise profiles are incompatible (#27653): allocations are nearly
deterministic, residency needs 10-20%, and peak is quantized to 1 MB.
Any single tolerance is too tight for one metric or too slack for
another. This migrates the remaining users of 'all' (and of the 'all'
default) to explicit per-metric collection, ahead of removing 'all'
from the driver.
peak_megabytes_allocated is dropped everywhere: its 1 MB granularity
makes tight relative windows meaningless (#27613), and it is sensitive
to GC timing. In #27489 it drifted by -5.3% while max_bytes_used moved
by less than 0.1%. Where a test guards a memory property,
max_bytes_used covers it at byte granularity.
Where the motivating ticket was about compile-time memory
(T11545, T15304, T26425), residency remains gated via max_bytes_used,
now with a residency-appropriate tolerance.
max_bytes_used is dropped where residency was only ever an accident of
'all':
* T15630, T15630a, T20261: the underlying tickets (#15630, #20261)
contain no memory data at all. One is a simplifier-ticks blowup and
the other is stated entirely in allocation numbers, so the 20%
window never had teeth.
* T21839c: #21839's measurements show residency essentially flat
(+0.16%) while allocations moved +7%, so allocations are the
discriminating metric. They are already gated at 1% via
collect_compiler_runtime. The ghc/max gate had previously broken CI
spuriously (9fd11585eb widened it from 1% to 10% for that reason).
Allocation tolerances are tightened to the testsuite's conventional 2%
where 'all' previously left them at 10-20%.
Assisted-by: Claude Fable 5
- - - - -
72dd2432 by Simon Jakobi at 2026-09-02T07:18:06-04:00
testsuite: Remove the 'all' metric argument of collect_stats
'all' gated bytes allocated, max_bytes_used and peak_megabytes_allocated
at a single tolerance, although their noise profiles are incompatible,
making such tests either flaky or toothless (#27653).
Closes #27653.
Assisted-by: Claude Fable 5
- - - - -
2228cb30 by Simon Jakobi at 2026-09-02T07:18:06-04:00
testsuite: Make the deviation argument of collect_stats mandatory
Almost every caller passes an explicit tolerance matched to the
metric's noise profile, and the silent 20% default is far slacker than
'bytes allocated' merits. Only two tests relied on it. They now state
their tolerance explicitly:
large-project gets 10%, in line with other large compile-time tests.
T9848 gets 2%: its metric is byte-for-byte deterministic across CI
jobs and platforms of a given test_env, has drifted only about 2.5%
since 2015, and the fusion failure it guards against would show up as
a roughly +30000% jump.
Assisted-by: Claude Fable 5
- - - - -
56291fc5 by Cheng Shao at 2026-09-02T07:18:45-04:00
autoconf/ghc-toolchain: bump llvm upper bound to support llvm 23
This commit bumps llvm upper bound to support llvm 23.
- - - - -
20eb3f41 by Cheng Shao at 2026-09-02T07:18:45-04:00
rts: fix compilation issues with clang 23
clang 23 has broadened `-Wall`/`-Wextra` ranges, exposing some minor
issues in the rts when building with validate flavours:
- Unused locals
- `#pragma GCC diagnostic pop` mismatch
This commit fixes those.
- - - - -
9e7739ab by Simon Jakobi at 2026-09-03T13:45:43-04:00
Add -Wimplicit-field-strictness (#16836)
This opt-in warning fires when a data constructor field lacks an
explicit strictness annotation (`!` or `~`). It complements the
LazyFieldAnnotations extension (4762a8bf30f) from GHC proposal 752,
which makes `~` annotations available for this purpose.
Deciding which fields to report requires their levity, so the check
runs after typechecking. To keep the noise down, the diagnostic is
emitted once per data declaration, grouped by constructor.
Closes #16836.
Assisted-by: Claude Fable 5
- - - - -
6a44d591 by mangoiv at 2026-09-03T13:46:22-04:00
simplifier: remove a bogus `assert` in `rebuild_app'` in `GHC.CoreToStg.Prep.cpeApp`.
Prior to this commit
commit 08bc245be70d95801bc1138804ed1de9474fbdc0
Author: sheaf <sam.derbyshire(a)gmail.com>
Date: Sat Feb 28 16:30:43 2026 +0100
Clean up join points, casts & ticks
This commit shores up the logic dealing with casts and ticks occurring
in between a join point binding and a jump
any `PlaceRuntime` ticks we observed were profiling ticks, even though that
isn't necessary.
The more liberal rules in the above commit allow e.g. breakpoint ticks
(which are valid PlaceRuntime ticks) to legitimately appear in an argument
position.
Fixes #27556
- - - - -
21b4e759 by Duncan Coutts at 2026-09-04T11:55:16+01:00
Enable printf warnings for trace functions and fix resulting warnings
Most of the existing printf-style functions are annotated with
attributes to enable gcc/clang warnings for the printf format string,
but several trace functions in Trace.h were missing this annotation.
Enable them, and fix the resulting warnings.
- - - - -
138 changed files:
- + changelog.d/27556
- + changelog.d/27627
- + changelog.d/27703
- + changelog.d/27704
- + changelog.d/27717
- + changelog.d/T27744
- + changelog.d/implicit-field-strictness-warning
- changelog.d/lazy-field-annotations
- + changelog.d/llvm-23
- compiler/GHC/Core.hs
- compiler/GHC/Core/Opt/Specialise.hs
- compiler/GHC/Core/Opt/WorkWrap/Utils.hs
- compiler/GHC/Core/TyCon.hs
- compiler/GHC/Core/Type.hs
- compiler/GHC/Core/Utils.hs
- compiler/GHC/CoreToStg/Prep.hs
- compiler/GHC/Driver/Flags.hs
- compiler/GHC/Driver/Session.hs
- compiler/GHC/IfaceToCore.hs
- compiler/GHC/Tc/Errors/Ppr.hs
- compiler/GHC/Tc/Errors/Types.hs
- compiler/GHC/Tc/TyCl.hs
- compiler/GHC/Tc/TyCl/PatSyn.hs
- compiler/GHC/Types/Demand.hs
- compiler/GHC/Types/Error/Codes.hs
- compiler/GHC/Types/Hint.hs
- compiler/GHC/Types/Hint/Ppr.hs
- compiler/GHC/Types/Id/Info.hs
- compiler/GHC/Types/Id/Make.hs
- compiler/GHC/Types/Literal.hs
- compiler/GHC/Types/Tickish.hs
- configure.ac
- docs/users_guide/exts/strict.rst
- docs/users_guide/using-warnings.rst
- libraries/base/tests/all.T
- libraries/stm
- rts/Capability.c
- rts/IOManager.c
- rts/Messages.c
- rts/Printer.c
- rts/ProfHeap.c
- rts/STM.c
- rts/Schedule.c
- rts/Sparks.c
- rts/StableName.c
- rts/ThreadPaused.c
- rts/Threads.c
- rts/Trace.h
- rts/eventlog/EventLog.c
- rts/posix/Poll.c
- rts/posix/Timeout.c
- rts/prim/atomic.c
- rts/sm/Compact.c
- rts/sm/GC.c
- rts/sm/GCUtils.c
- rts/sm/MBlock.c
- rts/sm/NonMoving.c
- rts/sm/NonMovingMark.c
- rts/sm/Storage.c
- rts/sm/Sweep.c
- testsuite/driver/README.md
- testsuite/driver/testlib.py
- + testsuite/tests/core-to-stg/T27627/Callee.hs
- + testsuite/tests/core-to-stg/T27627/Caller.hs
- + testsuite/tests/core-to-stg/T27627/Main.hs
- + testsuite/tests/core-to-stg/T27627/T27627.stdout
- + testsuite/tests/core-to-stg/T27627/all.T
- + testsuite/tests/core-to-stg/T27627a/Callee.hs
- + testsuite/tests/core-to-stg/T27627a/Caller.hs
- + testsuite/tests/core-to-stg/T27627a/Main.hs
- + testsuite/tests/core-to-stg/T27627a/T27627a.stdout
- + testsuite/tests/core-to-stg/T27627a/all.T
- + testsuite/tests/core-to-stg/T27627b/Callee.hs
- + testsuite/tests/core-to-stg/T27627b/Caller.hs
- + testsuite/tests/core-to-stg/T27627b/Main.hs
- + testsuite/tests/core-to-stg/T27627b/T27627b.stdout
- + testsuite/tests/core-to-stg/T27627b/all.T
- + testsuite/tests/core-to-stg/T27627c/Callee.hs
- + testsuite/tests/core-to-stg/T27627c/Caller.hs
- + testsuite/tests/core-to-stg/T27627c/Main.hs
- + testsuite/tests/core-to-stg/T27627c/T27627c.stdout
- + testsuite/tests/core-to-stg/T27627c/all.T
- + testsuite/tests/core-to-stg/T27627e.hs
- + testsuite/tests/core-to-stg/T27627e.stdout
- + testsuite/tests/core-to-stg/T27627f/Callee.hs
- + testsuite/tests/core-to-stg/T27627f/Caller.hs
- + testsuite/tests/core-to-stg/T27627f/Inst.hs
- + testsuite/tests/core-to-stg/T27627f/Main.hs
- + testsuite/tests/core-to-stg/T27627f/T27627f.stdout
- + testsuite/tests/core-to-stg/T27627f/all.T
- + testsuite/tests/core-to-stg/T27704/Callee.hs
- + testsuite/tests/core-to-stg/T27704/Callee.hs-boot
- + testsuite/tests/core-to-stg/T27704/Main.hs
- + testsuite/tests/core-to-stg/T27704/Mid.hs
- + testsuite/tests/core-to-stg/T27704/T27704.stdout
- + testsuite/tests/core-to-stg/T27704/all.T
- + testsuite/tests/core-to-stg/T27704a/Callee.hs
- + testsuite/tests/core-to-stg/T27704a/Callee.hs-boot
- + testsuite/tests/core-to-stg/T27704a/Main.hs
- + testsuite/tests/core-to-stg/T27704a/Mid.hs
- + testsuite/tests/core-to-stg/T27704a/T27704a.stdout
- + testsuite/tests/core-to-stg/T27704a/all.T
- + testsuite/tests/core-to-stg/T27717/Callee.hs
- + testsuite/tests/core-to-stg/T27717/Callee.hs-boot
- + testsuite/tests/core-to-stg/T27717/Main.hs
- + testsuite/tests/core-to-stg/T27717/Mid.hs
- + testsuite/tests/core-to-stg/T27717/T27717.stdout
- + testsuite/tests/core-to-stg/T27717/Ty.hs
- + testsuite/tests/core-to-stg/T27717/all.T
- testsuite/tests/core-to-stg/all.T
- testsuite/tests/ffi/should_run/T21305.hs
- + testsuite/tests/patsyn/should_compile/T27744.hs
- testsuite/tests/patsyn/should_compile/all.T
- testsuite/tests/perf/compiler/all.T
- testsuite/tests/perf/compiler/large-project/all.T
- testsuite/tests/perf/should_run/T3586.hs
- testsuite/tests/perf/should_run/T3586.stdout
- testsuite/tests/perf/space_leaks/all.T
- testsuite/tests/simplCore/should_compile/T17966.stderr
- + testsuite/tests/simplCore/should_compile/T27556.hs
- + testsuite/tests/simplCore/should_compile/T27556.script
- testsuite/tests/simplCore/should_compile/T7785.stderr
- testsuite/tests/simplCore/should_compile/all.T
- testsuite/tests/simplCore/should_compile/spec004.hs
- testsuite/tests/simplCore/should_compile/spec004.stderr
- + testsuite/tests/simplCore/should_run/T27703/Lib.hs
- + testsuite/tests/simplCore/should_run/T27703/Main.hs
- + testsuite/tests/simplCore/should_run/T27703/T27703.stdout
- + testsuite/tests/simplCore/should_run/T27703/all.T
- + testsuite/tests/warnings/should_compile/T16836a.hs
- + testsuite/tests/warnings/should_compile/T16836a.stderr
- + testsuite/tests/warnings/should_compile/T16836b.hs
- + testsuite/tests/warnings/should_compile/T16836c.hs
- + testsuite/tests/warnings/should_compile/T16836c.stderr
- + testsuite/tests/warnings/should_compile/T16836d.hs
- + testsuite/tests/warnings/should_compile/T16836d.stderr
- testsuite/tests/warnings/should_compile/all.T
- utils/ghc-toolchain/src/GHC/Toolchain/Program.hs
The diff was not included because it is too large.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/2cb7c0a9f718ad3518cb9ca58a5a45…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/2cb7c0a9f718ad3518cb9ca58a5a45…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][wip/andrea/gbc-backport] 37 commits: rts/Interpreter: Factor out ctoi tuple info tables into data
by Andrea Vezzosi (@trac-Saizan) 04 Sep '26
by Andrea Vezzosi (@trac-Saizan) 04 Sep '26
04 Sep '26
Andrea Vezzosi pushed to branch wip/andrea/gbc-backport at Glasgow Haskell Compiler / GHC
Commits:
1019095a by Ben Gamari at 2026-09-04T12:02:08+02:00
rts/Interpreter: Factor out ctoi tuple info tables into data
Instead of a massive case let's put this into data which we can reuse
elsewhere.
(cherry picked from commit d2b89603425b6d08df199deb7b84cfdbd711869a)
- - - - -
afa759cb by Cheng Shao at 2026-09-04T12:02:08+02:00
compiler: only use `Name` instead of `Id` in `SptEntry`
As a part of #26298, this patch refactors `SptEntry` to only carry a
`Name` instead of `Id`: we do not care about extra information like
caffyness or type at all in any static pointer related codegen logic.
This is necessary to make `SptEntry` serializable, as a part of the
grand plan of serializable bytecode.
Co-authored-by: Codex <codex(a)openai.com>
(cherry picked from commit 39b2e3823da3ab168aa2b7365f0f27e01b1ecd0e)
- - - - -
f8b69618 by Oleg Grenrus at 2026-09-04T12:02:08+02:00
Generalise thNameToGhcName by adding HasHscEnv
There were multiple single monad-specific `getHscEnv` across codebase.
HasHscEnv is modelled on HasDynFlags.
My first idea was to simply add thNameToGhcNameHsc and
thNameToGhcNameTc, but those would been exactly the same
as thNameToGhcName already.
Also add an usage example to thNameToGhcName and mention that it's
recommended way of looking up names in GHC plugins
(cherry picked from commit eb2ab1e2cadde93c330330ca7fdc64b31cd61473)
- - - - -
70b2e828 by Simon Peyton Jones at 2026-09-04T12:02:08+02:00
Refactor ForAllCo
This is a pure refactor, addressing #26389.
It arranges that the kind coercion in a ForAllCo is a MCoercion, rather
than a plain Coercion, thus removing redundancy in the common case.
See (FC8) in Note [ForAllCo]
It's a nice cleanup.
(cherry picked from commit 94b62aa7e7b40b53442161a0c4f380955fda2f14)
- - - - -
abdab9fd by Cheng Shao at 2026-09-04T12:02:08+02:00
testsuite: remove legacy :shell trick
This commit makes use of the built-in :shell functionality in ghci in
the test cases, and remove the legacy :shell trick.
(cherry picked from commit 186054f7e7a190ee175a6e93c6cfb63b8a67baca)
- - - - -
3b5282e8 by Cheng Shao at 2026-09-04T12:02:16+02:00
compiler/ghci: replace the LoadDLL message with LoadDLLs
As a part of #25407, this commit changes the LoadDLL message to
LoadDLLs, which takes a list of DLL paths to load and returns the list
of remote pointer handles. The wasm dyld is refactored to take
advantage of LoadDLLs and harvest background parallelism. On other
platforms, LoadDLLs is based on a fallback codepath that does
sequential loading.
The driver is not actually emitting singular LoadDLLs message with
multiple DLLs yet, this is left in subsequent commits.
Co-authored-by: Codex <codex(a)openai.com>
(cherry picked from commit a4d664c78642438f0f178ffa243e2ffdbdf20397)
- - - - -
2ca382e1 by Cheng Shao at 2026-09-04T12:02:23+02:00
driver: separate downsweep/upsweep phase in loadPackages'
This commit refactors GHC.Linker.Loader.loadPackages' to be separated
into downsweep/upsweep phases:
- The downsweep phase performs dependency analysis and generates a
list of topologically sorted packages to load
- The upsweep phase sequentially loads these packages by calling
loadPackage
This is a necessary refactoring to make it possible to make loading of
DLLs concurrent.
(cherry picked from commit c7fc4baeddcfad500126b2e91ce94d2b9391560d)
- - - - -
2beb8401 by Cheng Shao at 2026-09-04T12:02:23+02:00
driver: emit single LoadDLLs message to load multiple DLLs
This commit refactors the driver so that it emits a single LoadDLLs
message to load multiple DLLs in GHC.Linker.Loader.loadPackages'.
Closes #25407.
-------------------------
Metric Increase:
MultiLayerModulesTH_OneShot
TcPlugin_RewritePerf
-------------------------
Co-authored-by: Codex <codex(a)openai.com>
(cherry picked from commit ab180104b61a930e8a1e906f013c151669ce6fff)
- - - - -
cee1c9f4 by Matthew Pickering at 2026-09-04T12:02:23+02:00
driver: Load bytecode static pointer entries during linking
Previously the entries were loaded too eagerly, during upsweep, but we
should delay loading them until we know that the relevant bytecode
object is demanded.
Towards #25230
(cherry picked from commit e9445c013fbccf9318739ca3d095a3e0a2e1be8a)
- - - - -
5d743963 by Matthew Pickering at 2026-09-04T12:02:23+02:00
loader: Unify loadDecls and loadModuleLinkables functions
These two functions nearly did the same thing. I have refactored them so
that `loadDecls` now calls `loadModuleLinkables`.
Fixes #26459
(cherry picked from commit da9633a9009a08132b974b0407c4057cae3577f7)
- - - - -
00f4f18d by Matthew Pickering at 2026-09-04T12:02:23+02:00
testsuite: Use ghci_ways to set ways in PackedDataCon/UnboxedTuples/UnliftedDataTypeInterp tests
These tests reimplemented the logic from `valid_way` in order to
determine what ways to run. It's easier to use this combination of
`only_ways` and `extra_ways` to only run in GHCi ways and always run in
GHCi ways.
(cherry picked from commit 1275d3607299734228adbbc47bfb69bf8800279f)
- - - - -
3115ff9c by Matthew Pickering at 2026-09-04T12:02:33+02:00
Rename interpreterBackend to bytecodeBackend
This is preparation for creating bytecode files.
The "interpreter" is one way in which we can run bytecode objects. It is
more accurate to describe that the backend produces bytecode, rather
than the means by which the code will eventually run.
The "interpreterBackend" binding is left as a deprecated alias.
(cherry picked from commit c06b534bbd6dbb570b760f2e82b3e375d3956bcf)
- - - - -
7485f54a by sheaf at 2026-09-04T12:02:36+02:00
Improvements to 'mayLookIdentical'
This commit makes significant improvements to the machinery that decides
when we should pretty-print the "invisible bits" of a type, such as:
- kind applications, e.g. '@k' in 'Proxy @k ty'
- RuntimeReps, e.g. 'TYPE r'
- multiplicities and linear arrows 'a %1 -> b'
To do this, this commit refactors 'mayLookIdentical' to return **which**
of the invisible bits don't match up, e.g. in
(a %1 -> b) ~ (a %Many -> b)
we find that the invisible bit that doesn't match up is a multiplicity,
so we should set 'sdocLinearTypes = True' when pretty-printing, and with
e.g.
Proxy @k1 ~ Proxy @k2
we find that the invisible bit that doesn't match up is an invisible
TyCon argument, so we set 'sdocPrintExplicitKinds = True'.
We leverage these changes to remove the ad-hoc treatment of linearity
of data constructors with 'dataConDisplayType' and 'dataConNonLinearType'.
This is now handled by the machinery of 'pprWithInvisibleBits'.
Fixes #26335 #26340
(cherry picked from commit 2b8baada761b13199b99268f243bebd009e6e1b5)
- - - - -
38779115 by sheaf at 2026-09-04T12:02:36+02:00
Store SDoc context in SourceError
This commits modifies the SourceError datatype which is used for
throwing and then reporting exceptions by storing all the info we need
to be able to print the SDoc, including whether we should print with
explicit kinds, explicit runtime-reps, etc.
This is done using the new datatype:
data SourceErrorContext
= SEC
!DiagOpts
!(DiagnosticOpts GhcMessage)
Now, when we come to report an error (by handling the exception), we
have access to the full context we need.
Fixes #26387
(cherry picked from commit 129ce32d3a4f2601f5c2d1bbaeeb2fbaf8dc9ef9)
- - - - -
c803903f by Matthew Pickering at 2026-09-04T12:02:36+02:00
Add support for generating bytecode objects
This commit adds the `-fwrite-byte-code` option which makes GHC emit a
`.gbc` file which contains a serialised representation of bytecode.
The bytecode can be loaded by the compiler to avoid having to
reinterpret a module when using the bytecode interpreter (for example,
in GHCi).
There are also the new options:
* -gbcdir=<DIR>: Specify the directory to place the gbc files
* -gbcsuf=<suffix>: Specify the suffix for gbc files
The option `-fbyte-code-and-object-code` now implies
`-fwrite-byte-code`.
These performance tests fail due to https://github.com/haskell/directory/issues/204
-------------------------
Metric Increase:
MultiComponentModules
MultiLayerModules
MultiComponentModulesRecomp
MultiLayerModulesRecomp
MultiLayerModulesTH_Make
MultiLayerModulesTH_OneShot
T13701
-------------------------
The bytecode serialisation part was implemented by Cheng Shao
Co-authored-by: Cheng Shao <terrorjack(a)type.dance>
(cherry picked from commit 6bd8155c991cfce7cca594bc63ba8f6ca6751667)
- - - - -
564365ab by Ben Gamari at 2026-09-04T12:02:36+02:00
rts: Eliminate uses of implicit constant arrays
Folding of `const`-sized variable-length arrays to a constant-length
array is a gnu extension which clang complains about.
Closes #26502.
(cherry picked from commit 0c00c9c3b4e9b8515d4839f2c1d7d771781dc6f4)
- - - - -
129bcbcb by Cheng Shao at 2026-09-04T12:02:36+02:00
rts: use computed goto for instruction dispatch in the bytecode interpreter
This patch uses computed goto for instruction dispatch in the bytecode
interpreter. Previously instruction dispatch is done by a classic
switch loop, so executing the next instruction requires two jumps: one
to the start of the switch loop and another to the case block based on
the instruction tag. By using computed goto, we can build a jump table
consisted of code addresses indexed by the instruction tags
themselves, so executing the next instruction requires only one jump,
to the destination directly fetched from the jump table.
Closes #12953.
(cherry picked from commit 39567e85fa514c0c4dfeaf8faa586a47dd3e11ca)
- - - - -
7dde6d20 by Georgios Karachalias at 2026-09-04T12:02:36+02:00
Remove the `CoreBindings` constructor from `LinkablePart`
Adjust HscRecompStatus to disallow unhydrated WholeCoreBindings
from being passed as input to getLinkDeps (which would previously
panic in this case).
Fixes #26497
(cherry picked from commit b253013ebeea5273a1cc7bb0082ed952456af906)
- - - - -
2b401a07 by Matthew Pickering at 2026-09-04T12:02:37+02:00
driver: Properly handle errors during LinkNode steps
Previously we were not properly catching errors during the LinkNode step
(see T9930fail test).
This is fixed by wrapping the `LinkNode` action in `wrapAction`, the
same handler which is used for module compilation.
Fixes #26496
(cherry picked from commit 6c91582f915f80daff774db7738094bda6ab3b44)
- - - - -
7d769220 by Sylvain Henry at 2026-09-04T12:02:41+02:00
Build external interpreter program on demand (#24731)
This patch teaches GHC how to build the external interpreter program
when it is missing. As long as we have the `ghci` library, doing this is
trivial so most of this patch is refactoring for doing it sanely.
(cherry picked from commit 55eab80d337e47decacbe979c29a1b7b47d0a872)
- - - - -
3ea68ab0 by Matthew Pickering at 2026-09-04T12:02:43+02:00
Use 'OsPath' in getModificationTimeIfExists
This part of the compiler is quite hot during recompilation checking in
particular since the filepaths will be translated to a string. It is
better to use the 'OsPath' native function, which turns out to be easy
to do.
(cherry picked from commit ef0dc33b2034fd91e11950751264e8b2e9f03fc4)
- - - - -
b427545f by Georgios Karachalias at 2026-09-04T12:02:43+02:00
Use OsPath in PkgDbRef and UnitDatabase, not FilePath
(cherry picked from commit fa3bd0a67eea13701d1b50053636f9645e999308)
- - - - -
f55a3e8b by Matthew Pickering at 2026-09-04T12:02:43+02:00
Add support for building bytecode libraries
A bytecode library is a collection of bytecode files (.gbc) and a
library which combines together additional object files.
A bytecode library is created by invoking GHC with the `-bytecodelib`
flag.
A library can be created from in-memory `ModuleByteCode` linkables or
by passing `.gbc` files as arguments on the command line.
Fixes #26298
(cherry picked from commit e36a5fcbeaa33abb17707173c9d1e81730f85008)
- - - - -
1c577e5a by Matthew Pickering at 2026-09-04T12:02:43+02:00
Load bytecode libraries to satisfy package dependencies
This commit allows you to use a bytecode library to satisfy a package
dependency when using the interpreter.
If a user enables `-fprefer-byte-code`, then if a package provides a
bytecode library, that will be loaded and used to satisfy the
dependency.
The main change is to separate the relevant parts of the `LoaderState`
into external and home package byte code. Bytecode is loaded into either
the home package or external part (similar to HPT/EPS split), HPT
bytecode can be unloaded. External bytecode is never unloaded.
The unload function has also only been called with an empty list of
"stable linkables" for a long time. It has been modified to directly
implement a complete unloading of the home package bytecode linkables.
At the moment, the bytecode libraries are found in the "library-dirs"
field from the package description. In the future when `Cabal`
implements support for "bytecode-library-dirs" field, we can read the
bytecode libraries from there. No changes to the Cabal submodule are
necessary at the moment.
Four new tests are added in testsuite/tests/cabal, which generate fake
package descriptions and test loading the libraries into GHCi.
Fixes #26298
(cherry picked from commit 8f9ae3397dbcbb4b0bd7a574ffc6b1fbaa137b6c)
- - - - -
dcf2659e by Hécate Kleidukos at 2026-09-04T12:03:00+02:00
driver: Hide source paths at verbosity level 1 by default
(cherry picked from commit aa9c5e2ca0d42f7361fc465d79f5beb056987a41)
- - - - -
a435d732 by Matthew Pickering at 2026-09-04T12:03:02+02:00
determinism: Use a deterministic renaming when writing bytecode files
Now when writing the bytecode file, a counter and substitution are used
to provide deterministic keys to local variables (rather than relying on
uniques). This change ensures that `.gbc` are produced
deterministically.
Fixes #26499
(cherry picked from commit 9b64ad3a458bf4e4165f125059e4bc3d518c5834)
- - - - -
fb92357f by Cheng Shao at 2026-09-04T12:07:41+02:00
compiler: implement string interning logic for BCONPtrFS
This patch adds a `FastStringEnv`-based cache of `MallocStrings`
requests to `Interp`, so that when we load bytecode with many
breakpoints that share the same module names & unit ids, we reuse the
allocated remote pointers instead of issuing duplicte `MallocStrings`
requests and bloating the C heap. Closes #26995.
(cherry picked from commit d054b4676298ea2189cd48fcf175676b97f559e7)
- - - - -
82aee316 by Cheng Shao at 2026-09-04T12:07:44+02:00
compiler: use `Binary` instance of `BCOByteArray` for bytecode objects
This commit defines `Binary` (from `compiler`) instance of
`BCOByteArray` which serializes the underlying buffer directly, and
uses it directly in bytecode object serialization. Previously we reuse
the `Binary` (from `binary`) instance, and this change allows us to
avoid double-copying via an intermediate `ByteString` when using
`put`/`get` in `binnary`. Also see added comment for explanation.
(cherry picked from commit fdf828aef8c78ae23ad058ed4696910b6beba232)
- - - - -
cdf574a5 by Luite Stegeman at 2026-09-04T12:07:51+02:00
Support arbitrary size unboxed tuples in bytecode
This stores the size (number of words on the stack) of the next
expected tuple in the TSO, ctoi_spill_size field, eliminating
the need of stg_ctoi_tN frames for each size.
Note: On 32 bit platform there is still a bytecode tuple size
limit of 255 words on the stack.
Fixes #26946
(cherry picked from commit a85bd503a480cfa31f5afa09eb38f56c85c237de)
- - - - -
d959a8a9 by Luite Stegeman at 2026-09-04T12:07:54+02:00
Add specialized frames for small tuples
Small tuples are now returned more efficiently to the interpreter.
They use one less word of stack space and don't need manipulation
of the TSO anymore.
(cherry picked from commit e2209031734770a51f04883e5b538b562449cba1)
- - - - -
c9f97fb1 by fendor at 2026-09-04T12:07:54+02:00
Extract Binary instances to `GHC.ByteCode.Binary`
(cherry picked from commit acffb1b1da1a59dbded9ec2dcb65e495aee31f40)
- - - - -
0fd46a1a by fendor at 2026-09-04T12:07:54+02:00
Add `seqNonEmpty` for evaluating `NonEmpty a`
(cherry picked from commit e2ea8e25c5128afa8191669c751942348ab3043b)
- - - - -
ff435758 by fendor at 2026-09-04T12:08:00+02:00
Record `LinkableUsage` instead of `Linkable` in `LoaderState`
Retaining a ByteCode `Linkable` after it has been loaded retains its
`UnlinkedBCO`, keeping it alive for the remainder of the program.
This starts accumulating a lot of `UnlinkedBCO` and memory over time.
However, the `Linkable` is merely used to later record its usage in
`mkObjectUsage`, which is used for recompilation checking.
However, this is incorrect, as the interface file and bytecode objects
could be in different states, e.g. the interface changes, but the
bytecode library hasn't changed so we don't need to recompile and vice
versa.
By computing a `Fingerprint` for the `ModuleByteCode`, and recording it
in the `LinkableUsage`, we know precisely whether the `ByteCode` object
on disk is outdated.
Thus, parts of this commit just makes sure that we efficiently compute a
`Fingerprint` for `ModuleByteCode` and store it in the on-disk
representation of `ModuleByteCode`.
We change the `LoaderState` to retain `LinkableUsage`, which is smaller
representation of a `Linkable`. This allows us to free the unneeded
fields of `Linkable` after linking them.
We declare the following memory invariants that this commit implements:
* No `LinkablePart` should be retained from `LoaderState`.
* `Linkable`s should be unloaded after they have been loaded.
These invariants are unfortunately tricky to automatically uphold, so we
are simply documenting our assumptions for now.
We introduce the `linkable-space` test which makes sure that after
loading, no `DotGBC` or `UnlinkedBCO` is retained.
-------------------------
Metric Increase:
MultiLayerModulesTH_OneShot
-------------------------
We allocate a bit more, but the peak number of bytes doesn't change.
While a bit unfortunate, accepting the metric increase.
We add multiple new performance measurements where we were able to
observe the desired memory invariants. Further, we add regression tests
to validate that the recompilation checker behaves more correct than
before.
(cherry picked from commit 048b00b7c231dec199a4441caac279f3bbcd75f4)
- - - - -
0e25154d by Matthew Pickering at 2026-09-04T12:08:02+02:00
bytecode: Add magic header/version to bytecode files
In order to avoid confusing errors when using stale interface files (ie
from an older compiler version), we add a simple header/version check
like the one for interface files.
Fixes #27068
(cherry picked from commit 5cbc2c823fb0710c37f6e9144382dd59e0353782)
- - - - -
b0303f7b by fendor at 2026-09-04T12:08:02+02:00
Add constants for bytecode in-memory buffer size
Introduce a common constant for the default size of the .gbc and
.bytecodelib binary buffer.
The buffer is by default set to 1 MB.
(cherry picked from commit d95a19366f64e6d866c8df0b1ab917884cc015dc)
- - - - -
1febf0fd by Cheng Shao at 2026-09-04T12:08:02+02:00
ghci: use ShortByteString for MkCostCentres message
This patch refactors ghci to use `ShortByteString` for `MkCostCentres`
messages as a first part of #27147. This also considerably lowers the
memory overhead of breakpoints when cost center profiling is enabled.
-------------------------
Metric Decrease:
interpreter_steplocal
-------------------------
Co-authored-by: Codex <codex(a)openai.com>
(cherry picked from commit 371ef200bcb2664c4511daca75f72a52c1473950)
- - - - -
1cc418c4 by fendor at 2026-09-04T12:08:04+02:00
Make HPC work with bytecode interpreter
Add support to generate .tix files from bytecode objects and the
bytecode interpreter.
Conceptually, we insert HPC ticks into the bytecode similar to how we insert
breakpoints.
HPC and breakpoints do not share the same tick array but we use a separate
tick-array for hpc/breakpoint ticks during bytecode generation.
We teach the bytecode interpreter to handle hpc ticks.
The implementation is quite trivial, simply increment the counter in the
global hpc_ticks array for the respective module.
This hpc_ticks array is generated as part of the `CStub`, so we can rely
on it existing.
A tricky bit is "registering" a bytecode object for HPC instrumentation.
In the compiled case, this is achieved via CStub and initializer/finalizers
`.init` sections which are called when the executable is run.
After the initializers have been invoked, which is before `hs_init_ghc`,
we then call `startup_hpc` in `hs_init_ghc` iff any modules were "registered"
for hpc instrumentation via `hs_hpc_module`.
Since bytecode objects are loaded after starting up GHCi, this workflow
doesn't work for supporting `hpc` and the `hpc` run-time is never
started, even if a module is added for instrumentation.
We fix this issue by employing the same technique as is for `SptEntry`s:
* We introduce a new field to `CompiledByteCode`, called `ByteCodeHpcInfo`
which contains enough information to call `hs_hpc_module`, allowing us to
register the module for `hpc` instrumentation`.
* After registering the module, we unconditionally call `startupHpc`, to make
sure the .tix file is written.
Calling `startupHpc` multiple times is safe.
Calling `hs_hpc_module` multiple times for the same module is also safe.
If we didn't register the hpc module in this way, evaluating a bytecode object
instrumented with `-fhpc` without registering it in the `hpc` run-time will
simply not generate any `.tix` files for this bytecode object.
However, this shouldn't happen if everything is set up correctly.
Closes #27036
(cherry picked from commit 28f07d702a1b47230122df22e84b60fda49897c5)
- - - - -
384 changed files:
- + changelog.d/bytecode-interpreter-hpc-support
- compiler/GHC.hs
- compiler/GHC/Builtin/PrimOps.hs
- compiler/GHC/ByteCode/Asm.hs
- + compiler/GHC/ByteCode/Binary.hs
- compiler/GHC/ByteCode/Breakpoints.hs
- compiler/GHC/ByteCode/Instr.hs
- compiler/GHC/ByteCode/Linker.hs
- + compiler/GHC/ByteCode/Recomp/Binary.hs
- + compiler/GHC/ByteCode/Serialize.hs
- compiler/GHC/ByteCode/Types.hs
- compiler/GHC/Core/Coercion.hs
- compiler/GHC/Core/Coercion.hs-boot
- compiler/GHC/Core/Coercion/Opt.hs
- compiler/GHC/Core/Lint.hs
- compiler/GHC/Core/Multiplicity.hs
- compiler/GHC/Core/Opt/Arity.hs
- compiler/GHC/Core/Opt/Monad.hs
- compiler/GHC/Core/Reduction.hs
- compiler/GHC/Core/TyCo/Compare.hs
- compiler/GHC/Core/TyCo/FVs.hs
- compiler/GHC/Core/TyCo/Ppr.hs
- compiler/GHC/Core/TyCo/Rep.hs
- compiler/GHC/Core/TyCo/Subst.hs
- compiler/GHC/Core/TyCo/Tidy.hs
- compiler/GHC/Core/Type.hs
- compiler/GHC/Core/Unify.hs
- compiler/GHC/CoreToIface.hs
- compiler/GHC/Data/FlatBag.hs
- compiler/GHC/Data/OsPath.hs
- compiler/GHC/Data/SmallArray.hs
- compiler/GHC/Driver/Backend.hs
- compiler/GHC/Driver/Backend/Internal.hs
- compiler/GHC/Driver/Backpack.hs
- + compiler/GHC/Driver/ByteCode.hs
- compiler/GHC/Driver/CodeOutput.hs
- compiler/GHC/Driver/Config/Finder.hs
- + compiler/GHC/Driver/Config/Interpreter.hs
- compiler/GHC/Driver/Config/Linker.hs
- compiler/GHC/Driver/Downsweep.hs
- compiler/GHC/Driver/DynFlags.hs
- compiler/GHC/Driver/Env.hs
- compiler/GHC/Driver/Env/Types.hs
- compiler/GHC/Driver/Errors.hs
- compiler/GHC/Driver/Errors/Ppr.hs
- compiler/GHC/Driver/Errors/Types.hs
- compiler/GHC/Driver/Flags.hs
- compiler/GHC/Driver/Hooks.hs
- compiler/GHC/Driver/Main.hs
- compiler/GHC/Driver/Make.hs
- compiler/GHC/Driver/MakeFile.hs
- compiler/GHC/Driver/Messager.hs
- compiler/GHC/Driver/Monad.hs
- compiler/GHC/Driver/Phases.hs
- compiler/GHC/Driver/Pipeline.hs
- compiler/GHC/Driver/Pipeline/Execute.hs
- compiler/GHC/Driver/Plugins.hs
- compiler/GHC/Driver/Session.hs
- compiler/GHC/Hs/Decls.hs
- compiler/GHC/Hs/Type.hs
- compiler/GHC/HsToCore.hs
- compiler/GHC/HsToCore/Breakpoints.hs
- compiler/GHC/HsToCore/Coverage.hs
- compiler/GHC/HsToCore/Ticks.hs
- compiler/GHC/HsToCore/Usage.hs
- compiler/GHC/Iface/Decl.hs
- compiler/GHC/Iface/Ext/Ast.hs
- compiler/GHC/Iface/Load.hs
- compiler/GHC/Iface/Make.hs
- compiler/GHC/Iface/Recomp.hs
- compiler/GHC/Iface/Recomp/Types.hs
- compiler/GHC/Iface/Rename.hs
- compiler/GHC/Iface/Syntax.hs
- compiler/GHC/Iface/Tidy.hs
- compiler/GHC/Iface/Tidy/StaticPtrTable.hs
- compiler/GHC/Iface/Type.hs
- compiler/GHC/IfaceToCore.hs
- + compiler/GHC/Linker/ByteCode.hs
- compiler/GHC/Linker/Config.hs
- compiler/GHC/Linker/Deps.hs
- compiler/GHC/Linker/Dynamic.hs
- + compiler/GHC/Linker/Executable.hs
- − compiler/GHC/Linker/ExtraObj.hs
- compiler/GHC/Linker/Loader.hs
- compiler/GHC/Linker/MacOS.hs
- compiler/GHC/Linker/Static.hs
- compiler/GHC/Linker/Types.hs
- compiler/GHC/Linker/Windows.hs
- compiler/GHC/Parser/Header.hs
- compiler/GHC/Plugins.hs
- compiler/GHC/Runtime/Debugger.hs
- compiler/GHC/Runtime/Eval.hs
- compiler/GHC/Runtime/Interpreter.hs
- + compiler/GHC/Runtime/Interpreter/C.hs
- + compiler/GHC/Runtime/Interpreter/Init.hs
- compiler/GHC/Runtime/Interpreter/Types.hs
- compiler/GHC/Runtime/Loader.hs
- compiler/GHC/Settings.hs
- compiler/GHC/StgToByteCode.hs
- compiler/GHC/StgToJS/StaticPtr.hs
- compiler/GHC/SysTools/Tasks.hs
- compiler/GHC/Tc/Errors/Ppr.hs
- compiler/GHC/Tc/Errors/Types.hs
- compiler/GHC/Tc/Gen/HsType.hs
- compiler/GHC/Tc/Gen/Splice.hs
- compiler/GHC/Tc/Module.hs
- compiler/GHC/Tc/Solver/Equality.hs
- compiler/GHC/Tc/TyCl.hs
- compiler/GHC/Tc/TyCl/Utils.hs
- compiler/GHC/Tc/Types.hs
- compiler/GHC/Tc/Types/CtLoc.hs
- compiler/GHC/Tc/Types/Origin.hs
- compiler/GHC/Tc/Utils/Monad.hs
- compiler/GHC/Tc/Utils/TcMType.hs
- compiler/GHC/Tc/Utils/TcType.hs
- compiler/GHC/Tc/Utils/Unify.hs
- compiler/GHC/Tc/Utils/Unify.hs-boot
- compiler/GHC/Types/Error/Codes.hs
- compiler/GHC/Types/HpcInfo.hs
- compiler/GHC/Types/Id/Make.hs
- compiler/GHC/Types/SourceError.hs
- compiler/GHC/Types/SptEntry.hs
- compiler/GHC/Types/Tickish.hs
- compiler/GHC/Types/TyThing/Ppr.hs
- compiler/GHC/Unit/Finder.hs
- compiler/GHC/Unit/Finder/Types.hs
- compiler/GHC/Unit/Home/ModInfo.hs
- compiler/GHC/Unit/Home/PackageTable.hs
- compiler/GHC/Unit/Info.hs
- compiler/GHC/Unit/Module/Deps.hs
- compiler/GHC/Unit/Module/Graph.hs
- compiler/GHC/Unit/Module/Location.hs
- compiler/GHC/Unit/Module/ModGuts.hs
- compiler/GHC/Unit/Module/ModSummary.hs
- compiler/GHC/Unit/Module/Status.hs
- compiler/GHC/Unit/Module/WholeCoreBindings.hs
- compiler/GHC/Unit/State.hs
- compiler/GHC/Utils/Binary.hs
- compiler/GHC/Utils/Error.hs
- compiler/GHC/Utils/Misc.hs
- compiler/GHC/Utils/Outputable.hs
- compiler/ghc.cabal.in
- docs/users_guide/extending_ghc.rst
- docs/users_guide/phases.rst
- docs/users_guide/separate_compilation.rst
- ghc/GHCi/Leak.hs
- ghc/GHCi/UI.hs
- ghc/Main.hs
- hadrian/src/Settings/Builders/RunTest.hs
- libraries/ghc-boot/GHC/Unit/Database.hs
- + libraries/ghci/GHCi/Coverage.hs
- libraries/ghci/GHCi/Message.hs
- libraries/ghci/GHCi/ObjLink.hs
- libraries/ghci/GHCi/Run.hs
- libraries/ghci/ghci.cabal.in
- rts/Apply.cmm
- rts/Continuation.c
- rts/ContinuationOps.cmm
- rts/Disassembler.c
- rts/Hpc.c
- rts/Interpreter.c
- rts/Printer.c
- rts/RaiseAsync.c
- rts/RtsSymbols.c
- rts/Schedule.c
- rts/StgMiscClosures.cmm
- rts/Threads.c
- rts/Threads.h
- rts/include/rts/Bytecodes.h
- rts/include/rts/storage/TSO.h
- rts/include/stg/MiscClosures.h
- rts/posix/OSMem.c
- testsuite/config/ghc
- testsuite/driver/testglobals.py
- testsuite/driver/testlib.py
- testsuite/mk/boilerplate.mk
- testsuite/mk/test.mk
- testsuite/tests/backpack/cabal/bkpcabal08/bkpcabal08.stdout
- testsuite/tests/bytecode/T24634/T24634a.stdout
- testsuite/tests/bytecode/T24634/T24634b.stdout
- + testsuite/tests/bytecode/TLinkable/BCOTemplate.hs
- + testsuite/tests/bytecode/TLinkable/LinkableUsage01.stderr
- + testsuite/tests/bytecode/TLinkable/LinkableUsage02.stderr
- + testsuite/tests/bytecode/TLinkable/Makefile
- + testsuite/tests/bytecode/TLinkable/all.T
- + testsuite/tests/bytecode/TLinkable/genLinkables.sh
- + testsuite/tests/bytecode/TLinkable/linkable-space.hs
- + testsuite/tests/bytecode/TLinkable/linkable-space.stdout
- + testsuite/tests/bytecode/tuplestress/ByteCode.hs
- + testsuite/tests/bytecode/tuplestress/Common.hs-incl
- + testsuite/tests/bytecode/tuplestress/Obj.hs
- + testsuite/tests/bytecode/tuplestress/TupleStress.hs
- + testsuite/tests/bytecode/tuplestress/TupleStress.stdout
- + testsuite/tests/bytecode/tuplestress/all.T
- + testsuite/tests/cabal/Bytecode.hs
- + testsuite/tests/cabal/BytecodeForeign.c
- + testsuite/tests/cabal/BytecodeForeign.hs
- testsuite/tests/cabal/Makefile
- testsuite/tests/cabal/all.T
- + testsuite/tests/cabal/bytecode.pkg
- + testsuite/tests/cabal/bytecode.script
- + testsuite/tests/cabal/bytecode_foreign.pkg
- + testsuite/tests/cabal/bytecode_foreign.script
- testsuite/tests/cabal/ghcpkg03.stderr
- testsuite/tests/cabal/ghcpkg03.stderr-mingw32
- testsuite/tests/cabal/ghcpkg05.stderr
- testsuite/tests/cabal/ghcpkg05.stderr-mingw32
- + testsuite/tests/cabal/pkg_bytecode.stderr
- + testsuite/tests/cabal/pkg_bytecode.stdout
- + testsuite/tests/cabal/pkg_bytecode_foreign.stderr
- + testsuite/tests/cabal/pkg_bytecode_foreign.stdout
- + testsuite/tests/cabal/pkg_bytecode_with_gbc.stderr
- + testsuite/tests/cabal/pkg_bytecode_with_gbc.stdout
- + testsuite/tests/cabal/pkg_bytecode_with_o.stderr
- + testsuite/tests/cabal/pkg_bytecode_with_o.stdout
- testsuite/tests/count-deps/CountDepsAst.stdout
- testsuite/tests/count-deps/CountDepsParser.stdout
- testsuite/tests/cpranal/should_compile/T18174.stderr
- testsuite/tests/driver/T11429c.stderr
- testsuite/tests/driver/T20030/test1/all.T
- testsuite/tests/driver/T20030/test2/all.T
- testsuite/tests/driver/T20030/test3/all.T
- testsuite/tests/driver/T20030/test4/all.T
- testsuite/tests/driver/T20030/test5/all.T
- testsuite/tests/driver/T20030/test6/all.T
- testsuite/tests/driver/T21682.stderr
- + testsuite/tests/driver/T24731.hs
- testsuite/tests/driver/T5313.hs
- testsuite/tests/driver/T8526/T8526.script
- testsuite/tests/driver/all.T
- + testsuite/tests/driver/bytecode-object/A.hs
- + testsuite/tests/driver/bytecode-object/BytecodeForeign.c
- + testsuite/tests/driver/bytecode-object/BytecodeForeign.hs
- + testsuite/tests/driver/bytecode-object/BytecodeMain.hs
- + testsuite/tests/driver/bytecode-object/BytecodeTest.hs
- + testsuite/tests/driver/bytecode-object/Makefile
- + testsuite/tests/driver/bytecode-object/all.T
- + testsuite/tests/driver/bytecode-object/bytecode_object12.stderr
- + testsuite/tests/driver/bytecode-object/bytecode_object13.stdout
- + testsuite/tests/driver/bytecode-object/bytecode_object14.stdout
- + testsuite/tests/driver/bytecode-object/bytecode_object15.stdout
- + testsuite/tests/driver/bytecode-object/bytecode_object16.stdout
- + testsuite/tests/driver/bytecode-object/bytecode_object17.stdout
- + testsuite/tests/driver/bytecode-object/bytecode_object18.stdout
- + testsuite/tests/driver/bytecode-object/bytecode_object19.script
- + testsuite/tests/driver/bytecode-object/bytecode_object19.stdout
- + testsuite/tests/driver/bytecode-object/bytecode_object20.stdout
- + testsuite/tests/driver/bytecode-object/bytecode_object21.stderr
- + testsuite/tests/driver/bytecode-object/bytecode_object21.stdout
- + testsuite/tests/driver/bytecode-object/bytecode_object23.stdout
- + testsuite/tests/driver/bytecode-object/bytecode_object24.stdout
- + testsuite/tests/driver/bytecode-object/bytecode_object25.stdout
- + testsuite/tests/driver/bytecode-object/bytecode_object4.stdout
- + testsuite/tests/driver/bytecode-object/bytecode_object5.stdout
- + testsuite/tests/driver/bytecode-object/bytecode_object6.stdout
- testsuite/tests/driver/dynamicToo/dynamicToo001/Makefile
- testsuite/tests/driver/fat-iface/T22405/T22405.stdout
- testsuite/tests/driver/fat-iface/T22405/T22405b.stdout
- testsuite/tests/driver/fat-iface/fat011.stderr
- testsuite/tests/driver/fat-iface/fat014.script
- testsuite/tests/driver/implicit-dyn-too/Makefile
- testsuite/tests/driver/multipleHomeUnits/all.T
- testsuite/tests/driver/multipleHomeUnits/multipleHomeUnits_recomp_th.stdout
- + testsuite/tests/driver/recomp022/A1.hs
- + testsuite/tests/driver/recomp022/A2.hs
- + testsuite/tests/driver/recomp022/A3.hs
- + testsuite/tests/driver/recomp022/B.hs
- + testsuite/tests/driver/recomp022/C.hs
- + testsuite/tests/driver/recomp022/Makefile
- + testsuite/tests/driver/recomp022/all.T
- + testsuite/tests/driver/recomp022/recomp022a.stdout
- + testsuite/tests/driver/recomp022/recomp022b.stdout
- testsuite/tests/ghc-api/T10052/T10052.hs
- testsuite/tests/ghc-api/T10942.hs
- testsuite/tests/ghc-api/T8639_api.hs
- testsuite/tests/ghc-api/annotations-literals/literals.hs
- testsuite/tests/ghc-api/apirecomp001/myghc.hs
- testsuite/tests/ghc-e/should_fail/T9930fail.stderr
- testsuite/tests/ghc-e/should_fail/all.T
- testsuite/tests/ghci.debugger/scripts/break022/all.T
- testsuite/tests/ghci.debugger/scripts/break022/break022.script
- testsuite/tests/ghci.debugger/scripts/break023/all.T
- testsuite/tests/ghci.debugger/scripts/break023/break023.script
- − testsuite/tests/ghci/linking/T11531.stderr
- testsuite/tests/ghci/linking/dyn/T3372.hs
- testsuite/tests/ghci/prog001/prog001.T
- testsuite/tests/ghci/prog001/prog001.script
- testsuite/tests/ghci/prog002/prog002.T
- testsuite/tests/ghci/prog002/prog002.script
- testsuite/tests/ghci/prog003/prog003.T
- testsuite/tests/ghci/prog003/prog003.script
- testsuite/tests/ghci/prog005/prog005.T
- testsuite/tests/ghci/prog005/prog005.script
- testsuite/tests/ghci/prog010/all.T
- testsuite/tests/ghci/prog010/ghci.prog010.script
- testsuite/tests/ghci/prog012/all.T
- testsuite/tests/ghci/prog012/prog012.script
- testsuite/tests/ghci/prog018/prog018.script
- testsuite/tests/ghci/recompTHghci/all.T
- testsuite/tests/ghci/recompTHghci/recompTHghci.script
- testsuite/tests/ghci/scripts/T13869.script
- testsuite/tests/ghci/scripts/T13997.script
- testsuite/tests/ghci/scripts/T17669.script
- testsuite/tests/ghci/scripts/T18330.script
- testsuite/tests/ghci/scripts/T18330.stdout
- testsuite/tests/ghci/scripts/T1914.script
- testsuite/tests/ghci/scripts/T20217.script
- testsuite/tests/ghci/scripts/T20587.script
- testsuite/tests/ghci/scripts/T6105.script
- testsuite/tests/ghci/scripts/T6106.script
- testsuite/tests/ghci/scripts/T8042.script
- testsuite/tests/ghci/scripts/T8042recomp.script
- testsuite/tests/ghci/scripts/T8353.script
- testsuite/tests/ghci/scripts/all.T
- testsuite/tests/ghci/scripts/ghci038.script
- testsuite/tests/ghci/scripts/ghci058.script
- testsuite/tests/ghci/scripts/ghci063.script
- − testsuite/tests/ghci/shell.hs
- testsuite/tests/ghci/should_run/Makefile
- testsuite/tests/ghci/should_run/PackedDataCon/packeddatacon.T
- testsuite/tests/ghci/should_run/UnboxedTuples/unboxedtuples.T
- testsuite/tests/ghci/should_run/UnliftedDataTypeInterp/unlifteddatatypeinterp.T
- testsuite/tests/hpc/Makefile
- testsuite/tests/hpc/T17073.stdout → testsuite/tests/hpc/T17073a.stdout
- + testsuite/tests/hpc/T17073b.stdout
- testsuite/tests/hpc/T20568.stdout → testsuite/tests/hpc/T20568a.stdout
- + testsuite/tests/hpc/T20568b.stdout
- testsuite/tests/hpc/all.T
- testsuite/tests/hpc/fork/Makefile
- testsuite/tests/hpc/function/Makefile
- testsuite/tests/hpc/function/test.T
- + testsuite/tests/hpc/function/tough1.stderr
- + testsuite/tests/hpc/function/tough1.stdout
- testsuite/tests/hpc/function2/test.T
- + testsuite/tests/hpc/function2/tough3.script
- + testsuite/tests/hpc/ghc_ghci/BytecodeMain.hs
- testsuite/tests/hpc/ghc_ghci/Makefile
- + testsuite/tests/hpc/ghc_ghci/hpc_ghc_ghci_bytecode.stdout
- + testsuite/tests/hpc/ghc_ghci/hpc_ghci01.stdout
- + testsuite/tests/hpc/ghc_ghci/hpc_ghci02.stdout
- testsuite/tests/hpc/ghc_ghci/test.T
- testsuite/tests/hpc/simple/Makefile
- + testsuite/tests/hpc/simple/hpc002.hs
- + testsuite/tests/hpc/simple/hpc002.stdout
- + testsuite/tests/hpc/simple/hpc003.hs
- + testsuite/tests/hpc/simple/hpc003.script
- + testsuite/tests/hpc/simple/hpc003.stdout
- testsuite/tests/hpc/simple/test.T
- testsuite/tests/indexed-types/should_fail/T14887.stderr
- testsuite/tests/linear/should_fail/T19361.stderr
- testsuite/tests/perf/compiler/Makefile
- testsuite/tests/perf/compiler/MultiLayerModulesDefsGhci.script
- + testsuite/tests/perf/compiler/MultiLayerModulesDefsGhciWithBytecodeFiles.script
- testsuite/tests/perf/compiler/all.T
- testsuite/tests/roles/should_compile/Roles13.stderr
- testsuite/tests/rts/T13676.script
- testsuite/tests/rts/linker/T2615.hs
- testsuite/tests/simplCore/should_compile/OpaqueNoCastWW.stderr
- testsuite/tests/simplCore/should_compile/T17673.stderr
- testsuite/tests/simplCore/should_compile/T18078.stderr
- testsuite/tests/simplCore/should_compile/T18995.stderr
- testsuite/tests/simplCore/should_compile/T19890.stderr
- testsuite/tests/simplCore/should_compile/T21948.stderr
- testsuite/tests/simplCore/should_compile/T21960.stderr
- testsuite/tests/simplCore/should_compile/T24808.stderr
- − testsuite/tests/simplCore/should_compile/T25713.stderr
- testsuite/tests/simplCore/should_compile/T4201.stdout
- testsuite/tests/simplCore/should_compile/T8331.stderr
- testsuite/tests/simplStg/should_compile/T22840.stderr
- testsuite/tests/typecheck/no_skolem_info/T20232.stderr
- testsuite/tests/typecheck/should_fail/T11672.stderr
- testsuite/tests/typecheck/should_fail/T12373.stderr
- testsuite/tests/typecheck/should_fail/T15807.stderr
- testsuite/tests/typecheck/should_fail/T16074.stderr
- testsuite/tests/typecheck/should_fail/T18357a.stderr
- testsuite/tests/typecheck/should_fail/T19627.stderr
- testsuite/tests/typecheck/should_fail/T21530a.stderr
- testsuite/tests/typecheck/should_fail/VisFlag1.stderr
- utils/check-exact/Parsers.hs
- utils/check-exact/Preprocess.hs
- utils/deriveConstants/Main.hs
- utils/ghc-pkg/Main.hs
- utils/iserv/iserv.cabal.in
- utils/jsffi/dyld.mjs
The diff was not included because it is too large.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/d78ff1a0bb83ce7c9553b9ad9598ba…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/d78ff1a0bb83ce7c9553b9ad9598ba…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][wip/dcoutts/io-manager-io-primop-exceptions] 24 commits: Never make an absent filler for a constraint type
by Duncan Coutts (@dcoutts) 04 Sep '26
by Duncan Coutts (@dcoutts) 04 Sep '26
04 Sep '26
Duncan Coutts pushed to branch wip/dcoutts/io-manager-io-primop-exceptions at Glasgow Haskell Compiler / GHC
Commits:
7bf546fc by Simon Peyton Jones at 2026-08-31T23:48:53-04:00
Never make an absent filler for a constraint type
mkAbsentFiller used isTerminatingType to decide, but that is not enough.
Consider
class Eq a => UC a where {}
let u :: UC Int -- UC Int is a "non-terminating type"
u = error "Absent"
let e :: Eq Int -- Eq Int is a "terminating type"
e = $p1UC u
We clearly must not make a filler for `e`, because we speculatively
evaluate it. But speculatively evaluating `e` forces `u`, so we must not
make one for `u` either.
Asking isDictTy instead is not enough either, because it does not catch a
constraint hidden behind an unreduced type family application:
type family F a :: Constraint
type instance F W = TC W
a :: F W => Int -> Int -- (F W) argument is absent
Oops! Entered absent arg Arg: irred
Type: F W
So play safe and use isPredTy: never make an absent filler for any
constraint-kinded type.
Fixes #27627
- - - - -
5f474953 by Zubin Duggal at 2026-08-31T23:48:53-04:00
Add tests for absent fillers at dictionary types
T27627 a unary class whose superclass is a non-unary class
T27627a ...whose superclass is a Constraint-kinded type family
T27627b ...whose superclass is a quantified constraint
T27627c a unary class applied to itself, (UC (UC (TC a)))
T27627e a (forall b. P b) dictionary that loops
- - - - -
cd5c6bcc by Zubin Duggal at 2026-08-31T23:48:53-04:00
An abstract TyCon may hide a unary class
A class declared in an hs-boot file is an AbstractTyCon inside the
module loop, and compiling the real declaration may reveal it to be a
UnaryClassTyCon.
- isTerminatingType returned True for such AbstractTyCons
- IfaceToCore set the unary flag to False in the DFunId
So we could end up speculating bottom dictionaries because inside a module
loop we see an UnaryClassTyCon as an AbstractTyCon
Use isTerminatingTyCon, which returns False for an abstract TyCon.
The Bool in DFunId is now a cache for isTerminatingTyCon, set in
mkDFunIdDetails.
Fixes #27704
- - - - -
abfc224a by Zubin Duggal at 2026-08-31T23:48:53-04:00
Specialise: don't replace dead args with absent fillers
specHeader decides an argument is dead by calling isDeadBinder on a binder of
the /optimised RHS/, then applies the filler to the /stable unfolding/
template instead. The two may differ, so the argument can be dead in
the RHS and not in the template.
The specialised function's unfolding then has an absent filler, and any call
site that inlines it evaluates the error thunk.
Dropping dead args in the specialiser is rarely worth it, to quote Simon,
"The later worker/wrapper pass will pick up the dead arg later if it is really dead. Keeps the specialiser simpler."
So instead of trying to check if the arg really is dead in the stable unfolding,
just drop the logic for dropping dead args in the specialiser altogeher.
Fixes #27703
- - - - -
1557fd1c by Zubin Duggal at 2026-08-31T23:48:53-04:00
CorePrep: don't speculate a call across an hs-boot edge
We take care not to evaluate things that might be bottom, like a
looping dictionary group, but our analysis is defeated by boot files.
We only track recursion within a module, so two dictionaries that
depend on each other across a module loop each look non-recursive, and
we might speculate them.
Any recursion we cannot see must cross an hs-boot edge, so refuse to
speculate calls that cross one.
Fixes #27717
- - - - -
4117e5ae by Wolfgang Jeltsch at 2026-08-31T23:49:35-04:00
Incorporate the `rethrowSTM` reexport into the `stm` submodule
- - - - -
4bfbf5c8 by ARATA Mizuki at 2026-09-01T18:55:08-04:00
testsuite: Fix out-of-bounds access in T3586
unsafeRead and unsafeWrite use 0-based index.
Looking at #3586, the expected output seems to be 2.8e8.
Addresses #27596
- - - - -
3f9db6d4 by ARATA Mizuki at 2026-09-01T18:55:08-04:00
testsuite: Fix out-of-bounds access in T21305
writeInt64Array# takes an index measured in units of Int64 elements.
Fixes #27596
- - - - -
44d7788f by Simon Peyton Jones at 2026-09-01T18:55:53-04:00
Fix buglet in INLINE-arity calculation for pattern synonyms
This fixes #27744.
The buglet was accidentally introduced by
commit 3a0f9a51c1dacc474c7fd128082edd8bf4081256
Author: Simon Peyton Jones <simon.peytonjones(a)gmail.com>
Date: Sat Aug 1 00:13:02 2026 +0100
Fix three bugs related to required type args and INLINE pragmas
I failed to find all the calls to `addInlinePragArity`!
- - - - -
df058f1d by Simon Jakobi at 2026-09-02T07:18:06-04:00
testsuite: Migrate perf tests off collect_compiler_stats('all')
The 'all' metric argument applies a single tolerance to bytes
allocated, max_bytes_used and peak_megabytes_allocated, although their
noise profiles are incompatible (#27653): allocations are nearly
deterministic, residency needs 10-20%, and peak is quantized to 1 MB.
Any single tolerance is too tight for one metric or too slack for
another. This migrates the remaining users of 'all' (and of the 'all'
default) to explicit per-metric collection, ahead of removing 'all'
from the driver.
peak_megabytes_allocated is dropped everywhere: its 1 MB granularity
makes tight relative windows meaningless (#27613), and it is sensitive
to GC timing. In #27489 it drifted by -5.3% while max_bytes_used moved
by less than 0.1%. Where a test guards a memory property,
max_bytes_used covers it at byte granularity.
Where the motivating ticket was about compile-time memory
(T11545, T15304, T26425), residency remains gated via max_bytes_used,
now with a residency-appropriate tolerance.
max_bytes_used is dropped where residency was only ever an accident of
'all':
* T15630, T15630a, T20261: the underlying tickets (#15630, #20261)
contain no memory data at all. One is a simplifier-ticks blowup and
the other is stated entirely in allocation numbers, so the 20%
window never had teeth.
* T21839c: #21839's measurements show residency essentially flat
(+0.16%) while allocations moved +7%, so allocations are the
discriminating metric. They are already gated at 1% via
collect_compiler_runtime. The ghc/max gate had previously broken CI
spuriously (9fd11585eb widened it from 1% to 10% for that reason).
Allocation tolerances are tightened to the testsuite's conventional 2%
where 'all' previously left them at 10-20%.
Assisted-by: Claude Fable 5
- - - - -
72dd2432 by Simon Jakobi at 2026-09-02T07:18:06-04:00
testsuite: Remove the 'all' metric argument of collect_stats
'all' gated bytes allocated, max_bytes_used and peak_megabytes_allocated
at a single tolerance, although their noise profiles are incompatible,
making such tests either flaky or toothless (#27653).
Closes #27653.
Assisted-by: Claude Fable 5
- - - - -
2228cb30 by Simon Jakobi at 2026-09-02T07:18:06-04:00
testsuite: Make the deviation argument of collect_stats mandatory
Almost every caller passes an explicit tolerance matched to the
metric's noise profile, and the silent 20% default is far slacker than
'bytes allocated' merits. Only two tests relied on it. They now state
their tolerance explicitly:
large-project gets 10%, in line with other large compile-time tests.
T9848 gets 2%: its metric is byte-for-byte deterministic across CI
jobs and platforms of a given test_env, has drifted only about 2.5%
since 2015, and the fusion failure it guards against would show up as
a roughly +30000% jump.
Assisted-by: Claude Fable 5
- - - - -
56291fc5 by Cheng Shao at 2026-09-02T07:18:45-04:00
autoconf/ghc-toolchain: bump llvm upper bound to support llvm 23
This commit bumps llvm upper bound to support llvm 23.
- - - - -
20eb3f41 by Cheng Shao at 2026-09-02T07:18:45-04:00
rts: fix compilation issues with clang 23
clang 23 has broadened `-Wall`/`-Wextra` ranges, exposing some minor
issues in the rts when building with validate flavours:
- Unused locals
- `#pragma GCC diagnostic pop` mismatch
This commit fixes those.
- - - - -
9e7739ab by Simon Jakobi at 2026-09-03T13:45:43-04:00
Add -Wimplicit-field-strictness (#16836)
This opt-in warning fires when a data constructor field lacks an
explicit strictness annotation (`!` or `~`). It complements the
LazyFieldAnnotations extension (4762a8bf30f) from GHC proposal 752,
which makes `~` annotations available for this purpose.
Deciding which fields to report requires their levity, so the check
runs after typechecking. To keep the noise down, the diagnostic is
emitted once per data declaration, grouped by constructor.
Closes #16836.
Assisted-by: Claude Fable 5
- - - - -
6a44d591 by mangoiv at 2026-09-03T13:46:22-04:00
simplifier: remove a bogus `assert` in `rebuild_app'` in `GHC.CoreToStg.Prep.cpeApp`.
Prior to this commit
commit 08bc245be70d95801bc1138804ed1de9474fbdc0
Author: sheaf <sam.derbyshire(a)gmail.com>
Date: Sat Feb 28 16:30:43 2026 +0100
Clean up join points, casts & ticks
This commit shores up the logic dealing with casts and ticks occurring
in between a join point binding and a jump
any `PlaceRuntime` ticks we observed were profiling ticks, even though that
isn't necessary.
The more liberal rules in the above commit allow e.g. breakpoint ticks
(which are valid PlaceRuntime ticks) to legitimately appear in an argument
position.
Fixes #27556
- - - - -
c87cbace by Duncan Coutts at 2026-09-04T10:59:31+01:00
Reorder cmm decls in HeapStackCheck for a better logical grouping
And put more section headers in to deliniate the groups.
We're about to add more here, so better to organise it first.
- - - - -
31ce5f0a by Duncan Coutts at 2026-09-04T10:59:31+01:00
Add raisePrimIOException and add it to RTS<->ghc-internal API
The raisePrimIOException is a new helper function that I/O primops will
use to help them report I/O errors. This is implemented in Haskell
(since that's a lot easier), but has a calling convention that is
easy(ish) to use from Cmm in the I/O primops.
So we add it to the RTS API struct, and since we'll use it from Cmm we
also need a field accessor macro for cmm (in deriveConstants).
See the Note about how we cannot have nice things due to async
exceptions and thunks preventing us from using catch.
- - - - -
a73f2f16 by Duncan Coutts at 2026-09-04T10:59:31+01:00
Add new blocking functions for I/O primops
See the long Note.
The point is, it will allow us to report synchronous exceptions from I/O
primops, and do so much more flexibly.
Previously the I/O managers could only report async exceptions and only
nullary exceptions. This was OK historically, but no good as we add
more I/O managers and expand the range of I/O operations we support.
- - - - -
ef18a91e by Duncan Coutts at 2026-09-04T10:59:31+01:00
Change the encoding of results from the I/O manager to I/O primops
Previously we just had async continue or heap overflow.
We now extend what we can report with synchronous success, and
synchronous failure with an errno.
See Note [Encoding of result of I/O manager operations]
We don't use these two new cases yet, but we will. In particular an
epoll I/O manager needs to be able to report synchronous success or
failure for waitRead#/waitWrite#.
- - - - -
a361b447 by Duncan Coutts at 2026-09-04T10:59:31+01:00
Switch waitRead/Write# to use new blocking return frames
and update the I/O managers to set the result before resuming the
blocked threads.
This makes it possible for I/O managers to report synchronous exceptions
from the I/O primops, but that will be done in a subsequent commit.
- - - - -
c66e7dd9 by Duncan Coutts at 2026-09-04T10:59:31+01:00
Switch Poll and Select I/O managers to report sync exceptions
rather than using raiseAsync with blockedOnBadFD_closure.
This uses the new mechanism in the blocking frame return code to report
synchronous exceptions.
- - - - -
57e8c0ff by Duncan Coutts at 2026-09-04T10:59:31+01:00
Remove now-unused blockedOnBadFD
It was previously thrown by the select and poll I/O managers, but now
they use raisePrimIOException (with an EBADF errno).
- - - - -
1e353093 by Duncan Coutts at 2026-09-04T10:59:32+01:00
Improve the docs for delay# waitRead# and waitWrite#
Document that the waitRead/Write# can throw exceptions (this was true
before too), and that all of them are async exception cancellation
points.
- - - - -
133 changed files:
- + changelog.d/27556
- + changelog.d/27627
- + changelog.d/27703
- + changelog.d/27704
- + changelog.d/27717
- + changelog.d/T27744
- + changelog.d/implicit-field-strictness-warning
- changelog.d/lazy-field-annotations
- + changelog.d/llvm-23
- compiler/GHC/Builtin/primops.txt.pp
- compiler/GHC/Core.hs
- compiler/GHC/Core/Opt/Specialise.hs
- compiler/GHC/Core/Opt/WorkWrap/Utils.hs
- compiler/GHC/Core/TyCon.hs
- compiler/GHC/Core/Type.hs
- compiler/GHC/Core/Utils.hs
- compiler/GHC/CoreToStg/Prep.hs
- compiler/GHC/Driver/Flags.hs
- compiler/GHC/Driver/Session.hs
- compiler/GHC/IfaceToCore.hs
- compiler/GHC/Tc/Errors/Ppr.hs
- compiler/GHC/Tc/Errors/Types.hs
- compiler/GHC/Tc/TyCl.hs
- compiler/GHC/Tc/TyCl/PatSyn.hs
- compiler/GHC/Types/Demand.hs
- compiler/GHC/Types/Error/Codes.hs
- compiler/GHC/Types/Hint.hs
- compiler/GHC/Types/Hint/Ppr.hs
- compiler/GHC/Types/Id/Info.hs
- compiler/GHC/Types/Id/Make.hs
- compiler/GHC/Types/Literal.hs
- compiler/GHC/Types/Tickish.hs
- configure.ac
- docs/users_guide/exts/strict.rst
- docs/users_guide/using-warnings.rst
- libraries/base/tests/all.T
- libraries/ghc-internal/include/RtsIfaceSymbols.h
- libraries/ghc-internal/src/GHC/Internal/Conc/IO.hs
- libraries/ghc-internal/src/GHC/Internal/Event/Thread.hs
- libraries/stm
- rts/HeapStackCheck.cmm
- rts/IOManager.c
- rts/IOManager.h
- rts/IOManagerInternals.h
- rts/Prelude.h
- rts/PrimOps.cmm
- rts/ProfHeap.c
- rts/eventlog/EventLog.c
- rts/include/rts/RtsToHsIface.h
- rts/include/stg/MiscClosures.h
- rts/posix/Poll.c
- rts/posix/Poll.h
- rts/posix/Select.c
- rts/prim/atomic.c
- testsuite/driver/README.md
- testsuite/driver/testlib.py
- + testsuite/tests/core-to-stg/T27627/Callee.hs
- + testsuite/tests/core-to-stg/T27627/Caller.hs
- + testsuite/tests/core-to-stg/T27627/Main.hs
- + testsuite/tests/core-to-stg/T27627/T27627.stdout
- + testsuite/tests/core-to-stg/T27627/all.T
- + testsuite/tests/core-to-stg/T27627a/Callee.hs
- + testsuite/tests/core-to-stg/T27627a/Caller.hs
- + testsuite/tests/core-to-stg/T27627a/Main.hs
- + testsuite/tests/core-to-stg/T27627a/T27627a.stdout
- + testsuite/tests/core-to-stg/T27627a/all.T
- + testsuite/tests/core-to-stg/T27627b/Callee.hs
- + testsuite/tests/core-to-stg/T27627b/Caller.hs
- + testsuite/tests/core-to-stg/T27627b/Main.hs
- + testsuite/tests/core-to-stg/T27627b/T27627b.stdout
- + testsuite/tests/core-to-stg/T27627b/all.T
- + testsuite/tests/core-to-stg/T27627c/Callee.hs
- + testsuite/tests/core-to-stg/T27627c/Caller.hs
- + testsuite/tests/core-to-stg/T27627c/Main.hs
- + testsuite/tests/core-to-stg/T27627c/T27627c.stdout
- + testsuite/tests/core-to-stg/T27627c/all.T
- + testsuite/tests/core-to-stg/T27627e.hs
- + testsuite/tests/core-to-stg/T27627e.stdout
- + testsuite/tests/core-to-stg/T27627f/Callee.hs
- + testsuite/tests/core-to-stg/T27627f/Caller.hs
- + testsuite/tests/core-to-stg/T27627f/Inst.hs
- + testsuite/tests/core-to-stg/T27627f/Main.hs
- + testsuite/tests/core-to-stg/T27627f/T27627f.stdout
- + testsuite/tests/core-to-stg/T27627f/all.T
- + testsuite/tests/core-to-stg/T27704/Callee.hs
- + testsuite/tests/core-to-stg/T27704/Callee.hs-boot
- + testsuite/tests/core-to-stg/T27704/Main.hs
- + testsuite/tests/core-to-stg/T27704/Mid.hs
- + testsuite/tests/core-to-stg/T27704/T27704.stdout
- + testsuite/tests/core-to-stg/T27704/all.T
- + testsuite/tests/core-to-stg/T27704a/Callee.hs
- + testsuite/tests/core-to-stg/T27704a/Callee.hs-boot
- + testsuite/tests/core-to-stg/T27704a/Main.hs
- + testsuite/tests/core-to-stg/T27704a/Mid.hs
- + testsuite/tests/core-to-stg/T27704a/T27704a.stdout
- + testsuite/tests/core-to-stg/T27704a/all.T
- + testsuite/tests/core-to-stg/T27717/Callee.hs
- + testsuite/tests/core-to-stg/T27717/Callee.hs-boot
- + testsuite/tests/core-to-stg/T27717/Main.hs
- + testsuite/tests/core-to-stg/T27717/Mid.hs
- + testsuite/tests/core-to-stg/T27717/T27717.stdout
- + testsuite/tests/core-to-stg/T27717/Ty.hs
- + testsuite/tests/core-to-stg/T27717/all.T
- testsuite/tests/core-to-stg/all.T
- testsuite/tests/ffi/should_run/T21305.hs
- + testsuite/tests/patsyn/should_compile/T27744.hs
- testsuite/tests/patsyn/should_compile/all.T
- testsuite/tests/perf/compiler/all.T
- testsuite/tests/perf/compiler/large-project/all.T
- testsuite/tests/perf/should_run/T3586.hs
- testsuite/tests/perf/should_run/T3586.stdout
- testsuite/tests/perf/space_leaks/all.T
- testsuite/tests/simplCore/should_compile/T17966.stderr
- + testsuite/tests/simplCore/should_compile/T27556.hs
- + testsuite/tests/simplCore/should_compile/T27556.script
- testsuite/tests/simplCore/should_compile/T7785.stderr
- testsuite/tests/simplCore/should_compile/all.T
- testsuite/tests/simplCore/should_compile/spec004.hs
- testsuite/tests/simplCore/should_compile/spec004.stderr
- + testsuite/tests/simplCore/should_run/T27703/Lib.hs
- + testsuite/tests/simplCore/should_run/T27703/Main.hs
- + testsuite/tests/simplCore/should_run/T27703/T27703.stdout
- + testsuite/tests/simplCore/should_run/T27703/all.T
- + testsuite/tests/warnings/should_compile/T16836a.hs
- + testsuite/tests/warnings/should_compile/T16836a.stderr
- + testsuite/tests/warnings/should_compile/T16836b.hs
- + testsuite/tests/warnings/should_compile/T16836c.hs
- + testsuite/tests/warnings/should_compile/T16836c.stderr
- + testsuite/tests/warnings/should_compile/T16836d.hs
- + testsuite/tests/warnings/should_compile/T16836d.stderr
- testsuite/tests/warnings/should_compile/all.T
- utils/deriveConstants/Main.hs
- utils/ghc-toolchain/src/GHC/Toolchain/Program.hs
The diff was not included because it is too large.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/dddc9414ee0f0fbeb4f1e58c9625ec…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/dddc9414ee0f0fbeb4f1e58c9625ec…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0