From eb2e928250066df9e40291fb9fb97308df16046e Mon Sep 17 00:00:00 2001
From: Ralf Jung <post@ralfj.de>
Date: Sun, 24 Nov 2024 12:22:01 +0100
Subject: [PATCH 1/4] target_features: control separately whether enabling and
 disabling a target feature is allowed

---
 compiler/rustc_codegen_gcc/src/gcc_util.rs    |  2 +-
 compiler/rustc_codegen_gcc/src/lib.rs         |  4 +-
 compiler/rustc_codegen_llvm/src/llvm_util.rs  |  6 +-
 .../rustc_codegen_ssa/src/target_features.rs  |  4 +-
 compiler/rustc_target/src/target_features.rs  | 77 +++++++++++++------
 5 files changed, 63 insertions(+), 30 deletions(-)

diff --git a/compiler/rustc_codegen_gcc/src/gcc_util.rs b/compiler/rustc_codegen_gcc/src/gcc_util.rs
index 88e5eefd7a156..058a874501b26 100644
--- a/compiler/rustc_codegen_gcc/src/gcc_util.rs
+++ b/compiler/rustc_codegen_gcc/src/gcc_util.rs
@@ -96,7 +96,7 @@ pub(crate) fn global_gcc_features(sess: &Session, diagnostics: bool) -> Vec<Stri
                     }
                     Some((_, stability, _)) => {
                         if let Err(reason) =
-                            stability.compute_toggleability(&sess.target).allow_toggle()
+                            stability.toggle_allowed(&sess.target, enable_disable == '+')
                         {
                             sess.dcx().emit_warn(ForbiddenCTargetFeature { feature, reason });
                         } else if stability.requires_nightly().is_some() {
diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs
index 764e84be1feb9..bb0f2fa5b014e 100644
--- a/compiler/rustc_codegen_gcc/src/lib.rs
+++ b/compiler/rustc_codegen_gcc/src/lib.rs
@@ -483,9 +483,9 @@ fn target_features_cfg(
         .rust_target_features()
         .iter()
         .filter(|(_, gate, _)| gate.in_cfg())
-        .filter_map(|&(feature, gate, _)| {
+        .filter_map(|(feature, gate, _)| {
             if sess.is_nightly_build() || allow_unstable || gate.requires_nightly().is_none() {
-                Some(feature)
+                Some(*feature)
             } else {
                 None
             }
diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs
index bfec7d708cfbc..194438af88c26 100644
--- a/compiler/rustc_codegen_llvm/src/llvm_util.rs
+++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs
@@ -373,9 +373,9 @@ pub fn target_features_cfg(sess: &Session, allow_unstable: bool) -> Vec<Symbol>
         .rust_target_features()
         .iter()
         .filter(|(_, gate, _)| gate.in_cfg())
-        .filter_map(|&(feature, gate, _)| {
+        .filter_map(|(feature, gate, _)| {
             if sess.is_nightly_build() || allow_unstable || gate.requires_nightly().is_none() {
-                Some(feature)
+                Some(*feature)
             } else {
                 None
             }
@@ -718,7 +718,7 @@ pub(crate) fn global_llvm_features(
                         }
                         Some((_, stability, _)) => {
                             if let Err(reason) =
-                                stability.compute_toggleability(&sess.target).allow_toggle()
+                                stability.toggle_allowed(&sess.target, enable_disable == '+')
                             {
                                 sess.dcx().emit_warn(ForbiddenCTargetFeature { feature, reason });
                             } else if stability.requires_nightly().is_some() {
diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs
index fa600ec7166ad..f4d4a9db1d898 100644
--- a/compiler/rustc_codegen_ssa/src/target_features.rs
+++ b/compiler/rustc_codegen_ssa/src/target_features.rs
@@ -65,7 +65,7 @@ pub(crate) fn from_target_feature_attr(
 
             // Only allow target features whose feature gates have been enabled
             // and which are permitted to be toggled.
-            if let Err(reason) = stability.allow_toggle() {
+            if let Err(reason) = stability.toggle_allowed(/*enable*/ true) {
                 tcx.dcx().emit_err(errors::ForbiddenTargetFeatureAttr {
                     span: item.span(),
                     feature,
@@ -160,7 +160,7 @@ pub(crate) fn provide(providers: &mut Providers) {
                     .target
                     .rust_target_features()
                     .iter()
-                    .map(|&(a, b, _)| (a.to_string(), b.compute_toggleability(target)))
+                    .map(|(a, b, _)| (a.to_string(), b.compute_toggleability(target)))
                     .collect()
             }
         },
diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs
index 29d3f826a1544..493c3a0ef6bc0 100644
--- a/compiler/rustc_target/src/target_features.rs
+++ b/compiler/rustc_target/src/target_features.rs
@@ -20,7 +20,7 @@ pub const RUSTC_SPECIAL_FEATURES: &[&str] = &["backchain"];
 /// `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, Copy)]
+#[derive(Debug, Clone)]
 pub enum Stability<Toggleability> {
     /// This target feature is stable, it can be used in `#[target_feature]` and
     /// `#[cfg(target_feature)]`.
@@ -44,11 +44,21 @@ pub enum Stability<Toggleability> {
     Forbidden { reason: &'static str },
 }
 
-/// `Stability` where `allow_toggle` has not been computed yet.
 /// Returns `Ok` if the toggle is allowed, `Err` with an explanation of not.
-pub type StabilityUncomputed = Stability<fn(&Target) -> Result<(), &'static str>>;
+/// 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<Result<(), &'static str>>;
+pub type StabilityComputed = Stability<AllowToggleComputed>;
 
 impl<CTX, Toggleability: HashStable<CTX>> HashStable<CTX> for Stability<Toggleability> {
     #[inline]
@@ -69,11 +79,20 @@ 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> {
     /// Returns whether the feature can be used in `cfg(target_feature)` ever.
     /// (It might still be nightly-only even if this returns `true`, so make sure to also check
     /// `requires_nightly`.)
-    pub fn in_cfg(self) -> bool {
+    pub fn in_cfg(&self) -> bool {
         !matches!(self, Stability::Forbidden { .. })
     }
 
@@ -85,24 +104,37 @@ impl<Toggleability> Stability<Toggleability> {
     /// Before calling this, ensure the feature is even permitted for this use:
     /// - for `#[target_feature]`/`-Ctarget-feature`, check `allow_toggle()`
     /// - for `cfg(target_feature)`, check `in_cfg`
-    pub fn requires_nightly(self) -> Option<Symbol> {
+    pub fn requires_nightly(&self) -> Option<Symbol> {
         match self {
-            Stability::Unstable { nightly_feature, .. } => Some(nightly_feature),
-            Stability::Stable { .. } => None,
-            Stability::Forbidden { .. } => panic!("forbidden features should not reach this far"),
+            &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 {
+    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: allow_toggle(target) },
-            Unstable { nightly_feature, allow_toggle } => {
-                Unstable { nightly_feature, allow_toggle: allow_toggle(target) }
+            &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 },
+            &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),
         }
     }
 }
@@ -111,19 +143,20 @@ 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 allow_toggle(self) -> Result<(), &'static str> {
-        match self {
+    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 } => Err(reason),
-        }
+            Stability::Forbidden { reason } => return Err(reason),
+        };
+        if enable { allow_toggle.enable } else { allow_toggle.disable }
     }
 }
 
 // Constructors for the list below, defaulting to "always allow toggle".
-const STABLE: StabilityUncomputed = Stability::Stable { allow_toggle: |_target| Ok(()) };
+const STABLE: StabilityUncomputed = Stability::Stable { allow_toggle: |_target, _enable| Ok(()) };
 const fn unstable(nightly_feature: Symbol) -> StabilityUncomputed {
-    Stability::Unstable { nightly_feature, allow_toggle: |_target| Ok(()) }
+    Stability::Unstable { nightly_feature, allow_toggle: |_target, _enable| Ok(()) }
 }
 
 // Here we list target features that rustc "understands": they can be used in `#[target_feature]`
@@ -184,7 +217,7 @@ const ARM_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
         "fpregs",
         Stability::Unstable {
             nightly_feature: sym::arm_target_feature,
-            allow_toggle: |target: &Target| {
+            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") {
@@ -481,7 +514,7 @@ const X86_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
         "x87",
         Stability::Unstable {
             nightly_feature: sym::x87_target_feature,
-            allow_toggle: |target: &Target| {
+            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") {

From 1f8236d4c78fc111da14f4e00639e92ca05707de Mon Sep 17 00:00:00 2001
From: Ralf Jung <post@ralfj.de>
Date: Sun, 24 Nov 2024 12:35:50 +0100
Subject: [PATCH 2/4] reject aarch64 target feature toggling that would change
 the float ABI

---
 compiler/rustc_target/src/spec/mod.rs         | 12 ++++++++++
 compiler/rustc_target/src/target_features.rs  | 24 ++++++++++++++++++-
 .../feature-hierarchy.aarch64-sve2.stderr     |  7 ++++++
 ...dfloat-target-feature-flag-disable-neon.rs | 10 ++++++++
 ...at-target-feature-flag-disable-neon.stderr |  7 ++++++
 5 files changed, 59 insertions(+), 1 deletion(-)
 create mode 100644 tests/ui/target-feature/feature-hierarchy.aarch64-sve2.stderr
 create mode 100644 tests/ui/target-feature/forbidden-hardfloat-target-feature-flag-disable-neon.rs
 create mode 100644 tests/ui/target-feature/forbidden-hardfloat-target-feature-flag-disable-neon.stderr

diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index 7d308c6c662cd..06d2099a446ce 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -2615,6 +2615,18 @@ impl TargetOptions {
             }
         })
     }
+
+    pub(crate) fn has_neg_feature(&self, search_feature: &str) -> bool {
+        self.features.split(',').any(|f| {
+            if let Some(f) = f.strip_prefix('-')
+                && f == search_feature
+            {
+                true
+            } else {
+                false
+            }
+        })
+    }
 }
 
 impl Default for TargetOptions {
diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs
index 493c3a0ef6bc0..e1f7884610c54 100644
--- a/compiler/rustc_target/src/target_features.rs
+++ b/compiler/rustc_target/src/target_features.rs
@@ -290,6 +290,7 @@ const AARCH64_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
     ("flagm", STABLE, &[]),
     // FEAT_FLAGM2
     ("flagm2", unstable(sym::aarch64_unstable_target_feature), &[]),
+    ("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"]),
@@ -325,7 +326,28 @@ const AARCH64_FEATURES: &[(&str, StabilityUncomputed, ImpliedFeatures)] = &[
     // FEAT_MTE & FEAT_MTE2
     ("mte", STABLE, &[]),
     // FEAT_AdvSimd & FEAT_FP
-    ("neon", STABLE, &[]),
+    (
+        "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")
+                }
+            },
+        },
+        &[],
+    ),
     // FEAT_PAUTH (address authentication)
     ("paca", STABLE, &[]),
     // FEAT_PAUTH (generic authentication)
diff --git a/tests/ui/target-feature/feature-hierarchy.aarch64-sve2.stderr b/tests/ui/target-feature/feature-hierarchy.aarch64-sve2.stderr
new file mode 100644
index 0000000000000..d5d7d7aa627ba
--- /dev/null
+++ b/tests/ui/target-feature/feature-hierarchy.aarch64-sve2.stderr
@@ -0,0 +1,7 @@
+warning: target feature `neon` cannot be toggled with `-Ctarget-feature`: unsound on hard-float targets 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>
+
+warning: 1 warning emitted
+
diff --git a/tests/ui/target-feature/forbidden-hardfloat-target-feature-flag-disable-neon.rs b/tests/ui/target-feature/forbidden-hardfloat-target-feature-flag-disable-neon.rs
new file mode 100644
index 0000000000000..95c366bb3f5be
--- /dev/null
+++ b/tests/ui/target-feature/forbidden-hardfloat-target-feature-flag-disable-neon.rs
@@ -0,0 +1,10 @@
+//@ compile-flags: --target=aarch64-unknown-linux-gnu --crate-type=lib
+//@ needs-llvm-components: aarch64
+//@ compile-flags: -Ctarget-feature=-neon
+// For now this is just a warning.
+//@ build-pass
+#![feature(no_core, lang_items)]
+#![no_core]
+
+#[lang = "sized"]
+pub trait Sized {}
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
new file mode 100644
index 0000000000000..d5d7d7aa627ba
--- /dev/null
+++ b/tests/ui/target-feature/forbidden-hardfloat-target-feature-flag-disable-neon.stderr
@@ -0,0 +1,7 @@
+warning: target feature `neon` cannot be toggled with `-Ctarget-feature`: unsound on hard-float targets 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>
+
+warning: 1 warning emitted
+

From 0c9d42cc56626bb383cbab3bb8529a8f1a117fed Mon Sep 17 00:00:00 2001
From: Ralf Jung <post@ralfj.de>
Date: Sun, 15 Dec 2024 09:25:11 +0100
Subject: [PATCH 3/4] apply review feedback

---
 compiler/rustc_target/src/spec/mod.rs        | 20 ++--------------
 compiler/rustc_target/src/target_features.rs | 24 ++++++++++----------
 2 files changed, 14 insertions(+), 30 deletions(-)

diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index 06d2099a446ce..f7cf12215f3bf 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -2605,27 +2605,11 @@ impl TargetOptions {
     }
 
     pub(crate) fn has_feature(&self, search_feature: &str) -> bool {
-        self.features.split(',').any(|f| {
-            if let Some(f) = f.strip_prefix('+')
-                && f == search_feature
-            {
-                true
-            } else {
-                false
-            }
-        })
+        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| {
-            if let Some(f) = f.strip_prefix('-')
-                && f == search_feature
-            {
-                true
-            } else {
-                false
-            }
-        })
+        self.features.split(',').any(|f| f.strip_prefix('-').is_some_and(|f| f == search_feature))
     }
 }
 
diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs
index e1f7884610c54..713b5dc70d7ad 100644
--- a/compiler/rustc_target/src/target_features.rs
+++ b/compiler/rustc_target/src/target_features.rs
@@ -105,10 +105,10 @@ impl<Toggleability> Stability<Toggleability> {
     /// - for `#[target_feature]`/`-Ctarget-feature`, check `allow_toggle()`
     /// - for `cfg(target_feature)`, check `in_cfg`
     pub fn requires_nightly(&self) -> Option<Symbol> {
-        match self {
-            &Stability::Unstable { nightly_feature, .. } => Some(nightly_feature),
-            &Stability::Stable { .. } => None,
-            &Stability::Forbidden { .. } => panic!("forbidden features should not reach this far"),
+        match *self {
+            Stability::Unstable { nightly_feature, .. } => Some(nightly_feature),
+            Stability::Stable { .. } => None,
+            Stability::Forbidden { .. } => panic!("forbidden features should not reach this far"),
         }
     }
 }
@@ -120,21 +120,21 @@ impl StabilityUncomputed {
             enable: f(target, true),
             disable: f(target, false),
         };
-        match self {
-            &Stable { allow_toggle } => Stable { allow_toggle: compute(allow_toggle) },
-            &Unstable { nightly_feature, allow_toggle } => {
+        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 },
+            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),
+        match *self {
+            Stable { allow_toggle } => allow_toggle(target, enable),
+            Unstable { allow_toggle, .. } => allow_toggle(target, enable),
+            Forbidden { reason } => Err(reason),
         }
     }
 }

From 74e2ac406ba620aeff8732d2dde96c0839dcacbf Mon Sep 17 00:00:00 2001
From: Ralf Jung <post@ralfj.de>
Date: Sun, 15 Dec 2024 11:20:00 +0100
Subject: [PATCH 4/4] advice against negative features in target specs

Co-authored-by: Jubilee <workingjubilee@gmail.com>
---
 compiler/rustc_target/src/spec/mod.rs | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index f7cf12215f3bf..a44d2af6b9086 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -2213,6 +2213,10 @@ pub struct TargetOptions {
     /// `-Ctarget-cpu` but can be overwritten with `-Ctarget-features`.
     /// Corresponds to `llc -mattr=$features`.
     /// Note that these are LLVM feature names, not Rust feature names!
+    ///
+    /// Generally it is a bad idea to use negative target features because they often interact very
+    /// poorly with how `-Ctarget-cpu` works. Instead, try to use a lower "base CPU" and enable the
+    /// features you want to use.
     pub features: StaticCow<str>,
     /// Direct or use GOT indirect to reference external data symbols
     pub direct_access_external_data: Option<bool>,