[Git][ghc/ghc][wip/sjakobi/T25450-march-native] 10 commits: Add --print-enabled-cpu-features flag
Simon Jakobi pushed to branch wip/sjakobi/T25450-march-native at Glasgow Haskell Compiler / GHC Commits: d177606f by Simon Jakobi at 2026-06-01T22:51:53+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 <noreply@anthropic.com> - - - - - 4851f684 by Simon Jakobi at 2026-06-02T00:37:14+02:00 Initial PLAN.md for T25450 - - - - - 69bd97c5 by Simon Jakobi at 2026-06-02T00:37:14+02:00 Initial implementation sketch from Codex - - - - - 58057a37 by Simon Jakobi at 2026-06-02T00:37:15+02:00 Update PLAN.md - - - - - 2ebb98e7 by Simon Jakobi at 2026-06-02T00:37:15+02:00 Add AGENTS.md - - - - - dcf90d0a by Simon Jakobi at 2026-06-02T00:37:15+02:00 AGENTS.md: Add implementation playbook - - - - - fc53b0c8 by Simon Jakobi at 2026-06-02T00:37:15+02:00 Updates - - - - - d56edd5c by Simon Jakobi at 2026-06-02T00:37:15+02:00 Implement -march=native for x86/x86_64 Probe the host CPU at flag-parse time and enable the matching CPU-feature DynFlags, so the effect applies to both the NCG and LLVM backends. The flag handler only records a marker (DynFlags.marchNative) since handlers are pure; the CPUID probe and feature application run in parseDynamicFlagsFull, which is in IO. detectX86CpuFeatures is memoized via cachedX86CpuFeatures so the probe runs once per process. Detected SSE/AVX and BMI levels are collapsed to their maximum and folded into the existing feature DynFlags, so the features are treated exactly like the corresponding -m... flags and reuse the existing implication logic. -march=native is additive: it enables host features in addition to any explicit -m... flags, independent of order, and never disables a feature. It is rejected for non-x86 targets and when cross-compiling (host arch /= target arch). The effective feature set is observable via --print-enabled-cpu-features, which the new driver tests use. Part of #25450. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> - - - - - 7aa7eb43 by Simon Jakobi at 2026-06-02T00:48:27+02:00 Add -march=native superset/additive tests; refresh PLAN.md status Add march_native_superset and march_native_additive driver tests and update PLAN.md to reflect the completed implementation and resolved open questions. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> - - - - - 10d359b2 by Simon Jakobi at 2026-06-02T01:26:06+02:00 Add NCG asm test that -march=native enables popcnt Compile-and-grep test confirming that -march=native enables SSE4.2 on a capable host, so popCount emits a popcnt instruction. Gated on the host actually having SSE4.2 via have_cpu_feature. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> - - - - - 25 changed files: - + PLAN.md - + 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/using.rst - ghc/GHC/Driver/Session/Lint.hs - ghc/GHC/Driver/Session/Mode.hs - ghc/Main.hs - 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 - + 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: ===================================== PLAN.md ===================================== @@ -0,0 +1,142 @@ +# Implement `-march=native` for x86/x86_64 Using `GHC.Driver.CpuFeatures` + +## Summary +Add support for `-march=native` (x86/x86_64, native compiler only). The flag probes the +host CPU at parse time via an in-process CPUID/XGETBV helper and enables the matching +CPU-feature `DynFlags`, so the effect applies to both the NCG and LLVM backends. + +This branch is based on `wip/sjakobi/T25450-print-cpu`, which already provides +`ghc --print-enabled-cpu-features` and the `enabledCpuFeatures` / `showEnabledCpuFeatures` +machinery in `GHC.Driver.Session`. That print mode is the primary tool for verifying and +testing `-march=native`: detection sets `DynFlags`, the print mode reports them. + +## Public Interface Changes +1. New dynamic flag: `-march=native`. +2. New users-guide docs entry under platform flags. +3. Behavior constraints: + - Supported only for target `x86`/`x86_64`. + - Rejected for cross-compilation (host CPU is irrelevant to a different target). + - Additive: applied once after the whole flag fold, it enables host features + *in addition* to any explicit `-m...` flags (order-independent, never disables). + +## Module / Architecture Changes +1. `compiler/cbits/cpu_features_x86.c` — in-process CPUID/XGETBV probe (done). +2. `compiler/GHC/Driver/CpuFeatures.hs` — mask decoder + naming (done). Build wiring in + `compiler/ghc.cabal.in` (done). +3. `compiler/GHC/Driver/DynFlags.hs` — add a marker field `marchNative :: Bool` (default + `False`). +4. `compiler/GHC/Driver/Session.hs` — flag registration + the IO application step (see below). + +## Detailed Implementation + +### 1. Flag parsing — pure marker only +- File: `compiler/GHC/Driver/Session.hs`, near the existing `-m...` flags (~line 1710). +- `make_ord_flag defGhcFlag "march=native" (noArg (\d -> d { marchNative = True }))`. +- IMPORTANT: flag handlers run in `DynP = EwM (CmdLineP DynFlags)`, which is **pure** — they + cannot run the CPUID probe. The handler only records the request via the marker field. +- Flag-surface note: a literal `"march=native"` entry means `-march=<other>` falls through to + a generic "unrecognised flag" error. For v1 this is acceptable; document that only `native` + is supported. (Optionally parse the `=value` later to emit a targeted message.) + +### 2. Feature application — the IO step +- File: `compiler/GHC/Driver/Session.hs`, inside `parseDynamicFlagsFull`. +- `parseDynamicFlagsFull` is already `MonadIO` and already performs `liftIO` work between the + pure flag fold (`dflags1`) and `makeDynFlagsConsistent` (`dflags2`/`dflags3`). Insert the + application there: after `dflags1`, before `makeDynFlagsConsistent`. + - If `marchNative dflags1` is set: + - Validate target arch is `ArchX86`/`ArchX86_64`; otherwise hard error. + - Validate not cross-compiling; otherwise hard error. (See open question on detection.) + - `feats <- liftIO detectX86CpuFeatures` (cached — see §4). + - Apply `feats` to `DynFlags` via a dedicated mapping helper (see §3), producing `dflags1'`. + - `makeDynFlagsConsistent` then propagates feature implications — do not reimplement them. +- This path is shared with `parseDynamicFilePragma`, so the same logic also covers + `{-# OPTIONS_GHC -march=native #-}`. Decide whether to permit that; if so, caching matters. + +### 3. Mapping detected features to `DynFlags` +The detected list is granular (`SSE2, SSE3, …, AVX2, BMI1, BMI2, …`), but several `DynFlags` +fields are single ordered levels, so the mapper must **collapse ladders to their maximum**: +- SSE/AVX ladder → highest present → `sseAvxVersion :: Maybe SseAvxVersion` + (`DynFlags.hs:461`). The `is*Enabled` predicates (`DynFlags.hs:1621-1642`) already imply the + lower rungs, so only the max level needs to be set. +- BMI1/BMI2 → highest present → `bmiVersion :: Maybe BmiVersion`. +- `AVX512F` + per-extension features → the individual bools `avx512f`, `avx512bw`, + `avx512cd`, `avx512dq`, `avx512vl`. +- `FMA` → `fma`; `GFNI` → `gfni`. +- `SSE2` is the x86_64 baseline (determined by the platform, not a flag) — nothing to set. + +### 4. Probe caching +CPUID results are constant for the lifetime of the process, but `parseDynamicFlagsFull` runs +per command-line *and* per file pragma. Memoize the probe once (top-level CAF backed by +`unsafePerformIO` with `{-# NOINLINE #-}`, or detect once and thread the result) so repeated +`-march=native` uses don't re-probe. + +### 5. Diagnostics and errors +- Unsupported arch: clear error that `-march=native` is x86/x86_64-only. +- Cross mode: clear error that `-march=native` is unavailable for cross-compilation. +- The probe is in-process and returns a `Word64`; there is no subprocess, command, or stderr + to report. The only anomaly is a probe returning `0` (or lacking SSE2 on x86_64, where it is + baseline) — treat that as an internal error with a short diagnostic. (The earlier + "failing command + brief stderr" wording referred to an abandoned toolchain-probe approach + and no longer applies.) + +### 6. Backend behavior +- NCG and LLVM are both driven by the same CPU-feature `DynFlags`, so no backend wiring + changes are needed — populating the fields in §3 is sufficient. + +### 7. Verification via `--print-enabled-cpu-features` +- The base branch already prints effective enabled features as single-line JSON: + `{"tag":"enabled-cpu-features","version":1,"target":<triple>,"features":[…],"as_m_flags":[…]}` + with conventional names (`SSE4.2`, `AVX2`, `BMI2`) in canonical order. +- `-march=native` must update the same `DynFlags` fields this mode reads, so its effect is + observable through `ghc -march=native --print-enabled-cpu-features`. + +### 8. Documentation +- File: `docs/users_guide/using.rst`, alongside the other `-m...` options. +- Describe: host CPU detection, x86/x86_64 only, non-cross only, positional, equivalent to + enabling the matching `-m...` options automatically. +- Add a `changelog.d/` entry (the base branch already adds `changelog.d/print-enabled-cpu-features`). + +## Tests and Scenarios +Lead with `--print-enabled-cpu-features` for deterministic, host-independent assertions. + +1. **Baseline (deterministic):** on x86_64, `ghc -march=native --print-enabled-cpu-features` + always lists `SSE2` in `features`. +2. **Superset:** `features(-march=native)` ⊇ `features()` (run the print mode with and without + the flag and compare). Avoids hard-coding host-specific feature sets. +3. **Additive:** an explicit `-m...` combined with `-march=native` yields the union (the + explicit feature is still present regardless of flag order). +4. **Arch / cross guards:** compile-fail test for a non-x86 target and/or a cross context, + asserting the §5 error messages. +5. **Optional NCG end-to-end:** one host-gated asm-grep test under + `testsuite/tests/codeGen/should_gen_asm` showing `-march=native` enables an expected x86 + instruction path. Keep it secondary to the print-mode tests; do not rely on it as the main + signal. (There is no longer a meaningful probe-failure compile-fail test, since the probe + has no external tool to break.) + +## Open Questions +1. **Cross-compilation detection.** *Resolved.* The guard compares the target arch against the + build-host arch: `arch == hostPlatformArch` in `applyMarchNative` (`Session.hs:3831`). A + mismatch raises the cross-compilation error from §5. +2. **File-pragma scope.** *Resolved (permitted).* `applyMarchNative` runs in the shared + `parseDynamicFlagsFull` (`Session.hs:913`), so `-march=native` also takes effect in + `{-# OPTIONS_GHC #-}`. Repeated probing is harmless because the probe is memoized + (`cachedX86CpuFeatures`, §4). + +## Status +1. Done: CPUID/XGETBV probe in `compiler/cbits/cpu_features_x86.c`. +2. Done: decoder + naming in `compiler/GHC/Driver/CpuFeatures.hs`. +3. Done: cabal wiring in `compiler/ghc.cabal.in`. +4. Done (base branch): `--print-enabled-cpu-features` + `enabledCpuFeatures` reporting. +5. Done: `marchNative` marker field in `DynFlags` (`DynFlags.hs:473,765`); flag registration + in `Session.hs:1752`. +6. Done: IO application step `applyMarchNative` in `parseDynamicFlagsFull` + (`Session.hs:3816`) — arch guard, cross guard, and the ladder mapping in + `applyX86CpuFeatures` (`Session.hs:3839`). +7. Done: probe caching via `cachedX86CpuFeatures` (top-level CAF, `NOINLINE`, + `CpuFeatures.hs:89`). +8. Done: docs (`docs/users_guide/using.rst:1857`) and changelog (`changelog.d/march-native`). +9. Tests. In `testsuite/tests/driver`: baseline SSE2 (`march_native`, §1), arch guard + (`march_native_unsupported_arch`, §4), superset (`march_native_superset`, §2), and additive + (`march_native_additive`, §3). In `testsuite/tests/codeGen/should_gen_asm`: NCG asm-grep + (`march-native-enables-popcnt`, §5) — gated on the host having SSE4.2 via + `have_cpu_feature`, so it deterministically expects a `popcnt` instruction. ===================================== changelog.d/march-native ===================================== @@ -0,0 +1,11 @@ +section: compiler +synopsis: Add -march=native flag +issues: #25450 + +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 ===================================== @@ -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/CpuFeatures.hs ===================================== @@ -0,0 +1,115 @@ +{-# LANGUAGE CPP #-} + +module GHC.Driver.CpuFeatures + ( X86CpuFeature(..) + , detectX86CpuFeatureMask + , detectX86CpuFeatures + , cachedX86CpuFeatures + , decodeX86CpuFeatureMask + , x86CpuFeatureConventionalName + ) where + +import GHC.Prelude + +import Data.Bits (testBit) +import Data.Word (Word64) +import System.IO.Unsafe (unsafePerformIO) + +-- | x86 CPU features understood by GHC's native CPU feature probe. +-- +-- The constructor order and decode order below are canonicalized for stable +-- debug output. +data X86CpuFeature + = SSE2 + | SSE3 + | SSSE3 + | SSE4_1 + | SSE4_2 + | AVX + | AVX2 + | AVX512F + | AVX512BW + | AVX512CD + | AVX512DQ + | AVX512VL + | BMI1 + | BMI2 + | FMA + | GFNI + deriving (Eq, Ord, Show) + +-- | Human-readable names used in the JSON/debug output. +x86CpuFeatureConventionalName :: X86CpuFeature -> String +x86CpuFeatureConventionalName feat = case feat of + SSE2 -> "SSE2" + SSE3 -> "SSE3" + SSSE3 -> "SSSE3" + SSE4_1 -> "SSE4.1" + SSE4_2 -> "SSE4.2" + AVX -> "AVX" + AVX2 -> "AVX2" + AVX512F -> "AVX512F" + AVX512BW -> "AVX512BW" + AVX512CD -> "AVX512CD" + AVX512DQ -> "AVX512DQ" + AVX512VL -> "AVX512VL" + BMI1 -> "BMI1" + BMI2 -> "BMI2" + FMA -> "FMA" + GFNI -> "GFNI" + +-- | Decode the bitmask returned by 'ghc_detect_x86_cpu_features'. +-- +-- NOTE: Bit positions must match the enum in @compiler/cbits/cpu_features_x86.c@. +decodeX86CpuFeatureMask :: Word64 -> [X86CpuFeature] +decodeX86CpuFeatureMask mask = + [ feat + | (bit_ix, feat) <- cpuFeatureBitLayout + , testBit mask bit_ix + ] + +-- | 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. +-- +-- CPUID results are constant for the lifetime of the process, so probing more +-- than once (e.g. once per @-march=native@ in a command line or file pragma) +-- is wasteful. This is referentially transparent despite the FFI call. +cachedX86CpuFeatures :: [X86CpuFeature] +cachedX86CpuFeatures = unsafePerformIO detectX86CpuFeatures +{-# NOINLINE cachedX86CpuFeatures #-} + +cpuFeatureBitLayout :: [(Int, X86CpuFeature)] +cpuFeatureBitLayout = + [ (0, SSE2) + , (1, SSE3) + , (2, SSSE3) + , (3, SSE4_1) + , (4, SSE4_2) + , (5, AVX) + , (6, AVX2) + , (7, AVX512F) + , (8, AVX512BW) + , (9, AVX512CD) + , (10, AVX512DQ) + , (11, AVX512VL) + , (12, BMI1) + , (13, BMI2) + , (14, FMA) + , (15, GFNI) + ] + +#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 ===================================== @@ -470,6 +470,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. @@ -760,6 +762,7 @@ defaultDynFlags mySettings = gfni = False, -- For LoongArch, la464 is used by default. la664 = False, + marchNative = False, maxInlineAllocSize = 128, maxInlineMemcpyInsns = 32, @@ -1615,6 +1618,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 +1709,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, @@ -243,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 @@ -277,6 +281,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 @@ -903,8 +908,12 @@ parseDynamicFlagsFull activeFlags cmdline logger dflags0 args = do unless (null errs) $ liftIO $ throwGhcExceptionIO $ errorsToGhcException $ map ((rdr . ppr . getLoc &&& unLoc) . errMsg) $ errs + -- Apply -march=native: probe the host CPU and enable the matching feature + -- flags. This needs IO (CPUID), so it cannot live in the pure flag handlers. + 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 $ @@ -1740,6 +1749,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 })) @@ -3677,6 +3687,185 @@ 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 ] + +-- | Apply a requested @-march=native@ by probing the host CPU and enabling the +-- matching CPU-feature flags. +-- +-- This runs in 'parseDynamicFlagsFull' rather than in a flag handler because the +-- CPUID probe needs 'IO', whereas flag handlers are pure. The detected features +-- are folded into the existing feature 'DynFlags' so that 'makeDynFlagsConsistent' +-- and the backends treat them exactly like the corresponding @-m...@ 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. SSE/AVX and BMI levels are collapsed to their maximum, +-- since 'sseAvxVersion' and 'bmiVersion' each record a single level. +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,177 @@ +#include <HsFFI.h> +#include <stdint.h> + +#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) +#include <immintrin.h> +#include <intrin.h> +#endif + +#if !defined(_MSC_VER) && (defined(__i386__) || defined(__x86_64__)) +#include <cpuid.h> +#endif + +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 +}; + +#define SET_FEAT(mask, bit) ((mask) |= ((HsWord64)1ULL << (bit))) + +static int ghc_cpuid_count(uint32_t leaf, uint32_t subleaf, + uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d) +{ +#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) + int regs[4]; + __cpuidex(regs, (int)leaf, (int)subleaf); + *a = (uint32_t)regs[0]; + *b = (uint32_t)regs[1]; + *c = (uint32_t)regs[2]; + *d = (uint32_t)regs[3]; + return 1; +#elif defined(__i386__) || defined(__x86_64__) + return __get_cpuid_count(leaf, subleaf, a, b, c, d); +#else + (void)leaf; + (void)subleaf; + (void)a; + (void)b; + (void)c; + (void)d; + return 0; +#endif +} + +static uint64_t ghc_xgetbv0(void) +{ +#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) + return (uint64_t)_xgetbv(0); +#elif defined(__i386__) || defined(__x86_64__) + uint32_t eax, edx; + __asm__ volatile(".byte 0x0f, 0x01, 0xd0" /* xgetbv */ + : "=a"(eax), "=d"(edx) + : "c"(0)); + return ((uint64_t)edx << 32) | (uint64_t)eax; +#else + return 0; +#endif +} + +HsWord64 ghc_detect_x86_cpu_features(void) +{ + HsWord64 feats = 0; + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__) + uint32_t a, b, c, d; + uint32_t max_basic = 0; + + if (!ghc_cpuid_count(0, 0, &a, &b, &c, &d)) { + return 0; + } + max_basic = a; + if (max_basic < 1) { + return 0; + } + + ghc_cpuid_count(1, 0, &a, &b, &c, &d); + + { + int has_sse2 = !!(d & (1u << 26)); + int has_sse3 = !!(c & (1u << 0)); + int has_ssse3 = !!(c & (1u << 9)); + int has_sse4_1 = !!(c & (1u << 19)); + int has_sse4_2 = !!(c & (1u << 20)); + int has_fma_hw = !!(c & (1u << 12)); + int has_avx_hw = !!(c & (1u << 28)); + int has_osxsave = !!(c & (1u << 27)); + + int avx_usable = 0; + int avx512_usable = 0; + + if (has_osxsave) { + uint64_t xcr0 = ghc_xgetbv0(); + avx_usable = ((xcr0 & 0x6u) == 0x6u); /* XMM + YMM state */ + avx512_usable = ((xcr0 & 0xE6u) == 0xE6u); /* XMM+YMM+opmask+ZMM */ + } + + if (has_sse2) { + SET_FEAT(feats, GHC_X86_FEAT_SSE2); + } + if (has_sse3) { + SET_FEAT(feats, GHC_X86_FEAT_SSE3); + } + if (has_ssse3) { + SET_FEAT(feats, GHC_X86_FEAT_SSSE3); + } + if (has_sse4_1) { + SET_FEAT(feats, GHC_X86_FEAT_SSE4_1); + } + if (has_sse4_2) { + SET_FEAT(feats, GHC_X86_FEAT_SSE4_2); + } + if (has_avx_hw && avx_usable) { + SET_FEAT(feats, GHC_X86_FEAT_AVX); + } + if (has_fma_hw && avx_usable) { + SET_FEAT(feats, GHC_X86_FEAT_FMA); + } + + if (max_basic >= 7 && ghc_cpuid_count(7, 0, &a, &b, &c, &d)) { + int has_bmi1 = !!(b & (1u << 3)); + int has_avx2_hw = !!(b & (1u << 5)); + int has_bmi2 = !!(b & (1u << 8)); + int has_avx512f = !!(b & (1u << 16)); + int has_avx512dq = !!(b & (1u << 17)); + int has_avx512cd = !!(b & (1u << 28)); + int has_avx512bw = !!(b & (1u << 30)); + int has_avx512vl = !!(b & (1u << 31)); + int has_gfni = !!(c & (1u << 8)); + + if (has_bmi1) { + SET_FEAT(feats, GHC_X86_FEAT_BMI1); + } + if (has_bmi2) { + SET_FEAT(feats, GHC_X86_FEAT_BMI2); + } + if (avx_usable && has_avx2_hw) { + SET_FEAT(feats, GHC_X86_FEAT_AVX2); + } + + if (avx512_usable && has_avx512f) { + SET_FEAT(feats, GHC_X86_FEAT_AVX512F); + if (has_avx512bw) { + SET_FEAT(feats, GHC_X86_FEAT_AVX512BW); + } + if (has_avx512cd) { + SET_FEAT(feats, GHC_X86_FEAT_AVX512CD); + } + if (has_avx512dq) { + SET_FEAT(feats, GHC_X86_FEAT_AVX512DQ); + } + if (has_avx512vl) { + SET_FEAT(feats, GHC_X86_FEAT_AVX512VL); + } + } + + if (has_gfni) { + SET_FEAT(feats, GHC_X86_FEAT_GFNI); + } + } + } +#endif + + return feats; +} ===================================== 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 @@ -514,6 +515,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/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 @@ -1844,6 +1854,30 @@ 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. + + .. 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. + + This flag is rejected when compiling for a non-x86 target or when + cross-compiling, since the host CPU is then unrelated to the target. + Haddock ------- ===================================== 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/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 ===================================== @@ -1,3 +1,18 @@ +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 + +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']) @@ -221,6 +236,77 @@ 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']) + +# -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_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 (comm -23 prints baseline-only entries; expect none). +# 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\' | sort > base.txt && ' + '{compiler} -march=native --print-enabled-cpu-features | ' + 'sed \'s/.*"features":\\[//;s/].*//;s/"//g\' | tr \',\' \'\\n\' | sort > native.txt && ' + 'comm -23 base.txt native.txt']) + +# -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 ===================================== 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/-/compare/19ca1ae48d71b92c7d56672cf406ab3... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/19ca1ae48d71b92c7d56672cf406ab3... You're receiving this email because of your account on gitlab.haskell.org.
participants (1)
-
Simon Jakobi (@sjakobi2)