[Git][ghc/ghc][wip/T26722] 8 commits: compiler: add targetHasRTSWays function
Simon Peyton Jones pushed to branch wip/T26722 at Glasgow Haskell Compiler / GHC Commits: 69e0ab59 by Cheng Shao at 2026-01-06T19:37:56-05:00 compiler: add targetHasRTSWays function This commit adds a `targetHasRTSWays` util function in `GHC.Driver.Session` to query if the target RTS has a given Ways (e.g. WayThreaded). - - - - - 25a0ab94 by Cheng Shao at 2026-01-06T19:37:56-05:00 compiler: link on-demand external interpreter with threaded RTS This commit makes the compiler link the on-demand external interpreter program with threaded RTS if it is available in the target RTS ways. This is a better default than the previous single-threaded RTS, and it enables the external interpreter to benefit from parallelism when deserializing CreateBCOs messages. - - - - - 92404a2b by Cheng Shao at 2026-01-06T19:37:56-05:00 hadrian: link iserv with threaded RTS This commit makes hadrian link iserv with threaded RTS if it's available in the RTS ways. Also cleans up the iserv main C program which can be replaced by the `-fkeep-cafs` link-time option. - - - - - a20542d2 by Cheng Shao at 2026-01-06T19:38:38-05:00 ghc-internal: remove unused GMP macros This patch removes unused GMP related macros from `ghc-internal`. The in-tree GMP version was hard coded and outdated, but it was not used anywhere anyway. - - - - - 4079dcd6 by Cheng Shao at 2026-01-06T19:38:38-05:00 hadrian: fix in-tree gmp configure error on newer c compilers Building in-tree gmp on newer c compilers that default to c23 fails at configure stage, this patch fixes it, see added comment for explanation. - - - - - 414d1fe1 by Cheng Shao at 2026-01-06T19:39:20-05:00 compiler: fix LLVM backend pdep/pext handling for i386 target This patch fixes LLVM backend's pdep/pext handling for i386 target, and also removes non-existent 128/256/512 bit hs_pdep/hs_pext callees. See amended note for more explanation. Fixes #26450. Co-authored-by: Codex <codex@openai.com> - - - - - c7f6fba3 by Cheng Shao at 2026-01-06T19:39:20-05:00 ci: remove allow_failure flag for i386 alpine job The LLVM codegen issue for i386 has been fixed, and the i386 alpine job should pass now. This commit removes the allow_failure flag so that other i386 regressions in the future are signaled more timely. - - - - - c0b944c2 by Simon Peyton Jones at 2026-01-07T12:50:35+00:00 Add evals for strict data-con args in worker-functions This fixes #26722, by adding an eval in a worker for arguments of strict data constructors, even if the function body uses them strictly. See (WIS1) in Note [Which Ids should be strictified] - - - - - 25 changed files: - .gitlab/generate-ci/gen_ci.hs - .gitlab/jobs.yaml - compiler/GHC.hs - compiler/GHC/CmmToLlvm/CodeGen.hs - compiler/GHC/Core/Opt/SpecConstr.hs - compiler/GHC/Core/Tidy.hs - compiler/GHC/Core/Utils.hs - compiler/GHC/Driver/Session.hs - compiler/GHC/Runtime/Interpreter/C.hs - compiler/GHC/Runtime/Interpreter/Init.hs - compiler/GHC/Stg/EnforceEpt.hs - compiler/GHC/StgToCmm/Closure.hs - compiler/GHC/StgToCmm/Expr.hs - hadrian/src/Flavour.hs - hadrian/src/Packages.hs - hadrian/src/Rules/Gmp.hs - hadrian/src/Settings/Packages.hs - libraries/ghc-internal/configure.ac - libraries/ghc-internal/include/HsIntegerGmp.h.in - testsuite/tests/simplCore/should_compile/T18013.stderr - + testsuite/tests/simplCore/should_compile/T26722.hs - + testsuite/tests/simplCore/should_compile/T26722.stderr - testsuite/tests/simplCore/should_compile/all.T - − utils/iserv/cbits/iservmain.c - utils/iserv/iserv.cabal.in Changes: ===================================== .gitlab/generate-ci/gen_ci.hs ===================================== @@ -1250,7 +1250,7 @@ alpine_x86 = , fullyStaticBrokenTests (disableValidate (allowFailureGroup (standardBuildsWithConfig Amd64 (Linux Alpine312) staticNativeInt))) -- Dynamically linked build, suitable for building your own static executables on alpine , disableValidate (standardBuildsWithConfig Amd64 (Linux Alpine323) (splitSectionsBroken vanilla)) - , allowFailureGroup (standardBuildsWithConfig I386 (Linux Alpine323) (splitSectionsBroken vanilla)) + , standardBuildsWithConfig I386 (Linux Alpine323) (splitSectionsBroken vanilla) ] where -- ghcilink002 broken due to #17869 ===================================== .gitlab/jobs.yaml ===================================== @@ -484,7 +484,7 @@ ".gitlab/ci.sh clean", "cat ci_timings.txt" ], - "allow_failure": true, + "allow_failure": false, "artifacts": { "expire_in": "2 weeks", "paths": [ @@ -1155,7 +1155,7 @@ ".gitlab/ci.sh clean", "cat ci_timings.txt" ], - "allow_failure": true, + "allow_failure": false, "artifacts": { "expire_in": "8 weeks", "paths": [ @@ -4034,7 +4034,7 @@ ".gitlab/ci.sh clean", "cat ci_timings.txt" ], - "allow_failure": true, + "allow_failure": false, "artifacts": { "expire_in": "1 year", "paths": [ ===================================== compiler/GHC.hs ===================================== @@ -719,7 +719,7 @@ setTopSessionDynFlags dflags = do { interpCreateProcess = createIservProcessHook (hsc_hooks hsc_env) } - interp <- liftIO $ initInterpreter tmpfs logger platform finder_cache unit_env interp_opts + interp <- liftIO $ initInterpreter dflags tmpfs logger platform finder_cache unit_env interp_opts modifySession $ \h -> hscSetFlags dflags h{ hsc_IC = (hsc_IC h){ ic_dflags = dflags } ===================================== compiler/GHC/CmmToLlvm/CodeGen.hs ===================================== @@ -248,6 +248,14 @@ Since x86 PDep/PExt instructions only exist for 32/64 bit widths we use the 32bit variant to compute the 8/16bit primops. To do so we extend/truncate the argument/result around the call. + +Note that the 64-bit intrinsics (`llvm.x86.bmi.pdep.64` and +`llvm.x86.bmi.pext.64`) are only legal on 64-bit x86 targets, not on +i386. Therefore on i386 we must fall back to the runtime helper +(`hs_pdep64`/`hs_pext64`) for the 64-bit primops. + +See https://github.com/llvm/llvm-project/issues/172857 for upstream +discussion about portable pdep/pext intrinsics. -} genCall (PrimTarget op@(MO_Pdep w)) [dst] args = do cfg <- getConfig @@ -970,36 +978,34 @@ cmmPrimOpFunctions mop = do W8 -> fsLit "llvm.x86.bmi.pdep.32" W16 -> fsLit "llvm.x86.bmi.pdep.32" W32 -> fsLit "llvm.x86.bmi.pdep.32" - W64 -> fsLit "llvm.x86.bmi.pdep.64" - W128 -> fsLit "llvm.x86.bmi.pdep.128" - W256 -> fsLit "llvm.x86.bmi.pdep.256" - W512 -> fsLit "llvm.x86.bmi.pdep.512" + W64 + | is32bit -> fsLit "hs_pdep64" + | otherwise -> fsLit "llvm.x86.bmi.pdep.64" + -- LLVM only provides x86 PDep/PExt intrinsics for 32/64 bits + _ -> unsupported | otherwise -> case w of W8 -> fsLit "hs_pdep8" W16 -> fsLit "hs_pdep16" W32 -> fsLit "hs_pdep32" W64 -> fsLit "hs_pdep64" - W128 -> fsLit "hs_pdep128" - W256 -> fsLit "hs_pdep256" - W512 -> fsLit "hs_pdep512" + _ -> unsupported MO_Pext w | isBmi2Enabled -> case w of -- See Note [LLVM PDep/PExt intrinsics] W8 -> fsLit "llvm.x86.bmi.pext.32" W16 -> fsLit "llvm.x86.bmi.pext.32" W32 -> fsLit "llvm.x86.bmi.pext.32" - W64 -> fsLit "llvm.x86.bmi.pext.64" - W128 -> fsLit "llvm.x86.bmi.pext.128" - W256 -> fsLit "llvm.x86.bmi.pext.256" - W512 -> fsLit "llvm.x86.bmi.pext.512" + W64 + | is32bit -> fsLit "hs_pext64" + | otherwise -> fsLit "llvm.x86.bmi.pext.64" + -- LLVM only provides x86 PDep/PExt intrinsics for 32/64 bits + _ -> unsupported | otherwise -> case w of W8 -> fsLit "hs_pext8" W16 -> fsLit "hs_pext16" W32 -> fsLit "hs_pext32" W64 -> fsLit "hs_pext64" - W128 -> fsLit "hs_pext128" - W256 -> fsLit "hs_pext256" - W512 -> fsLit "hs_pext512" + _ -> unsupported MO_AddIntC w -> case w of W8 -> fsLit "llvm.sadd.with.overflow.i8" ===================================== compiler/GHC/Core/Opt/SpecConstr.hs ===================================== @@ -2062,7 +2062,7 @@ mkSeqs seqees res_ty rhs = addEval :: Var -> CoreExpr -> CoreExpr addEval arg_id rhs -- Argument representing strict field and it's worth passing via cbv - | shouldStrictifyIdForCbv arg_id + | wantCbvForId arg_id = Case (Var arg_id) (localiseId arg_id) -- See (SCF1) in Note [SpecConstr and strict fields] res_ty ===================================== compiler/GHC/Core/Tidy.hs ===================================== @@ -38,7 +38,7 @@ import GHC.Utils.Outputable import GHC.Types.RepType (typePrimRep) import GHC.Utils.Panic import GHC.Types.Basic (isMarkedCbv, CbvMark (..)) -import GHC.Core.Utils (shouldUseCbvForId) +import GHC.Core.Utils ( wantCbvForId ) {- ************************************************************************ @@ -197,7 +197,7 @@ computeCbvInfo fun_id rhs isSimplePrimRep _ = False mkMark arg - | not $ shouldUseCbvForId arg = NotMarkedCbv + | not $ wantCbvForId arg = NotMarkedCbv -- We can only safely use cbv for strict arguments | (isStrUsedDmd (idDemandInfo arg)) , not (isDeadEndId fun_id) = MarkedCbv ===================================== compiler/GHC/Core/Utils.hs ===================================== @@ -58,7 +58,7 @@ module GHC.Core.Utils ( isJoinBind, -- * Tag inference - mkStrictFieldSeqs, shouldStrictifyIdForCbv, shouldUseCbvForId, + mkStrictFieldSeqs, wantCbvForId, -- * unsafeEqualityProof isUnsafeEqualityCase, @@ -2902,18 +2902,24 @@ Here comes the tricky part: If we make $wloop strict in both x/y and we get: }; end Rec } -Here both x and y are known to be tagged in the function body since we pass strict worker args using unlifted cbv. -This means the seqs on x and y both become no-ops and compared to the first version the seq on `y` disappears at runtime. - -The downside is that the caller of $wfoo potentially has to evaluate `y` once if we can't prove it isn't already evaluated. -But y coming out of a strict field is in WHNF so safe to evaluated. And most of the time it will be properly tagged+evaluated -already at the call site because of the EPT Invariant! See Note [EPT enforcement] for more in this. -This makes GHC itself around 1% faster despite doing slightly more work! So this is generally quite good. - -We only apply this when we think there is a benefit in doing so however. There are a number of cases in which -it would be useless to insert an extra seq. ShouldStrictifyIdForCbv tries to identify these to avoid churn in the +Here both x and y are known to be tagged in the function body since we pass +strict worker args using unlifted cbv. This means the seqs on x and y both +become no-ops (via (EPT-codegen) in Not [EPT enforcement]) and, compared to the +first version, the seq on `y` disappears at runtime. + +The downside is that the caller of $wfoo potentially has to evaluate `y` once if +we can't prove it isn't already evaluated. But y coming out of a strict field +is in WHNF so safe to evaluated. And most of the time it will be properly +tagged+evaluated already at the call site because of the EPT Invariant! See Note +[EPT enforcement] for more in this. This makes GHC itself around 1% faster +despite doing slightly more work! So this is generally quite good. + +We only apply this when we think there is a benefit in doing so however. There +are a number of cases in which it would be useless to insert an extra +seq. `wantCbvForId` tries to identify these to avoid churn in the simplifier. See Note [Which Ids should be strictified] for details on this. -} + mkStrictFieldSeqs :: [(Id,StrictnessMark)] -> CoreExpr -> (CoreExpr) mkStrictFieldSeqs args rhs = foldr addEval rhs args @@ -2923,7 +2929,7 @@ mkStrictFieldSeqs args rhs = addEval (arg_id,arg_cbv) (rhs) -- Argument representing strict field. | isMarkedStrict arg_cbv - , shouldStrictifyIdForCbv arg_id + , wantCbvForId arg_id -- Make sure to remove unfoldings here to avoid the simplifier dropping those for OtherCon[] unfoldings. = Case (Var $! zapIdUnfolding arg_id) arg_id case_ty ([Alt DEFAULT [] rhs]) -- Normal argument @@ -2943,87 +2949,99 @@ There are multiple reasons why we might not want to insert a seq in the rhs to strictify a functions argument: 1) The argument doesn't exist at runtime. - -For zero width types (like Types) there is no benefit as we don't operate on them -at runtime at all. This includes things like void#, coercions and state tokens. + For zero width types (like Types) there is no benefit as we don't operate on them + at runtime at all. This includes things like void#, coercions and state tokens. 2) The argument is a unlifted type. - -If the argument is a unlifted type the calling convention already is explicitly -cbv. This means inserting a seq on this argument wouldn't do anything as the seq -would be a no-op *and* it wouldn't affect the calling convention. + If the argument is a unlifted type the calling convention already is explicitly + cbv. This means inserting a seq on this argument wouldn't do anything as the seq + would be a no-op *and* it wouldn't affect the calling convention. 3) The argument is absent. + If the argument is absent in the body there is no advantage to it being passed as + cbv to the function. The function won't ever look at it so we don't save any work. -If the argument is absent in the body there is no advantage to it being passed as -cbv to the function. The function won't ever look at it so we don't safe any work. - -This mostly happens for join point. For example we might have: - - data T = MkT ![Int] [Char] - f t = case t of MkT xs{strict} ys-> snd (xs,ys) - -and abstract the case alternative to: + This mostly happens for join points. For example we might have: - f t = join j1 = \xs ys -> snd (xs,ys) - in case t of MkT xs{strict} ys-> j1 xs xy + data T = MkT ![Int] [Char] + f t = case t of MkT xs{strict} ys-> snd (xs,ys) -While we "use" xs inside `j1` it's not used inside the function `snd` we pass it to. -In short a absent demand means neither our RHS, nor any function we pass the argument -to will inspect it. So there is no work to be saved by forcing `xs` early. + and abstract the case alternative to: -NB: There is an edge case where if we rebox we *can* end up seqing an absent value. -Note [Absent fillers] has an example of this. However this is so rare it's not worth -caring about here. + f t = join j1 = \xs ys -> snd (xs,ys) + in case t of MkT xs{strict} ys-> j1 xs xy -4) The argument is already strict. + While we "use" xs inside `j1` it's not used inside the function `snd` we pass it to. + In short a absent demand means neither our RHS, nor any function we pass the argument + to will inspect it. So there is no work to be saved by forcing `xs` early. -Consider this code: - - data T = MkT ![Int] - f t = case t of MkT xs{strict} -> reverse xs - -The `xs{strict}` indicates that `xs` is used strictly by the `reverse xs`. -If we do a w/w split, and add the extra eval on `xs`, we'll get - - $wf xs = - case xs of xs1 -> - let t = MkT xs1 in - case t of MkT xs2 -> reverse xs2 - -That's not wrong; but the w/w body will simplify to - - $wf xs = case xs of xs1 -> reverse xs1 - -and now we'll drop the `case xs` because `xs1` is used strictly in its scope. -Adding that eval was a waste of time. So don't add it for strictly-demanded Ids. + NB: There is an edge case where if we rebox we *can* end up seqing an absent value. + Note [Absent fillers] has an example of this. However this is so rare it's not worth + caring about here. 5) Functions - -Functions are tricky (see Note [TagInfo of functions] in EnforceEpt). -But the gist of it even if we make a higher order function argument strict -we can't avoid the tag check when it's used later in the body. -So there is no benefit. + Functions are tricky (see Note [TagInfo of functions] in EnforceEpt). + But the gist of it even if we make a higher order function argument strict + we can't avoid the tag check when it's used later in the body. + So there is no benefit. + +Wrinkles: + +(WIS1) You might have thought that we can omit the eval if the argument is used + strictly demanded in the body. But you'd be wrong. Consider this code: + data T = MkT ![Int] + f t = case t of MkT xs{Dmd=STR} -> reverse xs + + The `xs{Dmd=STR}` indicates that `xs` is used strictly by the `reverse xs`. + If we do a w/w split, and add the extra eval on `xs`, we'll get + $wf xs = case xs of xs1 -> + let t = MkT xs1 in + case t of MkT xs2 -> reverse xs2 + + That's not wrong; but you might wonder if the eval on `xs` is needed + when it is certainly evaluated by the `reverse`. But yes, it is (#26722): + g s True t = f s t t + g s False t = g s True t + + f True (MkT xs) t = f False (MkT xs) t + f False (MkT xs) _ = xs + + After worker/wrapper we get: + g s b t = case t of MkT ww -> $wg s b ww + $wg s ds ww = case ds of { + False -> case ww of wg { __DEFAULT -> Bar.$wg s True wg } + True -> let { t1 = MkT ww } in f s t1 t1 } + + We must make `f` inline inside `$wg`, because `f` too is ww'd, and we + don't want to rebox `t1` before passing it to `f`. BUT while `t1` + looks like a HNF, `exprIsHNF` will say False because `MkT` is strict + and `ww` isn't evaluated. So `f` doesn't inline and we get lots of + reboxing. + + The Right Thing to to is to add the eval for the data con argument: + $wg s ds ww = case ww of ww' { DEFAULT -> + case ds of { + False -> case ww of wg { __DEFAULT -> Bar.$wg s True wg } + True -> let { t1 = MkT ww' } in f s t1 t1 } } + + Now `t1` will be a HNF, and `f` will inline, and we get + $wg s ds ww = case ww of ww' { DEFAULT -> + case ds of { + False -> Bar.$wg s True ww' + True -> $wf s ww' + + (Ultimately `$wg` will be a CBV function, so that `case ww` will be a + no-op: see (EPT-codegen) in Note [EPT enforcement] in GHC.Stg.EnforceEpt.) -} --- | Do we expect there to be any benefit if we make this var strict --- in order for it to get treated as as cbv argument? --- See Note [Which Ids should be strictified] --- See Note [CBV Function Ids] for more background. -shouldStrictifyIdForCbv :: Var -> Bool -shouldStrictifyIdForCbv = wantCbvForId False - --- Like shouldStrictifyIdForCbv but also wants to use cbv for strict args. -shouldUseCbvForId :: Var -> Bool -shouldUseCbvForId = wantCbvForId True -- When we strictify we want to skip strict args otherwise the logic is the same --- as for shouldUseCbvForId so we common up the logic here. +-- as for wantCbvForId so we common up the logic here. -- Basically returns true if it would be beneficial for runtime to pass this argument -- as CBV independent of weither or not it's correct. E.g. it might return true for lazy args -- we are not allowed to force. -wantCbvForId :: Bool -> Var -> Bool -wantCbvForId cbv_for_strict v +wantCbvForId :: Var -> Bool +wantCbvForId v -- Must be a runtime var. -- See Note [Which Ids should be strictified] point 1) | isId v @@ -3037,9 +3055,6 @@ wantCbvForId cbv_for_strict v , not $ isFunTy ty -- If the var is strict already a seq is redundant. -- See Note [Which Ids should be strictified] point 4) - , not (isStrictDmd dmd) || cbv_for_strict - -- If the var is absent a seq is almost always useless. - -- See Note [Which Ids should be strictified] point 3) , not (isAbsDmd dmd) = True | otherwise ===================================== compiler/GHC/Driver/Session.hs ===================================== @@ -197,6 +197,8 @@ module GHC.Driver.Session ( -- * Compiler configuration suitable for display to the user compilerInfo, + targetHasRTSWays, + wordAlignment, setUnsafeGlobalDynFlags, @@ -3635,6 +3637,15 @@ compilerInfo dflags queryCmdMaybe p f = expandDirectories (query (maybe "" (prgPath . p) . f)) queryFlagsMaybe p f = query (maybe "" (unwords . map escapeArg . prgFlags . p) . f) +-- | 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 +targetHasRTSWays dflags ways + | Just ws <- lookup "RTS ways" $ compilerInfo dflags = + waysTag ways + `elem` words ws + | otherwise = panic "RTS ways not found in settings" + -- Note [Special unit-ids] -- ~~~~~~~~~~~~~~~~~~~~~~~ -- Certain units are special to the compiler: ===================================== compiler/GHC/Runtime/Interpreter/C.hs ===================================== @@ -8,7 +8,9 @@ where import GHC.Prelude import GHC.Platform +import GHC.Platform.Ways import GHC.Data.FastString +import GHC.Driver.Session import GHC.Utils.Logger import GHC.Utils.TmpFs import GHC.Unit.Types @@ -18,11 +20,10 @@ import GHC.Unit.State import GHC.Utils.Panic.Plain import GHC.Linker.Executable import GHC.Linker.Config -import GHC.Utils.CliOption -- | Generate iserv program for the target -generateIservC :: Logger -> TmpFs -> ExecutableLinkOpts -> UnitEnv -> IO FilePath -generateIservC logger tmpfs opts unit_env = do +generateIservC :: DynFlags -> Logger -> TmpFs -> ExecutableLinkOpts -> UnitEnv -> IO FilePath +generateIservC dflags logger tmpfs opts unit_env = do -- get the unit-id of the ghci package. We need this to load the -- interpreter code. let unit_state = ue_homeUnitState unit_env @@ -60,6 +61,12 @@ generateIservC logger tmpfs opts unit_env = do -- must retain CAFs for running interpreted code. , leKeepCafs = True + -- link with -threaded if target has threaded RTS + , leWays = + let ways = leWays opts + ways' = addWay WayThreaded ways + in if targetHasRTSWays dflags ways' then ways' else ways + -- enable all rts options , leRtsOptsEnabled = RtsOptsAll ===================================== compiler/GHC/Runtime/Interpreter/Init.hs ===================================== @@ -9,6 +9,7 @@ where import GHC.Prelude +import GHC.Driver.DynFlags import GHC.Platform import GHC.Platform.Ways import GHC.Settings @@ -57,14 +58,15 @@ data InterpOpts = InterpOpts -- | Initialize code interpreter initInterpreter - :: TmpFs + :: DynFlags + -> TmpFs -> Logger -> Platform -> FinderCache -> UnitEnv -> InterpOpts -> IO (Maybe Interp) -initInterpreter tmpfs logger platform finder_cache unit_env opts = do +initInterpreter dflags tmpfs logger platform finder_cache unit_env opts = do lookup_cache <- liftIO $ mkInterpSymbolCache @@ -125,7 +127,7 @@ initInterpreter tmpfs logger platform finder_cache unit_env opts = do dynamic = interpWays opts `hasWay` WayDyn prog <- case interpProg opts of -- build iserv program if none specified - "" -> generateIservC logger tmpfs (interpExecutableLinkOpts opts) unit_env + "" -> generateIservC dflags logger tmpfs (interpExecutableLinkOpts opts) unit_env _ -> pure (interpProg opts ++ flavour) where flavour ===================================== compiler/GHC/Stg/EnforceEpt.hs ===================================== @@ -140,9 +140,11 @@ Afterwards, the *EPT rewriter* inserts the actual evals realising Upcasts. Implementation -------------- -* EPT analysis is implemented in GHC.Stg.EnforceEpt.inferTags. +(EPT-anal) EPT analysis is implemented in `GHC.Stg.EnforceEpt.inferTags.` It attaches its result to /binders/, not occurrence sites. -* The EPT rewriter establishes the EPT invariant by inserting evals. That is, if + +(EPT-rewrite) The EPT rewriter, `GHC.Stg.EnforceEpt.Rewrite.rewriteTopBinds`, + establishes the EPT invariant by inserting evals. That is, if (a) a binder x is used to * construct a strict field (`SP x y`), or * passed as a CBV argument (`$wf x`), @@ -152,17 +154,27 @@ Implementation case x of x' { __ DEFAULT -> SP x' y }. case x of x' { __ DEFAULT -> $wf x' }. (Recall that the case binder x' is always EPT.) - This is implemented in GHC.Stg.EnforceEpt.Rewrite.rewriteTopBinds. + This pass also propagates the EPTness from binders to occurrences. + It is sound to insert evals on strict fields (Note [Strict fields in Core]), and on CBV arguments as well (Note [CBV Function Ids]). -* We also export the EPTness of top level bindings to allow this optimisation + +(EPT-codegen) Finally, code generation for (case x of alts) skips the thunk check + when `x` is EPT. This is done (a bit indirectly) thus: + * GHC.StgToCmm.Expr.cgCase: builds a `sequel`, and recurses into `cgExpr` on `x`. + * When `cgExpr` sees a `x` goes to `cgIdApp`, which uses `getCallMethod`. + * Then `getCallMethod` sees that `x` is EPT (via `idTagSigMaybe`), and + returns `InferredReturnIt`. + * Now `cgIdApp` can jump straight to the case-alternative switch in the `sequel` + constructed by `cgCase`. + +(EPT-export) We also export the EPTness of top level bindings to allow this optimisation to work across module boundaries. + NB: The EPT Invariant *must* be upheld, regardless of the optimisation level; hence EPTness is practically part of the internal ABI of a strict data constructor or CBV function. Note [CBV Function Ids] contains the details. -* Finally, code generation skips the thunk check when branching on binders that - are EPT. This is done by `cgExpr`/`cgCase` in the backend. Evaluation ---------- ===================================== compiler/GHC/StgToCmm/Closure.hs ===================================== @@ -617,12 +617,15 @@ getCallMethod cfg name id (LFThunk _ _ updatable std_form_info is_fun) getCallMethod cfg name id (LFUnknown might_be_a_function) n_args _cg_locs _self_loop_info | n_args == 0 , Just sig <- idTagSig_maybe id - , isTaggedSig sig -- Infered to be already evaluated by EPT analysis - -- When profiling we must enter all potential functions to make sure we update the SCC - -- even if the function itself is already evaluated. + , isTaggedSig sig -- This `id` is evaluated and properly tagged; no need to enter it + -- See (EPT-codegen) in Note [EPT enforcement] in GHC.Stg.EnforceEpt + + -- When profiling we must enter all potential functions to make sure we update + -- the SCC even if the function itself is already evaluated. -- See Note [Evaluating functions with profiling] in rts/Apply.cmm , not (profileIsProfiling (stgToCmmProfile cfg) && might_be_a_function) - = InferedReturnIt -- See Note [EPT enforcement] + + = InferedReturnIt -- See (EPT-codegen) in Note [EPT enforcement] | might_be_a_function = SlowCall ===================================== compiler/GHC/StgToCmm/Expr.hs ===================================== @@ -1053,6 +1053,7 @@ cgIdApp fun_id args = do | otherwise -> emitReturn [fun] -- A value infered to be in WHNF, so we can just return it. + -- See (EPT-codegen) in Note [EPT enforcement] in GHC.Stg.EnforceEpt InferedReturnIt | isZeroBitTy (idType fun_id) -> trace >> emitReturn [] | otherwise -> trace >> assertTag >> ===================================== hadrian/src/Flavour.hs ===================================== @@ -70,7 +70,8 @@ flavourTransformers = M.fromList , "fully_static" =: fullyStatic , "host_fully_static" =: hostFullyStatic , "collect_timings" =: collectTimings - , "assertions" =: enableAssertions + , "assertions" =: enableAssertions Stage2 + , "assertions_stage1" =: enableAssertions Stage1 , "debug_ghc" =: debugGhc Stage2 , "debug_stage1_ghc" =: debugGhc Stage1 , "lint" =: enableLinting @@ -394,11 +395,11 @@ enableLateCCS = addArgs ? arg "-fprof-late" -- | Enable assertions for the stage2 compiler -enableAssertions :: Flavour -> Flavour -enableAssertions flav = flav { ghcDebugAssertions = f } +enableAssertions :: Stage -> Flavour -> Flavour +enableAssertions stage flav = flav { ghcDebugAssertions = f } where - f Stage2 = True - f st = ghcDebugAssertions flav st + f s | s == stage = True + | otherwise = ghcDebugAssertions flav s -- | Build the stage3 compiler using the non-moving GC. enableBootNonmovingGc :: Flavour -> Flavour ===================================== hadrian/src/Packages.hs ===================================== @@ -217,7 +217,7 @@ timeoutPath = "testsuite/timeout/install-inplace/bin/timeout" <.> exe -- TODO: Can we extract this information from Cabal files? -- | Some program packages should not be linked with Haskell main function. nonHsMainPackage :: Package -> Bool -nonHsMainPackage = (`elem` [hp2ps, iserv, unlit, ghciWrapper]) +nonHsMainPackage = (`elem` [hp2ps, unlit, ghciWrapper]) {- ===================================== hadrian/src/Rules/Gmp.hs ===================================== @@ -126,6 +126,12 @@ gmpRules = do interpretInContext ctx $ mconcat [ getStagedCCFlags + -- gmp fails to configure with newer compilers + -- that default to c23: + -- https://gmplib.org/list-archives/gmp-devel/2025-January/006279.html. + -- for now just manually specify -std=gnu11 until + -- next upstream release. + , arg "-std=gnu11" -- gmp symbols are only used by bignum logic in -- ghc-internal and shouldn't be exported by the -- ghc-internal shared library. ===================================== hadrian/src/Settings/Packages.hs ===================================== @@ -41,6 +41,8 @@ packageArgs = do libzstdLibraryDir <- getSetting LibZstdLibDir stageVersion <- readVersion <$> (expr $ ghcVersionStage stage) + rtsWays <- getRtsWays + mconcat --------------------------------- base --------------------------------- [ package base ? mconcat @@ -185,11 +187,15 @@ packageArgs = do -- -- The Solaris linker does not support --export-dynamic option. It also -- does not need it since it exports all dynamic symbols by default - , package iserv - ? expr isElfTarget + , package iserv ? mconcat [ + expr isElfTarget ? notM (expr $ anyTargetOs [OSFreeBSD, OSSolaris2])? mconcat [ builder (Ghc LinkHs) ? arg "-optl-Wl,--export-dynamic" ] + -- Link iserv with -threaded if possible + , builder (Cabal Flags) ? any (wayUnit Threaded) rtsWays `cabalFlag` "threaded" + ] + -------------------------------- haddock ------------------------------- , package haddockApi ? builder (Cabal Flags) ? arg "in-ghc-tree" ===================================== libraries/ghc-internal/configure.ac ===================================== @@ -195,28 +195,10 @@ dnl-------------------------------------------------------------------- if test "$HaveFrameworkGMP" = "YES" || test "$HaveLibGmp" = "YES" then AC_MSG_RESULT([no]) - UseIntreeGmp=0 AC_CHECK_HEADER([gmp.h], , [AC_MSG_ERROR([Cannot find gmp.h])]) - - AC_MSG_CHECKING([GMP version]) - AC_COMPUTE_INT(GhcGmpVerMj, __GNU_MP_VERSION, [#include <gmp.h>], - AC_MSG_ERROR([Unable to get value of __GNU_MP_VERSION])) - AC_COMPUTE_INT(GhcGmpVerMi, __GNU_MP_VERSION_MINOR, [#include <gmp.h>], - AC_MSG_ERROR([Unable to get value of __GNU_MP_VERSION_MINOR])) - AC_COMPUTE_INT(GhcGmpVerPl, __GNU_MP_VERSION_PATCHLEVEL, [#include <gmp.h>], - AC_MSG_ERROR([Unable to get value of __GNU_MP_VERSION_PATCHLEVEL])) - AC_MSG_RESULT([$GhcGmpVerMj.$GhcGmpVerMi.$GhcGmpVerPl]) - else AC_MSG_RESULT([yes]) - UseIntreeGmp=1 HaveSecurePowm=1 - - AC_MSG_CHECKING([GMP version]) - GhcGmpVerMj=6 - GhcGmpVerMi=1 - GhcGmpVerPl=2 - AC_MSG_RESULT([$GhcGmpVerMj.$GhcGmpVerMi.$GhcGmpVerPl]) fi GMP_INSTALL_INCLUDES="HsIntegerGmp.h ghc-gmp.h" @@ -231,10 +213,6 @@ AC_SUBST(GMP_INSTALL_INCLUDES) AC_SUBST(HaveLibGmp) AC_SUBST(HaveFrameworkGMP) AC_SUBST(HaveSecurePowm) -AC_SUBST(UseIntreeGmp) -AC_SUBST(GhcGmpVerMj) -AC_SUBST(GhcGmpVerMi) -AC_SUBST(GhcGmpVerPl) # Compute offsets/sizes used by jsbits/base.js if test "$host" = "javascript-ghcjs" ===================================== libraries/ghc-internal/include/HsIntegerGmp.h.in ===================================== @@ -1,14 +1,4 @@ #pragma once -/* Whether GMP is embedded into ghc-internal */ -#define GHC_GMP_INTREE @UseIntreeGmp@ - -/* The following values denote the GMP version used during GHC build-time */ -#define GHC_GMP_VERSION_MJ @GhcGmpVerMj@ -#define GHC_GMP_VERSION_MI @GhcGmpVerMi@ -#define GHC_GMP_VERSION_PL @GhcGmpVerPl@ -#define GHC_GMP_VERSION \ - (@GhcGmpVerMj@ * 10000 + @GhcGmpVerMi@ * 100 + @GhcGmpVerPl@) - /* Whether GMP supports mpz_powm_sec */ #define HAVE_SECURE_POWM @HaveSecurePowm@ ===================================== testsuite/tests/simplCore/should_compile/T18013.stderr ===================================== @@ -143,14 +143,14 @@ T18013.$wmapMaybeRule [InlPrag=NOINLINE] Unf=OtherCon []] T18013.$wmapMaybeRule = \ (@a) (@b) (@s) (ww :: s) (ww1 :: s -> a -> IO (Result s b)) -> + case ww of ww2 { __DEFAULT -> case ww1 of wild { __DEFAULT -> - case ww of wild1 { __DEFAULT -> T18013a.Rule @IO @(Maybe a) @(Maybe b) @s - wild1 + ww2 ((\ (s2 :: s) (a1 :: Maybe a) (s1 :: GHC.Internal.Prim.State# GHC.Internal.Prim.RealWorld) -> @@ -158,7 +158,7 @@ T18013.$wmapMaybeRule Nothing -> (# s1, T18013a.Result - @s @(Maybe b) wild1 (GHC.Internal.Maybe.Nothing @b) #); + @s @(Maybe b) ww2 (GHC.Internal.Maybe.Nothing @b) #); Just x -> case ((wild s2 x) `cast` <Co:4> :: IO (Result s b) ===================================== testsuite/tests/simplCore/should_compile/T26722.hs ===================================== @@ -0,0 +1,9 @@ +module T26722 where + +data T = MkT ![Int] + +g s True t = f s t t +g s False t = g s True t + +f True (MkT xs) t = f False (MkT xs) t +f False (MkT xs) _ = xs ===================================== testsuite/tests/simplCore/should_compile/T26722.stderr ===================================== @@ -0,0 +1 @@ + \ No newline at end of file ===================================== testsuite/tests/simplCore/should_compile/all.T ===================================== @@ -563,3 +563,6 @@ test('T26115', [grep_errmsg(r'DFun')], compile, ['-O -ddump-simpl -dsuppress-uni test('T26116', normal, compile, ['-O -ddump-rules']) test('T26117', [grep_errmsg(r'==')], compile, ['-O -ddump-simpl -dsuppress-uniques']) test('T26349', normal, compile, ['-O -ddump-rules']) + +# T26722: there should be no reboxing in $wg +test('T26722', [grep_errmsg(r'SPEC')], compile, ['-O -dno-typeable-binds']) ===================================== utils/iserv/cbits/iservmain.c deleted ===================================== @@ -1,18 +0,0 @@ -#include <ghcversion.h> -# include <rts/PosixSource.h> -#include <Rts.h> - -#include <HsFFI.h> - -int main (int argc, char *argv[]) -{ - RtsConfig conf = defaultRtsConfig; - - // We never know what symbols GHC will look up in the future, so - // we must retain CAFs for running interpreted code. - conf.keep_cafs = 1; - - conf.rts_opts_enabled = RtsOptsAll; - extern StgClosure ZCMain_main_closure; - hs_main(argc, argv, &ZCMain_main_closure, conf); -} ===================================== utils/iserv/iserv.cabal.in ===================================== @@ -23,11 +23,17 @@ Category: Development build-type: Simple cabal-version: >=1.10 +Flag threaded + Description: Link the iserv executable against the threaded RTS + Default: True + Manual: True + Executable iserv Default-Language: Haskell2010 - ghc-options: -no-hs-main + ghc-options: -fkeep-cafs -rtsopts + if flag(threaded) + ghc-options: -threaded Main-Is: Main.hs - C-Sources: cbits/iservmain.c Hs-Source-Dirs: src include-dirs: . Build-Depends: View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/ee290e4bfc327e60346f927f618cce4... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/ee290e4bfc327e60346f927f618cce4... You're receiving this email because of your account on gitlab.haskell.org.
participants (1)
-
Simon Peyton Jones (@simonpj)