From ac57534aef9f8fe7cae9791ac33a3c76da7f7b42 Mon Sep 17 00:00:00 2001 From: IC Rainbow Date: Thu, 24 Sep 2026 20:58:00 +0300 Subject: [PATCH 1/5] Detect GHC 9.4 and use -fprof-late --- .stan.toml | 10 +++++----- ChangeLog.md | 6 ++++++ src/Stack/Build/Source.hs | 13 +++++++++---- src/Stack/ConfigureOpts.hs | 14 +++++++++----- src/Stack/Constants.hs | 4 ++++ 5 files changed, 33 insertions(+), 14 deletions(-) diff --git a/.stan.toml b/.stan.toml index d26d158658..4343d4202a 100644 --- a/.stan.toml +++ b/.stan.toml @@ -245,16 +245,16 @@ # Anti-pattern: unsafe functions [[ignore]] - id = "OBS-STAN-0212-5rtOmw-507:33" + id = "OBS-STAN-0212-5rtOmw-511:33" # ✦ Description: Usage of unsafe functions breaks referential transparency # ✦ Category: #Unsafe #AntiPattern # ✦ File: src\Stack\Constants.hs # -# 506 ┃ -# 507 ┃ setupGhciShimCode = byteString $(do -# 508 ┃ path <- makeRelativeToProject "src/setup-shim/StackSetupShim.hs" -# 509 ┃ embedFile path) # 510 ┃ +# 511 ┃ setupGhciShimCode = byteString $(do +# 512 ┃ path <- makeRelativeToProject "src/setup-shim/StackSetupShim.hs" +# 513 ┃ embedFile path) +# 514 ┃ # Anti-pattern: unsafe functions [[ignore]] diff --git a/ChangeLog.md b/ChangeLog.md index f9a370e9cb..e7128509fe 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -10,6 +10,12 @@ Major changes: Behavior changes: +* With GHC 9.4.1 or later, when building with profiling, Stack passes GHC's + `-fprof-late` flag (rather than `-fprof-auto`) and Cabal's + `--profiling-detail=none` and `--library-profiling-detail=none` options. Cost + centres are then inserted after optimisation, so the profile reflects the + optimised program. Existing profiling builds will be rebuilt. + Other enhancements: * Consider Cabal 3.18 to be a tested library and remove warnings. diff --git a/src/Stack/Build/Source.hs b/src/Stack/Build/Source.hs index 61bdce6e4e..d2f3ac4b88 100644 --- a/src/Stack/Build/Source.hs +++ b/src/Stack/Build/Source.hs @@ -32,6 +32,7 @@ import Distribution.Version ( mkVersion ) import qualified Pantry.SHA256 as SHA256 import Stack.Build.Cache ( tryGetBuildCache ) import Stack.Build.Haddock ( shouldHaddockDeps ) +import Stack.Constants ( ghcSupportsProfLate ) import Stack.Package ( buildableBenchmarks, buildableExes, buildableTestSuites , hasBuildableMainLibrary, resolvePackage @@ -113,7 +114,6 @@ loadSourceMap targets boptsCli sma = do bconfig <- view buildConfigL let errsPackages = mapMaybe checkPackage packagesWithCliFlags compiler = sma.compiler - ghcVersion = getGhcVersion compiler buildOpts = bconfig.config.build infoTableProfSupported = ghcVersion >= mkVersion [9, 2, 1] infoTableProfUnsupportedWarning = @@ -146,6 +146,7 @@ loadSourceMap targets boptsCli sma = do , globalPkgs } where + ghcVersion = getGhcVersion sma.compiler cliFlags = boptsCli.flags targetsAndSmaDeps = targets.deps <> sma.deps packagesWithCliFlags = mapMaybe maybeProjectWithCliFlags $ Map.toList cliFlags @@ -212,6 +213,7 @@ loadSourceMap targets boptsCli sma = do ghcOptions = generalGhcOptions bconfig boptsCli + ghcVersion isTarget isProjectPackage isInfoTableProf @@ -276,7 +278,7 @@ hashSourceMapData boptsCli sm = do isInfoTableProf = ghcVersion >= mkVersion [9, 2, 1] && bc.config.build.infoTableProf bootGhcOpts = - map display (generalGhcOptions bc boptsCli False False isInfoTableProf) + map display (generalGhcOptions bc boptsCli ghcVersion False False isInfoTableProf) hashedContent = toLazyByteString $ compilerPath <> compilerInfo @@ -337,11 +339,12 @@ generalCabalConfigOpts bconfig boptsCli name isTarget isLocal = concat generalGhcOptions :: BuildConfig -> BuildOptsCLI + -> Version -> Bool -> Bool -> Bool -> [Text] -generalGhcOptions bconfig boptsCli isTarget isLocal isInfoTableProf = concat +generalGhcOptions bconfig boptsCli ghcVersion isTarget isLocal isInfoTableProf = concat [ Map.findWithDefault [] AGOEverything config.ghcOptionsByCat , if isLocal then Map.findWithDefault [] AGOLocals config.ghcOptionsByCat @@ -351,7 +354,7 @@ generalGhcOptions bconfig boptsCli isTarget isLocal isInfoTableProf = concat else [] , concat [["-fhpc"] | isLocal && bopts.testOpts.coverage] , if bopts.libProfile || bopts.exeProfile - then ["-fprof-auto", "-fprof-cafs"] + then [costCentreInsertion, "-fprof-cafs"] else [] , [ "-g" | not $ bopts.libStrip || bopts.exeStrip ] , if isInfoTableProf @@ -364,6 +367,8 @@ generalGhcOptions bconfig boptsCli isTarget isLocal isInfoTableProf = concat where bopts = config.build config = view configL bconfig + costCentreInsertion = + if ghcSupportsProfLate ghcVersion then "-fprof-late" else "-fprof-auto" includeExtraOptions = case config.applyGhcOptions of AGOTargets -> isTarget diff --git a/src/Stack/ConfigureOpts.hs b/src/Stack/ConfigureOpts.hs index 6a31ed3740..bdea29b33b 100644 --- a/src/Stack/ConfigureOpts.hs +++ b/src/Stack/ConfigureOpts.hs @@ -26,11 +26,12 @@ import Path ( (), parseRelDir ) import Path.Extra ( toFilePathNoTrailingSep ) import Stack.Constants ( bindirSuffix, compilerOptionsCabalFlag, docDirSuffix - , relDirEtc, relDirLib, relDirLibexec, relDirShare + , ghcSupportsProfLate, relDirEtc, relDirLib, relDirLibexec + , relDirShare ) import Stack.Prelude import Stack.Types.BuildOpts ( BuildOpts (..) ) -import Stack.Types.Compiler ( whichCompiler ) +import Stack.Types.Compiler ( getGhcVersion, whichCompiler ) import Stack.Types.Config ( Config (..), HasConfig (..) ) import Stack.Types.ConfigureOpts ( BaseConfigOpts (..), ConfigureOpts (..) @@ -123,10 +124,11 @@ configureOptsNonPathRelated :: -> [String] configureOptsNonPathRelated econfig bco deps isLocal package = concat [ depOptions - , [ "--enable-library-profiling" - | bopts.libProfile || bopts.exeProfile - ] + , ["--enable-library-profiling" | profiling] , ["--enable-profiling" | bopts.exeProfile && isLocal] + , if profiling && ghcSupportsProfLate ghcVersion + then ["--profiling-detail=none", "--library-profiling-detail=none"] + else [] , ["--enable-split-objs" | bopts.splitObjs] , [ "--disable-library-stripping" | not $ bopts.libStrip || bopts.exeStrip @@ -176,11 +178,13 @@ configureOptsNonPathRelated econfig bco deps isLocal package = concat in concatMap (\x -> [compilerOptionsCabalFlag wc, T.unpack x]) newArgs wc = view (actualCompilerVersionL . to whichCompiler) econfig + ghcVersion = view (actualCompilerVersionL . to getGhcVersion) econfig hideSourcePaths = config.hideSourcePaths config = view configL econfig bopts = bco.buildOpts + profiling = bopts.libProfile || bopts.exeProfile mapAndAppend fn = Map.foldrWithKey' (fmap (:) . fn) -- Unioning atop defaults is needed so that all flags are specified with -- --exact-configuration. diff --git a/src/Stack/Constants.hs b/src/Stack/Constants.hs index fb9216127c..315605df16 100644 --- a/src/Stack/Constants.hs +++ b/src/Stack/Constants.hs @@ -37,6 +37,7 @@ module Stack.Constants , defaultGlobalConfigPath , platformVariantEnvVar , compilerOptionsCabalFlag + , ghcSupportsProfLate , ghcColorForceFlag , minTerminalWidth , maxTerminalWidth @@ -359,6 +360,9 @@ platformVariantEnvVar = stackProgNameUpper ++ "_PLATFORM_VARIANT" compilerOptionsCabalFlag :: WhichCompiler -> String compilerOptionsCabalFlag Ghc = "--ghc-options" +ghcSupportsProfLate :: Version -> Bool +ghcSupportsProfLate ghcVersion = ghcVersion >= mkVersion [9, 4, 1] + -- | The flag to pass to GHC when we want to force its output to be -- colorized. ghcColorForceFlag :: String From d4780b17468a1cff86b356e3e2ba192230d5a193 Mon Sep 17 00:00:00 2001 From: Mike Pilgrem Date: Thu, 22 Oct 2026 01:11:44 +0100 Subject: [PATCH 2/5] Update online documentation of `--profile` flag --- doc/commands/build_command.md | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/doc/commands/build_command.md b/doc/commands/build_command.md index 2f9d7d5244..3d0fc23eb9 100644 --- a/doc/commands/build_command.md +++ b/doc/commands/build_command.md @@ -979,20 +979,24 @@ expressions, and generate a profiling report in tests or benchmarks. GHC provides various strategies for the automatic insertion of cost-centres into the compiled program. The flag enables the strategy recommended in the GHC -User's Guide (GHC flag `-fprof-auto`) and gives every Constant Applicative Form -(CAF, a top-level thunk) its own cost-centre stack) (GHC flag `-fprof-cafs`). +User's Guide and gives every Constant Applicative Form (CAF, a top-level thunk) +its own cost-centre stack) (GHC flag `-fprof-cafs`). -!!! note +The recommended strategy: - From GHC 9.4.1, the stategy recommended in the GHC User's Guide has been - `-fprof-late`. Stack has not yet been updated to reflect that. +* **for GHC 9.4.1 and later**, is enabled by GHC flag `-fprof-late`. This + strategy annotates, after GHC's optimizer has run and unfoldings have been + created, all top-level bindings (only) with `SCC` (Set Cost Centre). If a + function is inlined into a use site its costs will be counted against the + caller’s cost-centre; and -The recommended strategy annotates, before GHC's optimizer has run, all bindings (top-level or nested, whether exported or not), other than those marked -`INLINE`, with `SCC` (Set Cost Centre). +* **otherwise**, is enabled by GHC flag `-fprof-auto`. This strategy annotates, + before GHC's optimizer has run, all bindings (top-level or nested, whether + exported or not), other than those marked `INLINE`, with `SCC`. -The GHC flags set by the flag can be reversed with GHC flags `-fno-prof-auto` or -`-fno-prof-cafs` and other GHC flags applied. See the -[`--ghc-options`](#-ghc-options-option) option. +The GHC flags set by the flag can be reversed with GHC flags `-fno-prof-late` or +`-fno-prof-auto` (as applicable) or `-fno-prof-cafs` and other GHC flags +applied. See the [`--ghc-options`](#-ghc-options-option) option. The flag affects the location of the local project installation directory. See the [`stack path --local-install-root`](path_command.md) command. From 8e26c5a14282b791f3bb9de576022933bf572d8b Mon Sep 17 00:00:00 2001 From: Mike Pilgrem Date: Thu, 22 Oct 2026 20:30:03 +0100 Subject: [PATCH 3/5] Set Cabal's --profiling-detail=none, come what may --- src/Stack/ConfigureOpts.hs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Stack/ConfigureOpts.hs b/src/Stack/ConfigureOpts.hs index bdea29b33b..8d7708d757 100644 --- a/src/Stack/ConfigureOpts.hs +++ b/src/Stack/ConfigureOpts.hs @@ -26,12 +26,11 @@ import Path ( (), parseRelDir ) import Path.Extra ( toFilePathNoTrailingSep ) import Stack.Constants ( bindirSuffix, compilerOptionsCabalFlag, docDirSuffix - , ghcSupportsProfLate, relDirEtc, relDirLib, relDirLibexec - , relDirShare + , relDirEtc, relDirLib, relDirLibexec, relDirShare ) import Stack.Prelude import Stack.Types.BuildOpts ( BuildOpts (..) ) -import Stack.Types.Compiler ( getGhcVersion, whichCompiler ) +import Stack.Types.Compiler ( whichCompiler ) import Stack.Types.Config ( Config (..), HasConfig (..) ) import Stack.Types.ConfigureOpts ( BaseConfigOpts (..), ConfigureOpts (..) @@ -124,11 +123,16 @@ configureOptsNonPathRelated :: -> [String] configureOptsNonPathRelated econfig bco deps isLocal package = concat [ depOptions - , ["--enable-library-profiling" | profiling] + , [ "--enable-library-profiling" + | bopts.libProfile || bopts.exeProfile + ] , ["--enable-profiling" | bopts.exeProfile && isLocal] - , if profiling && ghcSupportsProfLate ghcVersion - then ["--profiling-detail=none", "--library-profiling-detail=none"] - else [] + -- The default --profiling-detail is exported-functions (equivalent to GHC's + -- -fprof-auto-exported) and toplevel-functions (equivalent to GHC's + -- -fprof-auto-top). Neither adds anything to GHC's -fprof-auto and neither + -- are required for GHC's -fprof-late. So, we turn it off, which applies to + -- both executables and libraries: + , ["--profiling-detail=none"] , ["--enable-split-objs" | bopts.splitObjs] , [ "--disable-library-stripping" | not $ bopts.libStrip || bopts.exeStrip @@ -178,13 +182,11 @@ configureOptsNonPathRelated econfig bco deps isLocal package = concat in concatMap (\x -> [compilerOptionsCabalFlag wc, T.unpack x]) newArgs wc = view (actualCompilerVersionL . to whichCompiler) econfig - ghcVersion = view (actualCompilerVersionL . to getGhcVersion) econfig hideSourcePaths = config.hideSourcePaths config = view configL econfig bopts = bco.buildOpts - profiling = bopts.libProfile || bopts.exeProfile mapAndAppend fn = Map.foldrWithKey' (fmap (:) . fn) -- Unioning atop defaults is needed so that all flags are specified with -- --exact-configuration. From 9d2eccf1e7321eb1ba059250779b3f0c63bbb3d5 Mon Sep 17 00:00:00 2001 From: Mike Pilgrem Date: Thu, 22 Oct 2026 21:02:39 +0100 Subject: [PATCH 4/5] Simplify generalGhcOptions --- .stan.toml | 10 +++++----- src/Stack/Build/Source.hs | 11 +++++++---- src/Stack/Constants.hs | 4 ---- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/.stan.toml b/.stan.toml index 4343d4202a..d26d158658 100644 --- a/.stan.toml +++ b/.stan.toml @@ -245,16 +245,16 @@ # Anti-pattern: unsafe functions [[ignore]] - id = "OBS-STAN-0212-5rtOmw-511:33" + id = "OBS-STAN-0212-5rtOmw-507:33" # ✦ Description: Usage of unsafe functions breaks referential transparency # ✦ Category: #Unsafe #AntiPattern # ✦ File: src\Stack\Constants.hs # +# 506 ┃ +# 507 ┃ setupGhciShimCode = byteString $(do +# 508 ┃ path <- makeRelativeToProject "src/setup-shim/StackSetupShim.hs" +# 509 ┃ embedFile path) # 510 ┃ -# 511 ┃ setupGhciShimCode = byteString $(do -# 512 ┃ path <- makeRelativeToProject "src/setup-shim/StackSetupShim.hs" -# 513 ┃ embedFile path) -# 514 ┃ # Anti-pattern: unsafe functions [[ignore]] diff --git a/src/Stack/Build/Source.hs b/src/Stack/Build/Source.hs index d2f3ac4b88..b2cae54b9d 100644 --- a/src/Stack/Build/Source.hs +++ b/src/Stack/Build/Source.hs @@ -32,7 +32,6 @@ import Distribution.Version ( mkVersion ) import qualified Pantry.SHA256 as SHA256 import Stack.Build.Cache ( tryGetBuildCache ) import Stack.Build.Haddock ( shouldHaddockDeps ) -import Stack.Constants ( ghcSupportsProfLate ) import Stack.Package ( buildableBenchmarks, buildableExes, buildableTestSuites , hasBuildableMainLibrary, resolvePackage @@ -354,7 +353,7 @@ generalGhcOptions bconfig boptsCli ghcVersion isTarget isLocal isInfoTableProf = else [] , concat [["-fhpc"] | isLocal && bopts.testOpts.coverage] , if bopts.libProfile || bopts.exeProfile - then [costCentreInsertion, "-fprof-cafs"] + then [recommendedProfStrategy, "-fprof-cafs"] else [] , [ "-g" | not $ bopts.libStrip || bopts.exeStrip ] , if isInfoTableProf @@ -367,8 +366,12 @@ generalGhcOptions bconfig boptsCli ghcVersion isTarget isLocal isInfoTableProf = where bopts = config.build config = view configL bconfig - costCentreInsertion = - if ghcSupportsProfLate ghcVersion then "-fprof-late" else "-fprof-auto" + -- The strategy for the automatic insertion of cost centres for profiling, as + -- recommended by the GHC User's Guide: + recommendedProfStrategy :: Text + recommendedProfStrategy + | ghcVersion >= mkVersion [9, 4, 1] = "-fprof-late" + | otherwise = "-fprof-auto" includeExtraOptions = case config.applyGhcOptions of AGOTargets -> isTarget diff --git a/src/Stack/Constants.hs b/src/Stack/Constants.hs index 315605df16..fb9216127c 100644 --- a/src/Stack/Constants.hs +++ b/src/Stack/Constants.hs @@ -37,7 +37,6 @@ module Stack.Constants , defaultGlobalConfigPath , platformVariantEnvVar , compilerOptionsCabalFlag - , ghcSupportsProfLate , ghcColorForceFlag , minTerminalWidth , maxTerminalWidth @@ -360,9 +359,6 @@ platformVariantEnvVar = stackProgNameUpper ++ "_PLATFORM_VARIANT" compilerOptionsCabalFlag :: WhichCompiler -> String compilerOptionsCabalFlag Ghc = "--ghc-options" -ghcSupportsProfLate :: Version -> Bool -ghcSupportsProfLate ghcVersion = ghcVersion >= mkVersion [9, 4, 1] - -- | The flag to pass to GHC when we want to force its output to be -- colorized. ghcColorForceFlag :: String From 55bf67c18e881201523a90cef070bf426a95c3ae Mon Sep 17 00:00:00 2001 From: Mike Pilgrem Date: Thu, 22 Oct 2026 21:29:10 +0100 Subject: [PATCH 5/5] Frame changelog in terms of catching up with GUG recommendation --- ChangeLog.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index e7128509fe..bc51a4cc17 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -10,11 +10,11 @@ Major changes: Behavior changes: -* With GHC 9.4.1 or later, when building with profiling, Stack passes GHC's - `-fprof-late` flag (rather than `-fprof-auto`) and Cabal's - `--profiling-detail=none` and `--library-profiling-detail=none` options. Cost - centres are then inserted after optimisation, so the profile reflects the - optimised program. Existing profiling builds will be rebuilt. +* From GHC 9.4.1, the strategy for the automatic insertion of cost-centres for + profiling recommended by the GHC User's Guide is `-fprof-late`. Stack's + `build --profile` command now adopts that recommendation (rather than + `-fprof-auto`, still applicable to earlier GHC versions). Existing profiling + builds will be rebuilt. Other enhancements: