about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2024-12-26 18:32:22 +0100
committerRalf Jung <post@ralfj.de>2024-12-31 12:41:20 +0100
commit2bf27e09beb4cd1c5f01369e7086e36b3de04f5c (patch)
treecbc3c2d940b7b06fbe2d03a5234f20459295a028
parent41b579660c0af700d42abe5b71856098db007783 (diff)
downloadrust-2bf27e09beb4cd1c5f01369e7086e36b3de04f5c.tar.gz
rust-2bf27e09beb4cd1c5f01369e7086e36b3de04f5c.zip
explicitly model that certain ABIs require/forbid certain target features
-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.rs220
-rw-r--r--compiler/rustc_codegen_ssa/messages.ftl2
-rw-r--r--compiler/rustc_codegen_ssa/src/target_features.rs52
-rw-r--r--compiler/rustc_middle/src/middle/codegen_fn_attrs.rs1
-rw-r--r--compiler/rustc_target/src/spec/mod.rs30
-rw-r--r--compiler/rustc_target/src/target_features.rs208
-rw-r--r--tests/codegen/tied-features-strength.rs7
-rw-r--r--tests/ui/target-feature/feature-hierarchy.aarch64-sve2.stderr2
-rw-r--r--tests/ui/target-feature/forbidden-hardfloat-target-feature-attribute.rs10
-rw-r--r--tests/ui/target-feature/forbidden-hardfloat-target-feature-attribute.stderr6
-rw-r--r--tests/ui/target-feature/forbidden-hardfloat-target-feature-flag-disable-neon.stderr2
-rw-r--r--tests/ui/target-feature/forbidden-hardfloat-target-feature-flag-disable.stderr8
-rw-r--r--tests/ui/target-feature/forbidden-target-feature-attribute.rs2
-rw-r--r--tests/ui/target-feature/forbidden-target-feature-attribute.stderr2
-rw-r--r--tests/ui/target-feature/forbidden-target-feature-flag-disable.stderr2
-rw-r--r--tests/ui/target-feature/forbidden-target-feature-flag.stderr2
18 files changed, 299 insertions, 268 deletions
diff --git a/compiler/rustc_codegen_llvm/messages.ftl b/compiler/rustc_codegen_llvm/messages.ftl
index 63c64269eb8..22445448907 100644
--- a/compiler/rustc_codegen_llvm/messages.ftl
+++ b/compiler/rustc_codegen_llvm/messages.ftl
@@ -8,7 +8,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>
 
@@ -22,8 +22,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 3cdb5b971d9..843d0d6c39f 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,
 }
 
@@ -206,12 +207,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..0486ae84b82 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,126 @@ 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_enable, abi_disable) = sess.target.abi_required_features();
+        let abi_enable_set = FxHashSet::from_iter(abi_enable.iter().copied());
+        let abi_disable_set = FxHashSet::from_iter(abi_disable.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 },
-                                }
-                            } else {
-                                UnknownCTargetFeature {
-                                    feature,
-                                    rust_feature: PossibleFeature::None,
+        // 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
                                 }
-                            };
-                            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.target, enable) {
+                            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_disable_set.contains(feature) {
+                        sess.dcx().emit_warn(ForbiddenCTargetFeature {
+                            feature,
+                            enabled: "enabled",
+                            reason: "this feature is incompatible with the target ABI",
+                        });
+                    }
+                } else {
+                    if abi_enable_set.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.)
+        for feature in abi_enable {
+            all_rust_features.push((true, feature));
+        }
+        for feature in abi_disable {
+            all_rust_features.push((false, feature));
+        }
+
+        // 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 +796,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 +830,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..af9ff311061 100644
--- a/compiler/rustc_codegen_ssa/src/target_features.rs
+++ b/compiler/rustc_codegen_ssa/src/target_features.rs
@@ -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_enable, abi_disable) = 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,7 +59,7 @@ pub(crate) fn from_target_feature_attr(
                     }
                 }
                 err.emit();
-                return None;
+                continue;
             };
 
             // Only allow target features whose feature gates have been enabled
@@ -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_disable.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
@@ -163,9 +157,13 @@ pub(crate) fn provide(providers: &mut Providers) {
                     .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_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index 02962d55a60..28011f632b3 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -2651,10 +2651,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 {
@@ -3201,7 +3197,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" => {
@@ -3209,7 +3206,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" => {
@@ -3243,6 +3241,26 @@ impl Target {
                     ));
                 }
             }
+            // Check that we don't mis-set any of the ABI-relevant features.
+            let (abi_enable, abi_disable) = self.abi_required_features();
+            for feat in abi_enable {
+                // 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_disable {
+                // The feature might be disable 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..dd9c3921fc2 100644
--- a/compiler/rustc_target/src/target_features.rs
+++ b/compiler/rustc_target/src/target_features.rs
@@ -213,22 +213,7 @@ const ARM_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
     ("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")
-                }
-            },
-        },
-        &[],
-    ),
+    ("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"]),
@@ -326,28 +311,7 @@ const AARCH64_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
     // FEAT_MTE & FEAT_MTE2
     ("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, &[]),
     // FEAT_PAUTH (generic authentication)
@@ -532,22 +496,7 @@ const X86_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
     ("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")
-                }
-            },
-        },
-        &[],
-    ),
+    ("x87", unstable(sym::x87_target_feature), &[]),
     ("xop", unstable(sym::xop_target_feature), &[/*"fma4", */ "avx", "sse4a"]),
     ("xsave", STABLE, &[]),
     ("xsavec", STABLE, &["xsave"]),
@@ -590,55 +539,8 @@ const RISCV_FEATURES: &[(&str, StabilityUncomputed, 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")
-                    }
-                }
-            },
-        },
-        &[],
-    ),
+    ("d", unstable(sym::riscv_target_feature), &["f"]),
+    ("e", unstable(sym::riscv_target_feature), &[]),
     (
         "f",
         Stability::Unstable {
@@ -904,27 +806,107 @@ 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.
+    ///
+    /// 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) -> (&'static [&'static str], &'static [&'static str]) {
+        const NOTHING: (&'static [&'static str], &'static [&'static str]) = (&[], &[]);
+        // 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" | "x86_64" => {
+                // We support 2 ABIs, hardfloat (default) and softfloat.
+                if self.has_feature("soft-float") {
+                    NOTHING
+                } else {
+                    // Hardfloat ABI. x87 must be enabled.
+                    (&["x87"], &[])
+                }
+            }
+            "arm" => {
+                // We support 2 ABIs, hardfloat (default) and softfloat.
+                if self.has_feature("soft-float") {
+                    NOTHING
+                } else {
+                    // Hardfloat ABI. x87 must be enabled.
+                    (&["fpregs"], &[])
+                }
+            }
+            "aarch64" | "arm64ec" => {
+                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 be 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.
+                        (&["neon"], &[])
+                    }
+                }
+            }
+            "riscv32" | "riscv64" => {
+                match &*self.llvm_abiname {
+                    "ilp32d" | "lp64d" => {
+                        // Requires d (which implies f), incompatible with e.
+                        (&["d"], &["e"])
+                    }
+                    "ilp32f" | "lp64f" => {
+                        // Requires f, incompatible with e.
+                        (&["f"], &["e"])
+                    }
+                    "ilp32" | "lp64" => {
+                        // Requires nothing, incompatible with e.
+                        (&[], &["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.
+                        (&[], &["d"])
+                    }
+                    "lp64e" => {
+                        // As above, `e` is not required.
+                        NOTHING
+                    }
+                    _ => unreachable!(),
+                }
+            }
+            _ => NOTHING,
+        }
+    }
 }
diff --git a/tests/codegen/tied-features-strength.rs b/tests/codegen/tied-features-strength.rs
index 1b2b63c3d1a..4d96a7cde78 100644
--- a/tests/codegen/tied-features-strength.rs
+++ b/tests/codegen/tied-features-strength.rs
@@ -1,5 +1,5 @@
 // ignore-tidy-linelength
-//@ revisions: ENABLE_SVE DISABLE_SVE DISABLE_NEON ENABLE_NEON
+//@ revisions: ENABLE_SVE DISABLE_SVE ENABLE_NEON
 //@ compile-flags: --crate-type=rlib --target=aarch64-unknown-linux-gnu
 //@ needs-llvm-components: aarch64
 
@@ -11,9 +11,10 @@
 // ENABLE_SVE: attributes #0 = { {{.*}} "target-features"="{{((\+outline-atomics,?)|(\+v8a,?)|(\+fpmr,?)?|(\+sve,?)|(\+neon,?)|(\+fp-armv8,?))*}}" }
 
 //@ [DISABLE_SVE] compile-flags: -C target-feature=-sve -Copt-level=0
-// DISABLE_SVE: attributes #0 = { {{.*}} "target-features"="{{((\+outline-atomics,?)|(\+v8a,?)|(\+fpmr,?)?|(-sve,?)|(\+neon,?))*}}" }
+// DISABLE_SVE: attributes #0 = { {{.*}} "target-features"="{{((\+outline-atomics,?)|(\+v8a,?)|(\+fpmr,?)?|(-sve,?)|(\+neon,?)|(\+fp-armv8,?))*}}" }
 
-//@ [DISABLE_NEON] compile-flags: -C target-feature=-neon -Copt-level=0
+// The DISABLE_NEON is disabled since neon is a required target feature for this targt, it cannot be disabled.
+// it would have: compile-flags: -C target-feature=-neon -Copt-level=0
 // DISABLE_NEON: attributes #0 = { {{.*}} "target-features"="{{((\+outline-atomics,?)|(\+v8a,?)|(\+fpmr,?)?|(-fp-armv8,?)|(-neon,?))*}}" }
 
 //@ [ENABLE_NEON] compile-flags: -C target-feature=+neon -Copt-level=0
diff --git a/tests/ui/target-feature/feature-hierarchy.aarch64-sve2.stderr b/tests/ui/target-feature/feature-hierarchy.aarch64-sve2.stderr
index d5d7d7aa627..b6c3ccdedfb 100644
--- a/tests/ui/target-feature/feature-hierarchy.aarch64-sve2.stderr
+++ b/tests/ui/target-feature/feature-hierarchy.aarch64-sve2.stderr
@@ -1,4 +1,4 @@
-warning: target feature `neon` cannot be toggled with `-Ctarget-feature`: unsound on hard-float targets because it changes float ABI
+warning: target feature `neon` cannot be disabled with `-Ctarget-feature`: this feature is required by the target ABI
    |
    = note: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #116344 <https://github.com/rust-lang/rust/issues/116344>
diff --git a/tests/ui/target-feature/forbidden-hardfloat-target-feature-attribute.rs b/tests/ui/target-feature/forbidden-hardfloat-target-feature-attribute.rs
index b3171d52c51..dab01179c0b 100644
--- a/tests/ui/target-feature/forbidden-hardfloat-target-feature-attribute.rs
+++ b/tests/ui/target-feature/forbidden-hardfloat-target-feature-attribute.rs
@@ -1,11 +1,11 @@
-//@ compile-flags: --target=x86_64-unknown-linux-gnu --crate-type=lib
-//@ needs-llvm-components: x86
-#![feature(no_core, lang_items)]
+//@ compile-flags: --target=riscv32e-unknown-none-elf --crate-type=lib
+//@ needs-llvm-components: riscv
+#![feature(no_core, lang_items, riscv_target_feature)]
 #![no_core]
 
 #[lang = "sized"]
 pub trait Sized {}
 
-#[target_feature(enable = "x87")]
-//~^ERROR: cannot be toggled with
+#[target_feature(enable = "d")]
+//~^ERROR: cannot be enabled with
 pub unsafe fn my_fun() {}
diff --git a/tests/ui/target-feature/forbidden-hardfloat-target-feature-attribute.stderr b/tests/ui/target-feature/forbidden-hardfloat-target-feature-attribute.stderr
index 3ebbe69d8ae..9df56d86729 100644
--- a/tests/ui/target-feature/forbidden-hardfloat-target-feature-attribute.stderr
+++ b/tests/ui/target-feature/forbidden-hardfloat-target-feature-attribute.stderr
@@ -1,8 +1,8 @@
-error: target feature `x87` cannot be toggled with `#[target_feature]`: unsound on hard-float targets because it changes float ABI
+error: target feature `d` cannot be enabled with `#[target_feature]`: this feature is incompatible with the target ABI
   --> $DIR/forbidden-hardfloat-target-feature-attribute.rs:9:18
    |
-LL | #[target_feature(enable = "x87")]
-   |                  ^^^^^^^^^^^^^^
+LL | #[target_feature(enable = "d")]
+   |                  ^^^^^^^^^^^^
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/target-feature/forbidden-hardfloat-target-feature-flag-disable-neon.stderr b/tests/ui/target-feature/forbidden-hardfloat-target-feature-flag-disable-neon.stderr
index d5d7d7aa627..b6c3ccdedfb 100644
--- a/tests/ui/target-feature/forbidden-hardfloat-target-feature-flag-disable-neon.stderr
+++ b/tests/ui/target-feature/forbidden-hardfloat-target-feature-flag-disable-neon.stderr
@@ -1,4 +1,4 @@
-warning: target feature `neon` cannot be toggled with `-Ctarget-feature`: unsound on hard-float targets because it changes float ABI
+warning: target feature `neon` cannot be disabled with `-Ctarget-feature`: this feature is required by the target ABI
    |
    = note: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #116344 <https://github.com/rust-lang/rust/issues/116344>
diff --git a/tests/ui/target-feature/forbidden-hardfloat-target-feature-flag-disable.stderr b/tests/ui/target-feature/forbidden-hardfloat-target-feature-flag-disable.stderr
index 604ad2f991a..6191681286a 100644
--- a/tests/ui/target-feature/forbidden-hardfloat-target-feature-flag-disable.stderr
+++ b/tests/ui/target-feature/forbidden-hardfloat-target-feature-flag-disable.stderr
@@ -1,7 +1,11 @@
-warning: target feature `x87` cannot be toggled with `-Ctarget-feature`: unsound on hard-float targets because it changes float ABI
+warning: unstable feature specified for `-Ctarget-feature`: `x87`
+   |
+   = note: this feature is not stably supported; its behavior can change in the future
+
+warning: target feature `x87` cannot be disabled with `-Ctarget-feature`: this feature is required by the target ABI
    |
    = note: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #116344 <https://github.com/rust-lang/rust/issues/116344>
 
-warning: 1 warning emitted
+warning: 2 warnings emitted
 
diff --git a/tests/ui/target-feature/forbidden-target-feature-attribute.rs b/tests/ui/target-feature/forbidden-target-feature-attribute.rs
index f13cdd17da6..2ba5f2215c7 100644
--- a/tests/ui/target-feature/forbidden-target-feature-attribute.rs
+++ b/tests/ui/target-feature/forbidden-target-feature-attribute.rs
@@ -7,5 +7,5 @@
 pub trait Sized {}
 
 #[target_feature(enable = "soft-float")]
-//~^ERROR: cannot be toggled with
+//~^ERROR: cannot be enabled with
 pub unsafe fn my_fun() {}
diff --git a/tests/ui/target-feature/forbidden-target-feature-attribute.stderr b/tests/ui/target-feature/forbidden-target-feature-attribute.stderr
index 27ac4aaf960..f3d54cc19d3 100644
--- a/tests/ui/target-feature/forbidden-target-feature-attribute.stderr
+++ b/tests/ui/target-feature/forbidden-target-feature-attribute.stderr
@@ -1,4 +1,4 @@
-error: target feature `soft-float` cannot be toggled with `#[target_feature]`: unsound because it changes float ABI
+error: target feature `soft-float` cannot be enabled with `#[target_feature]`: unsound because it changes float ABI
   --> $DIR/forbidden-target-feature-attribute.rs:9:18
    |
 LL | #[target_feature(enable = "soft-float")]
diff --git a/tests/ui/target-feature/forbidden-target-feature-flag-disable.stderr b/tests/ui/target-feature/forbidden-target-feature-flag-disable.stderr
index 508e1fe0cf4..797cd4be5c2 100644
--- a/tests/ui/target-feature/forbidden-target-feature-flag-disable.stderr
+++ b/tests/ui/target-feature/forbidden-target-feature-flag-disable.stderr
@@ -1,4 +1,4 @@
-warning: target feature `soft-float` cannot be toggled with `-Ctarget-feature`: unsound because it changes float ABI
+warning: target feature `soft-float` cannot be disabled with `-Ctarget-feature`: unsound because it changes float ABI
    |
    = note: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #116344 <https://github.com/rust-lang/rust/issues/116344>
diff --git a/tests/ui/target-feature/forbidden-target-feature-flag.stderr b/tests/ui/target-feature/forbidden-target-feature-flag.stderr
index 508e1fe0cf4..075666fbf66 100644
--- a/tests/ui/target-feature/forbidden-target-feature-flag.stderr
+++ b/tests/ui/target-feature/forbidden-target-feature-flag.stderr
@@ -1,4 +1,4 @@
-warning: target feature `soft-float` cannot be toggled with `-Ctarget-feature`: unsound because it changes float ABI
+warning: target feature `soft-float` cannot be enabled with `-Ctarget-feature`: unsound because it changes float ABI
    |
    = note: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #116344 <https://github.com/rust-lang/rust/issues/116344>