[Git][ghc/ghc][wip/sjakobi/T25450-print-cpu] Add --print-enabled-cpu-features flag
Simon Jakobi pushed to branch wip/sjakobi/T25450-print-cpu at Glasgow Haskell Compiler / GHC Commits: 9af6e598 by Simon Jakobi at 2026-06-01T22:45:12+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":["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: - - - - - 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,132 @@ 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, all in GHC.Driver.DynFlags: + + 1. The flag registration (e.g. `make_ord_flag defGhcFlag "mnewfeat" ...`) + 2. The corresponding `is*Enabled` predicate + 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/9af6e598a33a0b01dc73e4f85f82392e... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/9af6e598a33a0b01dc73e4f85f82392e... You're receiving this email because of your account on gitlab.haskell.org.
participants (1)
-
Simon Jakobi (@sjakobi2)