Simon Jakobi pushed to branch wip/sjakobi/T25450-print-cpu at Glasgow Haskell Compiler / GHC
Commits:
a039c13d by Simon Jakobi at 2026-06-26T13:23:30+02:00
Add --print-enabled-cpu-features flag
GHC now supports a new mode flag --print-enabled-cpu-features, which
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.
$ ghc -mavx2 --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"]}
The primary purpose of this flag is for testing the -march=native option
(#25450).
Assisted-by: Claude Opus 4.8
- - - - -
14 changed files:
- + changelog.d/print-enabled-cpu-features
- compiler/GHC/Driver/DynFlags.hs
- compiler/GHC/Driver/Session.hs
- docs/users_guide/using.rst
- ghc/GHC/Driver/Session/Lint.hs
- ghc/GHC/Driver/Session/Mode.hs
- ghc/Main.hs
- testsuite/tests/driver/all.T
- + testsuite/tests/driver/print_enabled_cpu_features.stdout
- + testsuite/tests/driver/print_enabled_cpu_features_avx2.stdout
- + testsuite/tests/driver/print_enabled_cpu_features_avx512.stdout
- + testsuite/tests/driver/print_enabled_cpu_features_bmi2.stdout
- + testsuite/tests/driver/print_enabled_cpu_features_fma.stdout
- + testsuite/tests/driver/print_enabled_cpu_features_unknown_flag.stderr
Changes:
=====================================
changelog.d/print-enabled-cpu-features
=====================================
@@ -0,0 +1,16 @@
+section: compiler
+synopsis: Add --print-enabled-cpu-features flag
+issues: #25450
+mrs: !16117
+
+description:
+ GHC now supports a new mode flag ``--print-enabled-cpu-features``, which
+ 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. ::
+
+ $ ghc -mavx2 --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"]}
=====================================
compiler/GHC/Driver/DynFlags.hs
=====================================
@@ -1615,6 +1615,7 @@ initPromotionTickContext dflags =
-- -----------------------------------------------------------------------------
-- SSE, AVX, FMA
+-- See Note [Keeping enabledCpuFeatures in sync] in GHC.Driver.Session
isSse3Enabled :: DynFlags -> Bool
isSse3Enabled dflags = sseAvxVersion dflags >= Just SSE3 || isAvxEnabled dflags
@@ -1705,11 +1706,14 @@ We handle this as follows:
-- -----------------------------------------------------------------------------
-- LA664
+-- See Note [Keeping enabledCpuFeatures in sync] in GHC.Driver.Session
+
isLa664Enabled :: DynFlags -> Bool
isLa664Enabled dflags = la664 dflags
-- -----------------------------------------------------------------------------
-- BMI2
+-- See Note [Keeping enabledCpuFeatures in sync] in GHC.Driver.Session
isBmiEnabled :: DynFlags -> Bool
isBmiEnabled dflags = case platformArch (targetPlatform dflags) of
=====================================
compiler/GHC/Driver/Session.hs
=====================================
@@ -196,6 +196,8 @@ module GHC.Driver.Session (
-- * Compiler configuration suitable for display to the user
compilerInfo,
+ showEnabledCpuFeatures,
+ enabledCpuFeatures,
targetHasRTSWays,
@@ -277,6 +279,7 @@ import GHC.Utils.TmpFs
import GHC.Utils.Fingerprint
import GHC.Utils.Outputable
import GHC.Utils.Error (emptyDiagOpts, logInfo)
+import GHC.Utils.Json
import GHC.Settings
import GHC.CmmToAsm.CFG.Weight
import GHC.Core.Opt.CallerCC
@@ -3677,6 +3680,133 @@ compilerInfo dflags
queryCmdMaybe p f = expandDirectories (query (maybe "" (prgPath . p) . f))
queryFlagsMaybe p f = query (maybe "" (unwords . map escapeArg . prgFlags . p) . f)
+showEnabledCpuFeatures :: DynFlags -> String
+showEnabledCpuFeatures dflags = showSDocUnsafe $ renderJSON $ JSObject
+ [ ("tag", JSString "enabled-cpu-features")
+ -- Schema version of this JSON object; bump it whenever the shape or
+ -- meaning of the fields changes, so consumers can detect incompatibility.
+ , ("version", JSInt 1)
+ , ("target", JSString (platformMisc_targetPlatformString (platformMisc dflags)))
+ , ("features", JSArray (map JSString features))
+ -- A set of `-m...` flags that, passed to GHC for this target, reproduce
+ -- the effective feature set above. Note this need not be the flags the
+ -- user actually passed: implied features are folded in, and a feature
+ -- enabled by default may be reproduced by the empty set.
+ , ("as_m_flags", JSArray (map JSString asMFlags))
+ ]
+ where
+ (features, asMFlags) = enabledCpuFeatures dflags
+
+{- Note [Keeping enabledCpuFeatures in sync]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+`enabledCpuFeatures` must be updated whenever a new CPU feature flag is added
+to GHC. The three places to touch are:
+
+ 1. The flag registration (e.g. `make_ord_flag defGhcFlag "mnewfeat" ...`),
+ in this module
+ 2. The corresponding `is*Enabled` predicate, in GHC.Driver.DynFlags
+ 3. The `enabledCpuFeatures` function below — add the feature to `features`
+ and, if it has a GHC `-m...` flag, to `as_m_flags` via the appropriate
+ architecture branch.
+
+See Note [Implications between X86 CPU feature flags] in GHC.Driver.DynFlags
+for the implication structure that `x86FeaturesAndFlags` and `x86AsMFlags`
+must respect.
+-}
+
+enabledCpuFeatures :: DynFlags -> ([String], [String])
+enabledCpuFeatures dflags = case platformArch (targetPlatform dflags) of
+ ArchX86_64 -> x86FeaturesAndFlags dflags
+ ArchX86 -> x86FeaturesAndFlags dflags
+ ArchLoongArch64 ->
+ ( fmaFeature ++ [ "LA664" | isLa664Enabled dflags ]
+ , fmaFlag ++ [ "-mla664" | isLa664Enabled dflags ]
+ )
+ _ -> (fmaFeature, fmaFlag)
+ where
+ -- `-mfma` is a cross-platform flag. On x86 it is folded into the
+ -- SSE/AVX hierarchy (handled in x86FeaturesAndFlags); on every other
+ -- architecture FMA stands on its own and gates FMA codegen via
+ -- isFmaEnabled in stgToCmmAllowFMAInstr. `fma dflags` is the source of
+ -- truth (it defaults to True on AArch64).
+ fmaFeature = [ "FMA" | fma dflags ]
+ fmaFlag = [ "-mfma" | fma dflags ]
+
+x86FeaturesAndFlags :: DynFlags -> ([String], [String])
+x86FeaturesAndFlags dflags =
+ -- SSE/SSE2 are determined by the target platform rather than a dynamic
+ -- flag, hence those predicates take Platform while the others take DynFlags.
+ ( [ "SSE" | isSseEnabled platform ]
+ ++ [ "SSE2" | isSse2Enabled platform ]
+ ++ [ "SSE3" | isSse3Enabled dflags ]
+ ++ [ "SSSE3" | isSsse3Enabled dflags ]
+ ++ [ "SSE4.1" | isSse4_1Enabled dflags ]
+ ++ [ "SSE4.2" | isSse4_2Enabled dflags ]
+ ++ [ "AVX" | isAvxEnabled dflags ]
+ ++ [ "AVX2" | isAvx2Enabled dflags ]
+ ++ [ "AVX512F" | isAvx512fEnabled dflags ]
+ ++ [ "AVX512BW" | isAvx512bwEnabled dflags ]
+ ++ [ "AVX512CD" | isAvx512cdEnabled dflags ]
+ ++ [ "AVX512DQ" | isAvx512dqEnabled dflags ]
+ ++ [ "AVX512ER" | isAvx512erEnabled dflags ]
+ ++ [ "AVX512PF" | isAvx512pfEnabled dflags ]
+ ++ [ "AVX512VL" | isAvx512vlEnabled dflags ]
+ ++ [ "BMI1" | isBmiEnabled dflags ]
+ ++ [ "BMI2" | isBmi2Enabled dflags ]
+ ++ [ "FMA" | isFmaEnabled dflags ]
+ ++ [ "GFNI" | isGfniEnabled dflags ]
+ , x86AsMFlags dflags
+ )
+ where
+ platform = targetPlatform dflags
+
+x86AsMFlags :: DynFlags -> [String]
+x86AsMFlags dflags =
+ avx512Flags
+ ++ vectorFlags
+ ++ bmiFlags
+ ++ fmaFlags
+ ++ gfniFlags
+ where
+ avx512Extensions =
+ [ ("-mavx512bw", avx512bw dflags)
+ , ("-mavx512cd", avx512cd dflags)
+ , ("-mavx512dq", avx512dq dflags)
+ , ("-mavx512er", avx512er dflags)
+ , ("-mavx512pf", avx512pf dflags)
+ , ("-mavx512vl", avx512vl dflags)
+ ]
+
+ hasAvx512Extension = any snd avx512Extensions
+ hasAvx512 = avx512f dflags || hasAvx512Extension
+
+ avx512Flags =
+ [ "-mavx512f" | avx512f dflags && not hasAvx512Extension ]
+ ++ [ flag | (flag, True) <- avx512Extensions ]
+
+ vectorFlags
+ | hasAvx512 = []
+ | otherwise =
+ case sseAvxVersion dflags of
+ Just AVX2 -> ["-mavx2"]
+ Just AVX1 -> ["-mavx"]
+ Just SSE42 -> ["-msse4.2"]
+ Just SSE4 -> ["-msse4"]
+ Just SSSE3 -> ["-mssse3"]
+ Just SSE3 -> ["-msse3"]
+ _ -> []
+
+ bmiFlags = case bmiVersion dflags of
+ Just BMI2 -> ["-mbmi2"]
+ Just BMI1 -> ["-mbmi"]
+ Nothing -> []
+
+ fmaFlags
+ | fma dflags && not hasAvx512 = ["-mfma"]
+ | otherwise = []
+
+ gfniFlags = [ "-mgfni" | gfni dflags ]
+
-- | 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
=====================================
docs/users_guide/using.rst
=====================================
@@ -488,6 +488,16 @@ The available mode flags are:
List the flags passed to the C compiler for the linking step
during GHC build.
+.. ghc-flag:: --print-enabled-cpu-features
+ :shortdesc: display the effective enabled CPU features for code generation
+ :type: mode
+ :category: modes
+
+ 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.
+
.. ghc-flag:: --print-debug-on
:shortdesc: print whether GHC was built with ``-DDEBUG``
:type: mode
=====================================
ghc/GHC/Driver/Session/Lint.hs
=====================================
@@ -1,6 +1,6 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE NondecreasingIndentation #-}
-module GHC.Driver.Session.Lint (checkOptions) where
+module GHC.Driver.Session.Lint (checkOptions, unknownFlagsErr) where
import GHC.Driver.Backend
import GHC.Driver.Phases
=====================================
ghc/GHC/Driver/Session/Mode.hs
=====================================
@@ -77,6 +77,7 @@ isShowGhciUsageMode _ = False
data PostLoadMode
= ShowInterface FilePath -- ghc --show-iface
+ | PrintEnabledCpuFeatures -- ghc --print-enabled-cpu-features
| DoMkDependHS -- ghc -M
| StopBefore StopPhase -- ghc -E | -C | -S
-- StopBefore StopLn is the default
@@ -90,12 +91,13 @@ data PostLoadMode
| DoFrontend ModuleName -- ghc --frontend Plugin.Module
doMkDependHSMode, doMakeMode, doInteractiveMode, doRunMode,
- doAbiHashMode, showUnitsMode :: Mode
+ doAbiHashMode, printEnabledCpuFeaturesMode, showUnitsMode :: Mode
doMkDependHSMode = mkPostLoadMode DoMkDependHS
doMakeMode = mkPostLoadMode DoMake
doInteractiveMode = mkPostLoadMode DoInteractive
doRunMode = mkPostLoadMode DoRun
doAbiHashMode = mkPostLoadMode DoAbiHash
+printEnabledCpuFeaturesMode = mkPostLoadMode PrintEnabledCpuFeatures
showUnitsMode = mkPostLoadMode ShowPackages
showInterfaceMode :: FilePath -> Mode
@@ -203,6 +205,8 @@ mode_flags =
, defFlag "-show-options" (PassFlag (setMode showOptionsMode))
, defFlag "-supported-languages" (PassFlag (setMode showSupportedExtensionsMode))
, defFlag "-supported-extensions" (PassFlag (setMode showSupportedExtensionsMode))
+ , defFlag "-print-enabled-cpu-features"
+ (PassFlag (setMode printEnabledCpuFeaturesMode))
, defFlag "-show-packages" (PassFlag (setMode showUnitsMode))
] ++
[ defFlag k' (PassFlag (setMode (printSetting k)))
=====================================
ghc/Main.hs
=====================================
@@ -229,55 +229,64 @@ main' postLoadMode units dflags0 args flagWarnings = do
liftIO $ exitWith (ExitFailure 1)) $ do
liftIO $ printOrThrowDiagnostics logger4 (initPrintConfig dflags4) diag_opts flagWarnings'
- liftIO $ showBanner postLoadMode dflags4
-
- let (dflags5, srcs, objs) = parseTargetFiles dflags4 (map unLoc fileish_args)
-
- -- we've finished manipulating the DynFlags, update the session
- _ <- GHC.setSessionDynFlags dflags5
- dflags6 <- GHC.getSessionDynFlags
-
- -- Must do this before loading plugins
- liftIO $ initUniqSupply (initialUnique dflags6) (uniqueIncrement dflags6)
-
- -- Initialise plugins here because the plugin author might already expect this
- -- subsequent call to `getLogger` to be affected by a plugin.
- initializeSessionPlugins
- hsc_env <- getSession
- logger <- getLogger
-
-
- ---------------- Display configuration -----------
- case verbosity dflags6 of
- v | v == 4 -> liftIO $ dumpUnitsSimple hsc_env
- | v >= 5 -> liftIO $ dumpUnits hsc_env
- | otherwise -> return ()
-
- ---------------- Final sanity checking -----------
- liftIO $ checkOptions postLoadMode dflags6 srcs objs units
-
- ---------------- Do the business -----------
- handleSourceError (\e -> do
- GHC.printException e
- liftIO $ exitWith (ExitFailure 1)) $ do
- case postLoadMode of
- ShowInterface f -> liftIO $ showIface logger
- (hsc_dflags hsc_env)
- (hsc_units hsc_env)
- (hsc_NC hsc_env)
- f
- DoMake -> doMake units srcs
- DoMkDependHS -> doMkDependHS (map fst srcs)
- StopBefore p -> liftIO (oneShot hsc_env p srcs)
- DoInteractive -> ghciUI units srcs Nothing
- DoEval exprs -> ghciUI units srcs $ Just $ reverse exprs
- DoRun -> doRun units srcs args
- DoAbiHash -> abiHash (map fst srcs)
- ShowPackages -> liftIO $ showUnits hsc_env
- DoFrontend f -> doFrontend f srcs
- DoBackpack -> doBackpack (map fst srcs)
-
- liftIO $ dumpFinalStats logger
+ case postLoadMode of
+ PrintEnabledCpuFeatures -> liftIO $ do
+ -- This mode bypasses parseTargetFiles/checkOptions, so reject any
+ -- leftover flag-like arguments (e.g. a mistyped -mavx22) ourselves;
+ -- otherwise the typo would be silently ignored.
+ let unknown_opts = [ f | f@('-':_) <- map unLoc fileish_args ]
+ when (not (null unknown_opts)) (unknownFlagsErr unknown_opts)
+ putStrLn (showEnabledCpuFeatures dflags4)
+ _ -> do
+ liftIO $ showBanner postLoadMode dflags4
+
+ let (dflags5, srcs, objs) = parseTargetFiles dflags4 (map unLoc fileish_args)
+
+ -- we've finished manipulating the DynFlags, update the session
+ _ <- GHC.setSessionDynFlags dflags5
+ dflags6 <- GHC.getSessionDynFlags
+
+ -- Must do this before loading plugins
+ liftIO $ initUniqSupply (initialUnique dflags6) (uniqueIncrement dflags6)
+
+ -- Initialise plugins here because the plugin author might already expect this
+ -- subsequent call to `getLogger` to be affected by a plugin.
+ initializeSessionPlugins
+ hsc_env <- getSession
+ logger <- getLogger
+
+
+ ---------------- Display configuration -----------
+ case verbosity dflags6 of
+ v | v == 4 -> liftIO $ dumpUnitsSimple hsc_env
+ | v >= 5 -> liftIO $ dumpUnits hsc_env
+ | otherwise -> return ()
+
+ ---------------- Final sanity checking -----------
+ liftIO $ checkOptions postLoadMode dflags6 srcs objs units
+
+ ---------------- Do the business -----------
+ handleSourceError (\e -> do
+ GHC.printException e
+ liftIO $ exitWith (ExitFailure 1)) $ do
+ case postLoadMode of
+ ShowInterface f -> liftIO $ showIface logger
+ (hsc_dflags hsc_env)
+ (hsc_units hsc_env)
+ (hsc_NC hsc_env)
+ f
+ DoMake -> doMake units srcs
+ DoMkDependHS -> doMkDependHS (map fst srcs)
+ StopBefore p -> liftIO (oneShot hsc_env p srcs)
+ DoInteractive -> ghciUI units srcs Nothing
+ DoEval exprs -> ghciUI units srcs $ Just $ reverse exprs
+ DoRun -> doRun units srcs args
+ DoAbiHash -> abiHash (map fst srcs)
+ ShowPackages -> liftIO $ showUnits hsc_env
+ DoFrontend f -> doFrontend f srcs
+ DoBackpack -> doBackpack (map fst srcs)
+
+ liftIO $ dumpFinalStats logger
doRun :: [String] -> [(FilePath, Maybe Phase)] -> [Located String] -> Ghc ()
doRun units srcs args = do
@@ -515,4 +524,3 @@ abiHash strs = do
f <- fingerprintBinMem bh
putStrLn (showPpr dflags f)
-
=====================================
testsuite/tests/driver/all.T
=====================================
@@ -1,3 +1,12 @@
+def normalise_enabled_cpu_target(msg):
+ return re.sub(r'"target":"[^"]+"', '"target":"TARGET"', msg)
+
+def normalise_unknown_flag(msg):
+ # Keep only the stable 'unrecognised flag' line; the program-name prefix,
+ # the suggestion list, and the usage trailer vary across configurations.
+ m = re.search(r'unrecognised flag: \S+', 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'])
@@ -221,6 +230,41 @@ test('T9938B', [], makefile_test, [])
test('T9963', exit_code(1), run_command,
['{compiler} --interactive -ignore-dot-ghci --print-libdir'])
+test('print_enabled_cpu_features',
+ [unless(arch('x86_64') or arch('i386'), skip),
+ normalise_fun(normalise_enabled_cpu_target)],
+ run_command,
+ ['{compiler} --print-enabled-cpu-features'])
+
+test('print_enabled_cpu_features_avx2',
+ [unless(arch('x86_64') or arch('i386'), skip),
+ normalise_fun(normalise_enabled_cpu_target)],
+ run_command,
+ ['{compiler} -mavx2 --print-enabled-cpu-features'])
+
+test('print_enabled_cpu_features_bmi2',
+ [unless(arch('x86_64') or arch('i386'), skip),
+ normalise_fun(normalise_enabled_cpu_target)],
+ run_command,
+ ['{compiler} -mbmi2 --print-enabled-cpu-features'])
+
+test('print_enabled_cpu_features_fma',
+ [unless(arch('x86_64') or arch('i386'), skip),
+ normalise_fun(normalise_enabled_cpu_target)],
+ run_command,
+ ['{compiler} -mfma --print-enabled-cpu-features'])
+
+test('print_enabled_cpu_features_avx512',
+ [unless(arch('x86_64'), skip),
+ normalise_fun(normalise_enabled_cpu_target)],
+ run_command,
+ ['{compiler} -mavx512dq -mavx512vl --print-enabled-cpu-features'])
+
+test('print_enabled_cpu_features_unknown_flag',
+ [normalise_fun(normalise_unknown_flag), exit_code(1)],
+ run_command,
+ ['{compiler} -mavx22 --print-enabled-cpu-features'])
+
test('T10219', normal, run_command,
# `-x hspp` in make mode should work.
# Note: need to specify `-x hspp` before the filename.
=====================================
testsuite/tests/driver/print_enabled_cpu_features.stdout
=====================================
@@ -0,0 +1 @@
+{"tag":"enabled-cpu-features","version":1,"target":"TARGET","features":["SSE","SSE2"],"as_m_flags":[]}
=====================================
testsuite/tests/driver/print_enabled_cpu_features_avx2.stdout
=====================================
@@ -0,0 +1 @@
+{"tag":"enabled-cpu-features","version":1,"target":"TARGET","features":["SSE","SSE2","SSE3","SSSE3","SSE4.1","SSE4.2","AVX","AVX2"],"as_m_flags":["-mavx2"]}
=====================================
testsuite/tests/driver/print_enabled_cpu_features_avx512.stdout
=====================================
@@ -0,0 +1 @@
+{"tag":"enabled-cpu-features","version":1,"target":"TARGET","features":["SSE","SSE2","SSE3","SSSE3","SSE4.1","SSE4.2","AVX","AVX2","AVX512F","AVX512DQ","AVX512VL","FMA"],"as_m_flags":["-mavx512dq","-mavx512vl"]}
=====================================
testsuite/tests/driver/print_enabled_cpu_features_bmi2.stdout
=====================================
@@ -0,0 +1 @@
+{"tag":"enabled-cpu-features","version":1,"target":"TARGET","features":["SSE","SSE2","BMI1","BMI2"],"as_m_flags":["-mbmi2"]}
=====================================
testsuite/tests/driver/print_enabled_cpu_features_fma.stdout
=====================================
@@ -0,0 +1 @@
+{"tag":"enabled-cpu-features","version":1,"target":"TARGET","features":["SSE","SSE2","SSE3","SSSE3","SSE4.1","SSE4.2","AVX","FMA"],"as_m_flags":["-mfma"]}
=====================================
testsuite/tests/driver/print_enabled_cpu_features_unknown_flag.stderr
=====================================
@@ -0,0 +1,6 @@
+ghc: unrecognised flag: -mavx22
+did you mean one of:
+ -mavx2
+ -mavx
+
+Usage: For basic information, try the `--help' option.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/a039c13d93602b75ac2ac1c607bd13cd...
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/a039c13d93602b75ac2ac1c607bd13cd...
You're receiving this email because of your account on gitlab.haskell.org.