about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_codegen_gcc/src/errors.rs1
-rw-r--r--compiler/rustc_codegen_gcc/src/gcc_util.rs205
-rw-r--r--compiler/rustc_codegen_llvm/messages.ftl4
-rw-r--r--compiler/rustc_codegen_llvm/src/errors.rs7
-rw-r--r--compiler/rustc_codegen_llvm/src/llvm_util.rs230
-rw-r--r--compiler/rustc_codegen_ssa/messages.ftl2
-rw-r--r--compiler/rustc_codegen_ssa/src/target_features.rs61
-rw-r--r--compiler/rustc_middle/src/middle/codegen_fn_attrs.rs1
-rw-r--r--compiler/rustc_middle/src/query/mod.rs2
-rw-r--r--compiler/rustc_target/src/spec/mod.rs30
-rw-r--r--compiler/rustc_target/src/target_features.rs1007
11 files changed, 786 insertions, 764 deletions
diff --git a/compiler/rustc_codegen_gcc/src/errors.rs b/compiler/rustc_codegen_gcc/src/errors.rs
index 56849cc8610..c896246866b 100644
--- a/compiler/rustc_codegen_gcc/src/errors.rs
+++ b/compiler/rustc_codegen_gcc/src/errors.rs
@@ -28,6 +28,7 @@ pub(crate) struct UnstableCTargetFeature<'a> {
 #[diag(codegen_gcc_forbidden_ctarget_feature)]
 pub(crate) struct ForbiddenCTargetFeature<'a> {
     pub feature: &'a str,
+    pub enabled: &'a str,
     pub reason: &'a str,
 }
 
diff --git a/compiler/rustc_codegen_gcc/src/gcc_util.rs b/compiler/rustc_codegen_gcc/src/gcc_util.rs
index 058a874501b..1994a2a3c53 100644
--- a/compiler/rustc_codegen_gcc/src/gcc_util.rs
+++ b/compiler/rustc_codegen_gcc/src/gcc_util.rs
@@ -1,9 +1,11 @@
+use std::iter::FromIterator;
+
 #[cfg(feature = "master")]
 use gccjit::Context;
 use rustc_codegen_ssa::codegen_attrs::check_tied_features;
 use rustc_codegen_ssa::errors::TargetFeatureDisableOrEnable;
-use rustc_data_structures::fx::FxHashMap;
-use rustc_middle::bug;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::unord::UnordSet;
 use rustc_session::Session;
 use rustc_target::target_features::RUSTC_SPECIFIC_FEATURES;
 use smallvec::{SmallVec, smallvec};
@@ -37,82 +39,137 @@ pub(crate) fn global_gcc_features(sess: &Session, diagnostics: bool) -> Vec<Stri
     let mut features = vec![];
 
     // Features implied by an implicit or explicit `--target`.
-    features.extend(
-        sess.target
-            .features
-            .split(',')
-            .filter(|v| !v.is_empty() && backend_feature_name(v).is_some())
-            .map(String::from),
-    );
+    features.extend(sess.target.features.split(',').filter(|v| !v.is_empty()).map(String::from));
 
     // -Ctarget-features
     let known_features = sess.target.rust_target_features();
     let mut featsmap = FxHashMap::default();
-    let feats = sess
-        .opts
-        .cg
-        .target_feature
-        .split(',')
-        .filter_map(|s| {
-            let enable_disable = match s.chars().next() {
-                None => return None,
-                Some(c @ ('+' | '-')) => c,
-                Some(_) => {
-                    if diagnostics {
-                        sess.dcx().emit_warn(UnknownCTargetFeaturePrefix { feature: s });
-                    }
-                    return None;
-                }
-            };
 
-            // Get the backend feature name, if any.
-            // This excludes rustc-specific features, that do not get passed down to GCC.
-            let feature = backend_feature_name(s)?;
-            // Warn against use of GCC specific feature names on the CLI.
+    // Ensure that all ABI-required features are enabled, and the ABI-forbidden ones
+    // are disabled.
+    let abi_feature_constraints = sess.target.abi_required_features();
+    let abi_incompatible_set =
+        FxHashSet::from_iter(abi_feature_constraints.incompatible.iter().copied());
+
+    // Compute implied features
+    let mut all_rust_features = vec![];
+    for feature in sess.opts.cg.target_feature.split(',') {
+        if let Some(feature) = feature.strip_prefix('+') {
+            all_rust_features.extend(
+                UnordSet::from(sess.target.implied_target_features(std::iter::once(feature)))
+                    .to_sorted_stable_ord()
+                    .iter()
+                    .map(|&&s| (true, s)),
+            )
+        } else if let Some(feature) = feature.strip_prefix('-') {
+            // FIXME: Why do we not remove implied features on "-" here?
+            // We do the equivalent above in `target_features_cfg`.
+            // See <https://github.com/rust-lang/rust/issues/134792>.
+            all_rust_features.push((false, feature));
+        } else if !feature.is_empty() {
             if diagnostics {
-                let feature_state = known_features.iter().find(|&&(v, _, _)| v == feature);
-                match feature_state {
-                    None => {
-                        let rust_feature =
-                            known_features.iter().find_map(|&(rust_feature, _, _)| {
-                                let gcc_features = to_gcc_features(sess, rust_feature);
-                                if gcc_features.contains(&feature)
-                                    && !gcc_features.contains(&rust_feature)
-                                {
-                                    Some(rust_feature)
-                                } else {
-                                    None
-                                }
-                            });
-                        let unknown_feature = if let Some(rust_feature) = rust_feature {
-                            UnknownCTargetFeature {
-                                feature,
-                                rust_feature: PossibleFeature::Some { rust_feature },
-                            }
-                        } else {
-                            UnknownCTargetFeature { feature, rust_feature: PossibleFeature::None }
-                        };
-                        sess.dcx().emit_warn(unknown_feature);
-                    }
-                    Some((_, stability, _)) => {
-                        if let Err(reason) =
-                            stability.toggle_allowed(&sess.target, enable_disable == '+')
+                sess.dcx().emit_warn(UnknownCTargetFeaturePrefix { feature });
+            }
+        }
+    }
+    // Remove features that are meant for rustc, not codegen.
+    all_rust_features.retain(|(_, feature)| {
+        // Retain if it is not a rustc feature
+        !RUSTC_SPECIFIC_FEATURES.contains(feature)
+    });
+
+    // Check feature validity.
+    if diagnostics {
+        for &(enable, feature) in &all_rust_features {
+            let feature_state = known_features.iter().find(|&&(v, _, _)| v == feature);
+            match feature_state {
+                None => {
+                    let rust_feature = known_features.iter().find_map(|&(rust_feature, _, _)| {
+                        let gcc_features = to_gcc_features(sess, rust_feature);
+                        if gcc_features.contains(&feature) && !gcc_features.contains(&rust_feature)
                         {
-                            sess.dcx().emit_warn(ForbiddenCTargetFeature { feature, reason });
-                        } else if stability.requires_nightly().is_some() {
-                            // An unstable feature. Warn about using it. (It makes little sense
-                            // to hard-error here since we just warn about fully unknown
-                            // features above).
-                            sess.dcx().emit_warn(UnstableCTargetFeature { feature });
+                            Some(rust_feature)
+                        } else {
+                            None
                         }
+                    });
+                    let unknown_feature = if let Some(rust_feature) = rust_feature {
+                        UnknownCTargetFeature {
+                            feature,
+                            rust_feature: PossibleFeature::Some { rust_feature },
+                        }
+                    } else {
+                        UnknownCTargetFeature { feature, rust_feature: PossibleFeature::None }
+                    };
+                    sess.dcx().emit_warn(unknown_feature);
+                }
+                Some((_, stability, _)) => {
+                    if let Err(reason) = stability.toggle_allowed() {
+                        sess.dcx().emit_warn(ForbiddenCTargetFeature {
+                            feature,
+                            enabled: if enable { "enabled" } else { "disabled" },
+                            reason,
+                        });
+                    } else if stability.requires_nightly().is_some() {
+                        // An unstable feature. Warn about using it. (It makes little sense
+                        // to hard-error here since we just warn about fully unknown
+                        // features above).
+                        sess.dcx().emit_warn(UnstableCTargetFeature { feature });
                     }
                 }
+            }
 
-                // FIXME(nagisa): figure out how to not allocate a full hashset here.
-                featsmap.insert(feature, enable_disable == '+');
+            // Ensure that the features we enable/disable are compatible with the ABI.
+            if enable {
+                if abi_incompatible_set.contains(feature) {
+                    sess.dcx().emit_warn(ForbiddenCTargetFeature {
+                        feature,
+                        enabled: "enabled",
+                        reason: "this feature is incompatible with the target ABI",
+                    });
+                }
+            } else {
+                // FIXME: we have to request implied features here since
+                // negative features do not handle implied features above.
+                for &required in abi_feature_constraints.required.iter() {
+                    let implied = sess.target.implied_target_features(std::iter::once(required));
+                    if implied.contains(feature) {
+                        sess.dcx().emit_warn(ForbiddenCTargetFeature {
+                            feature,
+                            enabled: "disabled",
+                            reason: "this feature is required by the target ABI",
+                        });
+                    }
+                }
             }
 
-            // ... otherwise though we run through `to_gcc_features` when
+            // FIXME(nagisa): figure out how to not allocate a full hashset here.
+            featsmap.insert(feature, enable);
+        }
+    }
+
+    // To be sure the ABI-relevant features are all in the right state, we explicitly
+    // (un)set them here. This means if the target spec sets those features wrong,
+    // we will silently correct them rather than silently producing wrong code.
+    // (The target sanity check tries to catch this, but we can't know which features are
+    // enabled in GCC by default so we can't be fully sure about that check.)
+    // We add these at the beginning of the list so that `-Ctarget-features` can
+    // still override it... that's unsound, but more compatible with past behavior.
+    all_rust_features.splice(
+        0..0,
+        abi_feature_constraints
+            .required
+            .iter()
+            .map(|&f| (true, f))
+            .chain(abi_feature_constraints.incompatible.iter().map(|&f| (false, f))),
+    );
+
+    // Translate this into GCC features.
+    let feats = all_rust_features
+        .iter()
+        .filter_map(|&(enable, feature)| {
+            let enable_disable = if enable { '+' } else { '-' };
+            // We run through `to_gcc_features` when
             // passing requests down to GCC. This means that all in-language
             // features also work on the command line instead of having two
             // different names when the GCC name and the Rust name differ.
@@ -146,26 +203,12 @@ pub(crate) fn global_gcc_features(sess: &Session, diagnostics: bool) -> Vec<Stri
     features
 }
 
-/// Returns a feature name for the given `+feature` or `-feature` string.
-///
-/// Only allows features that are backend specific (i.e. not [`RUSTC_SPECIFIC_FEATURES`].)
-fn backend_feature_name(s: &str) -> Option<&str> {
-    // features must start with a `+` or `-`.
-    let feature = s.strip_prefix(&['+', '-'][..]).unwrap_or_else(|| {
-        bug!("target feature `{}` must begin with a `+` or `-`", s);
-    });
-    // Rustc-specific feature requests like `+crt-static` or `-crt-static`
-    // are not passed down to GCC.
-    if RUSTC_SPECIFIC_FEATURES.contains(&feature) {
-        return None;
-    }
-    Some(feature)
-}
-
 // To find a list of GCC's names, check https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html
 pub fn to_gcc_features<'a>(sess: &Session, s: &'a str) -> SmallVec<[&'a str; 2]> {
     let arch = if sess.target.arch == "x86_64" { "x86" } else { &*sess.target.arch };
     match (arch, s) {
+        // FIXME: seems like x87 does not exist?
+        ("x86", "x87") => smallvec![],
         ("x86", "sse4.2") => smallvec!["sse4.2", "crc32"],
         ("x86", "pclmulqdq") => smallvec!["pclmul"],
         ("x86", "rdrand") => smallvec!["rdrnd"],
diff --git a/compiler/rustc_codegen_llvm/messages.ftl b/compiler/rustc_codegen_llvm/messages.ftl
index 3982c37528d..9585848cbf0 100644
--- a/compiler/rustc_codegen_llvm/messages.ftl
+++ b/compiler/rustc_codegen_llvm/messages.ftl
@@ -10,7 +10,7 @@ codegen_llvm_dynamic_linking_with_lto =
 codegen_llvm_fixed_x18_invalid_arch = the `-Zfixed-x18` flag is not supported on the `{$arch}` architecture
 
 codegen_llvm_forbidden_ctarget_feature =
-    target feature `{$feature}` cannot be toggled with `-Ctarget-feature`: {$reason}
+    target feature `{$feature}` cannot be {$enabled} with `-Ctarget-feature`: {$reason}
     .note = this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
 codegen_llvm_forbidden_ctarget_feature_issue = for more information, see issue #116344 <https://github.com/rust-lang/rust/issues/116344>
 
@@ -24,8 +24,6 @@ codegen_llvm_invalid_minimum_alignment_not_power_of_two =
 codegen_llvm_invalid_minimum_alignment_too_large =
     invalid minimum global alignment: {$align} is too large
 
-codegen_llvm_invalid_target_feature_prefix = target feature `{$feature}` must begin with a `+` or `-`"
-
 codegen_llvm_load_bitcode = failed to load bitcode of module "{$name}"
 codegen_llvm_load_bitcode_with_llvm_err = failed to load bitcode of module "{$name}": {$llvm_err}
 
diff --git a/compiler/rustc_codegen_llvm/src/errors.rs b/compiler/rustc_codegen_llvm/src/errors.rs
index f340b06e876..f4c9491f758 100644
--- a/compiler/rustc_codegen_llvm/src/errors.rs
+++ b/compiler/rustc_codegen_llvm/src/errors.rs
@@ -37,6 +37,7 @@ pub(crate) struct UnstableCTargetFeature<'a> {
 #[note(codegen_llvm_forbidden_ctarget_feature_issue)]
 pub(crate) struct ForbiddenCTargetFeature<'a> {
     pub feature: &'a str,
+    pub enabled: &'a str,
     pub reason: &'a str,
 }
 
@@ -214,12 +215,6 @@ pub(crate) struct MismatchedDataLayout<'a> {
 }
 
 #[derive(Diagnostic)]
-#[diag(codegen_llvm_invalid_target_feature_prefix)]
-pub(crate) struct InvalidTargetFeaturePrefix<'a> {
-    pub feature: &'a str,
-}
-
-#[derive(Diagnostic)]
 #[diag(codegen_llvm_fixed_x18_invalid_arch)]
 pub(crate) struct FixedX18InvalidArch<'a> {
     pub arch: &'a str,
diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs
index 628c0b1c29c..e18e91e569f 100644
--- a/compiler/rustc_codegen_llvm/src/llvm_util.rs
+++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs
@@ -21,8 +21,8 @@ use rustc_target::target_features::{RUSTC_SPECIAL_FEATURES, RUSTC_SPECIFIC_FEATU
 
 use crate::back::write::create_informational_target_machine;
 use crate::errors::{
-    FixedX18InvalidArch, ForbiddenCTargetFeature, InvalidTargetFeaturePrefix, PossibleFeature,
-    UnknownCTargetFeature, UnknownCTargetFeaturePrefix, UnstableCTargetFeature,
+    FixedX18InvalidArch, ForbiddenCTargetFeature, PossibleFeature, UnknownCTargetFeature,
+    UnknownCTargetFeaturePrefix, UnstableCTargetFeature,
 };
 use crate::llvm;
 
@@ -348,7 +348,16 @@ pub fn target_features_cfg(sess: &Session, allow_unstable: bool) -> Vec<Symbol>
     {
         if enabled {
             // Also add all transitively implied features.
-            features.extend(sess.target.implied_target_features(std::iter::once(feature)));
+
+            // We don't care about the order in `features` since the only thing we use it for is the
+            // `features.contains` below.
+            #[allow(rustc::potential_query_instability)]
+            features.extend(
+                sess.target
+                    .implied_target_features(std::iter::once(feature.as_str()))
+                    .iter()
+                    .map(|s| Symbol::intern(s)),
+            );
         } else {
             // Remove transitively reverse-implied features.
 
@@ -356,7 +365,11 @@ pub fn target_features_cfg(sess: &Session, allow_unstable: bool) -> Vec<Symbol>
             // `features.contains` below.
             #[allow(rustc::potential_query_instability)]
             features.retain(|f| {
-                if sess.target.implied_target_features(std::iter::once(*f)).contains(&feature) {
+                if sess
+                    .target
+                    .implied_target_features(std::iter::once(f.as_str()))
+                    .contains(&feature.as_str())
+                {
                     // If `f` if implies `feature`, then `!feature` implies `!f`, so we have to
                     // remove `f`. (This is the standard logical contraposition principle.)
                     false
@@ -638,7 +651,7 @@ pub(crate) fn global_llvm_features(
         sess.target
             .features
             .split(',')
-            .filter(|v| !v.is_empty() && backend_feature_name(sess, v).is_some())
+            .filter(|v| !v.is_empty())
             // Drop +v8plus feature introduced in LLVM 20.
             .filter(|v| *v != "+v8plus" || get_version() >= (20, 0, 0))
             .map(String::from),
@@ -651,89 +664,136 @@ pub(crate) fn global_llvm_features(
     // -Ctarget-features
     if !only_base_features {
         let known_features = sess.target.rust_target_features();
+        // Will only be filled when `diagnostics` is set!
         let mut featsmap = FxHashMap::default();
 
-        // insert implied features
+        // Ensure that all ABI-required features are enabled, and the ABI-forbidden ones
+        // are disabled.
+        let abi_feature_constraints = sess.target.abi_required_features();
+        let abi_incompatible_set =
+            FxHashSet::from_iter(abi_feature_constraints.incompatible.iter().copied());
+
+        // Compute implied features
         let mut all_rust_features = vec![];
         for feature in sess.opts.cg.target_feature.split(',') {
-            match feature.strip_prefix('+') {
-                Some(feature) => all_rust_features.extend(
-                    UnordSet::from(
-                        sess.target
-                            .implied_target_features(std::iter::once(Symbol::intern(feature))),
-                    )
-                    .to_sorted_stable_ord()
-                    .iter()
-                    .map(|s| format!("+{}", s.as_str())),
-                ),
-                _ => all_rust_features.push(feature.to_string()),
+            if let Some(feature) = feature.strip_prefix('+') {
+                all_rust_features.extend(
+                    UnordSet::from(sess.target.implied_target_features(std::iter::once(feature)))
+                        .to_sorted_stable_ord()
+                        .iter()
+                        .map(|&&s| (true, s)),
+                )
+            } else if let Some(feature) = feature.strip_prefix('-') {
+                // FIXME: Why do we not remove implied features on "-" here?
+                // We do the equivalent above in `target_features_cfg`.
+                // See <https://github.com/rust-lang/rust/issues/134792>.
+                all_rust_features.push((false, feature));
+            } else if !feature.is_empty() {
+                if diagnostics {
+                    sess.dcx().emit_warn(UnknownCTargetFeaturePrefix { feature });
+                }
             }
         }
+        // Remove features that are meant for rustc, not LLVM.
+        all_rust_features.retain(|(_, feature)| {
+            // Retain if it is not a rustc feature
+            !RUSTC_SPECIFIC_FEATURES.contains(feature)
+        });
 
-        let feats = all_rust_features
-            .iter()
-            .filter_map(|s| {
-                let enable_disable = match s.chars().next() {
-                    None => return None,
-                    Some(c @ ('+' | '-')) => c,
-                    Some(_) => {
-                        if diagnostics {
-                            sess.dcx().emit_warn(UnknownCTargetFeaturePrefix { feature: s });
-                        }
-                        return None;
-                    }
-                };
-
-                // Get the backend feature name, if any.
-                // This excludes rustc-specific features, which do not get passed to LLVM.
-                let feature = backend_feature_name(sess, s)?;
-                // Warn against use of LLVM specific feature names and unstable features on the CLI.
-                if diagnostics {
-                    let feature_state = known_features.iter().find(|&&(v, _, _)| v == feature);
-                    match feature_state {
-                        None => {
-                            let rust_feature =
-                                known_features.iter().find_map(|&(rust_feature, _, _)| {
-                                    let llvm_features = to_llvm_features(sess, rust_feature)?;
-                                    if llvm_features.contains(feature)
-                                        && !llvm_features.contains(rust_feature)
-                                    {
-                                        Some(rust_feature)
-                                    } else {
-                                        None
-                                    }
-                                });
-                            let unknown_feature = if let Some(rust_feature) = rust_feature {
-                                UnknownCTargetFeature {
-                                    feature,
-                                    rust_feature: PossibleFeature::Some { rust_feature },
+        // Check feature validity.
+        if diagnostics {
+            for &(enable, feature) in &all_rust_features {
+                let feature_state = known_features.iter().find(|&&(v, _, _)| v == feature);
+                match feature_state {
+                    None => {
+                        let rust_feature =
+                            known_features.iter().find_map(|&(rust_feature, _, _)| {
+                                let llvm_features = to_llvm_features(sess, rust_feature)?;
+                                if llvm_features.contains(feature)
+                                    && !llvm_features.contains(rust_feature)
+                                {
+                                    Some(rust_feature)
+                                } else {
+                                    None
                                 }
-                            } else {
-                                UnknownCTargetFeature {
-                                    feature,
-                                    rust_feature: PossibleFeature::None,
-                                }
-                            };
-                            sess.dcx().emit_warn(unknown_feature);
-                        }
-                        Some((_, stability, _)) => {
-                            if let Err(reason) =
-                                stability.toggle_allowed(&sess.target, enable_disable == '+')
-                            {
-                                sess.dcx().emit_warn(ForbiddenCTargetFeature { feature, reason });
-                            } else if stability.requires_nightly().is_some() {
-                                // An unstable feature. Warn about using it. It makes little sense
-                                // to hard-error here since we just warn about fully unknown
-                                // features above.
-                                sess.dcx().emit_warn(UnstableCTargetFeature { feature });
+                            });
+                        let unknown_feature = if let Some(rust_feature) = rust_feature {
+                            UnknownCTargetFeature {
+                                feature,
+                                rust_feature: PossibleFeature::Some { rust_feature },
                             }
+                        } else {
+                            UnknownCTargetFeature { feature, rust_feature: PossibleFeature::None }
+                        };
+                        sess.dcx().emit_warn(unknown_feature);
+                    }
+                    Some((_, stability, _)) => {
+                        if let Err(reason) = stability.toggle_allowed() {
+                            sess.dcx().emit_warn(ForbiddenCTargetFeature {
+                                feature,
+                                enabled: if enable { "enabled" } else { "disabled" },
+                                reason,
+                            });
+                        } else if stability.requires_nightly().is_some() {
+                            // An unstable feature. Warn about using it. It makes little sense
+                            // to hard-error here since we just warn about fully unknown
+                            // features above.
+                            sess.dcx().emit_warn(UnstableCTargetFeature { feature });
                         }
                     }
+                }
 
-                    // FIXME(nagisa): figure out how to not allocate a full hashset here.
-                    featsmap.insert(feature, enable_disable == '+');
+                // Ensure that the features we enable/disable are compatible with the ABI.
+                if enable {
+                    if abi_incompatible_set.contains(feature) {
+                        sess.dcx().emit_warn(ForbiddenCTargetFeature {
+                            feature,
+                            enabled: "enabled",
+                            reason: "this feature is incompatible with the target ABI",
+                        });
+                    }
+                } else {
+                    // FIXME: we have to request implied features here since
+                    // negative features do not handle implied features above.
+                    for &required in abi_feature_constraints.required.iter() {
+                        let implied =
+                            sess.target.implied_target_features(std::iter::once(required));
+                        if implied.contains(feature) {
+                            sess.dcx().emit_warn(ForbiddenCTargetFeature {
+                                feature,
+                                enabled: "disabled",
+                                reason: "this feature is required by the target ABI",
+                            });
+                        }
+                    }
                 }
 
+                // FIXME(nagisa): figure out how to not allocate a full hashset here.
+                featsmap.insert(feature, enable);
+            }
+        }
+
+        // To be sure the ABI-relevant features are all in the right state, we explicitly
+        // (un)set them here. This means if the target spec sets those features wrong,
+        // we will silently correct them rather than silently producing wrong code.
+        // (The target sanity check tries to catch this, but we can't know which features are
+        // enabled in LLVM by default so we can't be fully sure about that check.)
+        // We add these at the beginning of the list so that `-Ctarget-features` can
+        // still override it... that's unsound, but more compatible with past behavior.
+        all_rust_features.splice(
+            0..0,
+            abi_feature_constraints
+                .required
+                .iter()
+                .map(|&f| (true, f))
+                .chain(abi_feature_constraints.incompatible.iter().map(|&f| (false, f))),
+        );
+
+        // Translate this into LLVM features.
+        let feats = all_rust_features
+            .iter()
+            .filter_map(|&(enable, feature)| {
+                let enable_disable = if enable { '+' } else { '-' };
                 // We run through `to_llvm_features` when
                 // passing requests down to LLVM. This means that all in-language
                 // features also work on the command line instead of having two
@@ -746,9 +806,9 @@ pub(crate) fn global_llvm_features(
                         enable_disable, llvm_feature.llvm_feature_name
                     ))
                     .chain(llvm_feature.dependency.into_iter().filter_map(
-                        move |feat| match (enable_disable, feat) {
-                            ('-' | '+', TargetFeatureFoldStrength::Both(f))
-                            | ('+', TargetFeatureFoldStrength::EnableOnly(f)) => {
+                        move |feat| match (enable, feat) {
+                            (_, TargetFeatureFoldStrength::Both(f))
+                            | (true, TargetFeatureFoldStrength::EnableOnly(f)) => {
                                 Some(format!("{enable_disable}{f}"))
                             }
                             _ => None,
@@ -780,22 +840,6 @@ pub(crate) fn global_llvm_features(
     features
 }
 
-/// Returns a feature name for the given `+feature` or `-feature` string.
-///
-/// Only allows features that are backend specific (i.e. not [`RUSTC_SPECIFIC_FEATURES`].)
-fn backend_feature_name<'a>(sess: &Session, s: &'a str) -> Option<&'a str> {
-    // features must start with a `+` or `-`.
-    let feature = s
-        .strip_prefix(&['+', '-'][..])
-        .unwrap_or_else(|| sess.dcx().emit_fatal(InvalidTargetFeaturePrefix { feature: s }));
-    // Rustc-specific feature requests like `+crt-static` or `-crt-static`
-    // are not passed down to LLVM.
-    if s.is_empty() || RUSTC_SPECIFIC_FEATURES.contains(&feature) {
-        return None;
-    }
-    Some(feature)
-}
-
 pub(crate) fn tune_cpu(sess: &Session) -> Option<&str> {
     let name = sess.opts.unstable_opts.tune_cpu.as_ref()?;
     Some(handle_native(name))
diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl
index 56188714b44..484f467068a 100644
--- a/compiler/rustc_codegen_ssa/messages.ftl
+++ b/compiler/rustc_codegen_ssa/messages.ftl
@@ -67,7 +67,7 @@ codegen_ssa_failed_to_write = failed to write {$path}: {$error}
 codegen_ssa_field_associated_value_expected = associated value expected for `{$name}`
 
 codegen_ssa_forbidden_target_feature_attr =
-    target feature `{$feature}` cannot be toggled with `#[target_feature]`: {$reason}
+    target feature `{$feature}` cannot be enabled with `#[target_feature]`: {$reason}
 
 codegen_ssa_ignoring_emit_path = ignoring emit path because multiple .{$extension} files were produced
 
diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs
index 7e80d014ea2..d8b9bdb55da 100644
--- a/compiler/rustc_codegen_ssa/src/target_features.rs
+++ b/compiler/rustc_codegen_ssa/src/target_features.rs
@@ -19,7 +19,7 @@ use crate::errors;
 pub(crate) fn from_target_feature_attr(
     tcx: TyCtxt<'_>,
     attr: &hir::Attribute,
-    rust_target_features: &UnordMap<String, target_features::StabilityComputed>,
+    rust_target_features: &UnordMap<String, target_features::Stability>,
     target_features: &mut Vec<TargetFeature>,
 ) {
     let Some(list) = attr.meta_item_list() else { return };
@@ -32,7 +32,7 @@ pub(crate) fn from_target_feature_attr(
             .emit();
     };
     let rust_features = tcx.features();
-    let mut added_target_features = Vec::new();
+    let abi_feature_constraints = tcx.sess.target.abi_required_features();
     for item in list {
         // Only `enable = ...` is accepted in the meta-item list.
         if !item.has_name(sym::enable) {
@@ -47,7 +47,7 @@ pub(crate) fn from_target_feature_attr(
         };
 
         // We allow comma separation to enable multiple features.
-        added_target_features.extend(value.as_str().split(',').filter_map(|feature| {
+        for feature in value.as_str().split(',') {
             let Some(stability) = rust_target_features.get(feature) else {
                 let msg = format!("the feature named `{feature}` is not valid for this target");
                 let mut err = tcx.dcx().struct_span_err(item.span(), msg);
@@ -59,12 +59,12 @@ pub(crate) fn from_target_feature_attr(
                     }
                 }
                 err.emit();
-                return None;
+                continue;
             };
 
             // Only allow target features whose feature gates have been enabled
             // and which are permitted to be toggled.
-            if let Err(reason) = stability.toggle_allowed(/*enable*/ true) {
+            if let Err(reason) = stability.toggle_allowed() {
                 tcx.dcx().emit_err(errors::ForbiddenTargetFeatureAttr {
                     span: item.span(),
                     feature,
@@ -80,31 +80,25 @@ pub(crate) fn from_target_feature_attr(
                     format!("the target feature `{feature}` is currently unstable"),
                 )
                 .emit();
+            } else {
+                // Add this and the implied features.
+                let feature_sym = Symbol::intern(feature);
+                for &name in tcx.implied_target_features(feature_sym) {
+                    // But ensure the ABI does not forbid enabling this.
+                    // Here we do assume that LLVM doesn't add even more implied features
+                    // we don't know about, at least no features that would have ABI effects!
+                    if abi_feature_constraints.incompatible.contains(&name.as_str()) {
+                        tcx.dcx().emit_err(errors::ForbiddenTargetFeatureAttr {
+                            span: item.span(),
+                            feature: name.as_str(),
+                            reason: "this feature is incompatible with the target ABI",
+                        });
+                    }
+                    target_features.push(TargetFeature { name, implied: name != feature_sym })
+                }
             }
-            Some(Symbol::intern(feature))
-        }));
-    }
-
-    // Add explicit features
-    target_features.extend(
-        added_target_features.iter().copied().map(|name| TargetFeature { name, implied: false }),
-    );
-
-    // Add implied features
-    let mut implied_target_features = UnordSet::new();
-    for feature in added_target_features.iter() {
-        implied_target_features.extend(tcx.implied_target_features(*feature).clone());
-    }
-    for feature in added_target_features.iter() {
-        implied_target_features.remove(feature);
+        }
     }
-    target_features.extend(
-        implied_target_features
-            .into_sorted_stable_ord()
-            .iter()
-            .copied()
-            .map(|name| TargetFeature { name, implied: true }),
-    )
 }
 
 /// Computes the set of target features used in a function for the purposes of
@@ -147,25 +141,28 @@ pub(crate) fn provide(providers: &mut Providers) {
     *providers = Providers {
         rust_target_features: |tcx, cnum| {
             assert_eq!(cnum, LOCAL_CRATE);
-            let target = &tcx.sess.target;
             if tcx.sess.opts.actually_rustdoc {
                 // rustdoc needs to be able to document functions that use all the features, so
                 // whitelist them all
                 rustc_target::target_features::all_rust_features()
-                    .map(|(a, b)| (a.to_string(), b.compute_toggleability(target)))
+                    .map(|(a, b)| (a.to_string(), b))
                     .collect()
             } else {
                 tcx.sess
                     .target
                     .rust_target_features()
                     .iter()
-                    .map(|(a, b, _)| (a.to_string(), b.compute_toggleability(target)))
+                    .map(|(a, b, _)| (a.to_string(), *b))
                     .collect()
             }
         },
-        implied_target_features: |tcx, feature| {
+        implied_target_features: |tcx, feature: Symbol| {
+            let feature = feature.as_str();
             UnordSet::from(tcx.sess.target.implied_target_features(std::iter::once(feature)))
                 .into_sorted_stable_ord()
+                .into_iter()
+                .map(|s| Symbol::intern(s))
+                .collect()
         },
         asm_target_features,
         ..*providers
diff --git a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs
index 241767fe249..16d868300db 100644
--- a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs
+++ b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs
@@ -28,6 +28,7 @@ pub struct CodegenFnAttrs {
     pub link_ordinal: Option<u16>,
     /// The `#[target_feature(enable = "...")]` attribute and the enabled
     /// features (only enabled features are supported right now).
+    /// Implied target features have already been applied.
     pub target_features: Vec<TargetFeature>,
     /// The `#[linkage = "..."]` attribute on Rust-defined items and the value we found.
     pub linkage: Option<Linkage>,
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index c960f03cf06..95995b956cd 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -2357,7 +2357,7 @@ rustc_queries! {
     }
 
     /// Returns the Rust target features for the current target. These are not always the same as LLVM target features!
-    query rust_target_features(_: CrateNum) -> &'tcx UnordMap<String, rustc_target::target_features::StabilityComputed> {
+    query rust_target_features(_: CrateNum) -> &'tcx UnordMap<String, rustc_target::target_features::Stability> {
         arena_cache
         eval_always
         desc { "looking up Rust target features" }
diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index bfe6f444147..ddfa6e248c3 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -2653,10 +2653,6 @@ impl TargetOptions {
     pub(crate) fn has_feature(&self, search_feature: &str) -> bool {
         self.features.split(',').any(|f| f.strip_prefix('+').is_some_and(|f| f == search_feature))
     }
-
-    pub(crate) fn has_neg_feature(&self, search_feature: &str) -> bool {
-        self.features.split(',').any(|f| f.strip_prefix('-').is_some_and(|f| f == search_feature))
-    }
 }
 
 impl Default for TargetOptions {
@@ -3203,7 +3199,8 @@ impl Target {
                 check_matches!(
                     &*self.llvm_abiname,
                     "ilp32" | "ilp32f" | "ilp32d" | "ilp32e",
-                    "invalid RISC-V ABI name"
+                    "invalid RISC-V ABI name: {}",
+                    self.llvm_abiname,
                 );
             }
             "riscv64" => {
@@ -3211,7 +3208,8 @@ impl Target {
                 check_matches!(
                     &*self.llvm_abiname,
                     "lp64" | "lp64f" | "lp64d" | "lp64e",
-                    "invalid RISC-V ABI name"
+                    "invalid RISC-V ABI name: {}",
+                    self.llvm_abiname,
                 );
             }
             "arm" => {
@@ -3245,6 +3243,26 @@ impl Target {
                     ));
                 }
             }
+            // Check that we don't mis-set any of the ABI-relevant features.
+            let abi_feature_constraints = self.abi_required_features();
+            for feat in abi_feature_constraints.required {
+                // The feature might be enabled by default so we can't *require* it to show up.
+                // But it must not be *disabled*.
+                if features_disabled.contains(feat) {
+                    return Err(format!(
+                        "target feature `{feat}` is required by the ABI but gets disabled in target spec"
+                    ));
+                }
+            }
+            for feat in abi_feature_constraints.incompatible {
+                // The feature might be disabled by default so we can't *require* it to show up.
+                // But it must not be *enabled*.
+                if features_enabled.contains(feat) {
+                    return Err(format!(
+                        "target feature `{feat}` is incompatible with the ABI but gets enabled in target spec"
+                    ));
+                }
+            }
         }
 
         Ok(())
diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs
index 5372437b0d2..f594d20f928 100644
--- a/compiler/rustc_target/src/target_features.rs
+++ b/compiler/rustc_target/src/target_features.rs
@@ -5,7 +5,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
 use rustc_span::{Symbol, sym};
 
-use crate::spec::Target;
+use crate::spec::{FloatAbi, Target};
 
 /// Features that control behaviour of rustc, rather than the codegen.
 /// These exist globally and are not in the target-specific lists below.
@@ -17,60 +17,34 @@ pub const RUSTC_SPECIFIC_FEATURES: &[&str] = &["crt-static"];
 pub const RUSTC_SPECIAL_FEATURES: &[&str] = &["backchain"];
 
 /// Stability information for target features.
-/// `Toggleability` is the type storing whether (un)stable features can be toggled:
-/// this is initially a function since it can depend on `Target`, but for stable hashing
-/// it needs to be something hashable to we have to make the type generic.
-#[derive(Debug, Clone)]
-pub enum Stability<Toggleability> {
+#[derive(Debug, Copy, Clone)]
+pub enum Stability {
     /// This target feature is stable, it can be used in `#[target_feature]` and
     /// `#[cfg(target_feature)]`.
-    Stable {
-        /// When enabling/disabling the feature via `-Ctarget-feature` or `#[target_feature]`,
-        /// determine if that is allowed.
-        allow_toggle: Toggleability,
-    },
+    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 {
+    Unstable(
         /// This must be a *language* feature, or else rustc will ICE when reporting a missing
         /// feature gate!
-        nightly_feature: Symbol,
-        /// See `Stable::allow_toggle` comment above.
-        allow_toggle: Toggleability,
-    },
+        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 that change the floating-point ABI.
+    /// 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::*;
 
-/// Returns `Ok` if the toggle is allowed, `Err` with an explanation of not.
-/// The `bool` indicates whether the feature is being enabled (`true`) or disabled.
-pub type AllowToggleUncomputed = fn(&Target, bool) -> Result<(), &'static str>;
-
-/// The computed result of whether a feature can be enabled/disabled on the current target.
-#[derive(Debug, Clone)]
-pub struct AllowToggleComputed {
-    enable: Result<(), &'static str>,
-    disable: Result<(), &'static str>,
-}
-
-/// `Stability` where `allow_toggle` has not been computed yet.
-pub type StabilityUncomputed = Stability<AllowToggleUncomputed>;
-/// `Stability` where `allow_toggle` has already been computed.
-pub type StabilityComputed = Stability<AllowToggleComputed>;
-
-impl<CTX, Toggleability: HashStable<CTX>> HashStable<CTX> for Stability<Toggleability> {
+impl<CTX> HashStable<CTX> for Stability {
     #[inline]
     fn hash_stable(&self, hcx: &mut CTX, hasher: &mut StableHasher) {
         std::mem::discriminant(self).hash_stable(hcx, hasher);
         match self {
-            Stability::Stable { allow_toggle } => {
-                allow_toggle.hash_stable(hcx, hasher);
-            }
-            Stability::Unstable { nightly_feature, allow_toggle } => {
+            Stability::Stable => {}
+            Stability::Unstable(nightly_feature) => {
                 nightly_feature.hash_stable(hcx, hasher);
-                allow_toggle.hash_stable(hcx, hasher);
             }
             Stability::Forbidden { reason } => {
                 reason.hash_stable(hcx, hasher);
@@ -79,16 +53,7 @@ impl<CTX, Toggleability: HashStable<CTX>> HashStable<CTX> for Stability<Toggleab
     }
 }
 
-impl<CTX> HashStable<CTX> for AllowToggleComputed {
-    #[inline]
-    fn hash_stable(&self, hcx: &mut CTX, hasher: &mut StableHasher) {
-        let AllowToggleComputed { enable, disable } = self;
-        enable.hash_stable(hcx, hasher);
-        disable.hash_stable(hcx, hasher);
-    }
-}
-
-impl<Toggleability> Stability<Toggleability> {
+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`.)
@@ -106,59 +71,23 @@ impl<Toggleability> Stability<Toggleability> {
     /// - for `cfg(target_feature)`, check `in_cfg`
     pub fn requires_nightly(&self) -> Option<Symbol> {
         match *self {
-            Stability::Unstable { nightly_feature, .. } => Some(nightly_feature),
+            Stability::Unstable(nightly_feature) => Some(nightly_feature),
             Stability::Stable { .. } => None,
             Stability::Forbidden { .. } => panic!("forbidden features should not reach this far"),
         }
     }
-}
 
-impl StabilityUncomputed {
-    pub fn compute_toggleability(&self, target: &Target) -> StabilityComputed {
-        use Stability::*;
-        let compute = |f: AllowToggleUncomputed| AllowToggleComputed {
-            enable: f(target, true),
-            disable: f(target, false),
-        };
-        match *self {
-            Stable { allow_toggle } => Stable { allow_toggle: compute(allow_toggle) },
-            Unstable { nightly_feature, allow_toggle } => {
-                Unstable { nightly_feature, allow_toggle: compute(allow_toggle) }
-            }
-            Forbidden { reason } => Forbidden { reason },
-        }
-    }
-
-    pub fn toggle_allowed(&self, target: &Target, enable: bool) -> Result<(), &'static str> {
-        use Stability::*;
-        match *self {
-            Stable { allow_toggle } => allow_toggle(target, enable),
-            Unstable { allow_toggle, .. } => allow_toggle(target, enable),
-            Forbidden { reason } => Err(reason),
-        }
-    }
-}
-
-impl StabilityComputed {
     /// 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, enable: bool) -> Result<(), &'static str> {
-        let allow_toggle = match self {
-            Stability::Stable { allow_toggle } => allow_toggle,
-            Stability::Unstable { allow_toggle, .. } => allow_toggle,
-            Stability::Forbidden { reason } => return Err(reason),
-        };
-        if enable { allow_toggle.enable } else { allow_toggle.disable }
+    pub fn toggle_allowed(&self) -> Result<(), &'static str> {
+        match self {
+            Stability::Forbidden { reason } => Err(reason),
+            _ => Ok(()),
+        }
     }
 }
 
-// Constructors for the list below, defaulting to "always allow toggle".
-const STABLE: StabilityUncomputed = Stability::Stable { allow_toggle: |_target, _enable| Ok(()) };
-const fn unstable(nightly_feature: Symbol) -> StabilityUncomputed {
-    Stability::Unstable { nightly_feature, allow_toggle: |_target, _enable| Ok(()) }
-}
-
 // 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`.
@@ -204,218 +133,182 @@ const fn unstable(nightly_feature: Symbol) -> StabilityUncomputed {
 // Both of these are also applied transitively.
 type ImpliedFeatures = &'static [&'static str];
 
-const ARM_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
+const ARM_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
-    ("aclass", unstable(sym::arm_target_feature), &[]),
-    ("aes", unstable(sym::arm_target_feature), &["neon"]),
-    ("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"]),
-    (
-        "fpregs",
-        Stability::Unstable {
-            nightly_feature: sym::arm_target_feature,
-            allow_toggle: |target: &Target, _enable| {
-                // Only allow toggling this if the target has `soft-float` set. With `soft-float`,
-                // `fpregs` isn't needed so changing it cannot affect the ABI.
-                if target.has_feature("soft-float") {
-                    Ok(())
-                } else {
-                    Err("unsound on hard-float targets because it changes float ABI")
-                }
-            },
-        },
-        &[],
-    ),
-    ("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"]),
-    ("soft-float", Stability::Forbidden { reason: "unsound because it changes float ABI" }, &[]),
+    ("aclass", Unstable(sym::arm_target_feature), &[]),
+    ("aes", Unstable(sym::arm_target_feature), &["neon"]),
+    ("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"]),
+    ("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 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), &[]),
+    ("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
 ];
 
-const AARCH64_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
+const AARCH64_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
     // FEAT_AES & FEAT_PMULL
-    ("aes", STABLE, &["neon"]),
+    ("aes", Stable, &["neon"]),
     // FEAT_BF16
-    ("bf16", STABLE, &[]),
+    ("bf16", Stable, &[]),
     // FEAT_BTI
-    ("bti", STABLE, &[]),
+    ("bti", Stable, &[]),
     // FEAT_CRC
-    ("crc", STABLE, &[]),
+    ("crc", Stable, &[]),
     // FEAT_CSSC
-    ("cssc", unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("cssc", Unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_DIT
-    ("dit", STABLE, &[]),
+    ("dit", Stable, &[]),
     // FEAT_DotProd
-    ("dotprod", STABLE, &["neon"]),
+    ("dotprod", Stable, &["neon"]),
     // FEAT_DPB
-    ("dpb", STABLE, &[]),
+    ("dpb", Stable, &[]),
     // FEAT_DPB2
-    ("dpb2", STABLE, &["dpb"]),
+    ("dpb2", Stable, &["dpb"]),
     // FEAT_ECV
-    ("ecv", unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("ecv", Unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_F32MM
-    ("f32mm", STABLE, &["sve"]),
+    ("f32mm", Stable, &["sve"]),
     // FEAT_F64MM
-    ("f64mm", STABLE, &["sve"]),
+    ("f64mm", Stable, &["sve"]),
     // FEAT_FAMINMAX
-    ("faminmax", unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("faminmax", Unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_FCMA
-    ("fcma", STABLE, &["neon"]),
+    ("fcma", Stable, &["neon"]),
     // FEAT_FHM
-    ("fhm", STABLE, &["fp16"]),
+    ("fhm", Stable, &["fp16"]),
     // FEAT_FLAGM
-    ("flagm", STABLE, &[]),
+    ("flagm", Stable, &[]),
     // FEAT_FLAGM2
-    ("flagm2", unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("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_FP16
     // Rust ties FP and Neon: https://github.com/rust-lang/rust/pull/91608
-    ("fp16", STABLE, &["neon"]),
+    ("fp16", Stable, &["neon"]),
     // FEAT_FP8
-    ("fp8", unstable(sym::aarch64_unstable_target_feature), &["faminmax", "lut", "bf16"]),
+    ("fp8", Unstable(sym::aarch64_unstable_target_feature), &["faminmax", "lut", "bf16"]),
     // FEAT_FP8DOT2
-    ("fp8dot2", unstable(sym::aarch64_unstable_target_feature), &["fp8dot4"]),
+    ("fp8dot2", Unstable(sym::aarch64_unstable_target_feature), &["fp8dot4"]),
     // FEAT_FP8DOT4
-    ("fp8dot4", unstable(sym::aarch64_unstable_target_feature), &["fp8fma"]),
+    ("fp8dot4", Unstable(sym::aarch64_unstable_target_feature), &["fp8fma"]),
     // FEAT_FP8FMA
-    ("fp8fma", unstable(sym::aarch64_unstable_target_feature), &["fp8"]),
+    ("fp8fma", Unstable(sym::aarch64_unstable_target_feature), &["fp8"]),
     // FEAT_FRINTTS
-    ("frintts", STABLE, &[]),
+    ("frintts", Stable, &[]),
     // FEAT_HBC
-    ("hbc", unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("hbc", Unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_I8MM
-    ("i8mm", STABLE, &[]),
+    ("i8mm", Stable, &[]),
     // FEAT_JSCVT
     // Rust ties FP and Neon: https://github.com/rust-lang/rust/pull/91608
-    ("jsconv", STABLE, &["neon"]),
+    ("jsconv", Stable, &["neon"]),
     // FEAT_LOR
-    ("lor", STABLE, &[]),
+    ("lor", Stable, &[]),
     // FEAT_LSE
-    ("lse", STABLE, &[]),
+    ("lse", Stable, &[]),
     // FEAT_LSE128
-    ("lse128", unstable(sym::aarch64_unstable_target_feature), &["lse"]),
+    ("lse128", Unstable(sym::aarch64_unstable_target_feature), &["lse"]),
     // FEAT_LSE2
-    ("lse2", unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("lse2", Unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_LUT
-    ("lut", unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("lut", Unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_MOPS
-    ("mops", unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("mops", Unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_MTE & FEAT_MTE2
-    ("mte", STABLE, &[]),
+    ("mte", Stable, &[]),
     // FEAT_AdvSimd & FEAT_FP
-    (
-        "neon",
-        Stability::Stable {
-            allow_toggle: |target, enable| {
-                if target.abi == "softfloat" {
-                    // `neon` has no ABI implications for softfloat targets, we can allow this.
-                    Ok(())
-                } else if enable
-                    && !target.has_neg_feature("fp-armv8")
-                    && !target.has_neg_feature("neon")
-                {
-                    // neon is enabled by default, and has not been disabled, so enabling it again
-                    // is redundant and we can permit it. Forbidding this would be a breaking change
-                    // since this feature is stable.
-                    Ok(())
-                } else {
-                    Err("unsound on hard-float targets because it changes float ABI")
-                }
-            },
-        },
-        &[],
-    ),
+    ("neon", Stable, &[]),
     // FEAT_PAUTH (address authentication)
-    ("paca", STABLE, &[]),
+    ("paca", Stable, &[]),
     // FEAT_PAUTH (generic authentication)
-    ("pacg", STABLE, &[]),
+    ("pacg", Stable, &[]),
     // FEAT_PAN
-    ("pan", STABLE, &[]),
+    ("pan", Stable, &[]),
     // FEAT_PAuth_LR
-    ("pauth-lr", unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("pauth-lr", Unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_PMUv3
-    ("pmuv3", STABLE, &[]),
+    ("pmuv3", Stable, &[]),
     // FEAT_RNG
-    ("rand", STABLE, &[]),
+    ("rand", Stable, &[]),
     // FEAT_RAS & FEAT_RASv1p1
-    ("ras", STABLE, &[]),
+    ("ras", Stable, &[]),
     // FEAT_LRCPC
-    ("rcpc", STABLE, &[]),
+    ("rcpc", Stable, &[]),
     // FEAT_LRCPC2
-    ("rcpc2", STABLE, &["rcpc"]),
+    ("rcpc2", Stable, &["rcpc"]),
     // FEAT_LRCPC3
-    ("rcpc3", unstable(sym::aarch64_unstable_target_feature), &["rcpc2"]),
+    ("rcpc3", Unstable(sym::aarch64_unstable_target_feature), &["rcpc2"]),
     // FEAT_RDM
-    ("rdm", STABLE, &["neon"]),
+    ("rdm", Stable, &["neon"]),
     // This is needed for inline assembly, but shouldn't be stabilized as-is
     // since it should be enabled globally using -Zfixed-x18, not
     // #[target_feature].
     // Note that cfg(target_feature = "reserve-x18") is currently not set for
     // targets that reserve x18 by default.
-    ("reserve-x18", unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("reserve-x18", Unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_SB
-    ("sb", STABLE, &[]),
+    ("sb", Stable, &[]),
     // FEAT_SHA1 & FEAT_SHA256
-    ("sha2", STABLE, &["neon"]),
+    ("sha2", Stable, &["neon"]),
     // FEAT_SHA512 & FEAT_SHA3
-    ("sha3", STABLE, &["sha2"]),
+    ("sha3", Stable, &["sha2"]),
     // FEAT_SM3 & FEAT_SM4
-    ("sm4", STABLE, &["neon"]),
+    ("sm4", Stable, &["neon"]),
     // FEAT_SME
-    ("sme", unstable(sym::aarch64_unstable_target_feature), &["bf16"]),
+    ("sme", Unstable(sym::aarch64_unstable_target_feature), &["bf16"]),
     // FEAT_SME_B16B16
-    ("sme-b16b16", unstable(sym::aarch64_unstable_target_feature), &["bf16", "sme2", "sve-b16b16"]),
+    ("sme-b16b16", Unstable(sym::aarch64_unstable_target_feature), &["bf16", "sme2", "sve-b16b16"]),
     // FEAT_SME_F16F16
-    ("sme-f16f16", unstable(sym::aarch64_unstable_target_feature), &["sme2"]),
+    ("sme-f16f16", Unstable(sym::aarch64_unstable_target_feature), &["sme2"]),
     // FEAT_SME_F64F64
-    ("sme-f64f64", unstable(sym::aarch64_unstable_target_feature), &["sme"]),
+    ("sme-f64f64", Unstable(sym::aarch64_unstable_target_feature), &["sme"]),
     // FEAT_SME_F8F16
-    ("sme-f8f16", unstable(sym::aarch64_unstable_target_feature), &["sme-f8f32"]),
+    ("sme-f8f16", Unstable(sym::aarch64_unstable_target_feature), &["sme-f8f32"]),
     // FEAT_SME_F8F32
-    ("sme-f8f32", unstable(sym::aarch64_unstable_target_feature), &["sme2", "fp8"]),
+    ("sme-f8f32", Unstable(sym::aarch64_unstable_target_feature), &["sme2", "fp8"]),
     // FEAT_SME_FA64
-    ("sme-fa64", unstable(sym::aarch64_unstable_target_feature), &["sme", "sve2"]),
+    ("sme-fa64", Unstable(sym::aarch64_unstable_target_feature), &["sme", "sve2"]),
     // FEAT_SME_I16I64
-    ("sme-i16i64", unstable(sym::aarch64_unstable_target_feature), &["sme"]),
+    ("sme-i16i64", Unstable(sym::aarch64_unstable_target_feature), &["sme"]),
     // FEAT_SME_LUTv2
-    ("sme-lutv2", unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("sme-lutv2", Unstable(sym::aarch64_unstable_target_feature), &[]),
     // FEAT_SME2
-    ("sme2", unstable(sym::aarch64_unstable_target_feature), &["sme"]),
+    ("sme2", Unstable(sym::aarch64_unstable_target_feature), &["sme"]),
     // FEAT_SME2p1
-    ("sme2p1", unstable(sym::aarch64_unstable_target_feature), &["sme2"]),
+    ("sme2p1", Unstable(sym::aarch64_unstable_target_feature), &["sme2"]),
     // FEAT_SPE
-    ("spe", STABLE, &[]),
+    ("spe", Stable, &[]),
     // FEAT_SSBS & FEAT_SSBS2
-    ("ssbs", STABLE, &[]),
+    ("ssbs", Stable, &[]),
     // FEAT_SSVE_FP8FDOT2
-    ("ssve-fp8dot2", unstable(sym::aarch64_unstable_target_feature), &["ssve-fp8dot4"]),
+    ("ssve-fp8dot2", Unstable(sym::aarch64_unstable_target_feature), &["ssve-fp8dot4"]),
     // FEAT_SSVE_FP8FDOT4
-    ("ssve-fp8dot4", unstable(sym::aarch64_unstable_target_feature), &["ssve-fp8fma"]),
+    ("ssve-fp8dot4", Unstable(sym::aarch64_unstable_target_feature), &["ssve-fp8fma"]),
     // FEAT_SSVE_FP8FMA
-    ("ssve-fp8fma", unstable(sym::aarch64_unstable_target_feature), &["sme2", "fp8"]),
+    ("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
     //
@@ -423,46 +316,46 @@ const AARCH64_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
     // 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"]),
+    ("sve", Stable, &["neon"]),
     // FEAT_SVE_B16B16 (SVE or SME Z-targeting instructions)
-    ("sve-b16b16", unstable(sym::aarch64_unstable_target_feature), &["bf16"]),
+    ("sve-b16b16", Unstable(sym::aarch64_unstable_target_feature), &["bf16"]),
     // FEAT_SVE2
-    ("sve2", STABLE, &["sve"]),
+    ("sve2", Stable, &["sve"]),
     // FEAT_SVE_AES & FEAT_SVE_PMULL128
-    ("sve2-aes", STABLE, &["sve2", "aes"]),
+    ("sve2-aes", Stable, &["sve2", "aes"]),
     // FEAT_SVE2_BitPerm
-    ("sve2-bitperm", STABLE, &["sve2"]),
+    ("sve2-bitperm", Stable, &["sve2"]),
     // FEAT_SVE2_SHA3
-    ("sve2-sha3", STABLE, &["sve2", "sha3"]),
+    ("sve2-sha3", Stable, &["sve2", "sha3"]),
     // FEAT_SVE2_SM4
-    ("sve2-sm4", STABLE, &["sve2", "sm4"]),
+    ("sve2-sm4", Stable, &["sve2", "sm4"]),
     // FEAT_SVE2p1
-    ("sve2p1", unstable(sym::aarch64_unstable_target_feature), &["sve2"]),
+    ("sve2p1", Unstable(sym::aarch64_unstable_target_feature), &["sve2"]),
     // FEAT_TME
-    ("tme", STABLE, &[]),
-    ("v8.1a", unstable(sym::aarch64_ver_target_feature), &[
+    ("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", 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"]),
+    ("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, &[]),
+    ("vh", Stable, &[]),
     // FEAT_WFxT
-    ("wfxt", unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("wfxt", Unstable(sym::aarch64_unstable_target_feature), &[]),
     // tidy-alphabetical-end
 ];
 
@@ -470,337 +363,260 @@ const AARCH64_TIED_FEATURES: &[&[&str]] = &[
     &["paca", "pacg"], // Together these represent `pauth` in LLVM
 ];
 
-const X86_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
+const X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
-    ("adx", STABLE, &[]),
-    ("aes", STABLE, &["sse2"]),
-    ("amx-bf16", unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
-    ("amx-complex", 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-tile", unstable(sym::x86_amx_intrinsics), &[]),
-    ("avx", STABLE, &["sse4.2"]),
-    ("avx2", STABLE, &["avx"]),
-    ("avx512bf16", unstable(sym::avx512_target_feature), &["avx512bw"]),
-    ("avx512bitalg", unstable(sym::avx512_target_feature), &["avx512bw"]),
-    ("avx512bw", unstable(sym::avx512_target_feature), &["avx512f"]),
-    ("avx512cd", unstable(sym::avx512_target_feature), &["avx512f"]),
-    ("avx512dq", unstable(sym::avx512_target_feature), &["avx512f"]),
-    ("avx512f", unstable(sym::avx512_target_feature), &["avx2", "fma", "f16c"]),
-    ("avx512fp16", unstable(sym::avx512_target_feature), &["avx512bw", "avx512vl", "avx512dq"]),
-    ("avx512ifma", unstable(sym::avx512_target_feature), &["avx512f"]),
-    ("avx512vbmi", unstable(sym::avx512_target_feature), &["avx512bw"]),
-    ("avx512vbmi2", unstable(sym::avx512_target_feature), &["avx512bw"]),
-    ("avx512vl", unstable(sym::avx512_target_feature), &["avx512f"]),
-    ("avx512vnni", unstable(sym::avx512_target_feature), &["avx512f"]),
-    ("avx512vp2intersect", unstable(sym::avx512_target_feature), &["avx512f"]),
-    ("avx512vpopcntdq", unstable(sym::avx512_target_feature), &["avx512f"]),
-    ("avxifma", unstable(sym::avx512_target_feature), &["avx2"]),
-    ("avxneconvert", unstable(sym::avx512_target_feature), &["avx2"]),
-    ("avxvnni", unstable(sym::avx512_target_feature), &["avx2"]),
-    ("avxvnniint16", unstable(sym::avx512_target_feature), &["avx2"]),
-    ("avxvnniint8", unstable(sym::avx512_target_feature), &["avx2"]),
-    ("bmi1", STABLE, &[]),
-    ("bmi2", STABLE, &[]),
-    ("cmpxchg16b", STABLE, &[]),
-    ("ermsb", unstable(sym::ermsb_target_feature), &[]),
-    ("f16c", STABLE, &["avx"]),
-    ("fma", STABLE, &["avx"]),
-    ("fxsr", STABLE, &[]),
-    ("gfni", unstable(sym::avx512_target_feature), &["sse2"]),
-    ("lahfsahf", unstable(sym::lahfsahf_target_feature), &[]),
-    ("lzcnt", STABLE, &[]),
-    ("movbe", STABLE, &[]),
-    ("pclmulqdq", STABLE, &["sse2"]),
-    ("popcnt", STABLE, &[]),
-    ("prfchw", unstable(sym::prfchw_target_feature), &[]),
-    ("rdrand", STABLE, &[]),
-    ("rdseed", STABLE, &[]),
-    ("rtm", unstable(sym::rtm_target_feature), &[]),
-    ("sha", STABLE, &["sse2"]),
-    ("sha512", unstable(sym::sha512_sm_x86), &["avx2"]),
-    ("sm3", unstable(sym::sha512_sm_x86), &["avx"]),
-    ("sm4", unstable(sym::sha512_sm_x86), &["avx2"]),
+    ("adx", Stable, &[]),
+    ("aes", Stable, &["sse2"]),
+    ("amx-bf16", Unstable(sym::x86_amx_intrinsics), &["amx-tile"]),
+    ("amx-complex", 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-tile", Unstable(sym::x86_amx_intrinsics), &[]),
+    ("avx", Stable, &["sse4.2"]),
+    ("avx2", Stable, &["avx"]),
+    ("avx512bf16", Unstable(sym::avx512_target_feature), &["avx512bw"]),
+    ("avx512bitalg", Unstable(sym::avx512_target_feature), &["avx512bw"]),
+    ("avx512bw", Unstable(sym::avx512_target_feature), &["avx512f"]),
+    ("avx512cd", Unstable(sym::avx512_target_feature), &["avx512f"]),
+    ("avx512dq", Unstable(sym::avx512_target_feature), &["avx512f"]),
+    ("avx512f", Unstable(sym::avx512_target_feature), &["avx2", "fma", "f16c"]),
+    ("avx512fp16", Unstable(sym::avx512_target_feature), &["avx512bw", "avx512vl", "avx512dq"]),
+    ("avx512ifma", Unstable(sym::avx512_target_feature), &["avx512f"]),
+    ("avx512vbmi", Unstable(sym::avx512_target_feature), &["avx512bw"]),
+    ("avx512vbmi2", Unstable(sym::avx512_target_feature), &["avx512bw"]),
+    ("avx512vl", Unstable(sym::avx512_target_feature), &["avx512f"]),
+    ("avx512vnni", Unstable(sym::avx512_target_feature), &["avx512f"]),
+    ("avx512vp2intersect", Unstable(sym::avx512_target_feature), &["avx512f"]),
+    ("avx512vpopcntdq", Unstable(sym::avx512_target_feature), &["avx512f"]),
+    ("avxifma", Unstable(sym::avx512_target_feature), &["avx2"]),
+    ("avxneconvert", Unstable(sym::avx512_target_feature), &["avx2"]),
+    ("avxvnni", Unstable(sym::avx512_target_feature), &["avx2"]),
+    ("avxvnniint16", Unstable(sym::avx512_target_feature), &["avx2"]),
+    ("avxvnniint8", Unstable(sym::avx512_target_feature), &["avx2"]),
+    ("bmi1", Stable, &[]),
+    ("bmi2", Stable, &[]),
+    ("cmpxchg16b", Stable, &[]),
+    ("ermsb", Unstable(sym::ermsb_target_feature), &[]),
+    ("f16c", Stable, &["avx"]),
+    ("fma", Stable, &["avx"]),
+    ("fxsr", Stable, &[]),
+    ("gfni", Unstable(sym::avx512_target_feature), &["sse2"]),
+    ("lahfsahf", Unstable(sym::lahfsahf_target_feature), &[]),
+    ("lzcnt", Stable, &[]),
+    ("movbe", Stable, &[]),
+    ("pclmulqdq", Stable, &["sse2"]),
+    ("popcnt", Stable, &[]),
+    ("prfchw", Unstable(sym::prfchw_target_feature), &[]),
+    ("rdrand", Stable, &[]),
+    ("rdseed", Stable, &[]),
+    ("rtm", Unstable(sym::rtm_target_feature), &[]),
+    ("sha", Stable, &["sse2"]),
+    ("sha512", Unstable(sym::sha512_sm_x86), &["avx2"]),
+    ("sm3", Unstable(sym::sha512_sm_x86), &["avx"]),
+    ("sm4", Unstable(sym::sha512_sm_x86), &["avx2"]),
     ("soft-float", Stability::Forbidden { reason: "unsound because it changes float ABI" }, &[]),
-    ("sse", STABLE, &[]),
-    ("sse2", STABLE, &["sse"]),
-    ("sse3", STABLE, &["sse2"]),
-    ("sse4.1", STABLE, &["ssse3"]),
-    ("sse4.2", STABLE, &["sse4.1"]),
-    ("sse4a", unstable(sym::sse4a_target_feature), &["sse3"]),
-    ("ssse3", STABLE, &["sse3"]),
-    ("tbm", unstable(sym::tbm_target_feature), &[]),
-    ("vaes", unstable(sym::avx512_target_feature), &["avx2", "aes"]),
-    ("vpclmulqdq", unstable(sym::avx512_target_feature), &["avx", "pclmulqdq"]),
-    (
-        "x87",
-        Stability::Unstable {
-            nightly_feature: sym::x87_target_feature,
-            allow_toggle: |target: &Target, _enable| {
-                // Only allow toggling this if the target has `soft-float` set. With `soft-float`,
-                // `fpregs` isn't needed so changing it cannot affect the ABI.
-                if target.has_feature("soft-float") {
-                    Ok(())
-                } else {
-                    Err("unsound on hard-float targets because it changes float ABI")
-                }
-            },
-        },
-        &[],
-    ),
-    ("xop", unstable(sym::xop_target_feature), &[/*"fma4", */ "avx", "sse4a"]),
-    ("xsave", STABLE, &[]),
-    ("xsavec", STABLE, &["xsave"]),
-    ("xsaveopt", STABLE, &["xsave"]),
-    ("xsaves", STABLE, &["xsave"]),
+    ("sse", Stable, &[]),
+    ("sse2", Stable, &["sse"]),
+    ("sse3", Stable, &["sse2"]),
+    ("sse4.1", Stable, &["ssse3"]),
+    ("sse4.2", Stable, &["sse4.1"]),
+    ("sse4a", Unstable(sym::sse4a_target_feature), &["sse3"]),
+    ("ssse3", Stable, &["sse3"]),
+    ("tbm", Unstable(sym::tbm_target_feature), &[]),
+    ("vaes", Unstable(sym::avx512_target_feature), &["avx2", "aes"]),
+    ("vpclmulqdq", Unstable(sym::avx512_target_feature), &["avx", "pclmulqdq"]),
+    ("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, StabilityUncomputed, ImpliedFeatures)] = &[
+const HEXAGON_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
-    ("hvx", unstable(sym::hexagon_target_feature), &[]),
-    ("hvx-length128b", unstable(sym::hexagon_target_feature), &["hvx"]),
+    ("hvx", Unstable(sym::hexagon_target_feature), &[]),
+    ("hvx-length128b", Unstable(sym::hexagon_target_feature), &["hvx"]),
     // tidy-alphabetical-end
 ];
 
-const POWERPC_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
+const POWERPC_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
-    ("altivec", unstable(sym::powerpc_target_feature), &[]),
-    ("partword-atomics", unstable(sym::powerpc_target_feature), &[]),
-    ("power10-vector", unstable(sym::powerpc_target_feature), &["power9-vector"]),
-    ("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"]),
-    ("quadword-atomics", unstable(sym::powerpc_target_feature), &[]),
-    ("vsx", unstable(sym::powerpc_target_feature), &["altivec"]),
+    ("altivec", Unstable(sym::powerpc_target_feature), &[]),
+    ("partword-atomics", Unstable(sym::powerpc_target_feature), &[]),
+    ("power10-vector", Unstable(sym::powerpc_target_feature), &["power9-vector"]),
+    ("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"]),
+    ("quadword-atomics", Unstable(sym::powerpc_target_feature), &[]),
+    ("vsx", Unstable(sym::powerpc_target_feature), &["altivec"]),
     // tidy-alphabetical-end
 ];
 
-const MIPS_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
+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), &[]),
+    ("fp64", Unstable(sym::mips_target_feature), &[]),
+    ("msa", Unstable(sym::mips_target_feature), &[]),
+    ("virt", Unstable(sym::mips_target_feature), &[]),
     // tidy-alphabetical-end
 ];
 
-const RISCV_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
+const RISCV_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
-    ("a", STABLE, &["zaamo", "zalrsc"]),
-    ("c", STABLE, &[]),
-    (
-        "d",
-        Stability::Unstable {
-            nightly_feature: sym::riscv_target_feature,
-            allow_toggle: |target, enable| match &*target.llvm_abiname {
-                "ilp32d" | "lp64d" if !enable => {
-                    // The ABI requires the `d` feature, so it cannot be disabled.
-                    Err("feature is required by ABI")
-                }
-                "ilp32e" if enable => {
-                    // ilp32e is incompatible with features that need aligned load/stores > 32 bits,
-                    // like `d`.
-                    Err("feature is incompatible with ABI")
-                }
-                _ => Ok(()),
-            },
-        },
-        &["f"],
-    ),
-    (
-        "e",
-        Stability::Unstable {
-            // Given that this is a negative feature, consider this before stabilizing:
-            // does it really make sense to enable this feature in an individual
-            // function with `#[target_feature]`?
-            nightly_feature: sym::riscv_target_feature,
-            allow_toggle: |target, enable| {
-                match &*target.llvm_abiname {
-                    _ if !enable => {
-                        // Disabling this feature means we can use more registers (x16-x31).
-                        // The "e" ABIs treat them 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.
-                        // On other ABIs, the feature is already disabled anyway.
-                        Ok(())
-                    }
-                    "ilp32e" | "lp64e" => {
-                        // Embedded ABIs should already have the feature anyway, it's fine to enable
-                        // it again from an ABI perspective.
-                        Ok(())
-                    }
-                    _ => {
-                        // *Not* an embedded ABI. Enabling `e` is invalid.
-                        Err("feature is incompatible with ABI")
-                    }
-                }
-            },
-        },
-        &[],
-    ),
-    (
-        "f",
-        Stability::Unstable {
-            nightly_feature: sym::riscv_target_feature,
-            allow_toggle: |target, enable| {
-                match &*target.llvm_abiname {
-                    "ilp32f" | "ilp32d" | "lp64f" | "lp64d" if !enable => {
-                        // The ABI requires the `f` feature, so it cannot be disabled.
-                        Err("feature is required by ABI")
-                    }
-                    _ => Ok(()),
-                }
-            },
-        },
-        &[],
-    ),
+    ("a", Stable, &["zaamo", "zalrsc"]),
+    ("c", Stable, &[]),
+    ("d", Unstable(sym::riscv_target_feature), &["f"]),
+    ("e", Unstable(sym::riscv_target_feature), &[]),
+    ("f", Unstable(sym::riscv_target_feature), &[]),
     (
         "forced-atomics",
         Stability::Forbidden { reason: "unsound because it changes the ABI of atomic operations" },
         &[],
     ),
-    ("m", STABLE, &[]),
-    ("relax", unstable(sym::riscv_target_feature), &[]),
-    ("unaligned-scalar-mem", unstable(sym::riscv_target_feature), &[]),
-    ("v", unstable(sym::riscv_target_feature), &[]),
-    ("zaamo", unstable(sym::riscv_target_feature), &[]),
-    ("zabha", unstable(sym::riscv_target_feature), &["zaamo"]),
-    ("zalrsc", unstable(sym::riscv_target_feature), &[]),
-    ("zba", STABLE, &[]),
-    ("zbb", STABLE, &[]),
-    ("zbc", STABLE, &[]),
-    ("zbkb", STABLE, &[]),
-    ("zbkc", STABLE, &[]),
-    ("zbkx", STABLE, &[]),
-    ("zbs", STABLE, &[]),
-    ("zdinx", unstable(sym::riscv_target_feature), &["zfinx"]),
-    ("zfh", unstable(sym::riscv_target_feature), &["zfhmin"]),
-    ("zfhmin", unstable(sym::riscv_target_feature), &["f"]),
-    ("zfinx", unstable(sym::riscv_target_feature), &[]),
-    ("zhinx", unstable(sym::riscv_target_feature), &["zhinxmin"]),
-    ("zhinxmin", unstable(sym::riscv_target_feature), &["zfinx"]),
-    ("zk", STABLE, &["zkn", "zkr", "zkt"]),
-    ("zkn", STABLE, &["zbkb", "zbkc", "zbkx", "zkne", "zknd", "zknh"]),
-    ("zknd", STABLE, &[]),
-    ("zkne", STABLE, &[]),
-    ("zknh", STABLE, &[]),
-    ("zkr", STABLE, &[]),
-    ("zks", STABLE, &["zbkb", "zbkc", "zbkx", "zksed", "zksh"]),
-    ("zksed", STABLE, &[]),
-    ("zksh", STABLE, &[]),
-    ("zkt", STABLE, &[]),
+    ("m", Stable, &[]),
+    ("relax", Unstable(sym::riscv_target_feature), &[]),
+    ("unaligned-scalar-mem", Unstable(sym::riscv_target_feature), &[]),
+    ("v", Unstable(sym::riscv_target_feature), &[]),
+    ("zaamo", Unstable(sym::riscv_target_feature), &[]),
+    ("zabha", Unstable(sym::riscv_target_feature), &["zaamo"]),
+    ("zalrsc", Unstable(sym::riscv_target_feature), &[]),
+    ("zba", Stable, &[]),
+    ("zbb", Stable, &[]),
+    ("zbc", Stable, &[]),
+    ("zbkb", Stable, &[]),
+    ("zbkc", Stable, &[]),
+    ("zbkx", Stable, &[]),
+    ("zbs", Stable, &[]),
+    ("zdinx", Unstable(sym::riscv_target_feature), &["zfinx"]),
+    ("zfh", Unstable(sym::riscv_target_feature), &["zfhmin"]),
+    ("zfhmin", Unstable(sym::riscv_target_feature), &["f"]),
+    ("zfinx", Unstable(sym::riscv_target_feature), &[]),
+    ("zhinx", Unstable(sym::riscv_target_feature), &["zhinxmin"]),
+    ("zhinxmin", Unstable(sym::riscv_target_feature), &["zfinx"]),
+    ("zk", Stable, &["zkn", "zkr", "zkt"]),
+    ("zkn", Stable, &["zbkb", "zbkc", "zbkx", "zkne", "zknd", "zknh"]),
+    ("zknd", Stable, &[]),
+    ("zkne", Stable, &[]),
+    ("zknh", Stable, &[]),
+    ("zkr", Stable, &[]),
+    ("zks", Stable, &["zbkb", "zbkc", "zbkx", "zksed", "zksh"]),
+    ("zksed", Stable, &[]),
+    ("zksh", Stable, &[]),
+    ("zkt", Stable, &[]),
     // tidy-alphabetical-end
 ];
 
-const WASM_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
+const 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, &[]),
-    ("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), &[]),
+    ("atomics", Unstable(sym::wasm_target_feature), &[]),
+    ("bulk-memory", Stable, &[]),
+    ("exception-handling", Unstable(sym::wasm_target_feature), &[]),
+    ("extended-const", Stable, &[]),
+    ("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, StabilityUncomputed, ImpliedFeatures)] =
-    &[("alu32", unstable(sym::bpf_target_feature), &[])];
+const BPF_FEATURES: &[(&str, Stability, ImpliedFeatures)] =
+    &[("alu32", Unstable(sym::bpf_target_feature), &[])];
 
-const CSKY_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
+const CSKY_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
-    ("10e60", unstable(sym::csky_target_feature), &["7e10"]),
-    ("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"]),
-    ("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), &[]),
+    ("10e60", Unstable(sym::csky_target_feature), &["7e10"]),
+    ("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"]),
+    ("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), &[]),
+    ("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
 ];
 
-const LOONGARCH_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
+const LOONGARCH_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
-    ("d", unstable(sym::loongarch_target_feature), &["f"]),
-    ("f", unstable(sym::loongarch_target_feature), &[]),
-    ("frecipe", unstable(sym::loongarch_target_feature), &[]),
-    ("lasx", unstable(sym::loongarch_target_feature), &["lsx"]),
-    ("lbt", unstable(sym::loongarch_target_feature), &[]),
-    ("lsx", unstable(sym::loongarch_target_feature), &["d"]),
-    ("lvz", unstable(sym::loongarch_target_feature), &[]),
-    ("relax", unstable(sym::loongarch_target_feature), &[]),
-    ("ual", unstable(sym::loongarch_target_feature), &[]),
+    ("d", Unstable(sym::loongarch_target_feature), &["f"]),
+    ("f", Unstable(sym::loongarch_target_feature), &[]),
+    ("frecipe", Unstable(sym::loongarch_target_feature), &[]),
+    ("lasx", Unstable(sym::loongarch_target_feature), &["lsx"]),
+    ("lbt", Unstable(sym::loongarch_target_feature), &[]),
+    ("lsx", Unstable(sym::loongarch_target_feature), &["d"]),
+    ("lvz", Unstable(sym::loongarch_target_feature), &[]),
+    ("relax", Unstable(sym::loongarch_target_feature), &[]),
+    ("ual", Unstable(sym::loongarch_target_feature), &[]),
     // tidy-alphabetical-end
 ];
 
-const IBMZ_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
+const IBMZ_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
-    ("backchain", unstable(sym::s390x_target_feature), &[]),
-    ("vector", unstable(sym::s390x_target_feature), &[]),
+    ("backchain", Unstable(sym::s390x_target_feature), &[]),
+    ("vector", Unstable(sym::s390x_target_feature), &[]),
     // tidy-alphabetical-end
 ];
 
-const SPARC_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
+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), &[]),
+    ("leoncasa", Unstable(sym::sparc_target_feature), &[]),
+    ("v8plus", Unstable(sym::sparc_target_feature), &[]),
+    ("v9", Unstable(sym::sparc_target_feature), &[]),
     // tidy-alphabetical-end
 ];
 
-const M68K_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
+const 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"]),
+    ("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"]),
+    ("isa-68881", Unstable(sym::m68k_target_feature), &[]),
+    ("isa-68882", Unstable(sym::m68k_target_feature), &["isa-68881"]),
     // tidy-alphabetical-end
 ];
 
@@ -808,7 +624,7 @@ const M68K_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
 /// 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, StabilityUncomputed)> {
+pub fn all_rust_features() -> impl Iterator<Item = (&'static str, Stability)> {
     std::iter::empty()
         .chain(ARM_FEATURES.iter())
         .chain(AARCH64_FEATURES.iter())
@@ -853,10 +669,16 @@ const CSKY_FEATURES_FOR_CORRECT_VECTOR_ABI: &'static [(u64, &'static str)] = &[(
 const LOONGARCH_FEATURES_FOR_CORRECT_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, StabilityUncomputed, ImpliedFeatures)] {
+    pub fn rust_target_features(&self) -> &'static [(&'static str, Stability, ImpliedFeatures)] {
         match &*self.arch {
             "arm" => ARM_FEATURES,
             "aarch64" | "arm64ec" => AARCH64_FEATURES,
@@ -904,27 +726,130 @@ impl Target {
         }
     }
 
-    pub fn implied_target_features(
+    pub fn implied_target_features<'a>(
         &self,
-        base_features: impl Iterator<Item = Symbol>,
-    ) -> FxHashSet<Symbol> {
-        let implied_features = self
-            .rust_target_features()
-            .iter()
-            .map(|(f, _, i)| (Symbol::intern(f), i))
-            .collect::<FxHashMap<_, _>>();
+        base_features: impl Iterator<Item = &'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 = base_features.collect::<Vec<Symbol>>();
+        let mut new_features = base_features.collect::<Vec<&str>>();
         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().map(Symbol::intern))
+                    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 sats 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 {
+            "x86" => {
+                // We support 2 ABIs, hardfloat (default) and softfloat.
+                // x86 has no sane ABI indicator so we have to use the target feature.
+                if self.has_feature("soft-float") {
+                    NOTHING
+                } else {
+                    // Hardfloat ABI. x87 must be enabled.
+                    FeatureConstraints { required: &["x87"], incompatible: &[] }
+                }
+            }
+            "x86_64" => {
+                // We support 2 ABIs, hardfloat (default) and softfloat.
+                // x86 has no sane ABI indicator so we have to use the target feature.
+                if self.has_feature("soft-float") {
+                    NOTHING
+                } else {
+                    // Hardfloat ABI. x87 and SSE2 must be enabled.
+                    FeatureConstraints { required: &["x87", "sse2"], incompatible: &[] }
+                }
+            }
+            "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.
+                        NOTHING
+                    }
+                    FloatAbi::Hard => {
+                        // Must have `fpregs` and must not have `soft-float`.
+                        FeatureConstraints { required: &["fpregs"], incompatible: &["soft-float"] }
+                    }
+                }
+            }
+            "aarch64" | "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.abi {
+                    "softfloat" => {
+                        // This is not fully correct, LLVM actually doesn't let us enforce the softfloat
+                        // ABI properly... see <https://github.com/rust-lang/rust/issues/134375>.
+                        // FIXME: should we forbid "neon" here? But that would be a breaking change.
+                        NOTHING
+                    }
+                    _ => {
+                        // Everything else is assumed to use a hardfloat ABI. neon and fp-armv8 must be enabled.
+                        // These are Rust feature names and we use "neon" to control both of them.
+                        FeatureConstraints { required: &["neon"], incompatible: &[] }
+                    }
+                }
+            }
+            "riscv32" | "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 {
+                    "ilp32d" | "lp64d" => {
+                        // Requires d (which implies f), incompatible with e.
+                        FeatureConstraints { required: &["d"], incompatible: &["e"] }
+                    }
+                    "ilp32f" | "lp64f" => {
+                        // Requires f, incompatible with e.
+                        FeatureConstraints { required: &["f"], incompatible: &["e"] }
+                    }
+                    "ilp32" | "lp64" => {
+                        // Requires nothing, incompatible with e.
+                        FeatureConstraints { required: &[], incompatible: &["e"] }
+                    }
+                    "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"] }
+                    }
+                    "lp64e" => {
+                        // As above, `e` is not required.
+                        NOTHING
+                    }
+                    _ => unreachable!(),
+                }
+            }
+            _ => NOTHING,
+        }
+    }
 }