//! Declares Rust's target feature names for each target.
//! Note that these are similar to but not always identical to LLVM's feature names,
//! and Rust adds some features that do not correspond to LLVM features at all.
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_macros::HashStable_Generic;
use rustc_span::{Symbol, sym};

use crate::spec::{Arch, FloatAbi, LlvmAbi, RustcAbi, Target};

/// Features that control behaviour of rustc, rather than the codegen.
/// These exist globally and are not in the target-specific lists below.
pub const RUSTC_SPECIFIC_FEATURES: &[&str] = &["crt-static"];

/// Stability information for target features.
#[derive(Debug, Copy, Clone, HashStable_Generic)]
pub enum Stability {
    /// This target feature is stable, it can be used in `#[target_feature]` and
    /// `#[cfg(target_feature)]`.
    Stable,
    /// This target feature is unstable. It is only present in `#[cfg(target_feature)]` on
    /// nightly and using it in `#[target_feature]` requires enabling the given nightly feature.
    Unstable(
        /// This must be a *language* feature, or else rustc will ICE when reporting a missing
        /// feature gate!
        Symbol,
    ),
    /// This feature can not be set via `-Ctarget-feature` or `#[target_feature]`, it can only be
    /// set in the target spec. It is never set in `cfg(target_feature)`. Used in
    /// particular for features are actually ABI configuration flags (not all targets are as nice as
    /// RISC-V and have an explicit way to set the ABI separate from target features).
    Forbidden { reason: &'static str },
}
use Stability::*;

impl Stability {
    /// Returns whether the feature can be used in `cfg(target_feature)` ever.
    /// (It might still be nightly-only even if this returns `true`, so make sure to also check
    /// `requires_nightly`.)
    pub fn in_cfg(&self) -> bool {
        matches!(self, Stability::Stable | Stability::Unstable { .. })
    }

    /// Returns the nightly feature that is required to toggle this target feature via
    /// `#[target_feature]`/`-Ctarget-feature` or to test it via `cfg(target_feature)`.
    /// (For `cfg` we only care whether the feature is nightly or not, we don't require
    /// the feature gate to actually be enabled when using a nightly compiler.)
    ///
    /// Before calling this, ensure the feature is even permitted for this use:
    /// - for `#[target_feature]`/`-Ctarget-feature`, check `toggle_allowed()`
    /// - for `cfg(target_feature)`, check `in_cfg()`
    pub fn requires_nightly(&self) -> Option<Symbol> {
        match *self {
            Stability::Unstable(nightly_feature) => Some(nightly_feature),
            Stability::Stable { .. } => None,
            Stability::Forbidden { .. } => panic!("forbidden features should not reach this far"),
        }
    }

    /// Returns whether the feature may be toggled via `#[target_feature]` or `-Ctarget-feature`.
    /// (It might still be nightly-only even if this returns `true`, so make sure to also check
    /// `requires_nightly`.)
    pub fn toggle_allowed(&self) -> Result<(), &'static str> {
        match self {
            Stability::Unstable(_) | Stability::Stable { .. } => Ok(()),
            Stability::Forbidden { reason } => Err(reason),
        }
    }
}

// Here we list target features that rustc "understands": they can be used in `#[target_feature]`
// and `#[cfg(target_feature)]`. They also do not trigger any warnings when used with
// `-Ctarget-feature`.
//
// Note that even unstable (and even entirely unlisted) features can be used with `-Ctarget-feature`
// on stable. Using a feature not on the list of Rust target features only emits a warning.
// Only `cfg(target_feature)` and `#[target_feature]` actually do any stability gating.
// `cfg(target_feature)` for unstable features just works on nightly without any feature gate.
// `#[target_feature]` requires a feature gate.
//
// When adding features to the below lists
// check whether they're named already elsewhere in rust
// e.g. in stdarch and whether the given name matches LLVM's
// if it doesn't, to_llvm_feature in llvm_util in rustc_codegen_llvm needs to be adapted.
// Additionally, if the feature is not available in older version of LLVM supported by the current
// rust, the same function must be updated to filter out these features to avoid triggering
// warnings.
//
// Also note that all target features listed here must be purely additive: for target_feature 1.1 to
// be sound, we can never allow features like `+soft-float` (on x86) to be controlled on a
// per-function level, since we would then allow safe calls from functions with `+soft-float` to
// functions without that feature!
//
// It is important for soundness to consider the interaction of targets features and the function
// call ABI. For example, disabling the `x87` feature on x86 changes how scalar floats are passed as
// arguments, so letting people toggle that feature would be unsound. To this end, the
// `abi_required_features` function computes which target features must and must not be enabled for
// any given target, and individual features can also be marked as `Forbidden`.
// See https://github.com/rust-lang/rust/issues/116344 for some more context.
//
// The one exception to features that change the ABI is features that enable larger vector
// registers. Those are permitted to be listed here. The `*_FOR_CORRECT_VECTOR_ABI` arrays store
// information about which target feature is ABI-required for which vector size; this is used to
// ensure that vectors can only be passed via `extern "C"` when the right feature is enabled. (For
// the "Rust" ABI we generally pass vectors by-ref exactly to avoid these issues.)
// Also see https://github.com/rust-lang/rust/issues/116558.
//
// Stabilizing a target feature requires t-lang approval.

// If feature A "implies" feature B, then:
// - when A gets enabled (via `-Ctarget-feature` or `#[target_feature]`), we also enable B
// - when B gets disabled (via `-Ctarget-feature`), we also disable A
//
// Both of these are also applied transitively.
type ImpliedFeatures = &'static [&'static str];

static ARM_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
    // tidy-alphabetical-start
    ("aclass", Unstable(sym::arm_target_feature), &[]),
    ("aes", Unstable(sym::arm_target_feature), &["neon"]),
    (
        "atomics-32",
        Stability::Forbidden { reason: "unsound because it changes the ABI of atomic operations" },
        &[],
    ),
    ("crc", Unstable(sym::arm_target_feature), &[]),
    ("d32", Unstable(sym::arm_target_feature), &[]),
    ("dotprod", Unstable(sym::arm_target_feature), &["neon"]),
    ("dsp", Unstable(sym::arm_target_feature), &[]),
    ("fp-armv8", Unstable(sym::arm_target_feature), &["vfp4"]),
    ("fp16", Unstable(sym::arm_target_feature), &["neon"]),
    ("fpregs", Unstable(sym::arm_target_feature), &[]),
    ("i8mm", Unstable(sym::arm_target_feature), &["neon"]),
    ("mclass", Unstable(sym::arm_target_feature), &[]),
    ("neon", Unstable(sym::arm_target_feature), &["vfp3"]),
    ("rclass", Unstable(sym::arm_target_feature), &[]),
    ("sha2", Unstable(sym::arm_target_feature), &["neon"]),
    // This can be *disabled* on non-`hf` targets to enable the use
    // of hardfloats while keeping the softfloat ABI.
    // FIXME before stabilization: Should we expose this as a `hard-float` target feature instead of
    // matching the odd negative feature LLVM uses?
    ("soft-float", Unstable(sym::arm_target_feature), &[]),
    // This is needed for inline assembly, but shouldn't be stabilized as-is
    // since it should be enabled per-function using #[instruction_set], not
    // #[target_feature].
    ("thumb-mode", Unstable(sym::arm_target_feature), &[]),
    ("thumb2", Unstable(sym::arm_target_feature), &[]),
    ("trustzone", Unstable(sym::arm_target_feature), &[]),
    ("v5te", Unstable(sym::arm_target_feature), &[]),
    ("v6", Unstable(sym::arm_target_feature), &["v5te"]),
    ("v6k", Unstable(sym::arm_target_feature), &["v6"]),
    ("v6t2", Unstable(sym::arm_target_feature), &["v6k", "thumb2"]),
    ("v7", Unstable(sym::arm_target_feature), &["v6t2"]),
    ("v8", Unstable(sym::arm_target_feature), &["v7"]),
    ("vfp2", Unstable(sym::arm_target_feature), &[]),
    ("vfp3", Unstable(sym::arm_target_feature), &["vfp2", "d32"]),
    ("vfp4", Unstable(sym::arm_target_feature), &["vfp3"]),
    ("virtualization", Unstable(sym::arm_target_feature), &[]),
    // tidy-alphabetical-end
];

static AARCH64_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
    // tidy-alphabetical-start
    // FEAT_AES & FEAT_PMULL
    ("aes", Stable, &["neon"]),
    // FEAT_BF16
    ("bf16", Stable, &[]),
    // FEAT_BTI
    ("bti", Stable, &[]),
    // FEAT_CRC
    ("crc", Stable, &[]),
    // FEAT_CSSC
    ("cssc", Unstable(sym::aarch64_unstable_target_feature), &[]),
    // FEAT_DIT
    ("dit", Stable, &[]),
    // FEAT_DotProd
    ("dotprod", Stable, &["neon"]),
    // FEAT_DPB
    ("dpb", Stable, &[]),
    // FEAT_DPB2
    ("dpb2", Stable, &["dpb"]),
    // FEAT_ECV
    ("ecv", Unstable(sym::aarch64_unstable_target_feature), &[]),
    // FEAT_F32MM
    ("f32mm", Stable, &["sve"]),
    // FEAT_F64MM
    ("f64mm", Stable, &["sve"]),
    // FEAT_FAMINMAX
    ("faminmax", Unstable(sym::aarch64_unstable_target_feature), &[]),
    // FEAT_FCMA
    ("fcma", Stable, &["neon"]),
    // FEAT_FHM
    ("fhm", Stable, &["fp16"]),
    // FEAT_FLAGM
    ("flagm", Stable, &[]),
    // FEAT_FLAGM2
    ("flagm2", Unstable(sym::aarch64_unstable_target_feature), &[]),
    // We forbid directly toggling just `fp-armv8`; it must be toggled with `neon`.
    ("fp-armv8", Stability::Forbidden { reason: "Rust ties `fp-armv8` to `neon`" }, &[]),
    // FEAT_FP8
    ("fp8", Unstable(sym::aarch64_unstable_target_feature), &["faminmax", "lut", "bf16"]),
    // FEAT_FP8DOT2
    ("fp8dot2", Unstable(sym::aarch64_unstable_target_feature), &["fp8dot4"]),
    // FEAT_FP8DOT4
    ("fp8dot4", Unstable(sym::aarch64_unstable_target_feature), &["fp8fma"]),
    // FEAT_FP8FMA
    ("fp8fma", Unstable(sym::aarch64_unstable_target_feature), &["fp8"]),
    // FEAT_FP16
    // Rust ties FP and Neon: https://github.com/rust-lang/rust/pull/91608
    ("fp16", Stable, &["neon"]),
    // FEAT_FRINTTS
    ("frintts", Stable, &[]),
    // FEAT_HBC
    ("hbc", Unstable(sym::aarch64_unstable_target_feature), &[]),
    // FEAT_I8MM
    ("i8mm", Stable, &[]),
    // FEAT_JSCVT
    // Rust ties FP and Neon: https://github.com/rust-lang/rust/pull/91608
    ("jsconv", Stable, &["neon"]),
    // FEAT_LOR
    ("lor", Stable, &[]),
    // FEAT_LSE
    ("lse", Stable, &[]),
    // FEAT_LSE2
    ("lse2", Unstable(sym::aarch64_unstable_target_feature), &[]),
    // FEAT_LSE128
    ("lse128", Unstable(sym::aarch64_unstable_target_feature), &["lse"]),
    // FEAT_LUT
    ("lut", Unstable(sym::aarch64_unstable_target_feature), &[]),
    // FEAT_MOPS
    ("mops", Unstable(sym::aarch64_unstable_target_feature), &[]),
    // FEAT_MTE & FEAT_MTE2
    ("mte", Stable, &[]),
    // FEAT_AdvSimd & FEAT_FP
    ("neon", Stable, &[]),
    // Backend option to turn atomic operations into an intrinsic call when `lse` is not known to be
    // available, so the intrinsic can do runtime LSE feature detection rather than unconditionally
    // using slower non-LSE operations. Unstable since it doesn't need to user-togglable.
    ("outline-atomics", Unstable(sym::aarch64_unstable_target_feature), &[]),
    // FEAT_PAUTH (address authentication)
    ("paca", Stable, &[]),
    // FEAT_PAUTH (generic authentication)
    ("pacg", Stable, &[]),
    // FEAT_PAN
    ("pan", Stable, &[]),
    // FEAT_PAuth_LR
    ("pauth-lr", Unstable(sym::aarch64_unstable_target_feature), &[]),
    // FEAT_PMUv3
    ("pmuv3", Stable, &[]),
    // FEAT_RNG
    ("rand", Stable, &[]),
    // FEAT_RAS & FEAT_RASv1p1
    ("ras", Stable, &[]),
    // FEAT_LRCPC
    ("rcpc", Stable, &[]),
    // FEAT_LRCPC2
    ("rcpc2", Stable, &["rcpc"]),
    // FEAT_LRCPC3
    ("rcpc3", Unstable(sym::aarch64_unstable_target_feature), &["rcpc2"]),
    // FEAT_RDM
    ("rdm", Stable, &["neon"]),
    ("reserve-x18", Forbidden { reason: "use `-Zfixed-x18` compiler flag instead" }, &[]),
    // FEAT_SB
    ("sb", Stable, &[]),
    // FEAT_SHA1 & FEAT_SHA256
    ("sha2", Stable, &["neon"]),
    // FEAT_SHA512 & FEAT_SHA3
    ("sha3", Stable, &["sha2"]),
    // FEAT_SM3 & FEAT_SM4
    ("sm4", Stable, &["neon"]),
    // FEAT_SME
    ("sme", Unstable(sym::aarch64_unstable_target_feature), &["bf16"]),
    // FEAT_SME_B16B16
    ("sme-b16b16", Unstable(sym::aarch64_unstable_target_feature), &["bf16", "sme2", "sve-b16b16"]),
    // FEAT_SME_F8F16
    ("sme-f8f16", Unstable(sym::aarch64_unstable_target_feature), &["sme-f8f32"]),
    // FEAT_SME_F8F32
    ("sme-f8f32", Unstable(sym::aarch64_unstable_target_feature), &["sme2", "fp8"]),
    // FEAT_SME_F16F16
    ("sme-f16f16", Unstable(sym::aarch64_unstable_target_feature), &["sme2"]),
    // FEAT_SME_F64F64
    ("sme-f64f64", Unstable(sym::aarch64_unstable_target_feature), &["sme"]),
    // FEAT_SME_FA64
    ("sme-fa64", Unstable(sym::aarch64_unstable_target_feature), &["sme", "sve2"]),
    // FEAT_SME_I16I64
    ("sme-i16i64", Unstable(sym::aarch64_unstable_target_feature), &["sme"]),
    // FEAT_SME_LUTv2
    ("sme-lutv2", Unstable(sym::aarch64_unstable_target_feature), &[]),
    // FEAT_SME2
    ("sme2", Unstable(sym::aarch64_unstable_target_feature), &["sme"]),
    // FEAT_SME2p1
    ("sme2p1", Unstable(sym::aarch64_unstable_target_feature), &["sme2"]),
    // FEAT_SPE
    ("spe", Stable, &[]),
    // FEAT_SSBS & FEAT_SSBS2
    ("ssbs", Stable, &[]),
    // FEAT_SSVE_FP8FDOT2
    ("ssve-fp8dot2", Unstable(sym::aarch64_unstable_target_feature), &["ssve-fp8dot4"]),
    // FEAT_SSVE_FP8FDOT4
    ("ssve-fp8dot4", Unstable(sym::aarch64_unstable_target_feature), &["ssve-fp8fma"]),
    // FEAT_SSVE_FP8FMA
    ("ssve-fp8fma", Unstable(sym::aarch64_unstable_target_feature), &["sme2", "fp8"]),
    // FEAT_SVE
    // It was decided that SVE requires Neon: https://github.com/rust-lang/rust/pull/91608
    //
    // LLVM doesn't enable Neon for SVE. ARM indicates that they're separate, but probably always
    // exist together: https://developer.arm.com/documentation/102340/0100/New-features-in-SVE2
    //
    // "For backwards compatibility, Neon and VFP are required in the latest architectures."
    ("sve", Stable, &["neon"]),
    // FEAT_SVE_B16B16 (SVE or SME Z-targeting instructions)
    ("sve-b16b16", Unstable(sym::aarch64_unstable_target_feature), &["bf16"]),
    // FEAT_SVE2
    ("sve2", Stable, &["sve"]),
    // FEAT_SVE_AES & FEAT_SVE_PMULL128
    ("sve2-aes", Stable, &["sve2", "aes"]),
    // FEAT_SVE2_BitPerm
    ("sve2-bitperm", Stable, &["sve2"]),
    // FEAT_SVE2_SHA3
    ("sve2-sha3", Stable, &["sve2", "sha3"]),
    // FEAT_SVE2_SM4
    ("sve2-sm4", Stable, &["sve2", "sm4"]),
    // FEAT_SVE2p1
    ("sve2p1", Unstable(sym::aarch64_unstable_target_feature), &["sve2"]),
    // FEAT_TME
    ("tme", Stable, &[]),
    (
        "v8.1a",
        Unstable(sym::aarch64_ver_target_feature),
        &["crc", "lse", "rdm", "pan", "lor", "vh"],
    ),
    ("v8.2a", Unstable(sym::aarch64_ver_target_feature), &["v8.1a", "ras", "dpb"]),
    (
        "v8.3a",
        Unstable(sym::aarch64_ver_target_feature),
        &["v8.2a", "rcpc", "paca", "pacg", "jsconv"],
    ),
    ("v8.4a", Unstable(sym::aarch64_ver_target_feature), &["v8.3a", "dotprod", "dit", "flagm"]),
    ("v8.5a", Unstable(sym::aarch64_ver_target_feature), &["v8.4a", "ssbs", "sb", "dpb2", "bti"]),
    ("v8.6a", Unstable(sym::aarch64_ver_target_feature), &["v8.5a", "bf16", "i8mm"]),
    ("v8.7a", Unstable(sym::aarch64_ver_target_feature), &["v8.6a", "wfxt"]),
    ("v8.8a", Unstable(sym::aarch64_ver_target_feature), &["v8.7a", "hbc", "mops"]),
    ("v8.9a", Unstable(sym::aarch64_ver_target_feature), &["v8.8a", "cssc"]),
    ("v9.1a", Unstable(sym::aarch64_ver_target_feature), &["v9a", "v8.6a"]),
    ("v9.2a", Unstable(sym::aarch64_ver_target_feature), &["v9.1a", "v8.7a"]),
    ("v9.3a", Unstable(sym::aarch64_ver_target_feature), &["v9.2a", "v8.8a"]),
    ("v9.4a", Unstable(sym::aarch64_ver_target_feature), &["v9.3a", "v8.9a"]),
    ("v9.5a", Unstable(sym::aarch64_ver_target_feature), &["v9.4a"]),
    ("v9a", Unstable(sym::aarch64_ver_target_feature), &["v8.5a", "sve2"]),
    // FEAT_VHE
    ("vh", Stable, &[]),
    // FEAT_WFxT
    ("wfxt", Unstable(sym::aarch64_unstable_target_feature), &[]),
    // tidy-alphabetical-end
];

const AARCH64_TIED_FEATURES: &[&[&str]] = &[
    &["paca", "pacg"], // Together these represent `pauth` in LLVM
];

static X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
    // tidy-alphabetical-start
    ("adx", Stable, &[]),
    ("aes", Stable, &["sse2"]),
    ("amx-avx512", Unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
    ("amx-bf16", Unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
    ("amx-complex", Unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
    ("amx-fp8", Unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
    ("amx-fp16", Unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
    ("amx-int8", Unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
    ("amx-movrs", Unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
    ("amx-tf32", Unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
    ("amx-tile", Unstable(sym::x86_amx_intrinsics), &[]),
    ("apxf", Unstable(sym::apx_target_feature), &[]),
    ("avx", Stable, &["sse4.2"]),
    ("avx2", Stable, &["avx"]),
    (
        "avx10.1",
        Unstable(sym::avx10_target_feature),
        &[
            "avx512bf16",
            "avx512bitalg",
            "avx512bw",
            "avx512cd",
            "avx512dq",
            "avx512f",
            "avx512fp16",
            "avx512ifma",
            "avx512vbmi",
            "avx512vbmi2",
            "avx512vl",
            "avx512vnni",
            "avx512vpopcntdq",
        ],
    ),
    (
        "avx10.2",
        Unstable(sym::avx10_target_feature),
        &["avx10.1", "avxvnni", "avxvnniint8", "avxvnniint16"],
    ),
    ("avx512bf16", Stable, &["avx512bw"]),
    ("avx512bitalg", Stable, &["avx512bw"]),
    ("avx512bw", Stable, &["avx512f"]),
    ("avx512cd", Stable, &["avx512f"]),
    ("avx512dq", Stable, &["avx512f"]),
    ("avx512f", Stable, &["avx2", "fma", "f16c"]),
    ("avx512fp16", Stable, &["avx512bw"]),
    ("avx512ifma", Stable, &["avx512f"]),
    ("avx512vbmi", Stable, &["avx512bw"]),
    ("avx512vbmi2", Stable, &["avx512bw"]),
    ("avx512vl", Stable, &["avx512f"]),
    ("avx512vnni", Stable, &["avx512f"]),
    ("avx512vp2intersect", Stable, &["avx512f"]),
    ("avx512vpopcntdq", Stable, &["avx512f"]),
    ("avxifma", Stable, &["avx2"]),
    ("avxneconvert", Stable, &["avx2"]),
    ("avxvnni", Stable, &["avx2"]),
    ("avxvnniint8", Stable, &["avx2"]),
    ("avxvnniint16", Stable, &["avx2"]),
    ("bmi1", Stable, &[]),
    ("bmi2", Stable, &[]),
    ("cmpxchg16b", Stable, &[]),
    ("ermsb", Unstable(sym::ermsb_target_feature), &[]),
    ("f16c", Stable, &["avx"]),
    ("fma", Stable, &["avx"]),
    ("fxsr", Stable, &[]),
    ("gfni", Stable, &["sse2"]),
    ("kl", Stable, &["sse2"]),
    ("lahfsahf", Unstable(sym::lahfsahf_target_feature), &[]),
    ("lzcnt", Stable, &[]),
    ("movbe", Stable, &[]),
    ("movrs", Unstable(sym::movrs_target_feature), &[]),
    ("pclmulqdq", Stable, &["sse2"]),
    ("popcnt", Stable, &[]),
    ("prfchw", Unstable(sym::prfchw_target_feature), &[]),
    ("rdrand", Stable, &[]),
    ("rdseed", Stable, &[]),
    (
        "retpoline-external-thunk",
        Stability::Forbidden { reason: "use `-Zretpoline-external-thunk` compiler flag instead" },
        &[],
    ),
    (
        "retpoline-indirect-branches",
        Stability::Forbidden { reason: "use `-Zretpoline` compiler flag instead" },
        &[],
    ),
    (
        "retpoline-indirect-calls",
        Stability::Forbidden { reason: "use `-Zretpoline` compiler flag instead" },
        &[],
    ),
    ("rtm", Unstable(sym::rtm_target_feature), &[]),
    ("sha", Stable, &["sse2"]),
    ("sha512", Stable, &["avx2"]),
    ("sm3", Stable, &["avx"]),
    ("sm4", Stable, &["avx2"]),
    ("soft-float", Stability::Forbidden { reason: "use a soft-float target instead" }, &[]),
    ("sse", Stable, &[]),
    ("sse2", Stable, &["sse"]),
    ("sse3", Stable, &["sse2"]),
    ("sse4.1", Stable, &["ssse3"]),
    ("sse4.2", Stable, &["sse4.1"]),
    ("sse4a", Stable, &["sse3"]),
    ("ssse3", Stable, &["sse3"]),
    ("tbm", Stable, &[]),
    ("vaes", Stable, &["avx2", "aes"]),
    ("vpclmulqdq", Stable, &["avx", "pclmulqdq"]),
    ("widekl", Stable, &["kl"]),
    ("x87", Unstable(sym::x87_target_feature), &[]),
    ("xop", Unstable(sym::xop_target_feature), &[/*"fma4", */ "avx", "sse4a"]),
    ("xsave", Stable, &[]),
    ("xsavec", Stable, &["xsave"]),
    ("xsaveopt", Stable, &["xsave"]),
    ("xsaves", Stable, &["xsave"]),
    // tidy-alphabetical-end
];

const HEXAGON_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
    // tidy-alphabetical-start
    ("hvx", Unstable(sym::hexagon_target_feature), &[]),
    ("hvx-ieee-fp", Unstable(sym::hexagon_target_feature), &["hvx"]),
    ("hvx-length64b", Unstable(sym::hexagon_target_feature), &["hvx"]),
    ("hvx-length128b", Unstable(sym::hexagon_target_feature), &["hvx"]),
    ("hvx-qfloat", Unstable(sym::hexagon_target_feature), &["hvx"]),
    ("hvxv60", Unstable(sym::hexagon_target_feature), &["hvx"]),
    ("hvxv62", Unstable(sym::hexagon_target_feature), &["hvxv60"]),
    ("hvxv65", Unstable(sym::hexagon_target_feature), &["hvxv62"]),
    ("hvxv66", Unstable(sym::hexagon_target_feature), &["hvxv65", "zreg"]),
    ("hvxv67", Unstable(sym::hexagon_target_feature), &["hvxv66"]),
    ("hvxv68", Unstable(sym::hexagon_target_feature), &["hvxv67"]),
    ("hvxv69", Unstable(sym::hexagon_target_feature), &["hvxv68"]),
    ("hvxv71", Unstable(sym::hexagon_target_feature), &["hvxv69"]),
    ("hvxv73", Unstable(sym::hexagon_target_feature), &["hvxv71"]),
    ("hvxv75", Unstable(sym::hexagon_target_feature), &["hvxv73"]),
    ("hvxv79", Unstable(sym::hexagon_target_feature), &["hvxv75"]),
    ("zreg", Unstable(sym::hexagon_target_feature), &[]),
    // tidy-alphabetical-end
];

static POWERPC_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
    // tidy-alphabetical-start
    ("altivec", Unstable(sym::powerpc_target_feature), &[]),
    ("msync", Unstable(sym::powerpc_target_feature), &[]),
    ("partword-atomics", Unstable(sym::powerpc_target_feature), &[]),
    ("power8-altivec", Unstable(sym::powerpc_target_feature), &["altivec"]),
    ("power8-crypto", Unstable(sym::powerpc_target_feature), &["power8-altivec"]),
    ("power8-vector", Unstable(sym::powerpc_target_feature), &["vsx", "power8-altivec"]),
    ("power9-altivec", Unstable(sym::powerpc_target_feature), &["power8-altivec"]),
    ("power9-vector", Unstable(sym::powerpc_target_feature), &["power8-vector", "power9-altivec"]),
    ("power10-vector", Unstable(sym::powerpc_target_feature), &["power9-vector"]),
    ("quadword-atomics", Unstable(sym::powerpc_target_feature), &[]),
    ("vsx", Unstable(sym::powerpc_target_feature), &["altivec"]),
    // tidy-alphabetical-end
];

const MIPS_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
    // tidy-alphabetical-start
    ("fp64", Unstable(sym::mips_target_feature), &[]),
    ("msa", Unstable(sym::mips_target_feature), &[]),
    ("virt", Unstable(sym::mips_target_feature), &[]),
    // tidy-alphabetical-end
];

const NVPTX_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
    // tidy-alphabetical-start
    ("sm_20", Unstable(sym::nvptx_target_feature), &[]),
    ("sm_21", Unstable(sym::nvptx_target_feature), &["sm_20"]),
    ("sm_30", Unstable(sym::nvptx_target_feature), &["sm_21"]),
    ("sm_32", Unstable(sym::nvptx_target_feature), &["sm_30"]),
    ("sm_35", Unstable(sym::nvptx_target_feature), &["sm_32"]),
    ("sm_37", Unstable(sym::nvptx_target_feature), &["sm_35"]),
    ("sm_50", Unstable(sym::nvptx_target_feature), &["sm_37"]),
    ("sm_52", Unstable(sym::nvptx_target_feature), &["sm_50"]),
    ("sm_53", Unstable(sym::nvptx_target_feature), &["sm_52"]),
    ("sm_60", Unstable(sym::nvptx_target_feature), &["sm_53"]),
    ("sm_61", Unstable(sym::nvptx_target_feature), &["sm_60"]),
    ("sm_62", Unstable(sym::nvptx_target_feature), &["sm_61"]),
    ("sm_70", Unstable(sym::nvptx_target_feature), &["sm_62"]),
    ("sm_72", Unstable(sym::nvptx_target_feature), &["sm_70"]),
    ("sm_75", Unstable(sym::nvptx_target_feature), &["sm_72"]),
    ("sm_80", Unstable(sym::nvptx_target_feature), &["sm_75"]),
    ("sm_86", Unstable(sym::nvptx_target_feature), &["sm_80"]),
    ("sm_87", Unstable(sym::nvptx_target_feature), &["sm_86"]),
    ("sm_89", Unstable(sym::nvptx_target_feature), &["sm_87"]),
    ("sm_90", Unstable(sym::nvptx_target_feature), &["sm_89"]),
    ("sm_90a", Unstable(sym::nvptx_target_feature), &["sm_90"]),
    // tidy-alphabetical-end
    // tidy-alphabetical-start
    ("sm_100", Unstable(sym::nvptx_target_feature), &["sm_90"]),
    ("sm_100a", Unstable(sym::nvptx_target_feature), &["sm_100"]),
    ("sm_101", Unstable(sym::nvptx_target_feature), &["sm_100"]),
    ("sm_101a", Unstable(sym::nvptx_target_feature), &["sm_101"]),
    ("sm_120", Unstable(sym::nvptx_target_feature), &["sm_101"]),
    ("sm_120a", Unstable(sym::nvptx_target_feature), &["sm_120"]),
    // tidy-alphabetical-end
    // tidy-alphabetical-start
    ("ptx32", Unstable(sym::nvptx_target_feature), &[]),
    ("ptx40", Unstable(sym::nvptx_target_feature), &["ptx32"]),
    ("ptx41", Unstable(sym::nvptx_target_feature), &["ptx40"]),
    ("ptx42", Unstable(sym::nvptx_target_feature), &["ptx41"]),
    ("ptx43", Unstable(sym::nvptx_target_feature), &["ptx42"]),
    ("ptx50", Unstable(sym::nvptx_target_feature), &["ptx43"]),
    ("ptx60", Unstable(sym::nvptx_target_feature), &["ptx50"]),
    ("ptx61", Unstable(sym::nvptx_target_feature), &["ptx60"]),
    ("ptx62", Unstable(sym::nvptx_target_feature), &["ptx61"]),
    ("ptx63", Unstable(sym::nvptx_target_feature), &["ptx62"]),
    ("ptx64", Unstable(sym::nvptx_target_feature), &["ptx63"]),
    ("ptx65", Unstable(sym::nvptx_target_feature), &["ptx64"]),
    ("ptx70", Unstable(sym::nvptx_target_feature), &["ptx65"]),
    ("ptx71", Unstable(sym::nvptx_target_feature), &["ptx70"]),
    ("ptx72", Unstable(sym::nvptx_target_feature), &["ptx71"]),
    ("ptx73", Unstable(sym::nvptx_target_feature), &["ptx72"]),
    ("ptx74", Unstable(sym::nvptx_target_feature), &["ptx73"]),
    ("ptx75", Unstable(sym::nvptx_target_feature), &["ptx74"]),
    ("ptx76", Unstable(sym::nvptx_target_feature), &["ptx75"]),
    ("ptx77", Unstable(sym::nvptx_target_feature), &["ptx76"]),
    ("ptx78", Unstable(sym::nvptx_target_feature), &["ptx77"]),
    ("ptx80", Unstable(sym::nvptx_target_feature), &["ptx78"]),
    ("ptx81", Unstable(sym::nvptx_target_feature), &["ptx80"]),
    ("ptx82", Unstable(sym::nvptx_target_feature), &["ptx81"]),
    ("ptx83", Unstable(sym::nvptx_target_feature), &["ptx82"]),
    ("ptx84", Unstable(sym::nvptx_target_feature), &["ptx83"]),
    ("ptx85", Unstable(sym::nvptx_target_feature), &["ptx84"]),
    ("ptx86", Unstable(sym::nvptx_target_feature), &["ptx85"]),
    ("ptx87", Unstable(sym::nvptx_target_feature), &["ptx86"]),
    // tidy-alphabetical-end
];

static RISCV_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
    // tidy-alphabetical-start
    ("a", Stable, &["zaamo", "zalrsc"]),
    ("b", Stable, &["zba", "zbb", "zbs"]),
    ("c", Stable, &["zca"]),
    ("d", Unstable(sym::riscv_target_feature), &["f"]),
    ("e", Unstable(sym::riscv_target_feature), &[]),
    ("f", Unstable(sym::riscv_target_feature), &["zicsr"]),
    (
        "forced-atomics",
        Stability::Forbidden { reason: "unsound because it changes the ABI of atomic operations" },
        &[],
    ),
    ("m", Stable, &[]),
    ("relax", Unstable(sym::riscv_target_feature), &[]),
    (
        "rva23u64",
        Unstable(sym::riscv_target_feature),
        &[
            "m",
            "a",
            "f",
            "d",
            "c",
            "b",
            "v",
            "zicsr",
            "zicntr",
            "zihpm",
            "ziccif",
            "ziccrse",
            "ziccamoa",
            "zicclsm",
            "zic64b",
            "za64rs",
            "zihintpause",
            "zba",
            "zbb",
            "zbs",
            "zicbom",
            "zicbop",
            "zicboz",
            "zfhmin",
            "zkt",
            "zvfhmin",
            "zvbb",
            "zvkt",
            "zihintntl",
            "zicond",
            "zimop",
            "zcmop",
            "zcb",
            "zfa",
            "zawrs",
            "supm",
        ],
    ),
    ("supm", Unstable(sym::riscv_target_feature), &[]),
    ("unaligned-scalar-mem", Unstable(sym::riscv_target_feature), &[]),
    ("unaligned-vector-mem", Unstable(sym::riscv_target_feature), &[]),
    ("v", Unstable(sym::riscv_target_feature), &["zvl128b", "zve64d"]),
    ("za64rs", Stable, &["za128rs"]), // Za64rs ⊃ Za128rs
    ("za128rs", Stable, &[]),
    ("zaamo", Stable, &[]),
    ("zabha", Stable, &["zaamo"]),
    ("zacas", Stable, &["zaamo"]),
    ("zalrsc", Stable, &[]),
    ("zama16b", Stable, &[]),
    ("zawrs", Stable, &[]),
    ("zba", Stable, &[]),
    ("zbb", Stable, &[]),
    ("zbc", Stable, &["zbkc"]), // Zbc ⊃ Zbkc
    ("zbkb", Stable, &[]),
    ("zbkc", Stable, &[]),
    ("zbkx", Stable, &[]),
    ("zbs", Stable, &[]),
    ("zca", Stable, &[]),
    ("zcb", Stable, &["zca"]),
    ("zcmop", Stable, &["zca"]),
    ("zdinx", Unstable(sym::riscv_target_feature), &["zfinx"]),
    ("zfa", Unstable(sym::riscv_target_feature), &["f"]),
    ("zfbfmin", Unstable(sym::riscv_target_feature), &["f"]), // and a subset of Zfhmin
    ("zfh", Unstable(sym::riscv_target_feature), &["zfhmin"]),
    ("zfhmin", Unstable(sym::riscv_target_feature), &["f"]),
    ("zfinx", Unstable(sym::riscv_target_feature), &["zicsr"]),
    ("zhinx", Unstable(sym::riscv_target_feature), &["zhinxmin"]),
    ("zhinxmin", Unstable(sym::riscv_target_feature), &["zfinx"]),
    ("zic64b", Stable, &[]),
    ("zicbom", Stable, &[]),
    ("zicbop", Stable, &[]),
    ("zicboz", Stable, &[]),
    ("ziccamoa", Stable, &[]),
    ("ziccif", Stable, &[]),
    ("zicclsm", Stable, &[]),
    ("ziccrse", Stable, &[]),
    ("zicntr", Stable, &["zicsr"]),
    ("zicond", Stable, &[]),
    ("zicsr", Stable, &[]),
    ("zifencei", Stable, &[]),
    ("zihintntl", Stable, &[]),
    ("zihintpause", Stable, &[]),
    ("zihpm", Stable, &["zicsr"]),
    ("zimop", Stable, &[]),
    ("zk", Stable, &["zkn", "zkr", "zkt"]),
    ("zkn", Stable, &["zbkb", "zbkc", "zbkx", "zkne", "zknd", "zknh"]),
    ("zknd", Stable, &["zkne_or_zknd"]),
    ("zkne", Stable, &["zkne_or_zknd"]),
    ("zkne_or_zknd", Unstable(sym::riscv_target_feature), &[]), // Not an extension
    ("zknh", Stable, &[]),
    ("zkr", Stable, &[]),
    ("zks", Stable, &["zbkb", "zbkc", "zbkx", "zksed", "zksh"]),
    ("zksed", Stable, &[]),
    ("zksh", Stable, &[]),
    ("zkt", Stable, &[]),
    ("ztso", Stable, &[]),
    ("zvbb", Unstable(sym::riscv_target_feature), &["zvkb"]), // Zvbb ⊃ Zvkb
    ("zvbc", Unstable(sym::riscv_target_feature), &["zve64x"]),
    ("zve32f", Unstable(sym::riscv_target_feature), &["zve32x", "f"]),
    ("zve32x", Unstable(sym::riscv_target_feature), &["zvl32b", "zicsr"]),
    ("zve64d", Unstable(sym::riscv_target_feature), &["zve64f", "d"]),
    ("zve64f", Unstable(sym::riscv_target_feature), &["zve32f", "zve64x"]),
    ("zve64x", Unstable(sym::riscv_target_feature), &["zve32x", "zvl64b"]),
    ("zvfbfmin", Unstable(sym::riscv_target_feature), &["zve32f"]),
    ("zvfbfwma", Unstable(sym::riscv_target_feature), &["zfbfmin", "zvfbfmin"]),
    ("zvfh", Unstable(sym::riscv_target_feature), &["zvfhmin", "zve32f", "zfhmin"]), // Zvfh ⊃ Zvfhmin
    ("zvfhmin", Unstable(sym::riscv_target_feature), &["zve32f"]),
    ("zvkb", Unstable(sym::riscv_target_feature), &["zve32x"]),
    ("zvkg", Unstable(sym::riscv_target_feature), &["zve32x"]),
    ("zvkn", Unstable(sym::riscv_target_feature), &["zvkned", "zvknhb", "zvkb", "zvkt"]),
    ("zvknc", Unstable(sym::riscv_target_feature), &["zvkn", "zvbc"]),
    ("zvkned", Unstable(sym::riscv_target_feature), &["zve32x"]),
    ("zvkng", Unstable(sym::riscv_target_feature), &["zvkn", "zvkg"]),
    ("zvknha", Unstable(sym::riscv_target_feature), &["zve32x"]),
    ("zvknhb", Unstable(sym::riscv_target_feature), &["zvknha", "zve64x"]), // Zvknhb ⊃ Zvknha
    ("zvks", Unstable(sym::riscv_target_feature), &["zvksed", "zvksh", "zvkb", "zvkt"]),
    ("zvksc", Unstable(sym::riscv_target_feature), &["zvks", "zvbc"]),
    ("zvksed", Unstable(sym::riscv_target_feature), &["zve32x"]),
    ("zvksg", Unstable(sym::riscv_target_feature), &["zvks", "zvkg"]),
    ("zvksh", Unstable(sym::riscv_target_feature), &["zve32x"]),
    ("zvkt", Unstable(sym::riscv_target_feature), &[]),
    ("zvl32b", Unstable(sym::riscv_target_feature), &[]),
    ("zvl64b", Unstable(sym::riscv_target_feature), &["zvl32b"]),
    ("zvl128b", Unstable(sym::riscv_target_feature), &["zvl64b"]),
    ("zvl256b", Unstable(sym::riscv_target_feature), &["zvl128b"]),
    ("zvl512b", Unstable(sym::riscv_target_feature), &["zvl256b"]),
    ("zvl1024b", Unstable(sym::riscv_target_feature), &["zvl512b"]),
    ("zvl2048b", Unstable(sym::riscv_target_feature), &["zvl1024b"]),
    ("zvl4096b", Unstable(sym::riscv_target_feature), &["zvl2048b"]),
    ("zvl8192b", Unstable(sym::riscv_target_feature), &["zvl4096b"]),
    ("zvl16384b", Unstable(sym::riscv_target_feature), &["zvl8192b"]),
    ("zvl32768b", Unstable(sym::riscv_target_feature), &["zvl16384b"]),
    ("zvl65536b", Unstable(sym::riscv_target_feature), &["zvl32768b"]),
    // tidy-alphabetical-end
];

static WASM_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
    // tidy-alphabetical-start
    ("atomics", Unstable(sym::wasm_target_feature), &[]),
    ("bulk-memory", Stable, &[]),
    ("exception-handling", Unstable(sym::wasm_target_feature), &[]),
    ("extended-const", Stable, &[]),
    ("gc", Unstable(sym::wasm_target_feature), &["reference-types"]),
    ("multivalue", Stable, &[]),
    ("mutable-globals", Stable, &[]),
    ("nontrapping-fptoint", Stable, &[]),
    ("reference-types", Stable, &[]),
    ("relaxed-simd", Stable, &["simd128"]),
    ("sign-ext", Stable, &[]),
    ("simd128", Stable, &[]),
    ("tail-call", Stable, &[]),
    ("wide-arithmetic", Unstable(sym::wasm_target_feature), &[]),
    // tidy-alphabetical-end
];

const BPF_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
    ("alu32", Unstable(sym::bpf_target_feature), &[]),
    ("allows-misaligned-mem-access", Unstable(sym::bpf_target_feature), &[]),
];

static CSKY_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
    // tidy-alphabetical-start
    ("2e3", Unstable(sym::csky_target_feature), &["e2"]),
    ("3e3r1", Unstable(sym::csky_target_feature), &[]),
    ("3e3r2", Unstable(sym::csky_target_feature), &["3e3r1", "doloop"]),
    ("3e3r3", Unstable(sym::csky_target_feature), &["doloop"]),
    ("3e7", Unstable(sym::csky_target_feature), &["2e3"]),
    ("7e10", Unstable(sym::csky_target_feature), &["3e7"]),
    ("10e60", Unstable(sym::csky_target_feature), &["7e10"]),
    ("cache", Unstable(sym::csky_target_feature), &[]),
    ("doloop", Unstable(sym::csky_target_feature), &[]),
    ("dsp1e2", Unstable(sym::csky_target_feature), &[]),
    ("dspe60", Unstable(sym::csky_target_feature), &[]),
    ("e1", Unstable(sym::csky_target_feature), &["elrw"]),
    ("e2", Unstable(sym::csky_target_feature), &["e2"]),
    ("edsp", Unstable(sym::csky_target_feature), &[]),
    ("elrw", Unstable(sym::csky_target_feature), &[]),
    ("float1e2", Unstable(sym::csky_target_feature), &[]),
    ("float1e3", Unstable(sym::csky_target_feature), &[]),
    ("float3e4", Unstable(sym::csky_target_feature), &[]),
    ("float7e60", Unstable(sym::csky_target_feature), &[]),
    ("floate1", Unstable(sym::csky_target_feature), &[]),
    ("hard-tp", Unstable(sym::csky_target_feature), &[]),
    ("high-registers", Unstable(sym::csky_target_feature), &[]),
    ("hwdiv", Unstable(sym::csky_target_feature), &[]),
    ("mp", Unstable(sym::csky_target_feature), &["2e3"]),
    ("mp1e2", Unstable(sym::csky_target_feature), &["3e7"]),
    ("nvic", Unstable(sym::csky_target_feature), &[]),
    ("trust", Unstable(sym::csky_target_feature), &[]),
    ("vdsp2e60f", Unstable(sym::csky_target_feature), &[]),
    ("vdspv1", Unstable(sym::csky_target_feature), &[]),
    ("vdspv2", Unstable(sym::csky_target_feature), &[]),
    // tidy-alphabetical-end
    //fpu
    // tidy-alphabetical-start
    ("fdivdu", Unstable(sym::csky_target_feature), &[]),
    ("fpuv2_df", Unstable(sym::csky_target_feature), &[]),
    ("fpuv2_sf", Unstable(sym::csky_target_feature), &[]),
    ("fpuv3_df", Unstable(sym::csky_target_feature), &[]),
    ("fpuv3_hf", Unstable(sym::csky_target_feature), &[]),
    ("fpuv3_hi", Unstable(sym::csky_target_feature), &[]),
    ("fpuv3_sf", Unstable(sym::csky_target_feature), &[]),
    ("hard-float", Unstable(sym::csky_target_feature), &[]),
    ("hard-float-abi", Unstable(sym::csky_target_feature), &[]),
    // tidy-alphabetical-end
];

static LOONGARCH_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
    // tidy-alphabetical-start
    ("32s", Unstable(sym::loongarch_target_feature), &[]),
    ("d", Stable, &["f"]),
    ("div32", Unstable(sym::loongarch_target_feature), &[]),
    ("f", Stable, &[]),
    ("frecipe", Stable, &[]),
    ("lam-bh", Unstable(sym::loongarch_target_feature), &[]),
    ("lamcas", Unstable(sym::loongarch_target_feature), &[]),
    ("lasx", Stable, &["lsx"]),
    ("lbt", Stable, &[]),
    ("ld-seq-sa", Unstable(sym::loongarch_target_feature), &[]),
    ("lsx", Stable, &["d"]),
    ("lvz", Stable, &[]),
    ("relax", Unstable(sym::loongarch_target_feature), &[]),
    ("scq", Unstable(sym::loongarch_target_feature), &[]),
    ("ual", Unstable(sym::loongarch_target_feature), &[]),
    // tidy-alphabetical-end
];

#[rustfmt::skip]
const IBMZ_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
    // tidy-alphabetical-start
    // For "backchain", https://github.com/rust-lang/rust/issues/142412 is a stabilization blocker
    ("backchain", Unstable(sym::s390x_target_feature), &[]),
    ("concurrent-functions", Unstable(sym::s390x_target_feature), &[]),
    ("deflate-conversion", Unstable(sym::s390x_target_feature), &[]),
    ("enhanced-sort", Unstable(sym::s390x_target_feature), &[]),
    ("guarded-storage", Unstable(sym::s390x_target_feature), &[]),
    ("high-word", Unstable(sym::s390x_target_feature), &[]),
    // LLVM does not define message-security-assist-extension versions 1, 2, 6, 10 and 11.
    ("message-security-assist-extension3", Unstable(sym::s390x_target_feature), &[]),
    ("message-security-assist-extension4", Unstable(sym::s390x_target_feature), &[]),
    ("message-security-assist-extension5", Unstable(sym::s390x_target_feature), &[]),
    ("message-security-assist-extension8", Unstable(sym::s390x_target_feature), &["message-security-assist-extension3"]),
    ("message-security-assist-extension9", Unstable(sym::s390x_target_feature), &["message-security-assist-extension3", "message-security-assist-extension4"]),
    ("message-security-assist-extension12", Unstable(sym::s390x_target_feature), &[]),
    ("miscellaneous-extensions-2", Stable, &[]),
    ("miscellaneous-extensions-3", Stable, &[]),
    ("miscellaneous-extensions-4", Stable, &[]),
    ("nnp-assist", Stable, &["vector"]),
    ("soft-float", Forbidden { reason: "unsupported ABI-configuration feature" }, &[]),
    ("transactional-execution", Unstable(sym::s390x_target_feature), &[]),
    ("vector", Stable, &[]),
    ("vector-enhancements-1", Stable, &["vector"]),
    ("vector-enhancements-2", Stable, &["vector-enhancements-1"]),
    ("vector-enhancements-3", Stable, &["vector-enhancements-2"]),
    ("vector-packed-decimal", Stable, &["vector"]),
    ("vector-packed-decimal-enhancement", Stable, &["vector-packed-decimal"]),
    ("vector-packed-decimal-enhancement-2", Stable, &["vector-packed-decimal-enhancement"]),
    ("vector-packed-decimal-enhancement-3", Stable, &["vector-packed-decimal-enhancement-2"]),
    // tidy-alphabetical-end
];

const SPARC_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
    // tidy-alphabetical-start
    ("leoncasa", Unstable(sym::sparc_target_feature), &[]),
    ("v8plus", Unstable(sym::sparc_target_feature), &[]),
    ("v9", Unstable(sym::sparc_target_feature), &[]),
    // tidy-alphabetical-end
];

static M68K_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
    // tidy-alphabetical-start
    ("isa-68000", Unstable(sym::m68k_target_feature), &[]),
    ("isa-68010", Unstable(sym::m68k_target_feature), &["isa-68000"]),
    ("isa-68020", Unstable(sym::m68k_target_feature), &["isa-68010"]),
    ("isa-68030", Unstable(sym::m68k_target_feature), &["isa-68020"]),
    ("isa-68040", Unstable(sym::m68k_target_feature), &["isa-68030", "isa-68882"]),
    ("isa-68060", Unstable(sym::m68k_target_feature), &["isa-68040"]),
    // FPU
    ("isa-68881", Unstable(sym::m68k_target_feature), &[]),
    ("isa-68882", Unstable(sym::m68k_target_feature), &["isa-68881"]),
    // tidy-alphabetical-end
];

static AVR_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
    // tidy-alphabetical-start
    ("addsubiw", Unstable(sym::avr_target_feature), &[]),
    ("break", Unstable(sym::avr_target_feature), &[]),
    ("eijmpcall", Unstable(sym::avr_target_feature), &[]),
    ("elpm", Unstable(sym::avr_target_feature), &[]),
    ("elpmx", Unstable(sym::avr_target_feature), &[]),
    ("ijmpcall", Unstable(sym::avr_target_feature), &[]),
    ("jmpcall", Unstable(sym::avr_target_feature), &[]),
    ("lowbytefirst", Unstable(sym::avr_target_feature), &[]),
    ("lpm", Unstable(sym::avr_target_feature), &[]),
    ("lpmx", Unstable(sym::avr_target_feature), &[]),
    ("movw", Unstable(sym::avr_target_feature), &[]),
    ("mul", Unstable(sym::avr_target_feature), &[]),
    ("rmw", Unstable(sym::avr_target_feature), &[]),
    ("spm", Unstable(sym::avr_target_feature), &[]),
    ("spmx", Unstable(sym::avr_target_feature), &[]),
    ("sram", Forbidden { reason: "devices that have no SRAM are unsupported" }, &[]),
    ("tinyencoding", Unstable(sym::avr_target_feature), &[]),
    // tidy-alphabetical-end
];

/// When rustdoc is running, provide a list of all known features so that all their respective
/// primitives may be documented.
///
/// IMPORTANT: If you're adding another feature list above, make sure to add it to this iterator!
pub fn all_rust_features() -> impl Iterator<Item = (&'static str, Stability)> {
    std::iter::empty()
        .chain(ARM_FEATURES.iter())
        .chain(AARCH64_FEATURES.iter())
        .chain(X86_FEATURES.iter())
        .chain(HEXAGON_FEATURES.iter())
        .chain(POWERPC_FEATURES.iter())
        .chain(MIPS_FEATURES.iter())
        .chain(NVPTX_FEATURES.iter())
        .chain(RISCV_FEATURES.iter())
        .chain(WASM_FEATURES.iter())
        .chain(BPF_FEATURES.iter())
        .chain(CSKY_FEATURES)
        .chain(LOONGARCH_FEATURES)
        .chain(IBMZ_FEATURES)
        .chain(SPARC_FEATURES)
        .chain(M68K_FEATURES)
        .chain(AVR_FEATURES)
        .cloned()
        .map(|(f, s, _)| (f, s))
}

// These arrays represent the least-constraining feature that is required for vector types up to a
// certain size to have their "proper" ABI on each architecture.
// Note that they must be kept sorted by vector size.
const X86_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI: &'static [(u64, &'static str)] =
    &[(128, "sse"), (256, "avx"), (512, "avx512f")]; // FIXME: might need changes for AVX10.
const AARCH64_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI: &'static [(u64, &'static str)] =
    &[(128, "neon")];

// We might want to add "helium" too.
const ARM_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI: &'static [(u64, &'static str)] =
    &[(128, "neon")];

const AMDGPU_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI: &'static [(u64, &'static str)] =
    &[(1024, "")];
const POWERPC_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI: &'static [(u64, &'static str)] =
    &[(128, "altivec")];
const WASM_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI: &'static [(u64, &'static str)] =
    &[(128, "simd128")];
const S390X_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI: &'static [(u64, &'static str)] =
    &[(128, "vector")];
const RISCV_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI: &'static [(u64, &'static str)] = &[
    (32, "zvl32b"),
    (64, "zvl64b"),
    (128, "zvl128b"),
    (256, "zvl256b"),
    (512, "zvl512b"),
    (1024, "zvl1024b"),
    (2048, "zvl2048b"),
    (4096, "zvl4096b"),
    (8192, "zvl8192b"),
    (16384, "zvl16384b"),
    (32768, "zvl32768b"),
    (65536, "zvl65536b"),
];
// Always error on SPARC, as the necessary target features cannot be enabled in Rust at the moment.
const SPARC_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI: &'static [(u64, &'static str)] =
    &[/*(64, "vis")*/];

const HEXAGON_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI: &'static [(u64, &'static str)] = &[
    (512, "hvx-length64b"),   // HvxVector in 64-byte mode
    (1024, "hvx-length128b"), // HvxVector in 128-byte mode, or HvxVectorPair in 64-byte mode
    (2048, "hvx-length128b"), // HvxVectorPair in 128-byte mode
];
const MIPS_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI: &'static [(u64, &'static str)] =
    &[(128, "msa")];
const CSKY_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI: &'static [(u64, &'static str)] =
    &[(128, "vdspv1")];
const LOONGARCH_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI: &'static [(u64, &'static str)] =
    &[(128, "lsx"), (256, "lasx")];

#[derive(Copy, Clone, Debug)]
pub struct FeatureConstraints {
    /// Features that must be enabled.
    pub required: &'static [&'static str],
    /// Features that must be disabled.
    pub incompatible: &'static [&'static str],
}

impl Target {
    pub fn rust_target_features(&self) -> &'static [(&'static str, Stability, ImpliedFeatures)] {
        match &self.arch {
            Arch::Arm => ARM_FEATURES,
            Arch::AArch64 | Arch::Arm64EC => AARCH64_FEATURES,
            Arch::X86 | Arch::X86_64 => X86_FEATURES,
            Arch::Hexagon => HEXAGON_FEATURES,
            Arch::Mips | Arch::Mips32r6 | Arch::Mips64 | Arch::Mips64r6 => MIPS_FEATURES,
            Arch::Nvptx64 => NVPTX_FEATURES,
            Arch::PowerPC | Arch::PowerPC64 => POWERPC_FEATURES,
            Arch::RiscV32 | Arch::RiscV64 => RISCV_FEATURES,
            Arch::Wasm32 | Arch::Wasm64 => WASM_FEATURES,
            Arch::Bpf => BPF_FEATURES,
            Arch::CSky => CSKY_FEATURES,
            Arch::LoongArch32 | Arch::LoongArch64 => LOONGARCH_FEATURES,
            Arch::S390x => IBMZ_FEATURES,
            Arch::Sparc | Arch::Sparc64 => SPARC_FEATURES,
            Arch::M68k => M68K_FEATURES,
            Arch::Avr => AVR_FEATURES,
            Arch::AmdGpu | Arch::Msp430 | Arch::SpirV | Arch::Xtensa | Arch::Other(_) => &[],
        }
    }

    pub fn features_for_correct_fixed_length_vector_abi(&self) -> &'static [(u64, &'static str)] {
        match &self.arch {
            Arch::X86 | Arch::X86_64 => X86_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI,
            Arch::AArch64 | Arch::Arm64EC => AARCH64_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI,
            Arch::Arm => ARM_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI,
            Arch::PowerPC | Arch::PowerPC64 => POWERPC_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI,
            Arch::LoongArch32 | Arch::LoongArch64 => {
                LOONGARCH_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI
            }
            Arch::RiscV32 | Arch::RiscV64 => RISCV_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI,
            Arch::Wasm32 | Arch::Wasm64 => WASM_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI,
            Arch::S390x => S390X_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI,
            Arch::Sparc | Arch::Sparc64 => SPARC_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI,
            Arch::Hexagon => HEXAGON_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI,
            Arch::Mips | Arch::Mips32r6 | Arch::Mips64 | Arch::Mips64r6 => {
                MIPS_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI
            }
            Arch::AmdGpu => AMDGPU_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI,
            Arch::Nvptx64 | Arch::Bpf | Arch::M68k | Arch::Avr => &[], // no vector ABI
            Arch::CSky => CSKY_FEATURES_FOR_CORRECT_FIXED_LENGTH_VECTOR_ABI,
            // FIXME: for some tier3 targets, we are overly cautious and always give warnings
            // when passing args in vector registers.
            Arch::Msp430 | Arch::SpirV | Arch::Xtensa | Arch::Other(_) => &[],
        }
    }

    pub fn features_for_correct_scalable_vector_abi(&self) -> Option<&'static str> {
        match &self.arch {
            Arch::AArch64 | Arch::Arm64EC => Some("sve"),
            // Other targets have no scalable vectors or they are unimplemented.
            _ => None,
        }
    }

    pub fn tied_target_features(&self) -> &'static [&'static [&'static str]] {
        match &self.arch {
            Arch::AArch64 | Arch::Arm64EC => AARCH64_TIED_FEATURES,
            _ => &[],
        }
    }

    // Note: the returned set includes `base_feature`.
    pub fn implied_target_features<'a>(&self, base_feature: &'a str) -> FxHashSet<&'a str> {
        let implied_features =
            self.rust_target_features().iter().map(|(f, _, i)| (f, i)).collect::<FxHashMap<_, _>>();

        // Implied target features have their own implied target features, so we traverse the
        // map until there are no more features to add.
        let mut features = FxHashSet::default();
        let mut new_features = vec![base_feature];
        while let Some(new_feature) = new_features.pop() {
            if features.insert(new_feature) {
                if let Some(implied_features) = implied_features.get(&new_feature) {
                    new_features.extend(implied_features.iter().copied())
                }
            }
        }
        features
    }

    /// Returns two lists of features:
    /// the first list contains target features that must be enabled for ABI reasons,
    /// and the second list contains target feature that must be disabled for ABI reasons.
    ///
    /// These features are automatically appended to whatever the target spec sets as default
    /// features for the target.
    ///
    /// All features enabled/disabled via `-Ctarget-features` and `#[target_features]` are checked
    /// against this. We also check any implied features, based on the information above. If LLVM
    /// implicitly enables more implied features than we do, that could bypass this check!
    pub fn abi_required_features(&self) -> FeatureConstraints {
        const NOTHING: FeatureConstraints = FeatureConstraints { required: &[], incompatible: &[] };
        // Some architectures don't have a clean explicit ABI designation; instead, the ABI is
        // defined by target features. When that is the case, those target features must be
        // "forbidden" in the list above to ensure that there is a consistent answer to the
        // questions "which ABI is used".
        match &self.arch {
            Arch::X86 => {
                // We use our own ABI indicator here; LLVM does not have anything native.
                // Every case should require or forbid `soft-float`!
                match self.rustc_abi {
                    None => {
                        // Default hardfloat ABI.
                        // x87 must be enabled, soft-float must be disabled.
                        FeatureConstraints { required: &["x87"], incompatible: &["soft-float"] }
                    }
                    Some(RustcAbi::X86Sse2) => {
                        // Extended hardfloat ABI. x87 and SSE2 must be enabled, soft-float must be disabled.
                        FeatureConstraints {
                            required: &["x87", "sse2"],
                            incompatible: &["soft-float"],
                        }
                    }
                    Some(RustcAbi::Softfloat) => {
                        // Softfloat ABI, requires corresponding target feature. That feature trumps
                        // `x87` and all other FPU features so those do not matter.
                        // Note that this one requirement is the entire implementation of the ABI!
                        // LLVM handles the rest.
                        FeatureConstraints { required: &["soft-float"], incompatible: &[] }
                    }
                }
            }
            Arch::X86_64 => {
                // We use our own ABI indicator here; LLVM does not have anything native.
                // Every case should require or forbid `soft-float`!
                match self.rustc_abi {
                    None => {
                        // Default hardfloat ABI. On x86-64, this always includes SSE2.
                        FeatureConstraints {
                            required: &["x87", "sse2"],
                            incompatible: &["soft-float"],
                        }
                    }
                    Some(RustcAbi::Softfloat) => {
                        // Softfloat ABI, requires corresponding target feature. That feature trumps
                        // `x87` and all other FPU features so those do not matter.
                        // Note that this one requirement is the entire implementation of the ABI!
                        // LLVM handles the rest.
                        FeatureConstraints { required: &["soft-float"], incompatible: &[] }
                    }
                    Some(r) => panic!("invalid Rust ABI for x86_64: {r:?}"),
                }
            }
            Arch::Arm => {
                // On ARM, ABI handling is reasonably sane; we use `llvm_floatabi` to indicate
                // to LLVM which ABI we are going for.
                match self.llvm_floatabi.unwrap() {
                    FloatAbi::Soft => {
                        // Nothing special required, will use soft-float ABI throughout.
                        // We can even allow `-soft-float` here; in fact that is useful as it lets
                        // people use FPU instructions with a softfloat ABI (corresponds to
                        // `-mfloat-abi=softfp` in GCC/clang).
                        NOTHING
                    }
                    FloatAbi::Hard => {
                        // Must have `fpregs` and must not have `soft-float`.
                        FeatureConstraints { required: &["fpregs"], incompatible: &["soft-float"] }
                    }
                }
            }
            Arch::AArch64 | Arch::Arm64EC => {
                // Aarch64 has no sane ABI specifier, and LLVM doesn't even have a way to force
                // the use of soft-float, so all we can do here is some crude hacks.
                match self.rustc_abi {
                    Some(RustcAbi::Softfloat) => {
                        // LLVM will use float registers when `fp-armv8` is available, e.g. for
                        // calls to built-ins. The only way to ensure a consistent softfloat ABI
                        // on aarch64 is to never enable `fp-armv8`, so we enforce that.
                        // In Rust we tie `neon` and `fp-armv8` together, therefore `neon` is the
                        // feature we have to mark as incompatible.
                        FeatureConstraints { required: &[], incompatible: &["neon"] }
                    }
                    None => {
                        // Everything else is assumed to use a hardfloat ABI. neon and fp-armv8 must be enabled.
                        // `FeatureConstraints` uses Rust feature names, hence only "neon" shows up.
                        FeatureConstraints { required: &["neon"], incompatible: &[] }
                    }
                    Some(r) => panic!("invalid Rust ABI for aarch64: {r:?}"),
                }
            }
            Arch::RiscV32 | Arch::RiscV64 => {
                // RISC-V handles ABI in a very sane way, being fully explicit via `llvm_abiname`
                // about what the intended ABI is.
                match &self.llvm_abiname {
                    LlvmAbi::Ilp32d | LlvmAbi::Lp64d => {
                        // Requires d (which implies f), incompatible with e and zfinx.
                        FeatureConstraints { required: &["d"], incompatible: &["e", "zfinx"] }
                    }
                    LlvmAbi::Ilp32f | LlvmAbi::Lp64f => {
                        // Requires f, incompatible with e and zfinx.
                        FeatureConstraints { required: &["f"], incompatible: &["e", "zfinx"] }
                    }
                    LlvmAbi::Ilp32 | LlvmAbi::Lp64 => {
                        // Requires nothing, incompatible with e.
                        FeatureConstraints { required: &[], incompatible: &["e"] }
                    }
                    LlvmAbi::Ilp32e => {
                        // ilp32e is documented to be incompatible with features that need aligned
                        // load/stores > 32 bits, like `d`. (One could also just generate more
                        // complicated code to align the stack when needed, but the RISCV
                        // architecture manual just explicitly rules out this combination so we
                        // might as well.)
                        // Note that the `e` feature is not required: the ABI treats the extra
                        // registers as caller-save, so it is safe to use them only in some parts of
                        // a program while the rest doesn't know they even exist.
                        FeatureConstraints { required: &[], incompatible: &["d"] }
                    }
                    LlvmAbi::Lp64e => {
                        // As above, `e` is not required.
                        NOTHING
                    }
                    _ => unreachable!(),
                }
            }
            Arch::LoongArch32 | Arch::LoongArch64 => {
                // LoongArch handles ABI in a very sane way, being fully explicit via `llvm_abiname`
                // about what the intended ABI is.
                match &self.llvm_abiname {
                    LlvmAbi::Ilp32d | LlvmAbi::Lp64d => {
                        // Requires d (which implies f), incompatible with nothing.
                        FeatureConstraints { required: &["d"], incompatible: &[] }
                    }
                    LlvmAbi::Ilp32f | LlvmAbi::Lp64f => {
                        // Requires f, incompatible with nothing.
                        FeatureConstraints { required: &["f"], incompatible: &[] }
                    }
                    LlvmAbi::Ilp32s | LlvmAbi::Lp64s => {
                        // The soft-float ABI does not require any features and is also not
                        // incompatible with any features. Rust targets explicitly specify the
                        // LLVM ABI names, which allows for enabling hard-float support even on
                        // soft-float targets, and ensures that the ABI behavior is as expected.
                        NOTHING
                    }
                    _ => unreachable!(),
                }
            }
            Arch::S390x => {
                // Same as x86, We use our own ABI indicator here;
                // LLVM does not have anything native and will switch ABI based
                // on the soft-float target feature.
                // Every case should require or forbid `soft-float`!
                // The "vector" target feature may only be used without soft-float
                // because the float and vector registers overlap and the
                // standard s390x C ABI may pass vectors via these registers.
                match self.rustc_abi {
                    None => {
                        // Default hardfloat ABI.
                        FeatureConstraints { required: &[], incompatible: &["soft-float"] }
                    }
                    Some(RustcAbi::Softfloat) => {
                        // Softfloat ABI, requires corresponding target feature.
                        // llvm will switch to soft-float ABI just based on this feature.
                        FeatureConstraints { required: &["soft-float"], incompatible: &["vector"] }
                    }
                    Some(r) => {
                        panic!("invalid Rust ABI for s390x: {r:?}");
                    }
                }
            }
            Arch::Avr => {
                // SRAM is minimum requirement for C/C++ in both avr-gcc and Clang,
                // and backends of them only support assembly for devices have no SRAM.
                // See the discussion in https://github.com/rust-lang/rust/pull/146900 for more.
                FeatureConstraints { required: &["sram"], incompatible: &[] }
            }
            _ => NOTHING,
        }
    }
}
