From b3914945ae2d77770c25ed124a8450d82c8f7fe9 Mon Sep 17 00:00:00 2001
From: Jeremy Smart <jeremy3141592@gmail.com>
Date: Wed, 18 Jun 2025 00:38:55 -0400
Subject: [PATCH 01/20] add ChildExt(::send_signal)

---
 library/std/src/os/unix/mod.rs                |  3 ++
 library/std/src/os/unix/process.rs            | 35 +++++++++++++++++++
 library/std/src/sys/pal/unix/linux/pidfd.rs   |  6 +++-
 library/std/src/sys/process/unix/fuchsia.rs   |  5 +++
 library/std/src/sys/process/unix/unix.rs      | 12 ++++---
 .../std/src/sys/process/unix/unsupported.rs   |  6 +++-
 library/std/src/sys/process/unix/vxworks.rs   |  8 +++--
 7 files changed, 67 insertions(+), 8 deletions(-)

diff --git a/library/std/src/os/unix/mod.rs b/library/std/src/os/unix/mod.rs
index 5802b6539651c..78c957270c451 100644
--- a/library/std/src/os/unix/mod.rs
+++ b/library/std/src/os/unix/mod.rs
@@ -116,6 +116,9 @@ pub mod prelude {
     #[stable(feature = "rust1", since = "1.0.0")]
     pub use super::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
     #[doc(no_inline)]
+    #[unstable(feature = "unix_send_signal", issue = "141975")]
+    pub use super::process::ChildExt;
+    #[doc(no_inline)]
     #[stable(feature = "rust1", since = "1.0.0")]
     pub use super::process::{CommandExt, ExitStatusExt};
     #[doc(no_inline)]
diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs
index 57ce3c5a4bf4a..3f4fe2e56ec31 100644
--- a/library/std/src/os/unix/process.rs
+++ b/library/std/src/os/unix/process.rs
@@ -378,6 +378,41 @@ impl ExitStatusExt for process::ExitStatusError {
     }
 }
 
+#[unstable(feature = "unix_send_signal", issue = "141975")]
+pub trait ChildExt: Sealed {
+    /// Sends a signal to a child process.
+    ///
+    /// # Errors
+    ///
+    /// This function will return an error if the signal is invalid. The integer values associated
+    /// with signals are implemenation-specific, so it's encouraged to use a crate that provides
+    /// posix bindings.
+    ///
+    /// # Examples
+    ///
+    /// ```rust
+    /// #![feature(unix_send_signal)]
+    ///
+    /// use std::{io, os::unix::process::ChildExt, process::{Command, Stdio}};
+    ///
+    /// use libc::SIGTERM;
+    ///
+    /// fn main() -> io::Result<()> {
+    ///     let child = Command::new("cat").stdin(Stdio::piped()).spawn()?;
+    ///     child.send_signal(SIGTERM)?;
+    ///     Ok(())
+    /// }
+    /// ```
+    fn send_signal(&self, signal: i32) -> io::Result<()>;
+}
+
+#[unstable(feature = "unix_send_signal", issue = "141975")]
+impl ChildExt for process::Child {
+    fn send_signal(&self, signal: i32) -> io::Result<()> {
+        self.handle.send_signal(signal)
+    }
+}
+
 #[stable(feature = "process_extensions", since = "1.2.0")]
 impl FromRawFd for process::Stdio {
     #[inline]
diff --git a/library/std/src/sys/pal/unix/linux/pidfd.rs b/library/std/src/sys/pal/unix/linux/pidfd.rs
index 2d949ec9e91f7..47e9a616bfdaf 100644
--- a/library/std/src/sys/pal/unix/linux/pidfd.rs
+++ b/library/std/src/sys/pal/unix/linux/pidfd.rs
@@ -13,11 +13,15 @@ pub(crate) struct PidFd(FileDesc);
 
 impl PidFd {
     pub fn kill(&self) -> io::Result<()> {
+        self.send_signal(libc::SIGKILL)
+    }
+
+    pub(crate) fn send_signal(&self, signal: i32) -> io::Result<()> {
         cvt(unsafe {
             libc::syscall(
                 libc::SYS_pidfd_send_signal,
                 self.0.as_raw_fd(),
-                libc::SIGKILL,
+                signal,
                 crate::ptr::null::<()>(),
                 0,
             )
diff --git a/library/std/src/sys/process/unix/fuchsia.rs b/library/std/src/sys/process/unix/fuchsia.rs
index 017ab91797ce6..d71be510b6afe 100644
--- a/library/std/src/sys/process/unix/fuchsia.rs
+++ b/library/std/src/sys/process/unix/fuchsia.rs
@@ -152,6 +152,11 @@ impl Process {
         Ok(())
     }
 
+    pub fn send_signal(&self, _signal: i32) -> io::Result<()> {
+        // Fuchsia doesn't have a direct equivalent for signals
+        unimplemented!()
+    }
+
     pub fn wait(&mut self) -> io::Result<ExitStatus> {
         let mut proc_info: zx_info_process_t = Default::default();
         let mut actual: size_t = 0;
diff --git a/library/std/src/sys/process/unix/unix.rs b/library/std/src/sys/process/unix/unix.rs
index 4f595ac9a1c5f..1fe80c13b81c8 100644
--- a/library/std/src/sys/process/unix/unix.rs
+++ b/library/std/src/sys/process/unix/unix.rs
@@ -963,9 +963,13 @@ impl Process {
         self.pid as u32
     }
 
-    pub fn kill(&mut self) -> io::Result<()> {
+    pub fn kill(&self) -> io::Result<()> {
+        self.send_signal(libc::SIGKILL)
+    }
+
+    pub(crate) fn send_signal(&self, signal: i32) -> io::Result<()> {
         // If we've already waited on this process then the pid can be recycled
-        // and used for another process, and we probably shouldn't be killing
+        // and used for another process, and we probably shouldn't be signaling
         // random processes, so return Ok because the process has exited already.
         if self.status.is_some() {
             return Ok(());
@@ -973,9 +977,9 @@ impl Process {
         #[cfg(target_os = "linux")]
         if let Some(pid_fd) = self.pidfd.as_ref() {
             // pidfd_send_signal predates pidfd_open. so if we were able to get an fd then sending signals will work too
-            return pid_fd.kill();
+            return pid_fd.send_signal(signal);
         }
-        cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(drop)
+        cvt(unsafe { libc::kill(self.pid, signal) }).map(drop)
     }
 
     pub fn wait(&mut self) -> io::Result<ExitStatus> {
diff --git a/library/std/src/sys/process/unix/unsupported.rs b/library/std/src/sys/process/unix/unsupported.rs
index e86561a5c5c4f..87403cd50f822 100644
--- a/library/std/src/sys/process/unix/unsupported.rs
+++ b/library/std/src/sys/process/unix/unsupported.rs
@@ -40,7 +40,11 @@ impl Process {
         0
     }
 
-    pub fn kill(&mut self) -> io::Result<()> {
+    pub fn kill(&self) -> io::Result<()> {
+        unsupported()
+    }
+
+    pub fn send_signal(&self, _signal: i32) -> io::Result<()> {
         unsupported()
     }
 
diff --git a/library/std/src/sys/process/unix/vxworks.rs b/library/std/src/sys/process/unix/vxworks.rs
index f33b4a375da83..51ae8c56bdb9b 100644
--- a/library/std/src/sys/process/unix/vxworks.rs
+++ b/library/std/src/sys/process/unix/vxworks.rs
@@ -146,14 +146,18 @@ impl Process {
         self.pid as u32
     }
 
-    pub fn kill(&mut self) -> io::Result<()> {
+    pub fn kill(&self) -> io::Result<()> {
+        self.send_signal(libc::SIGKILL)
+    }
+
+    pub fn send_signal(&self, signal: i32) -> io::Result<()> {
         // If we've already waited on this process then the pid can be recycled
         // and used for another process, and we probably shouldn't be killing
         // random processes, so return Ok because the process has exited already.
         if self.status.is_some() {
             Ok(())
         } else {
-            cvt(unsafe { libc::kill(self.pid, libc::SIGKILL) }).map(drop)
+            cvt(unsafe { libc::kill(self.pid, signal) }).map(drop)
         }
     }
 

From 54970a8fa25e5ac5dc542e52672a4d7b949e09d5 Mon Sep 17 00:00:00 2001
From: Folkert de Vries <folkert@folkertdev.nl>
Date: Mon, 16 Jun 2025 21:26:29 +0200
Subject: [PATCH 02/20] error on calls to ABIs that cannot be called

---
 compiler/rustc_hir_typeck/messages.ftl       |   6 +-
 compiler/rustc_hir_typeck/src/callee.rs      |  54 ++++++--
 compiler/rustc_hir_typeck/src/errors.rs      |   7 +-
 compiler/rustc_hir_typeck/src/expr.rs        |  10 +-
 tests/ui/abi/bad-custom.rs                   |  14 +-
 tests/ui/abi/bad-custom.stderr               |  56 +++++++-
 tests/ui/abi/cannot-be-called.avr.stderr     | 132 +++++++++++++++++++
 tests/ui/abi/cannot-be-called.i686.stderr    | 132 +++++++++++++++++++
 tests/ui/abi/cannot-be-called.msp430.stderr  | 132 +++++++++++++++++++
 tests/ui/abi/cannot-be-called.riscv32.stderr | 130 ++++++++++++++++++
 tests/ui/abi/cannot-be-called.riscv64.stderr | 130 ++++++++++++++++++
 tests/ui/abi/cannot-be-called.rs             |  93 +++++++++++++
 tests/ui/abi/cannot-be-called.x64.stderr     | 132 +++++++++++++++++++
 tests/ui/abi/cannot-be-called.x64_win.stderr | 132 +++++++++++++++++++
 tests/ui/abi/unsupported.aarch64.stderr      |  56 ++++----
 tests/ui/abi/unsupported.arm.stderr          |  56 ++++----
 tests/ui/abi/unsupported.i686.stderr         |  28 ++--
 tests/ui/abi/unsupported.riscv32.stderr      |  68 ++++++----
 tests/ui/abi/unsupported.riscv64.stderr      |  68 ++++++----
 tests/ui/abi/unsupported.rs                  |   2 +
 tests/ui/abi/unsupported.x64.stderr          |  54 +++++---
 tests/ui/abi/unsupported.x64_win.stderr      |  54 +++++---
 22 files changed, 1345 insertions(+), 201 deletions(-)
 create mode 100644 tests/ui/abi/cannot-be-called.avr.stderr
 create mode 100644 tests/ui/abi/cannot-be-called.i686.stderr
 create mode 100644 tests/ui/abi/cannot-be-called.msp430.stderr
 create mode 100644 tests/ui/abi/cannot-be-called.riscv32.stderr
 create mode 100644 tests/ui/abi/cannot-be-called.riscv64.stderr
 create mode 100644 tests/ui/abi/cannot-be-called.rs
 create mode 100644 tests/ui/abi/cannot-be-called.x64.stderr
 create mode 100644 tests/ui/abi/cannot-be-called.x64_win.stderr

diff --git a/compiler/rustc_hir_typeck/messages.ftl b/compiler/rustc_hir_typeck/messages.ftl
index ac7ff65528d94..258535f3742d4 100644
--- a/compiler/rustc_hir_typeck/messages.ftl
+++ b/compiler/rustc_hir_typeck/messages.ftl
@@ -1,6 +1,6 @@
-hir_typeck_abi_custom_call =
-    functions with the `"custom"` ABI cannot be called
-    .note = an `extern "custom"` function can only be called from within inline assembly
+hir_typeck_abi_cannot_be_called =
+    functions with the {$abi} ABI cannot be called
+    .note = an `extern {$abi}` function can only be called using inline assembly
 
 hir_typeck_add_missing_parentheses_in_range = you must surround the range in parentheses to call its `{$func_name}` function
 
diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs
index 80bff09d0a43f..7a3647df0c405 100644
--- a/compiler/rustc_hir_typeck/src/callee.rs
+++ b/compiler/rustc_hir_typeck/src/callee.rs
@@ -1,6 +1,6 @@
 use std::iter;
 
-use rustc_abi::ExternAbi;
+use rustc_abi::{CanonAbi, ExternAbi};
 use rustc_ast::util::parser::ExprPrecedence;
 use rustc_errors::{Applicability, Diag, ErrorGuaranteed, StashKey};
 use rustc_hir::def::{self, CtorKind, Namespace, Res};
@@ -16,6 +16,7 @@ use rustc_middle::ty::{self, GenericArgsRef, Ty, TyCtxt, TypeVisitableExt};
 use rustc_middle::{bug, span_bug};
 use rustc_span::def_id::LocalDefId;
 use rustc_span::{Span, sym};
+use rustc_target::spec::{AbiMap, AbiMapping};
 use rustc_trait_selection::error_reporting::traits::DefIdOrName;
 use rustc_trait_selection::infer::InferCtxtExt as _;
 use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
@@ -84,7 +85,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         while result.is_none() && autoderef.next().is_some() {
             result = self.try_overloaded_call_step(call_expr, callee_expr, arg_exprs, &autoderef);
         }
-        self.check_call_custom_abi(autoderef.final_ty(false), call_expr.span);
+
+        match autoderef.final_ty(false).kind() {
+            ty::FnDef(def_id, _) => {
+                let abi = self.tcx.fn_sig(def_id).skip_binder().skip_binder().abi;
+                self.check_call_abi(abi, call_expr.span);
+            }
+            ty::FnPtr(_, header) => {
+                self.check_call_abi(header.abi, call_expr.span);
+            }
+            _ => { /* cannot have a non-rust abi */ }
+        }
+
         self.register_predicates(autoderef.into_obligations());
 
         let output = match result {
@@ -137,19 +149,37 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         output
     }
 
-    /// Functions of type `extern "custom" fn(/* ... */)` cannot be called using `ExprKind::Call`.
+    /// Can a function with this ABI be called with a rust call expression?
     ///
-    /// These functions have a calling convention that is unknown to rust, hence it cannot generate
-    /// code for the call. The only way to execute such a function is via inline assembly.
-    fn check_call_custom_abi(&self, callee_ty: Ty<'tcx>, span: Span) {
-        let abi = match callee_ty.kind() {
-            ty::FnDef(def_id, _) => self.tcx.fn_sig(def_id).skip_binder().skip_binder().abi,
-            ty::FnPtr(_, header) => header.abi,
-            _ => return,
+    /// Some ABIs cannot be called from rust, either because rust does not know how to generate
+    /// code for the call, or because a call does not semantically make sense.
+    pub(crate) fn check_call_abi(&self, abi: ExternAbi, span: Span) {
+        let canon_abi = match AbiMap::from_target(&self.sess().target).canonize_abi(abi, false) {
+            AbiMapping::Direct(canon_abi) | AbiMapping::Deprecated(canon_abi) => canon_abi,
+            AbiMapping::Invalid => return,
+        };
+
+        let valid = match canon_abi {
+            // Rust doesn't know how to call functions with this ABI.
+            CanonAbi::Custom => false,
+
+            // These is an entry point for the host, and cannot be called on the GPU.
+            CanonAbi::GpuKernel => false,
+
+            // The interrupt ABIs should only be called by the CPU. They have complex
+            // pre- and postconditions, and can use non-standard instructions like `iret` on x86.
+            CanonAbi::Interrupt(_) => false,
+
+            CanonAbi::C
+            | CanonAbi::Rust
+            | CanonAbi::RustCold
+            | CanonAbi::Arm(_)
+            | CanonAbi::X86(_) => true,
         };
 
-        if let ExternAbi::Custom = abi {
-            self.tcx.dcx().emit_err(errors::AbiCustomCall { span });
+        if !valid {
+            let err = crate::errors::AbiCannotBeCalled { span, abi };
+            self.tcx.dcx().emit_err(err);
         }
     }
 
diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs
index abb8cdc1cdf3c..681135fdede58 100644
--- a/compiler/rustc_hir_typeck/src/errors.rs
+++ b/compiler/rustc_hir_typeck/src/errors.rs
@@ -2,6 +2,7 @@
 
 use std::borrow::Cow;
 
+use rustc_abi::ExternAbi;
 use rustc_ast::Label;
 use rustc_errors::codes::*;
 use rustc_errors::{
@@ -1165,8 +1166,10 @@ pub(crate) struct NakedFunctionsMustNakedAsm {
 }
 
 #[derive(Diagnostic)]
-#[diag(hir_typeck_abi_custom_call)]
-pub(crate) struct AbiCustomCall {
+#[diag(hir_typeck_abi_cannot_be_called)]
+pub(crate) struct AbiCannotBeCalled {
     #[primary_span]
+    #[note]
     pub span: Span,
+    pub abi: ExternAbi,
 }
diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs
index 55c39d960e7c2..7d2f4743bd761 100644
--- a/compiler/rustc_hir_typeck/src/expr.rs
+++ b/compiler/rustc_hir_typeck/src/expr.rs
@@ -5,7 +5,7 @@
 //!
 //! See [`rustc_hir_analysis::check`] for more context on type checking in general.
 
-use rustc_abi::{ExternAbi, FIRST_VARIANT, FieldIdx};
+use rustc_abi::{FIRST_VARIANT, FieldIdx};
 use rustc_ast::util::parser::ExprPrecedence;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::stack::ensure_sufficient_stack;
@@ -1651,13 +1651,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     Some(method.def_id),
                 );
 
-                // Functions of type `extern "custom" fn(/* ... */)` cannot be called using
-                // `ExprKind::MethodCall`. These functions have a calling convention that is
-                // unknown to rust, hence it cannot generate code for the call. The only way
-                // to execute such a function is via inline assembly.
-                if let ExternAbi::Custom = method.sig.abi {
-                    self.tcx.dcx().emit_err(crate::errors::AbiCustomCall { span: expr.span });
-                }
+                self.check_call_abi(method.sig.abi, expr.span);
 
                 method.sig.output()
             }
diff --git a/tests/ui/abi/bad-custom.rs b/tests/ui/abi/bad-custom.rs
index e792f0955b916..a3b15f244b72c 100644
--- a/tests/ui/abi/bad-custom.rs
+++ b/tests/ui/abi/bad-custom.rs
@@ -73,19 +73,19 @@ unsafe extern "custom" {
 
 fn caller(f: unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 {
     unsafe { f(x) }
-    //~^ ERROR functions with the `"custom"` ABI cannot be called
+    //~^ ERROR functions with the "custom" ABI cannot be called
 }
 
 fn caller_by_ref(f: &unsafe extern "custom" fn(i64) -> i64, mut x: i64) -> i64 {
     unsafe { f(x) }
-    //~^ ERROR functions with the `"custom"` ABI cannot be called
+    //~^ ERROR functions with the "custom" ABI cannot be called
 }
 
 type Custom = unsafe extern "custom" fn(i64) -> i64;
 
 fn caller_alias(f: Custom, mut x: i64) -> i64 {
     unsafe { f(x) }
-    //~^ ERROR functions with the `"custom"` ABI cannot be called
+    //~^ ERROR functions with the "custom" ABI cannot be called
 }
 
 #[unsafe(naked)]
@@ -107,15 +107,15 @@ fn no_promotion_to_fn_trait(f: unsafe extern "custom" fn()) -> impl Fn()  {
 pub fn main() {
     unsafe {
         assert_eq!(double(21), 42);
-        //~^ ERROR functions with the `"custom"` ABI cannot be called
+        //~^ ERROR functions with the "custom" ABI cannot be called
 
         assert_eq!(unsafe { increment(41) }, 42);
-        //~^ ERROR functions with the `"custom"` ABI cannot be called
+        //~^ ERROR functions with the "custom" ABI cannot be called
 
         assert!(Thing(41).is_even());
-        //~^ ERROR functions with the `"custom"` ABI cannot be called
+        //~^ ERROR functions with the "custom" ABI cannot be called
 
         assert_eq!(Thing::bitwise_not(42), !42);
-        //~^ ERROR functions with the `"custom"` ABI cannot be called
+        //~^ ERROR functions with the "custom" ABI cannot be called
     }
 }
diff --git a/tests/ui/abi/bad-custom.stderr b/tests/ui/abi/bad-custom.stderr
index ec0f11af89804..77cad1b872b23 100644
--- a/tests/ui/abi/bad-custom.stderr
+++ b/tests/ui/abi/bad-custom.stderr
@@ -245,43 +245,85 @@ LL +     #[unsafe(naked)]
 LL |     extern "custom" fn negate(a: i64) -> i64 {
    |
 
-error: functions with the `"custom"` ABI cannot be called
+error: functions with the "custom" ABI cannot be called
+  --> $DIR/bad-custom.rs:75:14
+   |
+LL |     unsafe { f(x) }
+   |              ^^^^
+   |
+note: an `extern "custom"` function can only be called using inline assembly
   --> $DIR/bad-custom.rs:75:14
    |
 LL |     unsafe { f(x) }
    |              ^^^^
 
-error: functions with the `"custom"` ABI cannot be called
+error: functions with the "custom" ABI cannot be called
+  --> $DIR/bad-custom.rs:80:14
+   |
+LL |     unsafe { f(x) }
+   |              ^^^^
+   |
+note: an `extern "custom"` function can only be called using inline assembly
   --> $DIR/bad-custom.rs:80:14
    |
 LL |     unsafe { f(x) }
    |              ^^^^
 
-error: functions with the `"custom"` ABI cannot be called
+error: functions with the "custom" ABI cannot be called
+  --> $DIR/bad-custom.rs:87:14
+   |
+LL |     unsafe { f(x) }
+   |              ^^^^
+   |
+note: an `extern "custom"` function can only be called using inline assembly
   --> $DIR/bad-custom.rs:87:14
    |
 LL |     unsafe { f(x) }
    |              ^^^^
 
-error: functions with the `"custom"` ABI cannot be called
+error: functions with the "custom" ABI cannot be called
+  --> $DIR/bad-custom.rs:109:20
+   |
+LL |         assert_eq!(double(21), 42);
+   |                    ^^^^^^^^^^
+   |
+note: an `extern "custom"` function can only be called using inline assembly
   --> $DIR/bad-custom.rs:109:20
    |
 LL |         assert_eq!(double(21), 42);
    |                    ^^^^^^^^^^
 
-error: functions with the `"custom"` ABI cannot be called
+error: functions with the "custom" ABI cannot be called
+  --> $DIR/bad-custom.rs:112:29
+   |
+LL |         assert_eq!(unsafe { increment(41) }, 42);
+   |                             ^^^^^^^^^^^^^
+   |
+note: an `extern "custom"` function can only be called using inline assembly
   --> $DIR/bad-custom.rs:112:29
    |
 LL |         assert_eq!(unsafe { increment(41) }, 42);
    |                             ^^^^^^^^^^^^^
 
-error: functions with the `"custom"` ABI cannot be called
+error: functions with the "custom" ABI cannot be called
+  --> $DIR/bad-custom.rs:115:17
+   |
+LL |         assert!(Thing(41).is_even());
+   |                 ^^^^^^^^^^^^^^^^^^^
+   |
+note: an `extern "custom"` function can only be called using inline assembly
   --> $DIR/bad-custom.rs:115:17
    |
 LL |         assert!(Thing(41).is_even());
    |                 ^^^^^^^^^^^^^^^^^^^
 
-error: functions with the `"custom"` ABI cannot be called
+error: functions with the "custom" ABI cannot be called
+  --> $DIR/bad-custom.rs:118:20
+   |
+LL |         assert_eq!(Thing::bitwise_not(42), !42);
+   |                    ^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: an `extern "custom"` function can only be called using inline assembly
   --> $DIR/bad-custom.rs:118:20
    |
 LL |         assert_eq!(Thing::bitwise_not(42), !42);
diff --git a/tests/ui/abi/cannot-be-called.avr.stderr b/tests/ui/abi/cannot-be-called.avr.stderr
new file mode 100644
index 0000000000000..64ce3efe52b20
--- /dev/null
+++ b/tests/ui/abi/cannot-be-called.avr.stderr
@@ -0,0 +1,132 @@
+warning: the calling convention "msp430-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:60:18
+   |
+LL | fn msp430_ptr(f: extern "msp430-interrupt" fn()) {
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+warning: the calling convention "riscv-interrupt-m" is not supported on this target
+  --> $DIR/cannot-be-called.rs:74:19
+   |
+LL | fn riscv_m_ptr(f: extern "riscv-interrupt-m" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+
+warning: the calling convention "riscv-interrupt-s" is not supported on this target
+  --> $DIR/cannot-be-called.rs:81:19
+   |
+LL | fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+
+warning: the calling convention "x86-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:88:15
+   |
+LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+
+error[E0570]: `"msp430-interrupt"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:36:1
+   |
+LL | extern "msp430-interrupt" fn msp430() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0570]: `"riscv-interrupt-m"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:40:1
+   |
+LL | extern "riscv-interrupt-m" fn riscv_m() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0570]: `"riscv-interrupt-s"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:42:1
+   |
+LL | extern "riscv-interrupt-s" fn riscv_s() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0570]: `"x86-interrupt"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:44:1
+   |
+LL | extern "x86-interrupt" fn x86() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: functions with the "avr-interrupt" ABI cannot be called
+  --> $DIR/cannot-be-called.rs:50:5
+   |
+LL |     avr();
+   |     ^^^^^
+   |
+note: an `extern "avr-interrupt"` function can only be called using inline assembly
+  --> $DIR/cannot-be-called.rs:50:5
+   |
+LL |     avr();
+   |     ^^^^^
+
+error: functions with the "avr-interrupt" ABI cannot be called
+  --> $DIR/cannot-be-called.rs:70:5
+   |
+LL |     f()
+   |     ^^^
+   |
+note: an `extern "avr-interrupt"` function can only be called using inline assembly
+  --> $DIR/cannot-be-called.rs:70:5
+   |
+LL |     f()
+   |     ^^^
+
+error: aborting due to 6 previous errors; 4 warnings emitted
+
+For more information about this error, try `rustc --explain E0570`.
+Future incompatibility report: Future breakage diagnostic:
+warning: the calling convention "msp430-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:60:18
+   |
+LL | fn msp430_ptr(f: extern "msp430-interrupt" fn()) {
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+Future breakage diagnostic:
+warning: the calling convention "riscv-interrupt-m" is not supported on this target
+  --> $DIR/cannot-be-called.rs:74:19
+   |
+LL | fn riscv_m_ptr(f: extern "riscv-interrupt-m" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+Future breakage diagnostic:
+warning: the calling convention "riscv-interrupt-s" is not supported on this target
+  --> $DIR/cannot-be-called.rs:81:19
+   |
+LL | fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+Future breakage diagnostic:
+warning: the calling convention "x86-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:88:15
+   |
+LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
diff --git a/tests/ui/abi/cannot-be-called.i686.stderr b/tests/ui/abi/cannot-be-called.i686.stderr
new file mode 100644
index 0000000000000..113b40eb67b85
--- /dev/null
+++ b/tests/ui/abi/cannot-be-called.i686.stderr
@@ -0,0 +1,132 @@
+warning: the calling convention "msp430-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:60:18
+   |
+LL | fn msp430_ptr(f: extern "msp430-interrupt" fn()) {
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+warning: the calling convention "avr-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:67:15
+   |
+LL | fn avr_ptr(f: extern "avr-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+
+warning: the calling convention "riscv-interrupt-m" is not supported on this target
+  --> $DIR/cannot-be-called.rs:74:19
+   |
+LL | fn riscv_m_ptr(f: extern "riscv-interrupt-m" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+
+warning: the calling convention "riscv-interrupt-s" is not supported on this target
+  --> $DIR/cannot-be-called.rs:81:19
+   |
+LL | fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+
+error[E0570]: `"msp430-interrupt"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:36:1
+   |
+LL | extern "msp430-interrupt" fn msp430() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0570]: `"avr-interrupt"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:38:1
+   |
+LL | extern "avr-interrupt" fn avr() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0570]: `"riscv-interrupt-m"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:40:1
+   |
+LL | extern "riscv-interrupt-m" fn riscv_m() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0570]: `"riscv-interrupt-s"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:42:1
+   |
+LL | extern "riscv-interrupt-s" fn riscv_s() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: functions with the "x86-interrupt" ABI cannot be called
+  --> $DIR/cannot-be-called.rs:56:5
+   |
+LL |     x86();
+   |     ^^^^^
+   |
+note: an `extern "x86-interrupt"` function can only be called using inline assembly
+  --> $DIR/cannot-be-called.rs:56:5
+   |
+LL |     x86();
+   |     ^^^^^
+
+error: functions with the "x86-interrupt" ABI cannot be called
+  --> $DIR/cannot-be-called.rs:91:5
+   |
+LL |     f()
+   |     ^^^
+   |
+note: an `extern "x86-interrupt"` function can only be called using inline assembly
+  --> $DIR/cannot-be-called.rs:91:5
+   |
+LL |     f()
+   |     ^^^
+
+error: aborting due to 6 previous errors; 4 warnings emitted
+
+For more information about this error, try `rustc --explain E0570`.
+Future incompatibility report: Future breakage diagnostic:
+warning: the calling convention "msp430-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:60:18
+   |
+LL | fn msp430_ptr(f: extern "msp430-interrupt" fn()) {
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+Future breakage diagnostic:
+warning: the calling convention "avr-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:67:15
+   |
+LL | fn avr_ptr(f: extern "avr-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+Future breakage diagnostic:
+warning: the calling convention "riscv-interrupt-m" is not supported on this target
+  --> $DIR/cannot-be-called.rs:74:19
+   |
+LL | fn riscv_m_ptr(f: extern "riscv-interrupt-m" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+Future breakage diagnostic:
+warning: the calling convention "riscv-interrupt-s" is not supported on this target
+  --> $DIR/cannot-be-called.rs:81:19
+   |
+LL | fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
diff --git a/tests/ui/abi/cannot-be-called.msp430.stderr b/tests/ui/abi/cannot-be-called.msp430.stderr
new file mode 100644
index 0000000000000..d7630d96f8cec
--- /dev/null
+++ b/tests/ui/abi/cannot-be-called.msp430.stderr
@@ -0,0 +1,132 @@
+warning: the calling convention "avr-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:67:15
+   |
+LL | fn avr_ptr(f: extern "avr-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+warning: the calling convention "riscv-interrupt-m" is not supported on this target
+  --> $DIR/cannot-be-called.rs:74:19
+   |
+LL | fn riscv_m_ptr(f: extern "riscv-interrupt-m" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+
+warning: the calling convention "riscv-interrupt-s" is not supported on this target
+  --> $DIR/cannot-be-called.rs:81:19
+   |
+LL | fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+
+warning: the calling convention "x86-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:88:15
+   |
+LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+
+error[E0570]: `"avr-interrupt"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:38:1
+   |
+LL | extern "avr-interrupt" fn avr() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0570]: `"riscv-interrupt-m"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:40:1
+   |
+LL | extern "riscv-interrupt-m" fn riscv_m() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0570]: `"riscv-interrupt-s"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:42:1
+   |
+LL | extern "riscv-interrupt-s" fn riscv_s() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0570]: `"x86-interrupt"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:44:1
+   |
+LL | extern "x86-interrupt" fn x86() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: functions with the "msp430-interrupt" ABI cannot be called
+  --> $DIR/cannot-be-called.rs:48:5
+   |
+LL |     msp430();
+   |     ^^^^^^^^
+   |
+note: an `extern "msp430-interrupt"` function can only be called using inline assembly
+  --> $DIR/cannot-be-called.rs:48:5
+   |
+LL |     msp430();
+   |     ^^^^^^^^
+
+error: functions with the "msp430-interrupt" ABI cannot be called
+  --> $DIR/cannot-be-called.rs:63:5
+   |
+LL |     f()
+   |     ^^^
+   |
+note: an `extern "msp430-interrupt"` function can only be called using inline assembly
+  --> $DIR/cannot-be-called.rs:63:5
+   |
+LL |     f()
+   |     ^^^
+
+error: aborting due to 6 previous errors; 4 warnings emitted
+
+For more information about this error, try `rustc --explain E0570`.
+Future incompatibility report: Future breakage diagnostic:
+warning: the calling convention "avr-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:67:15
+   |
+LL | fn avr_ptr(f: extern "avr-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+Future breakage diagnostic:
+warning: the calling convention "riscv-interrupt-m" is not supported on this target
+  --> $DIR/cannot-be-called.rs:74:19
+   |
+LL | fn riscv_m_ptr(f: extern "riscv-interrupt-m" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+Future breakage diagnostic:
+warning: the calling convention "riscv-interrupt-s" is not supported on this target
+  --> $DIR/cannot-be-called.rs:81:19
+   |
+LL | fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+Future breakage diagnostic:
+warning: the calling convention "x86-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:88:15
+   |
+LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
diff --git a/tests/ui/abi/cannot-be-called.riscv32.stderr b/tests/ui/abi/cannot-be-called.riscv32.stderr
new file mode 100644
index 0000000000000..9fadbd639b8bd
--- /dev/null
+++ b/tests/ui/abi/cannot-be-called.riscv32.stderr
@@ -0,0 +1,130 @@
+warning: the calling convention "msp430-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:60:18
+   |
+LL | fn msp430_ptr(f: extern "msp430-interrupt" fn()) {
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+warning: the calling convention "avr-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:67:15
+   |
+LL | fn avr_ptr(f: extern "avr-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+
+warning: the calling convention "x86-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:88:15
+   |
+LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+
+error[E0570]: `"msp430-interrupt"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:36:1
+   |
+LL | extern "msp430-interrupt" fn msp430() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0570]: `"avr-interrupt"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:38:1
+   |
+LL | extern "avr-interrupt" fn avr() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0570]: `"x86-interrupt"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:44:1
+   |
+LL | extern "x86-interrupt" fn x86() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: functions with the "riscv-interrupt-m" ABI cannot be called
+  --> $DIR/cannot-be-called.rs:52:5
+   |
+LL |     riscv_m();
+   |     ^^^^^^^^^
+   |
+note: an `extern "riscv-interrupt-m"` function can only be called using inline assembly
+  --> $DIR/cannot-be-called.rs:52:5
+   |
+LL |     riscv_m();
+   |     ^^^^^^^^^
+
+error: functions with the "riscv-interrupt-s" ABI cannot be called
+  --> $DIR/cannot-be-called.rs:54:5
+   |
+LL |     riscv_s();
+   |     ^^^^^^^^^
+   |
+note: an `extern "riscv-interrupt-s"` function can only be called using inline assembly
+  --> $DIR/cannot-be-called.rs:54:5
+   |
+LL |     riscv_s();
+   |     ^^^^^^^^^
+
+error: functions with the "riscv-interrupt-m" ABI cannot be called
+  --> $DIR/cannot-be-called.rs:77:5
+   |
+LL |     f()
+   |     ^^^
+   |
+note: an `extern "riscv-interrupt-m"` function can only be called using inline assembly
+  --> $DIR/cannot-be-called.rs:77:5
+   |
+LL |     f()
+   |     ^^^
+
+error: functions with the "riscv-interrupt-s" ABI cannot be called
+  --> $DIR/cannot-be-called.rs:84:5
+   |
+LL |     f()
+   |     ^^^
+   |
+note: an `extern "riscv-interrupt-s"` function can only be called using inline assembly
+  --> $DIR/cannot-be-called.rs:84:5
+   |
+LL |     f()
+   |     ^^^
+
+error: aborting due to 7 previous errors; 3 warnings emitted
+
+For more information about this error, try `rustc --explain E0570`.
+Future incompatibility report: Future breakage diagnostic:
+warning: the calling convention "msp430-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:60:18
+   |
+LL | fn msp430_ptr(f: extern "msp430-interrupt" fn()) {
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+Future breakage diagnostic:
+warning: the calling convention "avr-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:67:15
+   |
+LL | fn avr_ptr(f: extern "avr-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+Future breakage diagnostic:
+warning: the calling convention "x86-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:88:15
+   |
+LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
diff --git a/tests/ui/abi/cannot-be-called.riscv64.stderr b/tests/ui/abi/cannot-be-called.riscv64.stderr
new file mode 100644
index 0000000000000..9fadbd639b8bd
--- /dev/null
+++ b/tests/ui/abi/cannot-be-called.riscv64.stderr
@@ -0,0 +1,130 @@
+warning: the calling convention "msp430-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:60:18
+   |
+LL | fn msp430_ptr(f: extern "msp430-interrupt" fn()) {
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+warning: the calling convention "avr-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:67:15
+   |
+LL | fn avr_ptr(f: extern "avr-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+
+warning: the calling convention "x86-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:88:15
+   |
+LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+
+error[E0570]: `"msp430-interrupt"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:36:1
+   |
+LL | extern "msp430-interrupt" fn msp430() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0570]: `"avr-interrupt"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:38:1
+   |
+LL | extern "avr-interrupt" fn avr() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0570]: `"x86-interrupt"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:44:1
+   |
+LL | extern "x86-interrupt" fn x86() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: functions with the "riscv-interrupt-m" ABI cannot be called
+  --> $DIR/cannot-be-called.rs:52:5
+   |
+LL |     riscv_m();
+   |     ^^^^^^^^^
+   |
+note: an `extern "riscv-interrupt-m"` function can only be called using inline assembly
+  --> $DIR/cannot-be-called.rs:52:5
+   |
+LL |     riscv_m();
+   |     ^^^^^^^^^
+
+error: functions with the "riscv-interrupt-s" ABI cannot be called
+  --> $DIR/cannot-be-called.rs:54:5
+   |
+LL |     riscv_s();
+   |     ^^^^^^^^^
+   |
+note: an `extern "riscv-interrupt-s"` function can only be called using inline assembly
+  --> $DIR/cannot-be-called.rs:54:5
+   |
+LL |     riscv_s();
+   |     ^^^^^^^^^
+
+error: functions with the "riscv-interrupt-m" ABI cannot be called
+  --> $DIR/cannot-be-called.rs:77:5
+   |
+LL |     f()
+   |     ^^^
+   |
+note: an `extern "riscv-interrupt-m"` function can only be called using inline assembly
+  --> $DIR/cannot-be-called.rs:77:5
+   |
+LL |     f()
+   |     ^^^
+
+error: functions with the "riscv-interrupt-s" ABI cannot be called
+  --> $DIR/cannot-be-called.rs:84:5
+   |
+LL |     f()
+   |     ^^^
+   |
+note: an `extern "riscv-interrupt-s"` function can only be called using inline assembly
+  --> $DIR/cannot-be-called.rs:84:5
+   |
+LL |     f()
+   |     ^^^
+
+error: aborting due to 7 previous errors; 3 warnings emitted
+
+For more information about this error, try `rustc --explain E0570`.
+Future incompatibility report: Future breakage diagnostic:
+warning: the calling convention "msp430-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:60:18
+   |
+LL | fn msp430_ptr(f: extern "msp430-interrupt" fn()) {
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+Future breakage diagnostic:
+warning: the calling convention "avr-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:67:15
+   |
+LL | fn avr_ptr(f: extern "avr-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+Future breakage diagnostic:
+warning: the calling convention "x86-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:88:15
+   |
+LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
diff --git a/tests/ui/abi/cannot-be-called.rs b/tests/ui/abi/cannot-be-called.rs
new file mode 100644
index 0000000000000..89173655a4acf
--- /dev/null
+++ b/tests/ui/abi/cannot-be-called.rs
@@ -0,0 +1,93 @@
+//@ add-core-stubs
+//@ revisions: x64 x64_win i686 riscv32 riscv64 avr msp430
+//
+//@ [x64] needs-llvm-components: x86
+//@ [x64] compile-flags: --target=x86_64-unknown-linux-gnu --crate-type=rlib
+//@ [x64_win] needs-llvm-components: x86
+//@ [x64_win] compile-flags: --target=x86_64-pc-windows-msvc --crate-type=rlib
+//@ [i686] needs-llvm-components: x86
+//@ [i686] compile-flags: --target=i686-unknown-linux-gnu --crate-type=rlib
+//@ [riscv32] needs-llvm-components: riscv
+//@ [riscv32] compile-flags: --target=riscv32i-unknown-none-elf --crate-type=rlib
+//@ [riscv64] needs-llvm-components: riscv
+//@ [riscv64] compile-flags: --target=riscv64gc-unknown-none-elf --crate-type=rlib
+//@ [avr] needs-llvm-components: avr
+//@ [avr] compile-flags: --target=avr-none -C target-cpu=atmega328p --crate-type=rlib
+//@ [msp430] needs-llvm-components: msp430
+//@ [msp430] compile-flags: --target=msp430-none-elf --crate-type=rlib
+#![no_core]
+#![feature(
+    no_core,
+    lang_items,
+    abi_ptx,
+    abi_msp430_interrupt,
+    abi_avr_interrupt,
+    abi_gpu_kernel,
+    abi_x86_interrupt,
+    abi_riscv_interrupt,
+    abi_c_cmse_nonsecure_call,
+    abi_vectorcall,
+    cmse_nonsecure_entry
+)]
+
+extern crate minicore;
+use minicore::*;
+
+extern "msp430-interrupt" fn msp430() {}
+//[x64,x64_win,i686,riscv32,riscv64,avr]~^ ERROR is not a supported ABI
+extern "avr-interrupt" fn avr() {}
+//[x64,x64_win,i686,riscv32,riscv64,msp430]~^ ERROR is not a supported ABI
+extern "riscv-interrupt-m" fn riscv_m() {}
+//[x64,x64_win,i686,avr,msp430]~^ ERROR is not a supported ABI
+extern "riscv-interrupt-s" fn riscv_s() {}
+//[x64,x64_win,i686,avr,msp430]~^ ERROR is not a supported ABI
+extern "x86-interrupt" fn x86() {}
+//[riscv32,riscv64,avr,msp430]~^ ERROR is not a supported ABI
+
+fn call_the_interrupts() {
+    msp430();
+    //[msp430]~^ ERROR functions with the "msp430-interrupt" ABI cannot be called
+    avr();
+    //[avr]~^ ERROR functions with the "avr-interrupt" ABI cannot be called
+    riscv_m();
+    //[riscv32,riscv64]~^ ERROR functions with the "riscv-interrupt-m" ABI cannot be called
+    riscv_s();
+    //[riscv32,riscv64]~^ ERROR functions with the "riscv-interrupt-s" ABI cannot be called
+    x86();
+    //[x64,x64_win,i686]~^ ERROR functions with the "x86-interrupt" ABI cannot be called
+}
+
+fn msp430_ptr(f: extern "msp430-interrupt" fn()) {
+    //[x64,x64_win,i686,riscv32,riscv64,avr]~^ WARN unsupported_fn_ptr_calling_conventions
+    //[x64,x64_win,i686,riscv32,riscv64,avr]~^^ WARN this was previously accepted
+    f()
+    //[msp430]~^ ERROR functions with the "msp430-interrupt" ABI cannot be called
+}
+
+fn avr_ptr(f: extern "avr-interrupt" fn()) {
+    //[x64,x64_win,i686,riscv32,riscv64,msp430]~^ WARN unsupported_fn_ptr_calling_conventions
+    //[x64,x64_win,i686,riscv32,riscv64,msp430]~^^ WARN this was previously accepted
+    f()
+    //[avr]~^ ERROR functions with the "avr-interrupt" ABI cannot be called
+}
+
+fn riscv_m_ptr(f: extern "riscv-interrupt-m" fn()) {
+    //[x64,x64_win,i686,avr,msp430]~^ WARN unsupported_fn_ptr_calling_conventions
+    //[x64,x64_win,i686,avr,msp430]~^^ WARN this was previously accepted
+    f()
+    //[riscv32,riscv64]~^ ERROR functions with the "riscv-interrupt-m" ABI cannot be called
+}
+
+fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) {
+    //[x64,x64_win,i686,avr,msp430]~^ WARN unsupported_fn_ptr_calling_conventions
+    //[x64,x64_win,i686,avr,msp430]~^^ WARN this was previously accepted
+    f()
+    //[riscv32,riscv64]~^ ERROR functions with the "riscv-interrupt-s" ABI cannot be called
+}
+
+fn x86_ptr(f: extern "x86-interrupt" fn()) {
+    //[riscv32,riscv64,avr,msp430]~^ WARN unsupported_fn_ptr_calling_conventions
+    //[riscv32,riscv64,avr,msp430]~^^ WARN this was previously accepted
+    f()
+    //[x64,x64_win,i686]~^ ERROR functions with the "x86-interrupt" ABI cannot be called
+}
diff --git a/tests/ui/abi/cannot-be-called.x64.stderr b/tests/ui/abi/cannot-be-called.x64.stderr
new file mode 100644
index 0000000000000..113b40eb67b85
--- /dev/null
+++ b/tests/ui/abi/cannot-be-called.x64.stderr
@@ -0,0 +1,132 @@
+warning: the calling convention "msp430-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:60:18
+   |
+LL | fn msp430_ptr(f: extern "msp430-interrupt" fn()) {
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+warning: the calling convention "avr-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:67:15
+   |
+LL | fn avr_ptr(f: extern "avr-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+
+warning: the calling convention "riscv-interrupt-m" is not supported on this target
+  --> $DIR/cannot-be-called.rs:74:19
+   |
+LL | fn riscv_m_ptr(f: extern "riscv-interrupt-m" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+
+warning: the calling convention "riscv-interrupt-s" is not supported on this target
+  --> $DIR/cannot-be-called.rs:81:19
+   |
+LL | fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+
+error[E0570]: `"msp430-interrupt"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:36:1
+   |
+LL | extern "msp430-interrupt" fn msp430() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0570]: `"avr-interrupt"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:38:1
+   |
+LL | extern "avr-interrupt" fn avr() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0570]: `"riscv-interrupt-m"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:40:1
+   |
+LL | extern "riscv-interrupt-m" fn riscv_m() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0570]: `"riscv-interrupt-s"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:42:1
+   |
+LL | extern "riscv-interrupt-s" fn riscv_s() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: functions with the "x86-interrupt" ABI cannot be called
+  --> $DIR/cannot-be-called.rs:56:5
+   |
+LL |     x86();
+   |     ^^^^^
+   |
+note: an `extern "x86-interrupt"` function can only be called using inline assembly
+  --> $DIR/cannot-be-called.rs:56:5
+   |
+LL |     x86();
+   |     ^^^^^
+
+error: functions with the "x86-interrupt" ABI cannot be called
+  --> $DIR/cannot-be-called.rs:91:5
+   |
+LL |     f()
+   |     ^^^
+   |
+note: an `extern "x86-interrupt"` function can only be called using inline assembly
+  --> $DIR/cannot-be-called.rs:91:5
+   |
+LL |     f()
+   |     ^^^
+
+error: aborting due to 6 previous errors; 4 warnings emitted
+
+For more information about this error, try `rustc --explain E0570`.
+Future incompatibility report: Future breakage diagnostic:
+warning: the calling convention "msp430-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:60:18
+   |
+LL | fn msp430_ptr(f: extern "msp430-interrupt" fn()) {
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+Future breakage diagnostic:
+warning: the calling convention "avr-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:67:15
+   |
+LL | fn avr_ptr(f: extern "avr-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+Future breakage diagnostic:
+warning: the calling convention "riscv-interrupt-m" is not supported on this target
+  --> $DIR/cannot-be-called.rs:74:19
+   |
+LL | fn riscv_m_ptr(f: extern "riscv-interrupt-m" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+Future breakage diagnostic:
+warning: the calling convention "riscv-interrupt-s" is not supported on this target
+  --> $DIR/cannot-be-called.rs:81:19
+   |
+LL | fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
diff --git a/tests/ui/abi/cannot-be-called.x64_win.stderr b/tests/ui/abi/cannot-be-called.x64_win.stderr
new file mode 100644
index 0000000000000..113b40eb67b85
--- /dev/null
+++ b/tests/ui/abi/cannot-be-called.x64_win.stderr
@@ -0,0 +1,132 @@
+warning: the calling convention "msp430-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:60:18
+   |
+LL | fn msp430_ptr(f: extern "msp430-interrupt" fn()) {
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+warning: the calling convention "avr-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:67:15
+   |
+LL | fn avr_ptr(f: extern "avr-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+
+warning: the calling convention "riscv-interrupt-m" is not supported on this target
+  --> $DIR/cannot-be-called.rs:74:19
+   |
+LL | fn riscv_m_ptr(f: extern "riscv-interrupt-m" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+
+warning: the calling convention "riscv-interrupt-s" is not supported on this target
+  --> $DIR/cannot-be-called.rs:81:19
+   |
+LL | fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+
+error[E0570]: `"msp430-interrupt"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:36:1
+   |
+LL | extern "msp430-interrupt" fn msp430() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0570]: `"avr-interrupt"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:38:1
+   |
+LL | extern "avr-interrupt" fn avr() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0570]: `"riscv-interrupt-m"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:40:1
+   |
+LL | extern "riscv-interrupt-m" fn riscv_m() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0570]: `"riscv-interrupt-s"` is not a supported ABI for the current target
+  --> $DIR/cannot-be-called.rs:42:1
+   |
+LL | extern "riscv-interrupt-s" fn riscv_s() {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: functions with the "x86-interrupt" ABI cannot be called
+  --> $DIR/cannot-be-called.rs:56:5
+   |
+LL |     x86();
+   |     ^^^^^
+   |
+note: an `extern "x86-interrupt"` function can only be called using inline assembly
+  --> $DIR/cannot-be-called.rs:56:5
+   |
+LL |     x86();
+   |     ^^^^^
+
+error: functions with the "x86-interrupt" ABI cannot be called
+  --> $DIR/cannot-be-called.rs:91:5
+   |
+LL |     f()
+   |     ^^^
+   |
+note: an `extern "x86-interrupt"` function can only be called using inline assembly
+  --> $DIR/cannot-be-called.rs:91:5
+   |
+LL |     f()
+   |     ^^^
+
+error: aborting due to 6 previous errors; 4 warnings emitted
+
+For more information about this error, try `rustc --explain E0570`.
+Future incompatibility report: Future breakage diagnostic:
+warning: the calling convention "msp430-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:60:18
+   |
+LL | fn msp430_ptr(f: extern "msp430-interrupt" fn()) {
+   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+Future breakage diagnostic:
+warning: the calling convention "avr-interrupt" is not supported on this target
+  --> $DIR/cannot-be-called.rs:67:15
+   |
+LL | fn avr_ptr(f: extern "avr-interrupt" fn()) {
+   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+Future breakage diagnostic:
+warning: the calling convention "riscv-interrupt-m" is not supported on this target
+  --> $DIR/cannot-be-called.rs:74:19
+   |
+LL | fn riscv_m_ptr(f: extern "riscv-interrupt-m" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
+Future breakage diagnostic:
+warning: the calling convention "riscv-interrupt-s" is not supported on this target
+  --> $DIR/cannot-be-called.rs:81:19
+   |
+LL | fn riscv_s_ptr(f: extern "riscv-interrupt-s" fn()) {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = warning: 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 #130260 <https://github.com/rust-lang/rust/issues/130260>
+   = note: `#[warn(unsupported_fn_ptr_calling_conventions)]` on by default
+
diff --git a/tests/ui/abi/unsupported.aarch64.stderr b/tests/ui/abi/unsupported.aarch64.stderr
index 4721c26026d18..7b9b9d5c978c1 100644
--- a/tests/ui/abi/unsupported.aarch64.stderr
+++ b/tests/ui/abi/unsupported.aarch64.stderr
@@ -69,13 +69,13 @@ LL | fn riscv_ptr(f: extern "riscv-interrupt-m" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"riscv-interrupt-m"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:85:1
+  --> $DIR/unsupported.rs:86:1
    |
 LL | extern "riscv-interrupt-m" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "x86-interrupt" is not supported on this target
-  --> $DIR/unsupported.rs:90:15
+  --> $DIR/unsupported.rs:91:15
    |
 LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -84,13 +84,13 @@ LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"x86-interrupt"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:95:1
+  --> $DIR/unsupported.rs:97:1
    |
 LL | extern "x86-interrupt" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "thiscall" is not supported on this target
-  --> $DIR/unsupported.rs:100:20
+  --> $DIR/unsupported.rs:102:20
    |
 LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
    |                    ^^^^^^^^^^^^^^^^^^^^^^
@@ -99,13 +99,13 @@ LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"thiscall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:105:1
+  --> $DIR/unsupported.rs:107:1
    |
 LL | extern "thiscall" {}
    | ^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "stdcall" is not supported on this target
-  --> $DIR/unsupported.rs:112:19
+  --> $DIR/unsupported.rs:114:19
    |
 LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
    |                   ^^^^^^^^^^^^^^^^^^^^^
@@ -114,7 +114,7 @@ LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"stdcall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:119:1
+  --> $DIR/unsupported.rs:121:1
    |
 LL | extern "stdcall" {}
    | ^^^^^^^^^^^^^^^^^^^
@@ -122,7 +122,7 @@ LL | extern "stdcall" {}
    = help: if you need `extern "stdcall"` on win32 and `extern "C"` everywhere else, use `extern "system"`
 
 error[E0570]: `"stdcall-unwind"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:123:1
+  --> $DIR/unsupported.rs:125:1
    |
 LL | extern "stdcall-unwind" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -130,7 +130,7 @@ LL | extern "stdcall-unwind" {}
    = help: if you need `extern "stdcall-unwind"` on win32 and `extern "C-unwind"` everywhere else, use `extern "system-unwind"`
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:131:17
+  --> $DIR/unsupported.rs:133:17
    |
 LL | fn cdecl_ptr(f: extern "cdecl" fn()) {
    |                 ^^^^^^^^^^^^^^^^^^^
@@ -141,7 +141,7 @@ LL | fn cdecl_ptr(f: extern "cdecl" fn()) {
    = note: `#[warn(unsupported_calling_conventions)]` on by default
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:136:1
+  --> $DIR/unsupported.rs:138:1
    |
 LL | extern "cdecl" {}
    | ^^^^^^^^^^^^^^^^^
@@ -151,7 +151,7 @@ LL | extern "cdecl" {}
    = help: use `extern "C"` instead
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:139:1
+  --> $DIR/unsupported.rs:141:1
    |
 LL | extern "cdecl-unwind" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -161,7 +161,7 @@ LL | extern "cdecl-unwind" {}
    = help: use `extern "C-unwind"` instead
 
 warning: the calling convention "vectorcall" is not supported on this target
-  --> $DIR/unsupported.rs:145:22
+  --> $DIR/unsupported.rs:147:22
    |
 LL | fn vectorcall_ptr(f: extern "vectorcall" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -170,13 +170,13 @@ LL | fn vectorcall_ptr(f: extern "vectorcall" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"vectorcall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:150:1
+  --> $DIR/unsupported.rs:152:1
    |
 LL | extern "vectorcall" {}
    | ^^^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "C-cmse-nonsecure-call" is not supported on this target
-  --> $DIR/unsupported.rs:153:21
+  --> $DIR/unsupported.rs:155:21
    |
 LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -185,7 +185,7 @@ LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 warning: the calling convention "C-cmse-nonsecure-entry" is not supported on this target
-  --> $DIR/unsupported.rs:161:22
+  --> $DIR/unsupported.rs:163:22
    |
 LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -194,7 +194,7 @@ LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"C-cmse-nonsecure-entry"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:166:1
+  --> $DIR/unsupported.rs:168:1
    |
 LL | extern "C-cmse-nonsecure-entry" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -236,19 +236,19 @@ LL | extern "riscv-interrupt-m" fn riscv() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error[E0570]: `"x86-interrupt"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:88:1
+  --> $DIR/unsupported.rs:89:1
    |
 LL | extern "x86-interrupt" fn x86() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error[E0570]: `"thiscall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:98:1
+  --> $DIR/unsupported.rs:100:1
    |
 LL | extern "thiscall" fn thiscall() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error[E0570]: `"stdcall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:108:1
+  --> $DIR/unsupported.rs:110:1
    |
 LL | extern "stdcall" fn stdcall() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -256,7 +256,7 @@ LL | extern "stdcall" fn stdcall() {}
    = help: if you need `extern "stdcall"` on win32 and `extern "C"` everywhere else, use `extern "system"`
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:128:1
+  --> $DIR/unsupported.rs:130:1
    |
 LL | extern "cdecl" fn cdecl() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -266,13 +266,13 @@ LL | extern "cdecl" fn cdecl() {}
    = help: use `extern "C"` instead
 
 error[E0570]: `"vectorcall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:143:1
+  --> $DIR/unsupported.rs:145:1
    |
 LL | extern "vectorcall" fn vectorcall() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error[E0570]: `"C-cmse-nonsecure-entry"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:159:1
+  --> $DIR/unsupported.rs:161:1
    |
 LL | extern "C-cmse-nonsecure-entry" fn cmse_entry() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -337,7 +337,7 @@ LL | fn riscv_ptr(f: extern "riscv-interrupt-m" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "x86-interrupt" is not supported on this target
-  --> $DIR/unsupported.rs:90:15
+  --> $DIR/unsupported.rs:91:15
    |
 LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -348,7 +348,7 @@ LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "thiscall" is not supported on this target
-  --> $DIR/unsupported.rs:100:20
+  --> $DIR/unsupported.rs:102:20
    |
 LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
    |                    ^^^^^^^^^^^^^^^^^^^^^^
@@ -359,7 +359,7 @@ LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "stdcall" is not supported on this target
-  --> $DIR/unsupported.rs:112:19
+  --> $DIR/unsupported.rs:114:19
    |
 LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
    |                   ^^^^^^^^^^^^^^^^^^^^^
@@ -370,7 +370,7 @@ LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "vectorcall" is not supported on this target
-  --> $DIR/unsupported.rs:145:22
+  --> $DIR/unsupported.rs:147:22
    |
 LL | fn vectorcall_ptr(f: extern "vectorcall" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -381,7 +381,7 @@ LL | fn vectorcall_ptr(f: extern "vectorcall" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "C-cmse-nonsecure-call" is not supported on this target
-  --> $DIR/unsupported.rs:153:21
+  --> $DIR/unsupported.rs:155:21
    |
 LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -392,7 +392,7 @@ LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "C-cmse-nonsecure-entry" is not supported on this target
-  --> $DIR/unsupported.rs:161:22
+  --> $DIR/unsupported.rs:163:22
    |
 LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/tests/ui/abi/unsupported.arm.stderr b/tests/ui/abi/unsupported.arm.stderr
index ed9cd2ab2c5dc..5b057bdcbaeb2 100644
--- a/tests/ui/abi/unsupported.arm.stderr
+++ b/tests/ui/abi/unsupported.arm.stderr
@@ -54,13 +54,13 @@ LL | fn riscv_ptr(f: extern "riscv-interrupt-m" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"riscv-interrupt-m"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:85:1
+  --> $DIR/unsupported.rs:86:1
    |
 LL | extern "riscv-interrupt-m" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "x86-interrupt" is not supported on this target
-  --> $DIR/unsupported.rs:90:15
+  --> $DIR/unsupported.rs:91:15
    |
 LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -69,13 +69,13 @@ LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"x86-interrupt"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:95:1
+  --> $DIR/unsupported.rs:97:1
    |
 LL | extern "x86-interrupt" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "thiscall" is not supported on this target
-  --> $DIR/unsupported.rs:100:20
+  --> $DIR/unsupported.rs:102:20
    |
 LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
    |                    ^^^^^^^^^^^^^^^^^^^^^^
@@ -84,13 +84,13 @@ LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"thiscall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:105:1
+  --> $DIR/unsupported.rs:107:1
    |
 LL | extern "thiscall" {}
    | ^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "stdcall" is not supported on this target
-  --> $DIR/unsupported.rs:112:19
+  --> $DIR/unsupported.rs:114:19
    |
 LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
    |                   ^^^^^^^^^^^^^^^^^^^^^
@@ -99,7 +99,7 @@ LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"stdcall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:119:1
+  --> $DIR/unsupported.rs:121:1
    |
 LL | extern "stdcall" {}
    | ^^^^^^^^^^^^^^^^^^^
@@ -107,7 +107,7 @@ LL | extern "stdcall" {}
    = help: if you need `extern "stdcall"` on win32 and `extern "C"` everywhere else, use `extern "system"`
 
 error[E0570]: `"stdcall-unwind"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:123:1
+  --> $DIR/unsupported.rs:125:1
    |
 LL | extern "stdcall-unwind" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -115,7 +115,7 @@ LL | extern "stdcall-unwind" {}
    = help: if you need `extern "stdcall-unwind"` on win32 and `extern "C-unwind"` everywhere else, use `extern "system-unwind"`
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:131:17
+  --> $DIR/unsupported.rs:133:17
    |
 LL | fn cdecl_ptr(f: extern "cdecl" fn()) {
    |                 ^^^^^^^^^^^^^^^^^^^
@@ -126,7 +126,7 @@ LL | fn cdecl_ptr(f: extern "cdecl" fn()) {
    = note: `#[warn(unsupported_calling_conventions)]` on by default
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:136:1
+  --> $DIR/unsupported.rs:138:1
    |
 LL | extern "cdecl" {}
    | ^^^^^^^^^^^^^^^^^
@@ -136,7 +136,7 @@ LL | extern "cdecl" {}
    = help: use `extern "C"` instead
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:139:1
+  --> $DIR/unsupported.rs:141:1
    |
 LL | extern "cdecl-unwind" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -146,7 +146,7 @@ LL | extern "cdecl-unwind" {}
    = help: use `extern "C-unwind"` instead
 
 warning: the calling convention "vectorcall" is not supported on this target
-  --> $DIR/unsupported.rs:145:22
+  --> $DIR/unsupported.rs:147:22
    |
 LL | fn vectorcall_ptr(f: extern "vectorcall" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -155,13 +155,13 @@ LL | fn vectorcall_ptr(f: extern "vectorcall" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"vectorcall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:150:1
+  --> $DIR/unsupported.rs:152:1
    |
 LL | extern "vectorcall" {}
    | ^^^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "C-cmse-nonsecure-call" is not supported on this target
-  --> $DIR/unsupported.rs:153:21
+  --> $DIR/unsupported.rs:155:21
    |
 LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -170,7 +170,7 @@ LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 warning: the calling convention "C-cmse-nonsecure-entry" is not supported on this target
-  --> $DIR/unsupported.rs:161:22
+  --> $DIR/unsupported.rs:163:22
    |
 LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -179,7 +179,7 @@ LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"C-cmse-nonsecure-entry"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:166:1
+  --> $DIR/unsupported.rs:168:1
    |
 LL | extern "C-cmse-nonsecure-entry" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -215,19 +215,19 @@ LL | extern "riscv-interrupt-m" fn riscv() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error[E0570]: `"x86-interrupt"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:88:1
+  --> $DIR/unsupported.rs:89:1
    |
 LL | extern "x86-interrupt" fn x86() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error[E0570]: `"thiscall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:98:1
+  --> $DIR/unsupported.rs:100:1
    |
 LL | extern "thiscall" fn thiscall() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error[E0570]: `"stdcall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:108:1
+  --> $DIR/unsupported.rs:110:1
    |
 LL | extern "stdcall" fn stdcall() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -235,7 +235,7 @@ LL | extern "stdcall" fn stdcall() {}
    = help: if you need `extern "stdcall"` on win32 and `extern "C"` everywhere else, use `extern "system"`
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:128:1
+  --> $DIR/unsupported.rs:130:1
    |
 LL | extern "cdecl" fn cdecl() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -245,13 +245,13 @@ LL | extern "cdecl" fn cdecl() {}
    = help: use `extern "C"` instead
 
 error[E0570]: `"vectorcall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:143:1
+  --> $DIR/unsupported.rs:145:1
    |
 LL | extern "vectorcall" fn vectorcall() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error[E0570]: `"C-cmse-nonsecure-entry"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:159:1
+  --> $DIR/unsupported.rs:161:1
    |
 LL | extern "C-cmse-nonsecure-entry" fn cmse_entry() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -305,7 +305,7 @@ LL | fn riscv_ptr(f: extern "riscv-interrupt-m" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "x86-interrupt" is not supported on this target
-  --> $DIR/unsupported.rs:90:15
+  --> $DIR/unsupported.rs:91:15
    |
 LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -316,7 +316,7 @@ LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "thiscall" is not supported on this target
-  --> $DIR/unsupported.rs:100:20
+  --> $DIR/unsupported.rs:102:20
    |
 LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
    |                    ^^^^^^^^^^^^^^^^^^^^^^
@@ -327,7 +327,7 @@ LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "stdcall" is not supported on this target
-  --> $DIR/unsupported.rs:112:19
+  --> $DIR/unsupported.rs:114:19
    |
 LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
    |                   ^^^^^^^^^^^^^^^^^^^^^
@@ -338,7 +338,7 @@ LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "vectorcall" is not supported on this target
-  --> $DIR/unsupported.rs:145:22
+  --> $DIR/unsupported.rs:147:22
    |
 LL | fn vectorcall_ptr(f: extern "vectorcall" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -349,7 +349,7 @@ LL | fn vectorcall_ptr(f: extern "vectorcall" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "C-cmse-nonsecure-call" is not supported on this target
-  --> $DIR/unsupported.rs:153:21
+  --> $DIR/unsupported.rs:155:21
    |
 LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -360,7 +360,7 @@ LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "C-cmse-nonsecure-entry" is not supported on this target
-  --> $DIR/unsupported.rs:161:22
+  --> $DIR/unsupported.rs:163:22
    |
 LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/tests/ui/abi/unsupported.i686.stderr b/tests/ui/abi/unsupported.i686.stderr
index 4d903b435d872..5688416601991 100644
--- a/tests/ui/abi/unsupported.i686.stderr
+++ b/tests/ui/abi/unsupported.i686.stderr
@@ -69,13 +69,13 @@ LL | fn riscv_ptr(f: extern "riscv-interrupt-m" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"riscv-interrupt-m"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:85:1
+  --> $DIR/unsupported.rs:86:1
    |
 LL | extern "riscv-interrupt-m" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "C-cmse-nonsecure-call" is not supported on this target
-  --> $DIR/unsupported.rs:153:21
+  --> $DIR/unsupported.rs:155:21
    |
 LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -84,7 +84,7 @@ LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 warning: the calling convention "C-cmse-nonsecure-entry" is not supported on this target
-  --> $DIR/unsupported.rs:161:22
+  --> $DIR/unsupported.rs:163:22
    |
 LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -93,7 +93,7 @@ LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"C-cmse-nonsecure-entry"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:166:1
+  --> $DIR/unsupported.rs:168:1
    |
 LL | extern "C-cmse-nonsecure-entry" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -134,13 +134,25 @@ error[E0570]: `"riscv-interrupt-m"` is not a supported ABI for the current targe
 LL | extern "riscv-interrupt-m" fn riscv() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
+error: functions with the "x86-interrupt" ABI cannot be called
+  --> $DIR/unsupported.rs:94:5
+   |
+LL |     f()
+   |     ^^^
+   |
+note: an `extern "x86-interrupt"` function can only be called using inline assembly
+  --> $DIR/unsupported.rs:94:5
+   |
+LL |     f()
+   |     ^^^
+
 error[E0570]: `"C-cmse-nonsecure-entry"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:159:1
+  --> $DIR/unsupported.rs:161:1
    |
 LL | extern "C-cmse-nonsecure-entry" fn cmse_entry() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: aborting due to 13 previous errors; 7 warnings emitted
+error: aborting due to 14 previous errors; 7 warnings emitted
 
 For more information about this error, try `rustc --explain E0570`.
 Future incompatibility report: Future breakage diagnostic:
@@ -200,7 +212,7 @@ LL | fn riscv_ptr(f: extern "riscv-interrupt-m" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "C-cmse-nonsecure-call" is not supported on this target
-  --> $DIR/unsupported.rs:153:21
+  --> $DIR/unsupported.rs:155:21
    |
 LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -211,7 +223,7 @@ LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "C-cmse-nonsecure-entry" is not supported on this target
-  --> $DIR/unsupported.rs:161:22
+  --> $DIR/unsupported.rs:163:22
    |
 LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/tests/ui/abi/unsupported.riscv32.stderr b/tests/ui/abi/unsupported.riscv32.stderr
index 9e75dfafca0f5..124ba13cc236f 100644
--- a/tests/ui/abi/unsupported.riscv32.stderr
+++ b/tests/ui/abi/unsupported.riscv32.stderr
@@ -60,7 +60,7 @@ LL | extern "avr-interrupt" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "x86-interrupt" is not supported on this target
-  --> $DIR/unsupported.rs:90:15
+  --> $DIR/unsupported.rs:91:15
    |
 LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -69,13 +69,13 @@ LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"x86-interrupt"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:95:1
+  --> $DIR/unsupported.rs:97:1
    |
 LL | extern "x86-interrupt" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "thiscall" is not supported on this target
-  --> $DIR/unsupported.rs:100:20
+  --> $DIR/unsupported.rs:102:20
    |
 LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
    |                    ^^^^^^^^^^^^^^^^^^^^^^
@@ -84,13 +84,13 @@ LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"thiscall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:105:1
+  --> $DIR/unsupported.rs:107:1
    |
 LL | extern "thiscall" {}
    | ^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "stdcall" is not supported on this target
-  --> $DIR/unsupported.rs:112:19
+  --> $DIR/unsupported.rs:114:19
    |
 LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
    |                   ^^^^^^^^^^^^^^^^^^^^^
@@ -99,7 +99,7 @@ LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"stdcall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:119:1
+  --> $DIR/unsupported.rs:121:1
    |
 LL | extern "stdcall" {}
    | ^^^^^^^^^^^^^^^^^^^
@@ -107,7 +107,7 @@ LL | extern "stdcall" {}
    = help: if you need `extern "stdcall"` on win32 and `extern "C"` everywhere else, use `extern "system"`
 
 error[E0570]: `"stdcall-unwind"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:123:1
+  --> $DIR/unsupported.rs:125:1
    |
 LL | extern "stdcall-unwind" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -115,7 +115,7 @@ LL | extern "stdcall-unwind" {}
    = help: if you need `extern "stdcall-unwind"` on win32 and `extern "C-unwind"` everywhere else, use `extern "system-unwind"`
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:131:17
+  --> $DIR/unsupported.rs:133:17
    |
 LL | fn cdecl_ptr(f: extern "cdecl" fn()) {
    |                 ^^^^^^^^^^^^^^^^^^^
@@ -126,7 +126,7 @@ LL | fn cdecl_ptr(f: extern "cdecl" fn()) {
    = note: `#[warn(unsupported_calling_conventions)]` on by default
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:136:1
+  --> $DIR/unsupported.rs:138:1
    |
 LL | extern "cdecl" {}
    | ^^^^^^^^^^^^^^^^^
@@ -136,7 +136,7 @@ LL | extern "cdecl" {}
    = help: use `extern "C"` instead
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:139:1
+  --> $DIR/unsupported.rs:141:1
    |
 LL | extern "cdecl-unwind" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -146,7 +146,7 @@ LL | extern "cdecl-unwind" {}
    = help: use `extern "C-unwind"` instead
 
 warning: the calling convention "vectorcall" is not supported on this target
-  --> $DIR/unsupported.rs:145:22
+  --> $DIR/unsupported.rs:147:22
    |
 LL | fn vectorcall_ptr(f: extern "vectorcall" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -155,13 +155,13 @@ LL | fn vectorcall_ptr(f: extern "vectorcall" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"vectorcall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:150:1
+  --> $DIR/unsupported.rs:152:1
    |
 LL | extern "vectorcall" {}
    | ^^^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "C-cmse-nonsecure-call" is not supported on this target
-  --> $DIR/unsupported.rs:153:21
+  --> $DIR/unsupported.rs:155:21
    |
 LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -170,7 +170,7 @@ LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 warning: the calling convention "C-cmse-nonsecure-entry" is not supported on this target
-  --> $DIR/unsupported.rs:161:22
+  --> $DIR/unsupported.rs:163:22
    |
 LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -179,7 +179,7 @@ LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"C-cmse-nonsecure-entry"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:166:1
+  --> $DIR/unsupported.rs:168:1
    |
 LL | extern "C-cmse-nonsecure-entry" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -214,20 +214,32 @@ error[E0570]: `"avr-interrupt"` is not a supported ABI for the current target
 LL | extern "avr-interrupt" fn avr() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
+error: functions with the "riscv-interrupt-m" ABI cannot be called
+  --> $DIR/unsupported.rs:83:5
+   |
+LL |     f()
+   |     ^^^
+   |
+note: an `extern "riscv-interrupt-m"` function can only be called using inline assembly
+  --> $DIR/unsupported.rs:83:5
+   |
+LL |     f()
+   |     ^^^
+
 error[E0570]: `"x86-interrupt"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:88:1
+  --> $DIR/unsupported.rs:89:1
    |
 LL | extern "x86-interrupt" fn x86() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error[E0570]: `"thiscall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:98:1
+  --> $DIR/unsupported.rs:100:1
    |
 LL | extern "thiscall" fn thiscall() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error[E0570]: `"stdcall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:108:1
+  --> $DIR/unsupported.rs:110:1
    |
 LL | extern "stdcall" fn stdcall() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -235,7 +247,7 @@ LL | extern "stdcall" fn stdcall() {}
    = help: if you need `extern "stdcall"` on win32 and `extern "C"` everywhere else, use `extern "system"`
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:128:1
+  --> $DIR/unsupported.rs:130:1
    |
 LL | extern "cdecl" fn cdecl() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -245,18 +257,18 @@ LL | extern "cdecl" fn cdecl() {}
    = help: use `extern "C"` instead
 
 error[E0570]: `"vectorcall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:143:1
+  --> $DIR/unsupported.rs:145:1
    |
 LL | extern "vectorcall" fn vectorcall() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error[E0570]: `"C-cmse-nonsecure-entry"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:159:1
+  --> $DIR/unsupported.rs:161:1
    |
 LL | extern "C-cmse-nonsecure-entry" fn cmse_entry() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: aborting due to 20 previous errors; 14 warnings emitted
+error: aborting due to 21 previous errors; 14 warnings emitted
 
 For more information about this error, try `rustc --explain E0570`.
 Future incompatibility report: Future breakage diagnostic:
@@ -305,7 +317,7 @@ LL | fn avr_ptr(f: extern "avr-interrupt" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "x86-interrupt" is not supported on this target
-  --> $DIR/unsupported.rs:90:15
+  --> $DIR/unsupported.rs:91:15
    |
 LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -316,7 +328,7 @@ LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "thiscall" is not supported on this target
-  --> $DIR/unsupported.rs:100:20
+  --> $DIR/unsupported.rs:102:20
    |
 LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
    |                    ^^^^^^^^^^^^^^^^^^^^^^
@@ -327,7 +339,7 @@ LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "stdcall" is not supported on this target
-  --> $DIR/unsupported.rs:112:19
+  --> $DIR/unsupported.rs:114:19
    |
 LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
    |                   ^^^^^^^^^^^^^^^^^^^^^
@@ -338,7 +350,7 @@ LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "vectorcall" is not supported on this target
-  --> $DIR/unsupported.rs:145:22
+  --> $DIR/unsupported.rs:147:22
    |
 LL | fn vectorcall_ptr(f: extern "vectorcall" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -349,7 +361,7 @@ LL | fn vectorcall_ptr(f: extern "vectorcall" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "C-cmse-nonsecure-call" is not supported on this target
-  --> $DIR/unsupported.rs:153:21
+  --> $DIR/unsupported.rs:155:21
    |
 LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -360,7 +372,7 @@ LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "C-cmse-nonsecure-entry" is not supported on this target
-  --> $DIR/unsupported.rs:161:22
+  --> $DIR/unsupported.rs:163:22
    |
 LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/tests/ui/abi/unsupported.riscv64.stderr b/tests/ui/abi/unsupported.riscv64.stderr
index 9e75dfafca0f5..124ba13cc236f 100644
--- a/tests/ui/abi/unsupported.riscv64.stderr
+++ b/tests/ui/abi/unsupported.riscv64.stderr
@@ -60,7 +60,7 @@ LL | extern "avr-interrupt" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "x86-interrupt" is not supported on this target
-  --> $DIR/unsupported.rs:90:15
+  --> $DIR/unsupported.rs:91:15
    |
 LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -69,13 +69,13 @@ LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"x86-interrupt"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:95:1
+  --> $DIR/unsupported.rs:97:1
    |
 LL | extern "x86-interrupt" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "thiscall" is not supported on this target
-  --> $DIR/unsupported.rs:100:20
+  --> $DIR/unsupported.rs:102:20
    |
 LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
    |                    ^^^^^^^^^^^^^^^^^^^^^^
@@ -84,13 +84,13 @@ LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"thiscall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:105:1
+  --> $DIR/unsupported.rs:107:1
    |
 LL | extern "thiscall" {}
    | ^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "stdcall" is not supported on this target
-  --> $DIR/unsupported.rs:112:19
+  --> $DIR/unsupported.rs:114:19
    |
 LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
    |                   ^^^^^^^^^^^^^^^^^^^^^
@@ -99,7 +99,7 @@ LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"stdcall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:119:1
+  --> $DIR/unsupported.rs:121:1
    |
 LL | extern "stdcall" {}
    | ^^^^^^^^^^^^^^^^^^^
@@ -107,7 +107,7 @@ LL | extern "stdcall" {}
    = help: if you need `extern "stdcall"` on win32 and `extern "C"` everywhere else, use `extern "system"`
 
 error[E0570]: `"stdcall-unwind"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:123:1
+  --> $DIR/unsupported.rs:125:1
    |
 LL | extern "stdcall-unwind" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -115,7 +115,7 @@ LL | extern "stdcall-unwind" {}
    = help: if you need `extern "stdcall-unwind"` on win32 and `extern "C-unwind"` everywhere else, use `extern "system-unwind"`
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:131:17
+  --> $DIR/unsupported.rs:133:17
    |
 LL | fn cdecl_ptr(f: extern "cdecl" fn()) {
    |                 ^^^^^^^^^^^^^^^^^^^
@@ -126,7 +126,7 @@ LL | fn cdecl_ptr(f: extern "cdecl" fn()) {
    = note: `#[warn(unsupported_calling_conventions)]` on by default
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:136:1
+  --> $DIR/unsupported.rs:138:1
    |
 LL | extern "cdecl" {}
    | ^^^^^^^^^^^^^^^^^
@@ -136,7 +136,7 @@ LL | extern "cdecl" {}
    = help: use `extern "C"` instead
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:139:1
+  --> $DIR/unsupported.rs:141:1
    |
 LL | extern "cdecl-unwind" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -146,7 +146,7 @@ LL | extern "cdecl-unwind" {}
    = help: use `extern "C-unwind"` instead
 
 warning: the calling convention "vectorcall" is not supported on this target
-  --> $DIR/unsupported.rs:145:22
+  --> $DIR/unsupported.rs:147:22
    |
 LL | fn vectorcall_ptr(f: extern "vectorcall" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -155,13 +155,13 @@ LL | fn vectorcall_ptr(f: extern "vectorcall" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"vectorcall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:150:1
+  --> $DIR/unsupported.rs:152:1
    |
 LL | extern "vectorcall" {}
    | ^^^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "C-cmse-nonsecure-call" is not supported on this target
-  --> $DIR/unsupported.rs:153:21
+  --> $DIR/unsupported.rs:155:21
    |
 LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -170,7 +170,7 @@ LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 warning: the calling convention "C-cmse-nonsecure-entry" is not supported on this target
-  --> $DIR/unsupported.rs:161:22
+  --> $DIR/unsupported.rs:163:22
    |
 LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -179,7 +179,7 @@ LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"C-cmse-nonsecure-entry"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:166:1
+  --> $DIR/unsupported.rs:168:1
    |
 LL | extern "C-cmse-nonsecure-entry" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -214,20 +214,32 @@ error[E0570]: `"avr-interrupt"` is not a supported ABI for the current target
 LL | extern "avr-interrupt" fn avr() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
+error: functions with the "riscv-interrupt-m" ABI cannot be called
+  --> $DIR/unsupported.rs:83:5
+   |
+LL |     f()
+   |     ^^^
+   |
+note: an `extern "riscv-interrupt-m"` function can only be called using inline assembly
+  --> $DIR/unsupported.rs:83:5
+   |
+LL |     f()
+   |     ^^^
+
 error[E0570]: `"x86-interrupt"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:88:1
+  --> $DIR/unsupported.rs:89:1
    |
 LL | extern "x86-interrupt" fn x86() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error[E0570]: `"thiscall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:98:1
+  --> $DIR/unsupported.rs:100:1
    |
 LL | extern "thiscall" fn thiscall() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error[E0570]: `"stdcall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:108:1
+  --> $DIR/unsupported.rs:110:1
    |
 LL | extern "stdcall" fn stdcall() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -235,7 +247,7 @@ LL | extern "stdcall" fn stdcall() {}
    = help: if you need `extern "stdcall"` on win32 and `extern "C"` everywhere else, use `extern "system"`
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:128:1
+  --> $DIR/unsupported.rs:130:1
    |
 LL | extern "cdecl" fn cdecl() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -245,18 +257,18 @@ LL | extern "cdecl" fn cdecl() {}
    = help: use `extern "C"` instead
 
 error[E0570]: `"vectorcall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:143:1
+  --> $DIR/unsupported.rs:145:1
    |
 LL | extern "vectorcall" fn vectorcall() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error[E0570]: `"C-cmse-nonsecure-entry"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:159:1
+  --> $DIR/unsupported.rs:161:1
    |
 LL | extern "C-cmse-nonsecure-entry" fn cmse_entry() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: aborting due to 20 previous errors; 14 warnings emitted
+error: aborting due to 21 previous errors; 14 warnings emitted
 
 For more information about this error, try `rustc --explain E0570`.
 Future incompatibility report: Future breakage diagnostic:
@@ -305,7 +317,7 @@ LL | fn avr_ptr(f: extern "avr-interrupt" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "x86-interrupt" is not supported on this target
-  --> $DIR/unsupported.rs:90:15
+  --> $DIR/unsupported.rs:91:15
    |
 LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
    |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -316,7 +328,7 @@ LL | fn x86_ptr(f: extern "x86-interrupt" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "thiscall" is not supported on this target
-  --> $DIR/unsupported.rs:100:20
+  --> $DIR/unsupported.rs:102:20
    |
 LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
    |                    ^^^^^^^^^^^^^^^^^^^^^^
@@ -327,7 +339,7 @@ LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "stdcall" is not supported on this target
-  --> $DIR/unsupported.rs:112:19
+  --> $DIR/unsupported.rs:114:19
    |
 LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
    |                   ^^^^^^^^^^^^^^^^^^^^^
@@ -338,7 +350,7 @@ LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "vectorcall" is not supported on this target
-  --> $DIR/unsupported.rs:145:22
+  --> $DIR/unsupported.rs:147:22
    |
 LL | fn vectorcall_ptr(f: extern "vectorcall" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -349,7 +361,7 @@ LL | fn vectorcall_ptr(f: extern "vectorcall" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "C-cmse-nonsecure-call" is not supported on this target
-  --> $DIR/unsupported.rs:153:21
+  --> $DIR/unsupported.rs:155:21
    |
 LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -360,7 +372,7 @@ LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "C-cmse-nonsecure-entry" is not supported on this target
-  --> $DIR/unsupported.rs:161:22
+  --> $DIR/unsupported.rs:163:22
    |
 LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/tests/ui/abi/unsupported.rs b/tests/ui/abi/unsupported.rs
index 43bdfe3ea2401..4ddcbae409bcf 100644
--- a/tests/ui/abi/unsupported.rs
+++ b/tests/ui/abi/unsupported.rs
@@ -81,6 +81,7 @@ fn riscv_ptr(f: extern "riscv-interrupt-m" fn()) {
     //[x64,x64_win,i686,arm,aarch64]~^ WARN unsupported_fn_ptr_calling_conventions
     //[x64,x64_win,i686,arm,aarch64]~^^ WARN this was previously accepted
     f()
+    //[riscv32,riscv64]~^ ERROR functions with the "riscv-interrupt-m" ABI cannot be called
 }
 extern "riscv-interrupt-m" {}
 //[x64,x64_win,i686,arm,aarch64]~^ ERROR is not a supported ABI
@@ -91,6 +92,7 @@ fn x86_ptr(f: extern "x86-interrupt" fn()) {
     //[aarch64,arm,riscv32,riscv64]~^ WARN unsupported_fn_ptr_calling_conventions
     //[aarch64,arm,riscv32,riscv64]~^^ WARN this was previously accepted
     f()
+    //[x64,x64_win,i686]~^ ERROR functions with the "x86-interrupt" ABI cannot be called
 }
 extern "x86-interrupt" {}
 //[aarch64,arm,riscv32,riscv64]~^ ERROR is not a supported ABI
diff --git a/tests/ui/abi/unsupported.x64.stderr b/tests/ui/abi/unsupported.x64.stderr
index 5b55e5707fad9..737c4c670b839 100644
--- a/tests/ui/abi/unsupported.x64.stderr
+++ b/tests/ui/abi/unsupported.x64.stderr
@@ -69,13 +69,13 @@ LL | fn riscv_ptr(f: extern "riscv-interrupt-m" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"riscv-interrupt-m"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:85:1
+  --> $DIR/unsupported.rs:86:1
    |
 LL | extern "riscv-interrupt-m" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "thiscall" is not supported on this target
-  --> $DIR/unsupported.rs:100:20
+  --> $DIR/unsupported.rs:102:20
    |
 LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
    |                    ^^^^^^^^^^^^^^^^^^^^^^
@@ -84,13 +84,13 @@ LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"thiscall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:105:1
+  --> $DIR/unsupported.rs:107:1
    |
 LL | extern "thiscall" {}
    | ^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "stdcall" is not supported on this target
-  --> $DIR/unsupported.rs:112:19
+  --> $DIR/unsupported.rs:114:19
    |
 LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
    |                   ^^^^^^^^^^^^^^^^^^^^^
@@ -99,7 +99,7 @@ LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"stdcall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:119:1
+  --> $DIR/unsupported.rs:121:1
    |
 LL | extern "stdcall" {}
    | ^^^^^^^^^^^^^^^^^^^
@@ -107,7 +107,7 @@ LL | extern "stdcall" {}
    = help: if you need `extern "stdcall"` on win32 and `extern "C"` everywhere else, use `extern "system"`
 
 error[E0570]: `"stdcall-unwind"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:123:1
+  --> $DIR/unsupported.rs:125:1
    |
 LL | extern "stdcall-unwind" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -115,7 +115,7 @@ LL | extern "stdcall-unwind" {}
    = help: if you need `extern "stdcall-unwind"` on win32 and `extern "C-unwind"` everywhere else, use `extern "system-unwind"`
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:131:17
+  --> $DIR/unsupported.rs:133:17
    |
 LL | fn cdecl_ptr(f: extern "cdecl" fn()) {
    |                 ^^^^^^^^^^^^^^^^^^^
@@ -126,7 +126,7 @@ LL | fn cdecl_ptr(f: extern "cdecl" fn()) {
    = note: `#[warn(unsupported_calling_conventions)]` on by default
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:136:1
+  --> $DIR/unsupported.rs:138:1
    |
 LL | extern "cdecl" {}
    | ^^^^^^^^^^^^^^^^^
@@ -136,7 +136,7 @@ LL | extern "cdecl" {}
    = help: use `extern "C"` instead
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:139:1
+  --> $DIR/unsupported.rs:141:1
    |
 LL | extern "cdecl-unwind" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -146,7 +146,7 @@ LL | extern "cdecl-unwind" {}
    = help: use `extern "C-unwind"` instead
 
 warning: the calling convention "C-cmse-nonsecure-call" is not supported on this target
-  --> $DIR/unsupported.rs:153:21
+  --> $DIR/unsupported.rs:155:21
    |
 LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -155,7 +155,7 @@ LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 warning: the calling convention "C-cmse-nonsecure-entry" is not supported on this target
-  --> $DIR/unsupported.rs:161:22
+  --> $DIR/unsupported.rs:163:22
    |
 LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -164,7 +164,7 @@ LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"C-cmse-nonsecure-entry"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:166:1
+  --> $DIR/unsupported.rs:168:1
    |
 LL | extern "C-cmse-nonsecure-entry" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -205,14 +205,26 @@ error[E0570]: `"riscv-interrupt-m"` is not a supported ABI for the current targe
 LL | extern "riscv-interrupt-m" fn riscv() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
+error: functions with the "x86-interrupt" ABI cannot be called
+  --> $DIR/unsupported.rs:94:5
+   |
+LL |     f()
+   |     ^^^
+   |
+note: an `extern "x86-interrupt"` function can only be called using inline assembly
+  --> $DIR/unsupported.rs:94:5
+   |
+LL |     f()
+   |     ^^^
+
 error[E0570]: `"thiscall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:98:1
+  --> $DIR/unsupported.rs:100:1
    |
 LL | extern "thiscall" fn thiscall() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error[E0570]: `"stdcall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:108:1
+  --> $DIR/unsupported.rs:110:1
    |
 LL | extern "stdcall" fn stdcall() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -220,7 +232,7 @@ LL | extern "stdcall" fn stdcall() {}
    = help: if you need `extern "stdcall"` on win32 and `extern "C"` everywhere else, use `extern "system"`
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:128:1
+  --> $DIR/unsupported.rs:130:1
    |
 LL | extern "cdecl" fn cdecl() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -230,12 +242,12 @@ LL | extern "cdecl" fn cdecl() {}
    = help: use `extern "C"` instead
 
 error[E0570]: `"C-cmse-nonsecure-entry"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:159:1
+  --> $DIR/unsupported.rs:161:1
    |
 LL | extern "C-cmse-nonsecure-entry" fn cmse_entry() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: aborting due to 18 previous errors; 13 warnings emitted
+error: aborting due to 19 previous errors; 13 warnings emitted
 
 For more information about this error, try `rustc --explain E0570`.
 Future incompatibility report: Future breakage diagnostic:
@@ -295,7 +307,7 @@ LL | fn riscv_ptr(f: extern "riscv-interrupt-m" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "thiscall" is not supported on this target
-  --> $DIR/unsupported.rs:100:20
+  --> $DIR/unsupported.rs:102:20
    |
 LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
    |                    ^^^^^^^^^^^^^^^^^^^^^^
@@ -306,7 +318,7 @@ LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "stdcall" is not supported on this target
-  --> $DIR/unsupported.rs:112:19
+  --> $DIR/unsupported.rs:114:19
    |
 LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
    |                   ^^^^^^^^^^^^^^^^^^^^^
@@ -317,7 +329,7 @@ LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "C-cmse-nonsecure-call" is not supported on this target
-  --> $DIR/unsupported.rs:153:21
+  --> $DIR/unsupported.rs:155:21
    |
 LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -328,7 +340,7 @@ LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "C-cmse-nonsecure-entry" is not supported on this target
-  --> $DIR/unsupported.rs:161:22
+  --> $DIR/unsupported.rs:163:22
    |
 LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/tests/ui/abi/unsupported.x64_win.stderr b/tests/ui/abi/unsupported.x64_win.stderr
index 93b5a272e926d..f201a089d3f9b 100644
--- a/tests/ui/abi/unsupported.x64_win.stderr
+++ b/tests/ui/abi/unsupported.x64_win.stderr
@@ -69,13 +69,13 @@ LL | fn riscv_ptr(f: extern "riscv-interrupt-m" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"riscv-interrupt-m"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:85:1
+  --> $DIR/unsupported.rs:86:1
    |
 LL | extern "riscv-interrupt-m" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: the calling convention "thiscall" is not supported on this target
-  --> $DIR/unsupported.rs:100:20
+  --> $DIR/unsupported.rs:102:20
    |
 LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
    |                    ^^^^^^^^^^^^^^^^^^^^^^
@@ -84,13 +84,13 @@ LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"thiscall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:105:1
+  --> $DIR/unsupported.rs:107:1
    |
 LL | extern "thiscall" {}
    | ^^^^^^^^^^^^^^^^^^^^
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:112:19
+  --> $DIR/unsupported.rs:114:19
    |
 LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
    |                   ^^^^^^^^^^^^^^^^^^^^^
@@ -101,7 +101,7 @@ LL | fn stdcall_ptr(f: extern "stdcall" fn()) {
    = note: `#[warn(unsupported_calling_conventions)]` on by default
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:119:1
+  --> $DIR/unsupported.rs:121:1
    |
 LL | extern "stdcall" {}
    | ^^^^^^^^^^^^^^^^^^^
@@ -111,7 +111,7 @@ LL | extern "stdcall" {}
    = help: if you need `extern "stdcall"` on win32 and `extern "C"` everywhere else, use `extern "system"`
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:123:1
+  --> $DIR/unsupported.rs:125:1
    |
 LL | extern "stdcall-unwind" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -121,7 +121,7 @@ LL | extern "stdcall-unwind" {}
    = help: if you need `extern "stdcall-unwind"` on win32 and `extern "C-unwind"` everywhere else, use `extern "system-unwind"`
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:131:17
+  --> $DIR/unsupported.rs:133:17
    |
 LL | fn cdecl_ptr(f: extern "cdecl" fn()) {
    |                 ^^^^^^^^^^^^^^^^^^^
@@ -131,7 +131,7 @@ LL | fn cdecl_ptr(f: extern "cdecl" fn()) {
    = help: use `extern "C"` instead
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:136:1
+  --> $DIR/unsupported.rs:138:1
    |
 LL | extern "cdecl" {}
    | ^^^^^^^^^^^^^^^^^
@@ -141,7 +141,7 @@ LL | extern "cdecl" {}
    = help: use `extern "C"` instead
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:139:1
+  --> $DIR/unsupported.rs:141:1
    |
 LL | extern "cdecl-unwind" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^
@@ -151,7 +151,7 @@ LL | extern "cdecl-unwind" {}
    = help: use `extern "C-unwind"` instead
 
 warning: the calling convention "C-cmse-nonsecure-call" is not supported on this target
-  --> $DIR/unsupported.rs:153:21
+  --> $DIR/unsupported.rs:155:21
    |
 LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -160,7 +160,7 @@ LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 warning: the calling convention "C-cmse-nonsecure-entry" is not supported on this target
-  --> $DIR/unsupported.rs:161:22
+  --> $DIR/unsupported.rs:163:22
    |
 LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -169,13 +169,13 @@ LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    = note: for more information, see issue #130260 <https://github.com/rust-lang/rust/issues/130260>
 
 error[E0570]: `"C-cmse-nonsecure-entry"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:166:1
+  --> $DIR/unsupported.rs:168:1
    |
 LL | extern "C-cmse-nonsecure-entry" {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:171:1
+  --> $DIR/unsupported.rs:173:1
    |
 LL | extern "cdecl" {}
    | ^^^^^^^^^^^^^^^^^
@@ -220,14 +220,26 @@ error[E0570]: `"riscv-interrupt-m"` is not a supported ABI for the current targe
 LL | extern "riscv-interrupt-m" fn riscv() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
+error: functions with the "x86-interrupt" ABI cannot be called
+  --> $DIR/unsupported.rs:94:5
+   |
+LL |     f()
+   |     ^^^
+   |
+note: an `extern "x86-interrupt"` function can only be called using inline assembly
+  --> $DIR/unsupported.rs:94:5
+   |
+LL |     f()
+   |     ^^^
+
 error[E0570]: `"thiscall"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:98:1
+  --> $DIR/unsupported.rs:100:1
    |
 LL | extern "thiscall" fn thiscall() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:108:1
+  --> $DIR/unsupported.rs:110:1
    |
 LL | extern "stdcall" fn stdcall() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -237,7 +249,7 @@ LL | extern "stdcall" fn stdcall() {}
    = help: if you need `extern "stdcall"` on win32 and `extern "C"` everywhere else, use `extern "system"`
 
 warning: use of calling convention not supported on this target
-  --> $DIR/unsupported.rs:128:1
+  --> $DIR/unsupported.rs:130:1
    |
 LL | extern "cdecl" fn cdecl() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -247,12 +259,12 @@ LL | extern "cdecl" fn cdecl() {}
    = help: use `extern "C"` instead
 
 error[E0570]: `"C-cmse-nonsecure-entry"` is not a supported ABI for the current target
-  --> $DIR/unsupported.rs:159:1
+  --> $DIR/unsupported.rs:161:1
    |
 LL | extern "C-cmse-nonsecure-entry" fn cmse_entry() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: aborting due to 15 previous errors; 17 warnings emitted
+error: aborting due to 16 previous errors; 17 warnings emitted
 
 For more information about this error, try `rustc --explain E0570`.
 Future incompatibility report: Future breakage diagnostic:
@@ -312,7 +324,7 @@ LL | fn riscv_ptr(f: extern "riscv-interrupt-m" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "thiscall" is not supported on this target
-  --> $DIR/unsupported.rs:100:20
+  --> $DIR/unsupported.rs:102:20
    |
 LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
    |                    ^^^^^^^^^^^^^^^^^^^^^^
@@ -323,7 +335,7 @@ LL | fn thiscall_ptr(f: extern "thiscall" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "C-cmse-nonsecure-call" is not supported on this target
-  --> $DIR/unsupported.rs:153:21
+  --> $DIR/unsupported.rs:155:21
    |
 LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
    |                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -334,7 +346,7 @@ LL | fn cmse_call_ptr(f: extern "C-cmse-nonsecure-call" fn()) {
 
 Future breakage diagnostic:
 warning: the calling convention "C-cmse-nonsecure-entry" is not supported on this target
-  --> $DIR/unsupported.rs:161:22
+  --> $DIR/unsupported.rs:163:22
    |
 LL | fn cmse_entry_ptr(f: extern "C-cmse-nonsecure-entry" fn()) {
    |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

From b64fd13a04e03bd260b550430daa6fbdf1fb27c9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= <jana@donsz.nl>
Date: Sun, 9 Mar 2025 23:46:47 +0100
Subject: [PATCH 03/20] convert the `optimize` attribute to a new parser

---
 .../src/attributes.rs                         |  6 ++-
 .../src/attributes/codegen_attrs.rs           | 40 +++++++++++++++++++
 .../rustc_attr_parsing/src/attributes/mod.rs  |  1 +
 compiler/rustc_attr_parsing/src/context.rs    |  2 +
 compiler/rustc_codegen_ssa/messages.ftl       |  5 ---
 .../rustc_codegen_ssa/src/codegen_attrs.rs    | 29 +-------------
 compiler/rustc_codegen_ssa/src/errors.rs      | 14 -------
 .../src/error_codes/E0722.md                  |  8 +++-
 compiler/rustc_error_codes/src/lib.rs         |  1 +
 compiler/rustc_parse/src/validate_attr.rs     |  1 +
 compiler/rustc_passes/src/check_attr.rs       |  8 ++--
 .../feature-gate-optimize_attribute.rs        |  2 +-
 .../feature-gate-optimize_attribute.stderr    | 13 +++---
 13 files changed, 72 insertions(+), 58 deletions(-)
 create mode 100644 compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs

diff --git a/compiler/rustc_attr_data_structures/src/attributes.rs b/compiler/rustc_attr_data_structures/src/attributes.rs
index d4c4345604969..e0b0015f365c0 100644
--- a/compiler/rustc_attr_data_structures/src/attributes.rs
+++ b/compiler/rustc_attr_data_structures/src/attributes.rs
@@ -38,7 +38,8 @@ pub enum InstructionSetAttr {
     ArmT32,
 }
 
-#[derive(Clone, Encodable, Decodable, Debug, PartialEq, Eq, HashStable_Generic, Default)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, PrintAttribute)]
+#[derive(Encodable, Decodable, HashStable_Generic)]
 pub enum OptimizeAttr {
     /// No `#[optimize(..)]` attribute
     #[default]
@@ -226,7 +227,8 @@ pub enum AttributeKind {
 
     /// Represents `#[rustc_macro_transparency]`.
     MacroTransparency(Transparency),
-
+    /// Represents `#[optimize(size|speed)]`
+    Optimize(OptimizeAttr, Span),
     /// Represents [`#[repr]`](https://doc.rust-lang.org/stable/reference/type-layout.html#representations).
     Repr(ThinVec<(ReprAttr, Span)>),
 
diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
new file mode 100644
index 0000000000000..ddcf82cbf7cd5
--- /dev/null
+++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs
@@ -0,0 +1,40 @@
+use rustc_attr_data_structures::{AttributeKind, OptimizeAttr};
+use rustc_feature::{AttributeTemplate, template};
+use rustc_span::sym;
+
+use super::{AttributeOrder, OnDuplicate, SingleAttributeParser};
+use crate::context::{AcceptContext, Stage};
+use crate::parser::ArgParser;
+
+pub(crate) struct OptimizeParser;
+
+impl<S: Stage> SingleAttributeParser<S> for OptimizeParser {
+    const PATH: &[rustc_span::Symbol] = &[sym::optimize];
+    const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepLast;
+    const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError;
+    const TEMPLATE: AttributeTemplate = template!(List: "size|speed|none");
+
+    fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser<'_>) -> Option<AttributeKind> {
+        let Some(list) = args.list() else {
+            cx.expected_list(cx.attr_span);
+            return None;
+        };
+
+        let Some(single) = list.single() else {
+            cx.expected_single_argument(list.span);
+            return None;
+        };
+
+        let res = match single.meta_item().and_then(|i| i.path().word().map(|i| i.name)) {
+            Some(sym::size) => OptimizeAttr::Size,
+            Some(sym::speed) => OptimizeAttr::Speed,
+            Some(sym::none) => OptimizeAttr::DoNotOptimize,
+            _ => {
+                cx.expected_specific_argument(single.span(), vec!["size", "speed", "none"]);
+                OptimizeAttr::Default
+            }
+        };
+
+        Some(AttributeKind::Optimize(res, cx.attr_span))
+    }
+}
diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs
index fa2a6087506c1..3bb4c163d3268 100644
--- a/compiler/rustc_attr_parsing/src/attributes/mod.rs
+++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs
@@ -28,6 +28,7 @@ use crate::session_diagnostics::UnusedMultiple;
 
 pub(crate) mod allow_unstable;
 pub(crate) mod cfg;
+pub(crate) mod codegen_attrs;
 pub(crate) mod confusables;
 pub(crate) mod deprecation;
 pub(crate) mod inline;
diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs
index 51c1760da300c..18ed98d4a2591 100644
--- a/compiler/rustc_attr_parsing/src/context.rs
+++ b/compiler/rustc_attr_parsing/src/context.rs
@@ -15,6 +15,7 @@ use rustc_session::Session;
 use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol, sym};
 
 use crate::attributes::allow_unstable::{AllowConstFnUnstableParser, AllowInternalUnstableParser};
+use crate::attributes::codegen_attrs::OptimizeParser;
 use crate::attributes::confusables::ConfusablesParser;
 use crate::attributes::deprecation::DeprecationParser;
 use crate::attributes::inline::{InlineParser, RustcForceInlineParser};
@@ -107,6 +108,7 @@ attribute_parsers!(
         Single<ConstStabilityIndirectParser>,
         Single<DeprecationParser>,
         Single<InlineParser>,
+        Single<OptimizeParser>,
         Single<RustcForceInlineParser>,
         Single<TransparencyParser>,
         // tidy-alphabetical-end
diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl
index 5322fe58cf337..e4734b180935b 100644
--- a/compiler/rustc_codegen_ssa/messages.ftl
+++ b/compiler/rustc_codegen_ssa/messages.ftl
@@ -48,8 +48,6 @@ codegen_ssa_error_writing_def_file =
 
 codegen_ssa_expected_name_value_pair = expected name value pair
 
-codegen_ssa_expected_one_argument = expected one argument
-
 codegen_ssa_expected_used_symbol = expected `used`, `used(compiler)` or `used(linker)`
 
 codegen_ssa_extern_funcs_not_found = some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified
@@ -86,9 +84,6 @@ codegen_ssa_incorrect_cgu_reuse_type =
 
 codegen_ssa_insufficient_vs_code_product = VS Code is a different product, and is not sufficient.
 
-codegen_ssa_invalid_argument = invalid argument
-    .help = valid inline arguments are `always` and `never`
-
 codegen_ssa_invalid_instruction_set = invalid instruction set specified
 
 codegen_ssa_invalid_link_ordinal_nargs = incorrect number of arguments to `#[link_ordinal]`
diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
index 188a9a98ce7a0..33c457ead30a6 100644
--- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
+++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
@@ -465,33 +465,8 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
         codegen_fn_attrs.inline = InlineAttr::Never;
     }
 
-    codegen_fn_attrs.optimize = attrs.iter().fold(OptimizeAttr::Default, |ia, attr| {
-        if !attr.has_name(sym::optimize) {
-            return ia;
-        }
-        if attr.is_word() {
-            tcx.dcx().emit_err(errors::ExpectedOneArgumentOptimize { span: attr.span() });
-            return ia;
-        }
-        let Some(ref items) = attr.meta_item_list() else {
-            return OptimizeAttr::Default;
-        };
-
-        let [item] = &items[..] else {
-            tcx.dcx().emit_err(errors::ExpectedOneArgumentOptimize { span: attr.span() });
-            return OptimizeAttr::Default;
-        };
-        if item.has_name(sym::size) {
-            OptimizeAttr::Size
-        } else if item.has_name(sym::speed) {
-            OptimizeAttr::Speed
-        } else if item.has_name(sym::none) {
-            OptimizeAttr::DoNotOptimize
-        } else {
-            tcx.dcx().emit_err(errors::InvalidArgumentOptimize { span: item.span() });
-            OptimizeAttr::Default
-        }
-    });
+    codegen_fn_attrs.optimize =
+        find_attr!(attrs, AttributeKind::Optimize(i, _) => *i).unwrap_or(OptimizeAttr::Default);
 
     // #73631: closures inherit `#[target_feature]` annotations
     //
diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs
index 5387b2a7f8183..c60661a1410c5 100644
--- a/compiler/rustc_codegen_ssa/src/errors.rs
+++ b/compiler/rustc_codegen_ssa/src/errors.rs
@@ -208,20 +208,6 @@ pub(crate) struct OutOfRangeInteger {
     pub span: Span,
 }
 
-#[derive(Diagnostic)]
-#[diag(codegen_ssa_expected_one_argument, code = E0722)]
-pub(crate) struct ExpectedOneArgumentOptimize {
-    #[primary_span]
-    pub span: Span,
-}
-
-#[derive(Diagnostic)]
-#[diag(codegen_ssa_invalid_argument, code = E0722)]
-pub(crate) struct InvalidArgumentOptimize {
-    #[primary_span]
-    pub span: Span,
-}
-
 #[derive(Diagnostic)]
 #[diag(codegen_ssa_copy_path_buf)]
 pub(crate) struct CopyPathBuf {
diff --git a/compiler/rustc_error_codes/src/error_codes/E0722.md b/compiler/rustc_error_codes/src/error_codes/E0722.md
index 570717a92bd79..1799458d46cb0 100644
--- a/compiler/rustc_error_codes/src/error_codes/E0722.md
+++ b/compiler/rustc_error_codes/src/error_codes/E0722.md
@@ -1,8 +1,14 @@
+#### Note: this error code is no longer emitted by the compiler
+
+This is because it was too specific to the `optimize` attribute.
+Similar diagnostics occur for other attributes too.
+The example here will now emit `E0539`
+
 The `optimize` attribute was malformed.
 
 Erroneous code example:
 
-```compile_fail,E0722
+```compile_fail,E0539
 #![feature(optimize_attribute)]
 
 #[optimize(something)] // error: invalid argument
diff --git a/compiler/rustc_error_codes/src/lib.rs b/compiler/rustc_error_codes/src/lib.rs
index 6f5e4829802e9..22cc1e894da90 100644
--- a/compiler/rustc_error_codes/src/lib.rs
+++ b/compiler/rustc_error_codes/src/lib.rs
@@ -686,6 +686,7 @@ E0805: 0805,
 //  E0707, // multiple elided lifetimes used in arguments of `async fn`
 //  E0709, // multiple different lifetimes used in arguments of `async fn`
 //  E0721, // `await` keyword
+//  E0722, // replaced with a generic attribute input check
 //  E0723, // unstable feature in `const` context
 //  E0738, // Removed; errored on `#[track_caller] fn`s in `extern "Rust" { ... }`.
 //  E0744, // merged into E0728
diff --git a/compiler/rustc_parse/src/validate_attr.rs b/compiler/rustc_parse/src/validate_attr.rs
index b3096e46b09ca..1fbc970e2a719 100644
--- a/compiler/rustc_parse/src/validate_attr.rs
+++ b/compiler/rustc_parse/src/validate_attr.rs
@@ -290,6 +290,7 @@ fn emit_malformed_attribute(
             | sym::rustc_confusables
             | sym::repr
             | sym::deprecated
+            | sym::optimize
     ) {
         return;
     }
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 5ce803aa1f8a0..98e8b47127b23 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -128,6 +128,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                 Attribute::Parsed(AttributeKind::Inline(kind, attr_span)) => {
                     self.check_inline(hir_id, *attr_span, span, kind, target)
                 }
+                Attribute::Parsed(AttributeKind::Optimize(_, attr_span)) => {
+                    self.check_optimize(hir_id, *attr_span, span, target)
+                }
                 Attribute::Parsed(AttributeKind::AllowInternalUnstable(syms)) => self
                     .check_allow_internal_unstable(
                         hir_id,
@@ -163,7 +166,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
                             self.check_diagnostic_on_unimplemented(attr.span(), hir_id, target)
                         }
                         [sym::coverage, ..] => self.check_coverage(attr, span, target),
-                        [sym::optimize, ..] => self.check_optimize(hir_id, attr, span, target),
                         [sym::no_sanitize, ..] => {
                             self.check_no_sanitize(attr, span, target)
                         }
@@ -525,7 +527,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
 
     /// Checks that `#[optimize(..)]` is applied to a function/closure/method,
     /// or to an impl block or module.
-    fn check_optimize(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) {
+    fn check_optimize(&self, hir_id: HirId, attr_span: Span, span: Span, target: Target) {
         let is_valid = matches!(
             target,
             Target::Fn
@@ -534,7 +536,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
         );
         if !is_valid {
             self.dcx().emit_err(errors::OptimizeInvalidTarget {
-                attr_span: attr.span(),
+                attr_span,
                 defn_span: span,
                 on_crate: hir_id == CRATE_HIR_ID,
             });
diff --git a/tests/ui/feature-gates/feature-gate-optimize_attribute.rs b/tests/ui/feature-gates/feature-gate-optimize_attribute.rs
index 77cc307c9f453..ed5a11270f83e 100644
--- a/tests/ui/feature-gates/feature-gate-optimize_attribute.rs
+++ b/tests/ui/feature-gates/feature-gate-optimize_attribute.rs
@@ -11,5 +11,5 @@ fn none() {}
 
 #[optimize(banana)]
 //~^ ERROR the `#[optimize]` attribute is an experimental feature
-//~| ERROR E0722
+//~| ERROR malformed `optimize` attribute input [E0539]
 fn not_known() {}
diff --git a/tests/ui/feature-gates/feature-gate-optimize_attribute.stderr b/tests/ui/feature-gates/feature-gate-optimize_attribute.stderr
index 4e6e4ac2703a8..e7e62b4f98993 100644
--- a/tests/ui/feature-gates/feature-gate-optimize_attribute.stderr
+++ b/tests/ui/feature-gates/feature-gate-optimize_attribute.stderr
@@ -38,13 +38,16 @@ LL | #[optimize(banana)]
    = help: add `#![feature(optimize_attribute)]` to the crate attributes to enable
    = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
 
-error[E0722]: invalid argument
-  --> $DIR/feature-gate-optimize_attribute.rs:12:12
+error[E0539]: malformed `optimize` attribute input
+  --> $DIR/feature-gate-optimize_attribute.rs:12:1
    |
 LL | #[optimize(banana)]
-   |            ^^^^^^
+   | ^^^^^^^^^^^------^^
+   | |          |
+   | |          valid arguments are `size`, `speed` or `none`
+   | help: must be of the form: `#[optimize(size|speed|none)]`
 
 error: aborting due to 5 previous errors
 
-Some errors have detailed explanations: E0658, E0722.
-For more information about an error, try `rustc --explain E0658`.
+Some errors have detailed explanations: E0539, E0658.
+For more information about an error, try `rustc --explain E0539`.

From 5bd918fcd8be416600026a61df47a12c00788bfc Mon Sep 17 00:00:00 2001
From: Marijn Schouten <mhkbst@gmail.com>
Date: Wed, 18 Jun 2025 11:51:40 +0000
Subject: [PATCH 04/20] vec_deque tests: remove static mut

---
 .../src/collections/linked_list/tests.rs      | 18 +--------
 .../alloc/src/collections/vec_deque/tests.rs  | 38 ++++++-------------
 library/alloctests/testing/macros.rs          | 17 +++++++++
 library/alloctests/testing/mod.rs             |  1 +
 4 files changed, 31 insertions(+), 43 deletions(-)
 create mode 100644 library/alloctests/testing/macros.rs

diff --git a/library/alloc/src/collections/linked_list/tests.rs b/library/alloc/src/collections/linked_list/tests.rs
index 410e67d3fdb08..3d6c740e80b3b 100644
--- a/library/alloc/src/collections/linked_list/tests.rs
+++ b/library/alloc/src/collections/linked_list/tests.rs
@@ -1,4 +1,3 @@
-use std::cell::Cell;
 use std::panic::{AssertUnwindSafe, catch_unwind};
 use std::thread;
 
@@ -6,6 +5,7 @@ use rand::RngCore;
 
 use super::*;
 use crate::testing::crash_test::{CrashTestDummy, Panic};
+use crate::testing::macros::struct_with_counted_drop;
 use crate::vec::Vec;
 
 #[test]
@@ -1010,22 +1010,6 @@ fn extract_if_drop_panic_leak() {
     assert_eq!(d7.dropped(), 1);
 }
 
-macro_rules! struct_with_counted_drop {
-    ($struct_name:ident$(($elt_ty:ty))?, $drop_counter:ident $(=> $drop_stmt:expr)?) => {
-        thread_local! {static $drop_counter: Cell<u32> = Cell::new(0);}
-
-        struct $struct_name$(($elt_ty))?;
-
-        impl Drop for $struct_name {
-            fn drop(&mut self) {
-                $drop_counter.set($drop_counter.get() + 1);
-
-                $($drop_stmt(self))?
-            }
-        }
-    };
-}
-
 #[test]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn extract_if_pred_panic_leak() {
diff --git a/library/alloc/src/collections/vec_deque/tests.rs b/library/alloc/src/collections/vec_deque/tests.rs
index c90679f179775..ad76cb14deb86 100644
--- a/library/alloc/src/collections/vec_deque/tests.rs
+++ b/library/alloc/src/collections/vec_deque/tests.rs
@@ -1,9 +1,7 @@
-// FIXME(static_mut_refs): Do not allow `static_mut_refs` lint
-#![allow(static_mut_refs)]
-
 use core::iter::TrustedLen;
 
 use super::*;
+use crate::testing::macros::struct_with_counted_drop;
 
 #[bench]
 fn bench_push_back_100(b: &mut test::Bencher) {
@@ -1086,36 +1084,24 @@ fn test_clone_from() {
 
 #[test]
 fn test_vec_deque_truncate_drop() {
-    static mut DROPS: u32 = 0;
-    #[derive(Clone)]
-    struct Elem(#[allow(dead_code)] i32);
-    impl Drop for Elem {
-        fn drop(&mut self) {
-            unsafe {
-                DROPS += 1;
-            }
-        }
-    }
+    struct_with_counted_drop!(Elem, DROPS);
 
-    let v = vec![Elem(1), Elem(2), Elem(3), Elem(4), Elem(5)];
-    for push_front in 0..=v.len() {
-        let v = v.clone();
-        let mut tester = VecDeque::with_capacity(5);
-        for (index, elem) in v.into_iter().enumerate() {
+    const LEN: usize = 5;
+    for push_front in 0..=LEN {
+        let mut tester = VecDeque::with_capacity(LEN);
+        for index in 0..LEN {
             if index < push_front {
-                tester.push_front(elem);
+                tester.push_front(Elem);
             } else {
-                tester.push_back(elem);
+                tester.push_back(Elem);
             }
         }
-        assert_eq!(unsafe { DROPS }, 0);
+        assert_eq!(DROPS.get(), 0);
         tester.truncate(3);
-        assert_eq!(unsafe { DROPS }, 2);
+        assert_eq!(DROPS.get(), 2);
         tester.truncate(0);
-        assert_eq!(unsafe { DROPS }, 5);
-        unsafe {
-            DROPS = 0;
-        }
+        assert_eq!(DROPS.get(), 5);
+        DROPS.set(0);
     }
 }
 
diff --git a/library/alloctests/testing/macros.rs b/library/alloctests/testing/macros.rs
new file mode 100644
index 0000000000000..37cf59bc1693f
--- /dev/null
+++ b/library/alloctests/testing/macros.rs
@@ -0,0 +1,17 @@
+macro_rules! struct_with_counted_drop {
+    ($struct_name:ident$(($elt_ty:ty))?, $drop_counter:ident $(=> $drop_stmt:expr)?) => {
+        thread_local! {static $drop_counter: ::core::cell::Cell<u32> = ::core::cell::Cell::new(0);}
+
+        struct $struct_name$(($elt_ty))?;
+
+        impl ::std::ops::Drop for $struct_name {
+            fn drop(&mut self) {
+                $drop_counter.set($drop_counter.get() + 1);
+
+                $($drop_stmt(self))?
+            }
+        }
+    };
+}
+
+pub(crate) use struct_with_counted_drop;
diff --git a/library/alloctests/testing/mod.rs b/library/alloctests/testing/mod.rs
index c8457daf93e5a..66a4f6682b9f0 100644
--- a/library/alloctests/testing/mod.rs
+++ b/library/alloctests/testing/mod.rs
@@ -1,3 +1,4 @@
 pub(crate) mod crash_test;
+pub(crate) mod macros;
 pub(crate) mod ord_chaos;
 pub(crate) mod rng;

From 3c418ec505233927d562ff906d8eea309aee1905 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= <jana@donsz.nl>
Date: Wed, 18 Jun 2025 14:04:54 +0200
Subject: [PATCH 05/20] bump rustdoc json format number for pretty print change
 of attribute

---
 src/rustdoc-json-types/lib.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs
index 1f93895ae0762..d5de43feb588a 100644
--- a/src/rustdoc-json-types/lib.rs
+++ b/src/rustdoc-json-types/lib.rs
@@ -37,8 +37,8 @@ pub type FxHashMap<K, V> = HashMap<K, V>; // re-export for use in src/librustdoc
 // will instead cause conflicts. See #94591 for more. (This paragraph and the "Latest feature" line
 // are deliberately not in a doc comment, because they need not be in public docs.)
 //
-// Latest feature: Pretty printing of inline attributes changed
-pub const FORMAT_VERSION: u32 = 48;
+// Latest feature: Pretty printing of optimize attributes changed
+pub const FORMAT_VERSION: u32 = 49;
 
 /// The root of the emitted JSON blob.
 ///

From 021bcb9b4c5d3b1c6cfa27226c221611e86fce2a Mon Sep 17 00:00:00 2001
From: Marijn Schouten <mhkbst@gmail.com>
Date: Wed, 18 Jun 2025 12:29:22 +0000
Subject: [PATCH 06/20] fmt tests: remove static mut

---
 library/alloctests/tests/fmt.rs | 46 +++++++++++++++++++--------------
 1 file changed, 26 insertions(+), 20 deletions(-)

diff --git a/library/alloctests/tests/fmt.rs b/library/alloctests/tests/fmt.rs
index dbcf0c3a11410..0989a56b5543b 100644
--- a/library/alloctests/tests/fmt.rs
+++ b/library/alloctests/tests/fmt.rs
@@ -1,6 +1,4 @@
 #![deny(warnings)]
-// FIXME(static_mut_refs): Do not allow `static_mut_refs` lint
-#![allow(static_mut_refs)]
 #![allow(unnecessary_transmutes)]
 
 use std::cell::RefCell;
@@ -285,19 +283,32 @@ fn test_format_args() {
     t!(s, "args were: hello world");
 }
 
+macro_rules! counter_fn {
+    ($name:ident) => {
+        fn $name() -> u32 {
+            thread_local! {static COUNTER: ::core::cell::Cell<u32> = ::core::cell::Cell::new(0);}
+
+            COUNTER.set(COUNTER.get() + 1);
+            COUNTER.get()
+        }
+    };
+}
+
 #[test]
 fn test_order() {
-    // Make sure format!() arguments are always evaluated in a left-to-right
-    // ordering
-    fn foo() -> isize {
-        static mut FOO: isize = 0;
-        unsafe {
-            FOO += 1;
-            FOO
-        }
-    }
+    // Make sure format!() arguments are always evaluated in a left-to-right ordering
+    counter_fn!(count);
+
     assert_eq!(
-        format!("{} {} {a} {b} {} {c}", foo(), foo(), foo(), a = foo(), b = foo(), c = foo()),
+        format!(
+            "{} {} {a} {b} {} {c}",
+            count(),
+            count(),
+            count(),
+            a = count(),
+            b = count(),
+            c = count()
+        ),
         "1 2 4 5 3 6".to_string()
     );
 }
@@ -306,14 +317,9 @@ fn test_order() {
 fn test_once() {
     // Make sure each argument are evaluated only once even though it may be
     // formatted multiple times
-    fn foo() -> isize {
-        static mut FOO: isize = 0;
-        unsafe {
-            FOO += 1;
-            FOO
-        }
-    }
-    assert_eq!(format!("{0} {0} {0} {a} {a} {a}", foo(), a = foo()), "1 1 1 2 2 2".to_string());
+    counter_fn!(count);
+
+    assert_eq!(format!("{0} {0} {0} {a} {a} {a}", count(), a = count()), "1 1 1 2 2 2".to_string());
 }
 
 #[test]

From c6e77b3ba63cc9f824f99b7d356055e22bcf2ae2 Mon Sep 17 00:00:00 2001
From: Camille GILLOT <gillot.camille@gmail.com>
Date: Wed, 18 Jun 2025 15:14:48 +0000
Subject: [PATCH 07/20] Reduce uses of `hir_crate`.

---
 compiler/rustc_driver_impl/src/pretty.rs         |  6 +++++-
 compiler/rustc_interface/src/passes.rs           |  2 +-
 compiler/rustc_lint/src/expect.rs                |  3 +--
 compiler/rustc_middle/src/hir/map.rs             | 16 ++++++++--------
 compiler/rustc_middle/src/hir/mod.rs             | 10 +++++++---
 compiler/rustc_middle/src/ty/context.rs          |  2 +-
 src/librustdoc/core.rs                           |  2 --
 tests/ui/consts/const-unstable-intrinsic.stderr  | 10 ++--------
 ...stability-attribute-implies-no-feature.stderr |  5 +----
 .../const-traits/staged-api-user-crate.stderr    |  5 +----
 10 files changed, 27 insertions(+), 34 deletions(-)

diff --git a/compiler/rustc_driver_impl/src/pretty.rs b/compiler/rustc_driver_impl/src/pretty.rs
index ec77043cd1287..688307a941f30 100644
--- a/compiler/rustc_driver_impl/src/pretty.rs
+++ b/compiler/rustc_driver_impl/src/pretty.rs
@@ -292,7 +292,11 @@ pub fn print<'tcx>(sess: &Session, ppm: PpMode, ex: PrintExtra<'tcx>) {
         }
         HirTree => {
             debug!("pretty printing HIR tree");
-            format!("{:#?}", ex.tcx().hir_crate(()))
+            ex.tcx()
+                .hir_crate_items(())
+                .owners()
+                .map(|owner| format!("{:#?} => {:#?}\n", owner, ex.tcx().hir_owner_nodes(owner)))
+                .collect()
         }
         Mir => {
             let mut out = Vec::new();
diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs
index 0238d6a39475f..d29656fedd5b4 100644
--- a/compiler/rustc_interface/src/passes.rs
+++ b/compiler/rustc_interface/src/passes.rs
@@ -1011,7 +1011,7 @@ fn run_required_analyses(tcx: TyCtxt<'_>) {
     // Prefetch this to prevent multiple threads from blocking on it later.
     // This is needed since the `hir_id_validator::check_crate` call above is not guaranteed
     // to use `hir_crate`.
-    tcx.ensure_done().hir_crate(());
+    tcx.ensure_done().hir_crate_items(());
 
     let sess = tcx.sess;
     sess.time("misc_checking_1", || {
diff --git a/compiler/rustc_lint/src/expect.rs b/compiler/rustc_lint/src/expect.rs
index 4c2b82a9a23a6..481e116d06e01 100644
--- a/compiler/rustc_lint/src/expect.rs
+++ b/compiler/rustc_lint/src/expect.rs
@@ -1,5 +1,4 @@
 use rustc_data_structures::fx::FxHashSet;
-use rustc_hir::CRATE_OWNER_ID;
 use rustc_middle::lint::LintExpectation;
 use rustc_middle::query::Providers;
 use rustc_middle::ty::TyCtxt;
@@ -18,7 +17,7 @@ fn lint_expectations(tcx: TyCtxt<'_>, (): ()) -> Vec<(LintExpectationId, LintExp
 
     let mut expectations = Vec::new();
 
-    for owner in std::iter::once(CRATE_OWNER_ID).chain(krate.owners()) {
+    for owner in krate.owners() {
         let lints = tcx.shallow_lint_levels_on(owner);
         expectations.extend_from_slice(&lints.expectations);
     }
diff --git a/compiler/rustc_middle/src/hir/map.rs b/compiler/rustc_middle/src/hir/map.rs
index 3de97c8c0d99e..f75d75dd5b06e 100644
--- a/compiler/rustc_middle/src/hir/map.rs
+++ b/compiler/rustc_middle/src/hir/map.rs
@@ -328,8 +328,7 @@ impl<'tcx> TyCtxt<'tcx> {
     }
 
     /// Returns an iterator of the `DefId`s for all body-owners in this
-    /// crate. If you would prefer to iterate over the bodies
-    /// themselves, you can do `self.hir_crate(()).body_ids.iter()`.
+    /// crate.
     #[inline]
     pub fn hir_body_owners(self) -> impl Iterator<Item = LocalDefId> {
         self.hir_crate_items(()).body_owners.iter().copied()
@@ -396,12 +395,11 @@ impl<'tcx> TyCtxt<'tcx> {
     where
         V: Visitor<'tcx>,
     {
-        let krate = self.hir_crate(());
-        for info in krate.owners.iter() {
-            if let MaybeOwner::Owner(info) = info {
-                for attrs in info.attrs.map.values() {
-                    walk_list!(visitor, visit_attribute, *attrs);
-                }
+        let krate = self.hir_crate_items(());
+        for owner in krate.owners() {
+            let attrs = self.hir_attr_map(owner);
+            for attrs in attrs.map.values() {
+                walk_list!(visitor, visit_attribute, *attrs);
             }
         }
         V::Result::output()
@@ -1225,6 +1223,7 @@ pub(super) fn hir_module_items(tcx: TyCtxt<'_>, module_id: LocalModDefId) -> Mod
         ..
     } = collector;
     ModuleItems {
+        add_root: false,
         submodules: submodules.into_boxed_slice(),
         free_items: items.into_boxed_slice(),
         trait_items: trait_items.into_boxed_slice(),
@@ -1258,6 +1257,7 @@ pub(crate) fn hir_crate_items(tcx: TyCtxt<'_>, _: ()) -> ModuleItems {
     } = collector;
 
     ModuleItems {
+        add_root: true,
         submodules: submodules.into_boxed_slice(),
         free_items: items.into_boxed_slice(),
         trait_items: trait_items.into_boxed_slice(),
diff --git a/compiler/rustc_middle/src/hir/mod.rs b/compiler/rustc_middle/src/hir/mod.rs
index cb4760c18de5f..bb36b2c936477 100644
--- a/compiler/rustc_middle/src/hir/mod.rs
+++ b/compiler/rustc_middle/src/hir/mod.rs
@@ -24,6 +24,9 @@ use crate::ty::{EarlyBinder, ImplSubject, TyCtxt};
 /// bodies. The Ids are in visitor order. This is used to partition a pass between modules.
 #[derive(Debug, HashStable, Encodable, Decodable)]
 pub struct ModuleItems {
+    /// Whether this represents the whole crate, in which case we need to add `CRATE_OWNER_ID` to
+    /// the iterators if we want to account for the crate root.
+    add_root: bool,
     submodules: Box<[OwnerId]>,
     free_items: Box<[ItemId]>,
     trait_items: Box<[TraitItemId]>,
@@ -60,9 +63,10 @@ impl ModuleItems {
     }
 
     pub fn owners(&self) -> impl Iterator<Item = OwnerId> {
-        self.free_items
-            .iter()
-            .map(|id| id.owner_id)
+        self.add_root
+            .then_some(CRATE_OWNER_ID)
+            .into_iter()
+            .chain(self.free_items.iter().map(|id| id.owner_id))
             .chain(self.trait_items.iter().map(|id| id.owner_id))
             .chain(self.impl_items.iter().map(|id| id.owner_id))
             .chain(self.foreign_items.iter().map(|id| id.owner_id))
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index b4d8b2e717639..7b1403c5e7ef6 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -2118,7 +2118,7 @@ impl<'tcx> TyCtxt<'tcx> {
     ) -> &'tcx rustc_hir::def_path_hash_map::DefPathHashMap {
         // Create a dependency to the crate to be sure we re-execute this when the amount of
         // definitions change.
-        self.ensure_ok().hir_crate(());
+        self.ensure_ok().hir_crate_items(());
         // Freeze definitions once we start iterating on them, to prevent adding new ones
         // while iterating. If some query needs to add definitions, it should be `ensure`d above.
         self.untracked.definitions.freeze().def_path_hash_to_def_index_map()
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 204f8decffcc0..d73600a463631 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -387,8 +387,6 @@ pub(crate) fn run_global_ctxt(
         ctxt.external_traits.insert(sized_trait_did, sized_trait);
     }
 
-    debug!("crate: {:?}", tcx.hir_crate(()));
-
     let mut krate = tcx.sess.time("clean_crate", || clean::krate(&mut ctxt));
 
     if krate.module.doc_value().is_empty() {
diff --git a/tests/ui/consts/const-unstable-intrinsic.stderr b/tests/ui/consts/const-unstable-intrinsic.stderr
index 973c7bae58691..835113ccda51f 100644
--- a/tests/ui/consts/const-unstable-intrinsic.stderr
+++ b/tests/ui/consts/const-unstable-intrinsic.stderr
@@ -24,10 +24,7 @@ error: `size_of_val` is not yet stable as a const intrinsic
 LL |         unstable_intrinsic::size_of_val(&x);
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-help: add `#![feature(unstable)]` to the crate attributes to enable
-   |
-LL + #![feature(unstable)]
-   |
+   = help: add `#![feature(unstable)]` to the crate attributes to enable
 
 error: `align_of_val` is not yet stable as a const intrinsic
   --> $DIR/const-unstable-intrinsic.rs:20:9
@@ -35,10 +32,7 @@ error: `align_of_val` is not yet stable as a const intrinsic
 LL |         unstable_intrinsic::align_of_val(&x);
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-help: add `#![feature(unstable)]` to the crate attributes to enable
-   |
-LL + #![feature(unstable)]
-   |
+   = help: add `#![feature(unstable)]` to the crate attributes to enable
 
 error: const function that might be (indirectly) exposed to stable cannot use `#[feature(local)]`
   --> $DIR/const-unstable-intrinsic.rs:24:9
diff --git a/tests/ui/stability-attribute/const-stability-attribute-implies-no-feature.stderr b/tests/ui/stability-attribute/const-stability-attribute-implies-no-feature.stderr
index 0f2006e932b34..0a5f58288fa33 100644
--- a/tests/ui/stability-attribute/const-stability-attribute-implies-no-feature.stderr
+++ b/tests/ui/stability-attribute/const-stability-attribute-implies-no-feature.stderr
@@ -4,10 +4,7 @@ error: `foobar` is not yet stable as a const fn
 LL |     foobar();
    |     ^^^^^^^^
    |
-help: add `#![feature(const_foobar)]` to the crate attributes to enable
-   |
-LL + #![feature(const_foobar)]
-   |
+   = help: add `#![feature(const_foobar)]` to the crate attributes to enable
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/traits/const-traits/staged-api-user-crate.stderr b/tests/ui/traits/const-traits/staged-api-user-crate.stderr
index 8ac83770cf7a4..1365be7e3890b 100644
--- a/tests/ui/traits/const-traits/staged-api-user-crate.stderr
+++ b/tests/ui/traits/const-traits/staged-api-user-crate.stderr
@@ -15,10 +15,7 @@ error: `staged_api::MyTrait` is not yet stable as a const trait
 LL |     Unstable::func();
    |     ^^^^^^^^^^^^^^^^
    |
-help: add `#![feature(unstable)]` to the crate attributes to enable
-   |
-LL + #![feature(unstable)]
-   |
+   = help: add `#![feature(unstable)]` to the crate attributes to enable
 
 error: aborting due to 2 previous errors
 

From 7fa94af5565918146af0b86d3857321b097e956f Mon Sep 17 00:00:00 2001
From: Camille GILLOT <gillot.camille@gmail.com>
Date: Wed, 18 Jun 2025 16:52:38 +0000
Subject: [PATCH 08/20] Make feature suggestion more consistent.

---
 .../src/check_consts/check.rs                 |  8 -----
 .../rustc_const_eval/src/check_consts/ops.rs  | 22 ++-----------
 compiler/rustc_const_eval/src/errors.rs       |  4 +--
 compiler/rustc_hir_analysis/src/check/mod.rs  |  4 +--
 .../rustc_hir_analysis/src/check/wfcheck.rs   |  2 +-
 compiler/rustc_hir_typeck/src/errors.rs       |  6 ----
 compiler/rustc_hir_typeck/src/expr.rs         | 21 +++---------
 compiler/rustc_hir_typeck/src/method/probe.rs |  1 -
 compiler/rustc_middle/src/ty/context.rs       | 33 +++++++------------
 .../src/error_reporting/traits/suggestions.rs |  6 +---
 .../ui/consts/const-unstable-intrinsic.stderr | 10 ++++--
 ...bility-attribute-implies-no-feature.stderr |  5 ++-
 .../const-traits/staged-api-user-crate.stderr |  5 ++-
 13 files changed, 40 insertions(+), 87 deletions(-)

diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs
index 4f252f3ccd489..576b174369d46 100644
--- a/compiler/rustc_const_eval/src/check_consts/check.rs
+++ b/compiler/rustc_const_eval/src/check_consts/check.rs
@@ -463,12 +463,6 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
         );
     }
 
-    fn crate_inject_span(&self) -> Option<Span> {
-        self.tcx.hir_crate_items(()).definitions().next().and_then(|id| {
-            self.tcx.crate_level_attribute_injection_span(self.tcx.local_def_id_to_hir_id(id))
-        })
-    }
-
     /// Check the const stability of the given item (fn or trait).
     fn check_callee_stability(&mut self, def_id: DefId) {
         match self.tcx.lookup_const_stability(def_id) {
@@ -543,7 +537,6 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
                         feature,
                         feature_enabled,
                         safe_to_expose_on_stable: callee_safe_to_expose_on_stable,
-                        suggestion_span: self.crate_inject_span(),
                         is_function_call: self.tcx.def_kind(def_id) != DefKind::Trait,
                     });
                 }
@@ -919,7 +912,6 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                                 name: intrinsic.name,
                                 feature,
                                 const_stable_indirect: is_const_stable,
-                                suggestion: self.crate_inject_span(),
                             });
                         }
                         Some(attrs::ConstStability {
diff --git a/compiler/rustc_const_eval/src/check_consts/ops.rs b/compiler/rustc_const_eval/src/check_consts/ops.rs
index 9c30dbff99eb3..887275e7294d2 100644
--- a/compiler/rustc_const_eval/src/check_consts/ops.rs
+++ b/compiler/rustc_const_eval/src/check_consts/ops.rs
@@ -1,8 +1,8 @@
 //! Concrete error types for all operations which may be invalid in a certain const context.
 
 use hir::{ConstContext, LangItem};
+use rustc_errors::Diag;
 use rustc_errors::codes::*;
-use rustc_errors::{Applicability, Diag};
 use rustc_hir as hir;
 use rustc_hir::def_id::DefId;
 use rustc_infer::infer::TyCtxtInferExt;
@@ -384,7 +384,6 @@ pub(crate) struct CallUnstable {
     /// expose on stable.
     pub feature_enabled: bool,
     pub safe_to_expose_on_stable: bool,
-    pub suggestion_span: Option<Span>,
     /// true if `def_id` is the function we are calling, false if `def_id` is an unstable trait.
     pub is_function_call: bool,
 }
@@ -412,20 +411,7 @@ impl<'tcx> NonConstOp<'tcx> for CallUnstable {
                 def_path: ccx.tcx.def_path_str(self.def_id),
             })
         };
-        // FIXME: make this translatable
-        let msg = format!("add `#![feature({})]` to the crate attributes to enable", self.feature);
-        #[allow(rustc::untranslatable_diagnostic)]
-        if let Some(span) = self.suggestion_span {
-            err.span_suggestion_verbose(
-                span,
-                msg,
-                format!("#![feature({})]\n", self.feature),
-                Applicability::MachineApplicable,
-            );
-        } else {
-            err.help(msg);
-        }
-
+        ccx.tcx.disabled_nightly_features(&mut err, [(String::new(), self.feature)]);
         err
     }
 }
@@ -452,7 +438,6 @@ pub(crate) struct IntrinsicUnstable {
     pub name: Symbol,
     pub feature: Symbol,
     pub const_stable_indirect: bool,
-    pub suggestion: Option<Span>,
 }
 
 impl<'tcx> NonConstOp<'tcx> for IntrinsicUnstable {
@@ -472,8 +457,7 @@ impl<'tcx> NonConstOp<'tcx> for IntrinsicUnstable {
             span,
             name: self.name,
             feature: self.feature,
-            suggestion: self.suggestion,
-            help: self.suggestion.is_none(),
+            suggestion: ccx.tcx.crate_level_attribute_injection_span(),
         })
     }
 }
diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs
index 037cbf777e70b..69c71aef9f337 100644
--- a/compiler/rustc_const_eval/src/errors.rs
+++ b/compiler/rustc_const_eval/src/errors.rs
@@ -136,9 +136,7 @@ pub(crate) struct UnstableIntrinsic {
         code = "#![feature({feature})]\n",
         applicability = "machine-applicable"
     )]
-    pub suggestion: Option<Span>,
-    #[help(const_eval_unstable_intrinsic_suggestion)]
-    pub help: bool,
+    pub suggestion: Span,
 }
 
 #[derive(Diagnostic)]
diff --git a/compiler/rustc_hir_analysis/src/check/mod.rs b/compiler/rustc_hir_analysis/src/check/mod.rs
index c5c7e6b2aa722..ed43a0b21e406 100644
--- a/compiler/rustc_hir_analysis/src/check/mod.rs
+++ b/compiler/rustc_hir_analysis/src/check/mod.rs
@@ -302,9 +302,7 @@ fn default_body_is_unstable(
         reason: reason_str,
     });
 
-    let inject_span = item_did
-        .as_local()
-        .and_then(|id| tcx.crate_level_attribute_injection_span(tcx.local_def_id_to_hir_id(id)));
+    let inject_span = item_did.is_local().then(|| tcx.crate_level_attribute_injection_span());
     rustc_session::parse::add_feature_diagnostics_for_issue(
         &mut err,
         &tcx.sess,
diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs
index b8dc01cbc03cf..8198c24a7f003 100644
--- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs
+++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs
@@ -1064,7 +1064,7 @@ fn check_param_wf(tcx: TyCtxt<'_>, param: &hir::GenericParam<'_>) -> Result<(),
                     Ok(..) => Some(vec![(adt_const_params_feature_string, sym::adt_const_params)]),
                 };
                 if let Some(features) = may_suggest_feature {
-                    tcx.disabled_nightly_features(&mut diag, Some(param.hir_id), features);
+                    tcx.disabled_nightly_features(&mut diag, features);
                 }
 
                 Err(diag.emit())
diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs
index abb8cdc1cdf3c..5f59b3ad96e04 100644
--- a/compiler/rustc_hir_typeck/src/errors.rs
+++ b/compiler/rustc_hir_typeck/src/errors.rs
@@ -30,8 +30,6 @@ pub(crate) struct BaseExpressionDoubleDot {
     )]
     pub default_field_values_suggestion: Option<Span>,
     #[subdiagnostic]
-    pub default_field_values_help: Option<BaseExpressionDoubleDotEnableDefaultFieldValues>,
-    #[subdiagnostic]
     pub add_expr: Option<BaseExpressionDoubleDotAddExpr>,
     #[subdiagnostic]
     pub remove_dots: Option<BaseExpressionDoubleDotRemove>,
@@ -61,10 +59,6 @@ pub(crate) struct BaseExpressionDoubleDotAddExpr {
     pub span: Span,
 }
 
-#[derive(Subdiagnostic)]
-#[help(hir_typeck_base_expression_double_dot_enable_default_field_values)]
-pub(crate) struct BaseExpressionDoubleDotEnableDefaultFieldValues;
-
 #[derive(Diagnostic)]
 #[diag(hir_typeck_field_multiply_specified_in_initializer, code = E0062)]
 pub(crate) struct FieldMultiplySpecifiedInInitializer {
diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs
index 30bf557dc93a8..26370784f56ce 100644
--- a/compiler/rustc_hir_typeck/src/expr.rs
+++ b/compiler/rustc_hir_typeck/src/expr.rs
@@ -42,10 +42,9 @@ use crate::Expectation::{self, ExpectCastableToType, ExpectHasType, NoExpectatio
 use crate::coercion::{CoerceMany, DynamicCoerceMany};
 use crate::errors::{
     AddressOfTemporaryTaken, BaseExpressionDoubleDot, BaseExpressionDoubleDotAddExpr,
-    BaseExpressionDoubleDotEnableDefaultFieldValues, BaseExpressionDoubleDotRemove,
-    CantDereference, FieldMultiplySpecifiedInInitializer, FunctionalRecordUpdateOnNonStruct,
-    HelpUseLatestEdition, NakedAsmOutsideNakedFn, NoFieldOnType, NoFieldOnVariant,
-    ReturnLikeStatementKind, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive,
+    BaseExpressionDoubleDotRemove, CantDereference, FieldMultiplySpecifiedInInitializer,
+    FunctionalRecordUpdateOnNonStruct, HelpUseLatestEdition, NakedAsmOutsideNakedFn, NoFieldOnType,
+    NoFieldOnVariant, ReturnLikeStatementKind, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive,
     TypeMismatchFruTypo, YieldExprOutsideOfCoroutine,
 };
 use crate::{
@@ -2133,7 +2132,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 }
             }
             if !self.tcx.features().default_field_values() {
-                let sugg = self.tcx.crate_level_attribute_injection_span(expr.hir_id);
+                let sugg = self.tcx.crate_level_attribute_injection_span();
                 self.dcx().emit_err(BaseExpressionDoubleDot {
                     span: span.shrink_to_hi(),
                     // We only mention enabling the feature if this is a nightly rustc *and* the
@@ -2141,18 +2140,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     default_field_values_suggestion: if self.tcx.sess.is_nightly_build()
                         && missing_mandatory_fields.is_empty()
                         && !missing_optional_fields.is_empty()
-                        && sugg.is_some()
                     {
-                        sugg
-                    } else {
-                        None
-                    },
-                    default_field_values_help: if self.tcx.sess.is_nightly_build()
-                        && missing_mandatory_fields.is_empty()
-                        && !missing_optional_fields.is_empty()
-                        && sugg.is_none()
-                    {
-                        Some(BaseExpressionDoubleDotEnableDefaultFieldValues)
+                        Some(sugg)
                     } else {
                         None
                     },
diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs
index a3fdf200c8ea0..589dbb531168b 100644
--- a/compiler/rustc_hir_typeck/src/method/probe.rs
+++ b/compiler/rustc_hir_typeck/src/method/probe.rs
@@ -1727,7 +1727,6 @@ impl<'tcx> Pick<'tcx> {
             }
             tcx.disabled_nightly_features(
                 lint,
-                Some(scope_expr_id),
                 self.unstable_candidates.iter().map(|(candidate, feature)| {
                     (format!(" `{}`", tcx.def_path_str(candidate.item.def_id)), *feature)
                 }),
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index 7b1403c5e7ef6..23056820bc67e 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -3165,42 +3165,33 @@ impl<'tcx> TyCtxt<'tcx> {
         lint_level(self.sess, lint, level, Some(span.into()), decorate);
     }
 
-    /// Find the crate root and the appropriate span where `use` and outer attributes can be
-    /// inserted at.
-    pub fn crate_level_attribute_injection_span(self, hir_id: HirId) -> Option<Span> {
-        for (_hir_id, node) in self.hir_parent_iter(hir_id) {
-            if let hir::Node::Crate(m) = node {
-                return Some(m.spans.inject_use_span.shrink_to_lo());
-            }
-        }
-        None
+    /// Find the appropriate span where `use` and outer attributes can be inserted at.
+    pub fn crate_level_attribute_injection_span(self) -> Span {
+        let node = self.hir_node(hir::CRATE_HIR_ID);
+        let hir::Node::Crate(m) = node else { bug!() };
+        m.spans.inject_use_span.shrink_to_lo()
     }
 
     pub fn disabled_nightly_features<E: rustc_errors::EmissionGuarantee>(
         self,
         diag: &mut Diag<'_, E>,
-        hir_id: Option<HirId>,
         features: impl IntoIterator<Item = (String, Symbol)>,
     ) {
         if !self.sess.is_nightly_build() {
             return;
         }
 
-        let span = hir_id.and_then(|id| self.crate_level_attribute_injection_span(id));
+        let span = self.crate_level_attribute_injection_span();
         for (desc, feature) in features {
             // FIXME: make this string translatable
             let msg =
                 format!("add `#![feature({feature})]` to the crate attributes to enable{desc}");
-            if let Some(span) = span {
-                diag.span_suggestion_verbose(
-                    span,
-                    msg,
-                    format!("#![feature({feature})]\n"),
-                    Applicability::MaybeIncorrect,
-                );
-            } else {
-                diag.help(msg);
-            }
+            diag.span_suggestion_verbose(
+                span,
+                msg,
+                format!("#![feature({feature})]\n"),
+                Applicability::MaybeIncorrect,
+            );
         }
     }
 
diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs
index ee5a5b247ce8c..6d07ae021ae9d 100644
--- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs
+++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs
@@ -3575,11 +3575,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
             }
             ObligationCauseCode::TrivialBound => {
                 err.help("see issue #48214");
-                tcx.disabled_nightly_features(
-                    err,
-                    Some(tcx.local_def_id_to_hir_id(body_id)),
-                    [(String::new(), sym::trivial_bounds)],
-                );
+                tcx.disabled_nightly_features(err, [(String::new(), sym::trivial_bounds)]);
             }
             ObligationCauseCode::OpaqueReturnType(expr_info) => {
                 let (expr_ty, expr) = if let Some((expr_ty, hir_id)) = expr_info {
diff --git a/tests/ui/consts/const-unstable-intrinsic.stderr b/tests/ui/consts/const-unstable-intrinsic.stderr
index 835113ccda51f..973c7bae58691 100644
--- a/tests/ui/consts/const-unstable-intrinsic.stderr
+++ b/tests/ui/consts/const-unstable-intrinsic.stderr
@@ -24,7 +24,10 @@ error: `size_of_val` is not yet stable as a const intrinsic
 LL |         unstable_intrinsic::size_of_val(&x);
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: add `#![feature(unstable)]` to the crate attributes to enable
+help: add `#![feature(unstable)]` to the crate attributes to enable
+   |
+LL + #![feature(unstable)]
+   |
 
 error: `align_of_val` is not yet stable as a const intrinsic
   --> $DIR/const-unstable-intrinsic.rs:20:9
@@ -32,7 +35,10 @@ error: `align_of_val` is not yet stable as a const intrinsic
 LL |         unstable_intrinsic::align_of_val(&x);
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: add `#![feature(unstable)]` to the crate attributes to enable
+help: add `#![feature(unstable)]` to the crate attributes to enable
+   |
+LL + #![feature(unstable)]
+   |
 
 error: const function that might be (indirectly) exposed to stable cannot use `#[feature(local)]`
   --> $DIR/const-unstable-intrinsic.rs:24:9
diff --git a/tests/ui/stability-attribute/const-stability-attribute-implies-no-feature.stderr b/tests/ui/stability-attribute/const-stability-attribute-implies-no-feature.stderr
index 0a5f58288fa33..0f2006e932b34 100644
--- a/tests/ui/stability-attribute/const-stability-attribute-implies-no-feature.stderr
+++ b/tests/ui/stability-attribute/const-stability-attribute-implies-no-feature.stderr
@@ -4,7 +4,10 @@ error: `foobar` is not yet stable as a const fn
 LL |     foobar();
    |     ^^^^^^^^
    |
-   = help: add `#![feature(const_foobar)]` to the crate attributes to enable
+help: add `#![feature(const_foobar)]` to the crate attributes to enable
+   |
+LL + #![feature(const_foobar)]
+   |
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/traits/const-traits/staged-api-user-crate.stderr b/tests/ui/traits/const-traits/staged-api-user-crate.stderr
index 1365be7e3890b..8ac83770cf7a4 100644
--- a/tests/ui/traits/const-traits/staged-api-user-crate.stderr
+++ b/tests/ui/traits/const-traits/staged-api-user-crate.stderr
@@ -15,7 +15,10 @@ error: `staged_api::MyTrait` is not yet stable as a const trait
 LL |     Unstable::func();
    |     ^^^^^^^^^^^^^^^^
    |
-   = help: add `#![feature(unstable)]` to the crate attributes to enable
+help: add `#![feature(unstable)]` to the crate attributes to enable
+   |
+LL + #![feature(unstable)]
+   |
 
 error: aborting due to 2 previous errors
 

From d70ec32ea7e03e3a082a45eeba6c8aa4c653efb4 Mon Sep 17 00:00:00 2001
From: Ralf Jung <post@ralfj.de>
Date: Sat, 10 May 2025 22:53:38 +0200
Subject: [PATCH 09/20] move cfg(target_feature) computation into shared place

---
 compiler/rustc_codegen_gcc/src/lib.rs         | 59 +++++------
 compiler/rustc_codegen_llvm/src/llvm_util.rs  | 87 ++--------------
 .../rustc_codegen_ssa/src/target_features.rs  | 99 ++++++++++++++++++-
 3 files changed, 126 insertions(+), 119 deletions(-)

diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs
index aa57655921d44..f5ad28681c790 100644
--- a/compiler/rustc_codegen_gcc/src/lib.rs
+++ b/compiler/rustc_codegen_gcc/src/lib.rs
@@ -103,7 +103,9 @@ use rustc_codegen_ssa::back::write::{
 };
 use rustc_codegen_ssa::base::codegen_crate;
 use rustc_codegen_ssa::traits::{CodegenBackend, ExtraBackendMethods, WriteBackendMethods};
-use rustc_codegen_ssa::{CodegenResults, CompiledModule, ModuleCodegen, TargetConfig};
+use rustc_codegen_ssa::{
+    CodegenResults, CompiledModule, ModuleCodegen, TargetConfig, target_features,
+};
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_data_structures::sync::IntoDynSyncSend;
 use rustc_errors::DiagCtxtHandle;
@@ -476,42 +478,25 @@ fn to_gcc_opt_level(optlevel: Option<OptLevel>) -> OptimizationLevel {
 
 /// Returns the features that should be set in `cfg(target_feature)`.
 fn target_config(sess: &Session, target_info: &LockedTargetInfo) -> TargetConfig {
-    // TODO(antoyo): use global_gcc_features.
-    let f = |allow_unstable| {
-        sess.target
-            .rust_target_features()
-            .iter()
-            .filter_map(|&(feature, gate, _)| {
-                if allow_unstable
-                    || (gate.in_cfg()
-                        && (sess.is_nightly_build() || gate.requires_nightly().is_none()))
-                {
-                    Some(feature)
-                } else {
-                    None
-                }
-            })
-            .filter(|feature| {
-                // TODO: we disable Neon for now since we don't support the LLVM intrinsics for it.
-                if *feature == "neon" {
-                    return false;
-                }
-                target_info.cpu_supports(feature)
-                // cSpell:disable
-                /*
-                  adx, aes, avx, avx2, avx512bf16, avx512bitalg, avx512bw, avx512cd, avx512dq, avx512er, avx512f, avx512fp16, avx512ifma,
-                  avx512pf, avx512vbmi, avx512vbmi2, avx512vl, avx512vnni, avx512vp2intersect, avx512vpopcntdq,
-                  bmi1, bmi2, cmpxchg16b, ermsb, f16c, fma, fxsr, gfni, lzcnt, movbe, pclmulqdq, popcnt, rdrand, rdseed, rtm,
-                  sha, sse, sse2, sse3, sse4.1, sse4.2, sse4a, ssse3, tbm, vaes, vpclmulqdq, xsave, xsavec, xsaveopt, xsaves
-                */
-                // cSpell:enable
-            })
-            .map(Symbol::intern)
-            .collect()
-    };
-
-    let target_features = f(false);
-    let unstable_target_features = f(true);
+    let (unstable_target_features, target_features) = target_features::cfg_target_feature(
+        sess,
+        /* FIXME: we ignore `-Ctarget-feature` */ "",
+        |feature| {
+            // TODO: we disable Neon for now since we don't support the LLVM intrinsics for it.
+            if feature == "neon" {
+                return false;
+            }
+            target_info.cpu_supports(feature)
+            // cSpell:disable
+            /*
+              adx, aes, avx, avx2, avx512bf16, avx512bitalg, avx512bw, avx512cd, avx512dq, avx512er, avx512f, avx512fp16, avx512ifma,
+              avx512pf, avx512vbmi, avx512vbmi2, avx512vl, avx512vnni, avx512vp2intersect, avx512vpopcntdq,
+              bmi1, bmi2, cmpxchg16b, ermsb, f16c, fma, fxsr, gfni, lzcnt, movbe, pclmulqdq, popcnt, rdrand, rdseed, rtm,
+              sha, sse, sse2, sse3, sse4.1, sse4.2, sse4a, ssse3, tbm, vaes, vpclmulqdq, xsave, xsavec, xsaveopt, xsaves
+            */
+            // cSpell:enable
+        },
+    );
 
     let has_reliable_f16 = target_info.supports_target_dependent_type(CType::Float16);
     let has_reliable_f128 = target_info.supports_target_dependent_type(CType::Float128);
diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs
index 0e77bc43df805..f9ebf8616344e 100644
--- a/compiler/rustc_codegen_llvm/src/llvm_util.rs
+++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs
@@ -6,9 +6,9 @@ use std::sync::Once;
 use std::{ptr, slice, str};
 
 use libc::c_int;
-use rustc_codegen_ssa::TargetConfig;
 use rustc_codegen_ssa::base::wants_wasm_eh;
 use rustc_codegen_ssa::codegen_attrs::check_tied_features;
+use rustc_codegen_ssa::{TargetConfig, target_features};
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::small_c_str::SmallCStr;
 use rustc_data_structures::unord::UnordSet;
@@ -17,9 +17,8 @@ use rustc_middle::bug;
 use rustc_session::Session;
 use rustc_session::config::{PrintKind, PrintRequest};
 use rustc_session::features::{StabilityExt, retpoline_features_by_flags};
-use rustc_span::Symbol;
 use rustc_target::spec::{MergeFunctions, PanicStrategy, SmallDataThresholdSupport};
-use rustc_target::target_features::{RUSTC_SPECIAL_FEATURES, RUSTC_SPECIFIC_FEATURES};
+use rustc_target::target_features::RUSTC_SPECIFIC_FEATURES;
 use smallvec::{SmallVec, smallvec};
 
 use crate::back::write::create_informational_target_machine;
@@ -343,18 +342,11 @@ pub(crate) fn target_config(sess: &Session) -> TargetConfig {
     // the target CPU, that is still expanded to target features (with all their implied features)
     // by LLVM.
     let target_machine = create_informational_target_machine(sess, true);
-    // Compute which of the known target features are enabled in the 'base' target machine. We only
-    // consider "supported" features; "forbidden" features are not reflected in `cfg` as of now.
-    let mut features: FxHashSet<Symbol> = sess
-        .target
-        .rust_target_features()
-        .iter()
-        .filter(|(feature, _, _)| {
-            // skip checking special features, as LLVM may not understand them
-            if RUSTC_SPECIAL_FEATURES.contains(feature) {
-                return true;
-            }
+
+    let (unstable_target_features, target_features) =
+        target_features::cfg_target_feature(sess, &sess.opts.cg.target_feature, |feature| {
             if let Some(feat) = to_llvm_features(sess, feature) {
+                // All the LLVM features this expands to must be enabled.
                 for llvm_feature in feat {
                     let cstr = SmallCStr::new(llvm_feature);
                     // `LLVMRustHasFeature` is moderately expensive. On targets with many
@@ -368,73 +360,8 @@ pub(crate) fn target_config(sess: &Session) -> TargetConfig {
             } else {
                 false
             }
-        })
-        .map(|(feature, _, _)| Symbol::intern(feature))
-        .collect();
-
-    // Add enabled and remove disabled features.
-    for (enabled, feature) in
-        sess.opts.cg.target_feature.split(',').filter_map(|s| match s.chars().next() {
-            Some('+') => Some((true, Symbol::intern(&s[1..]))),
-            Some('-') => Some((false, Symbol::intern(&s[1..]))),
-            _ => None,
-        })
-    {
-        if enabled {
-            // Also add all transitively implied features.
-
-            // 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(feature.as_str())
-                    .iter()
-                    .map(|s| Symbol::intern(s)),
-            );
-        } else {
-            // Remove transitively reverse-implied features.
-
-            // 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.retain(|f| {
-                if sess.target.implied_target_features(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
-                } else {
-                    // We can keep `f`.
-                    true
-                }
-            });
-        }
-    }
-
-    // Filter enabled features based on feature gates.
-    let f = |allow_unstable| {
-        sess.target
-            .rust_target_features()
-            .iter()
-            .filter_map(|(feature, gate, _)| {
-                // The `allow_unstable` set is used by rustc internally to determined which target
-                // features are truly available, so we want to return even perma-unstable
-                // "forbidden" features.
-                if allow_unstable
-                    || (gate.in_cfg()
-                        && (sess.is_nightly_build() || gate.requires_nightly().is_none()))
-                {
-                    Some(Symbol::intern(feature))
-                } else {
-                    None
-                }
-            })
-            .filter(|feature| features.contains(&feature))
-            .collect()
-    };
+        });
 
-    let target_features = f(false);
-    let unstable_target_features = f(true);
     let mut cfg = TargetConfig {
         target_features,
         unstable_target_features,
diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs
index 640d197c219a8..476671d68550a 100644
--- a/compiler/rustc_codegen_ssa/src/target_features.rs
+++ b/compiler/rustc_codegen_ssa/src/target_features.rs
@@ -1,5 +1,5 @@
 use rustc_attr_data_structures::InstructionSetAttr;
-use rustc_data_structures::fx::FxIndexSet;
+use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
 use rustc_data_structures::unord::{UnordMap, UnordSet};
 use rustc_errors::Applicability;
 use rustc_hir as hir;
@@ -8,11 +8,12 @@ use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
 use rustc_middle::middle::codegen_fn_attrs::TargetFeature;
 use rustc_middle::query::Providers;
 use rustc_middle::ty::TyCtxt;
+use rustc_session::Session;
 use rustc_session::features::StabilityExt;
 use rustc_session::lint::builtin::AARCH64_SOFTFLOAT_NEON;
 use rustc_session::parse::feature_err;
 use rustc_span::{Span, Symbol, sym};
-use rustc_target::target_features::{self, Stability};
+use rustc_target::target_features::{self, RUSTC_SPECIAL_FEATURES, Stability};
 
 use crate::errors;
 
@@ -156,6 +157,100 @@ pub(crate) fn check_target_feature_trait_unsafe(tcx: TyCtxt<'_>, id: LocalDefId,
     }
 }
 
+/// Utility function for a codegen backend to compute `cfg(target_feature)`, or more specifically,
+/// to populate `sess.unstable_target_features` and `sess.target_features` (these are the first and
+/// 2nd component of the return value, respectively).
+///
+/// `target_feature_flag` is the value of `-Ctarget-feature` (giving the caller a chance to override it).
+/// `target_base_has_feature` should check whether the given feature (a Rust feature name!) is enabled
+/// in the "base" target machine, i.e., without applying `-Ctarget-feature`.
+///
+/// We do not have to worry about RUSTC_SPECIFIC_FEATURES here, those are handled elsewhere.
+pub fn cfg_target_feature(
+    sess: &Session,
+    target_feature_flag: &str,
+    mut is_feature_enabled: impl FnMut(&str) -> bool,
+) -> (Vec<Symbol>, Vec<Symbol>) {
+    // Compute which of the known target features are enabled in the 'base' target machine. We only
+    // consider "supported" features; "forbidden" features are not reflected in `cfg` as of now.
+    let mut features: FxHashSet<Symbol> = sess
+        .target
+        .rust_target_features()
+        .iter()
+        .filter(|(feature, _, _)| {
+            // Skip checking special features, those are not known to the backend.
+            if RUSTC_SPECIAL_FEATURES.contains(feature) {
+                return true;
+            }
+            is_feature_enabled(feature)
+        })
+        .map(|(feature, _, _)| Symbol::intern(feature))
+        .collect();
+
+    // Add enabled and remove disabled features.
+    for (enabled, feature) in
+        target_feature_flag.split(',').filter_map(|s| match s.chars().next() {
+            Some('+') => Some((true, Symbol::intern(&s[1..]))),
+            Some('-') => Some((false, Symbol::intern(&s[1..]))),
+            _ => None,
+        })
+    {
+        if enabled {
+            // Also add all transitively implied features.
+
+            // 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(feature.as_str())
+                    .iter()
+                    .map(|s| Symbol::intern(s)),
+            );
+        } else {
+            // Remove transitively reverse-implied features.
+
+            // 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.retain(|f| {
+                if sess.target.implied_target_features(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
+                } else {
+                    // We can keep `f`.
+                    true
+                }
+            });
+        }
+    }
+
+    // Filter enabled features based on feature gates.
+    let f = |allow_unstable| {
+        sess.target
+            .rust_target_features()
+            .iter()
+            .filter_map(|(feature, gate, _)| {
+                // The `allow_unstable` set is used by rustc internally to determine which target
+                // features are truly available, so we want to return even perma-unstable
+                // "forbidden" features.
+                if allow_unstable
+                    || (gate.in_cfg()
+                        && (sess.is_nightly_build() || gate.requires_nightly().is_none()))
+                {
+                    Some(Symbol::intern(feature))
+                } else {
+                    None
+                }
+            })
+            .filter(|feature| features.contains(&feature))
+            .collect()
+    };
+
+    (f(true), f(false))
+}
+
 pub(crate) fn provide(providers: &mut Providers) {
     *providers = Providers {
         rust_target_features: |tcx, cnum| {

From cd08652faa37a9119c2cf535b927129b1c4438b7 Mon Sep 17 00:00:00 2001
From: Ralf Jung <post@ralfj.de>
Date: Fri, 23 May 2025 08:07:42 +0200
Subject: [PATCH 10/20] move -Ctarget-feature handling into shared code

---
 compiler/rustc_codegen_gcc/messages.ftl       |  16 --
 compiler/rustc_codegen_gcc/src/errors.rs      |  26 +--
 compiler/rustc_codegen_gcc/src/gcc_util.rs    | 124 +++-----------
 compiler/rustc_codegen_llvm/messages.ftl      |  10 --
 compiler/rustc_codegen_llvm/src/errors.rs     |  26 +--
 compiler/rustc_codegen_llvm/src/llvm_util.rs  | 160 +++++------------
 compiler/rustc_codegen_ssa/messages.ftl       |  19 +++
 .../rustc_codegen_ssa/src/codegen_attrs.rs    |  26 +--
 compiler/rustc_codegen_ssa/src/errors.rs      |  89 +++++++---
 .../rustc_codegen_ssa/src/target_features.rs  | 161 +++++++++++++++++-
 compiler/rustc_session/messages.ftl           |   8 -
 compiler/rustc_session/src/errors.rs          |  17 --
 compiler/rustc_session/src/features.rs        |  59 -------
 compiler/rustc_session/src/lib.rs             |   1 -
 compiler/rustc_session/src/options.rs         |   8 -
 compiler/rustc_target/src/target_features.rs  |  30 ++--
 src/librustdoc/json/mod.rs                    |   5 +-
 tests/ui/check-cfg/target_feature.stderr      |   3 -
 18 files changed, 326 insertions(+), 462 deletions(-)
 delete mode 100644 compiler/rustc_session/src/features.rs

diff --git a/compiler/rustc_codegen_gcc/messages.ftl b/compiler/rustc_codegen_gcc/messages.ftl
index 18a8a5a1e048a..55a28bc9493e7 100644
--- a/compiler/rustc_codegen_gcc/messages.ftl
+++ b/compiler/rustc_codegen_gcc/messages.ftl
@@ -1,7 +1,3 @@
-codegen_gcc_unknown_ctarget_feature_prefix =
-    unknown feature specified for `-Ctarget-feature`: `{$feature}`
-    .note = features must begin with a `+` to enable or `-` to disable it
-
 codegen_gcc_unwinding_inline_asm =
     GCC backend does not support unwinding from inline asm
 
@@ -16,15 +12,3 @@ codegen_gcc_lto_disallowed = lto can only be run for executables, cdylibs and st
 codegen_gcc_lto_dylib = lto cannot be used for `dylib` crate type without `-Zdylib-lto`
 
 codegen_gcc_lto_bitcode_from_rlib = failed to get bitcode from object file for LTO ({$gcc_err})
-
-codegen_gcc_unknown_ctarget_feature =
-    unknown and unstable feature specified for `-Ctarget-feature`: `{$feature}`
-    .note = it is still passed through to the codegen backend, but use of this feature might be unsound and the behavior of this feature can change in the future
-    .possible_feature = you might have meant: `{$rust_feature}`
-    .consider_filing_feature_request = consider filing a feature request
-
-codegen_gcc_missing_features =
-    add the missing features in a `target_feature` attribute
-
-codegen_gcc_target_feature_disable_or_enable =
-    the target features {$features} must all be either enabled or disabled together
diff --git a/compiler/rustc_codegen_gcc/src/errors.rs b/compiler/rustc_codegen_gcc/src/errors.rs
index 7786be9ae5de9..b7e7343460fbf 100644
--- a/compiler/rustc_codegen_gcc/src/errors.rs
+++ b/compiler/rustc_codegen_gcc/src/errors.rs
@@ -1,30 +1,6 @@
-use rustc_macros::{Diagnostic, Subdiagnostic};
+use rustc_macros::Diagnostic;
 use rustc_span::Span;
 
-#[derive(Diagnostic)]
-#[diag(codegen_gcc_unknown_ctarget_feature_prefix)]
-#[note]
-pub(crate) struct UnknownCTargetFeaturePrefix<'a> {
-    pub feature: &'a str,
-}
-
-#[derive(Diagnostic)]
-#[diag(codegen_gcc_unknown_ctarget_feature)]
-#[note]
-pub(crate) struct UnknownCTargetFeature<'a> {
-    pub feature: &'a str,
-    #[subdiagnostic]
-    pub rust_feature: PossibleFeature<'a>,
-}
-
-#[derive(Subdiagnostic)]
-pub(crate) enum PossibleFeature<'a> {
-    #[help(codegen_gcc_possible_feature)]
-    Some { rust_feature: &'a str },
-    #[help(codegen_gcc_consider_filing_feature_request)]
-    None,
-}
-
 #[derive(Diagnostic)]
 #[diag(codegen_gcc_unwinding_inline_asm)]
 pub(crate) struct UnwindingInlineAsm {
diff --git a/compiler/rustc_codegen_gcc/src/gcc_util.rs b/compiler/rustc_codegen_gcc/src/gcc_util.rs
index 2e00d5fcb612f..562f412488296 100644
--- a/compiler/rustc_codegen_gcc/src/gcc_util.rs
+++ b/compiler/rustc_codegen_gcc/src/gcc_util.rs
@@ -1,20 +1,11 @@
 #[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_data_structures::unord::UnordSet;
+use rustc_codegen_ssa::target_features;
 use rustc_session::Session;
-use rustc_session::features::{StabilityExt, retpoline_features_by_flags};
-use rustc_target::target_features::RUSTC_SPECIFIC_FEATURES;
 use smallvec::{SmallVec, smallvec};
 
-use crate::errors::{PossibleFeature, UnknownCTargetFeature, UnknownCTargetFeaturePrefix};
-
-fn gcc_features_by_flags(sess: &Session) -> Vec<&str> {
-    let mut features: Vec<&str> = Vec::new();
-    retpoline_features_by_flags(sess, &mut features);
-    features
+fn gcc_features_by_flags(sess: &Session, features: &mut Vec<String>) {
+    target_features::retpoline_features_by_flags(sess, features);
 }
 
 /// The list of GCC features computed from CLI flags (`-Ctarget-cpu`, `-Ctarget-feature`,
@@ -44,98 +35,31 @@ pub(crate) fn global_gcc_features(sess: &Session, diagnostics: bool) -> Vec<Stri
     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();
-
-    // Compute implied features
-    let mut all_rust_features = vec![];
-    for feature in sess.opts.cg.target_feature.split(',').chain(gcc_features_by_flags(sess)) {
-        if let Some(feature) = feature.strip_prefix('+') {
-            all_rust_features.extend(
-                UnordSet::from(sess.target.implied_target_features(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_config`.
-            // See <https://github.com/rust-lang/rust/issues/134792>.
-            all_rust_features.push((false, feature));
-        } else if !feature.is_empty() && diagnostics {
-            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)
-                        {
-                            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, _)) => {
-                    stability.verify_feature_enabled_by_flag(sess, enable, feature);
-                }
-            }
-
-            // FIXME(nagisa): figure out how to not allocate a full hashset here.
-            featsmap.insert(feature, enable);
-        }
-    }
-
-    // Translate this into GCC features.
-    let feats =
-        all_rust_features.iter().flat_map(|&(enable, feature)| {
-            let enable_disable = if enable { '+' } else { '-' };
+    target_features::flag_to_backend_features(
+        sess,
+        diagnostics,
+        |feature| to_gcc_features(sess, feature),
+        |feature, enable| {
             // 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.
-            to_gcc_features(sess, feature)
-                .iter()
-                .flat_map(|feat| to_gcc_features(sess, feat).into_iter())
-                .map(|feature| {
-                    if enable_disable == '-' {
-                        format!("-{}", feature)
-                    } else {
-                        feature.to_string()
-                    }
-                })
-                .collect::<Vec<_>>()
-        });
-    features.extend(feats);
-
-    if diagnostics && let Some(f) = check_tied_features(sess, &featsmap) {
-        sess.dcx().emit_err(TargetFeatureDisableOrEnable {
-            features: f,
-            span: None,
-            missing_features: None,
-        });
-    }
+            features.extend(
+                to_gcc_features(sess, feature)
+                    .iter()
+                    .flat_map(|feat| to_gcc_features(sess, feat).into_iter())
+                    .map(
+                        |feature| {
+                            if !enable { format!("-{}", feature) } else { feature.to_string() }
+                        },
+                    ),
+            );
+        },
+    );
+
+    gcc_features_by_flags(sess, &mut features);
+
+    // FIXME: LLVM also sets +reserve-x18 here under some conditions.
 
     features
 }
diff --git a/compiler/rustc_codegen_llvm/messages.ftl b/compiler/rustc_codegen_llvm/messages.ftl
index 3faeb9b3b221b..3885f18271f1b 100644
--- a/compiler/rustc_codegen_llvm/messages.ftl
+++ b/compiler/rustc_codegen_llvm/messages.ftl
@@ -59,16 +59,6 @@ codegen_llvm_symbol_already_defined =
 codegen_llvm_target_machine = could not create LLVM TargetMachine for triple: {$triple}
 codegen_llvm_target_machine_with_llvm_err = could not create LLVM TargetMachine for triple: {$triple}: {$llvm_err}
 
-codegen_llvm_unknown_ctarget_feature =
-    unknown and unstable feature specified for `-Ctarget-feature`: `{$feature}`
-    .note = it is still passed through to the codegen backend, but use of this feature might be unsound and the behavior of this feature can change in the future
-    .possible_feature = you might have meant: `{$rust_feature}`
-    .consider_filing_feature_request = consider filing a feature request
-
-codegen_llvm_unknown_ctarget_feature_prefix =
-    unknown feature specified for `-Ctarget-feature`: `{$feature}`
-    .note = features must begin with a `+` to enable or `-` to disable it
-
 codegen_llvm_unknown_debuginfo_compression = unknown debuginfo compression algorithm {$algorithm} - will fall back to uncompressed debuginfo
 
 codegen_llvm_write_bytecode = failed to write bytecode to {$path}: {$err}
diff --git a/compiler/rustc_codegen_llvm/src/errors.rs b/compiler/rustc_codegen_llvm/src/errors.rs
index 8bc74fbec7ece..d50ad8a1a9cb4 100644
--- a/compiler/rustc_codegen_llvm/src/errors.rs
+++ b/compiler/rustc_codegen_llvm/src/errors.rs
@@ -3,35 +3,11 @@ use std::path::Path;
 
 use rustc_data_structures::small_c_str::SmallCStr;
 use rustc_errors::{Diag, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level};
-use rustc_macros::{Diagnostic, Subdiagnostic};
+use rustc_macros::Diagnostic;
 use rustc_span::Span;
 
 use crate::fluent_generated as fluent;
 
-#[derive(Diagnostic)]
-#[diag(codegen_llvm_unknown_ctarget_feature_prefix)]
-#[note]
-pub(crate) struct UnknownCTargetFeaturePrefix<'a> {
-    pub feature: &'a str,
-}
-
-#[derive(Diagnostic)]
-#[diag(codegen_llvm_unknown_ctarget_feature)]
-#[note]
-pub(crate) struct UnknownCTargetFeature<'a> {
-    pub feature: &'a str,
-    #[subdiagnostic]
-    pub rust_feature: PossibleFeature<'a>,
-}
-
-#[derive(Subdiagnostic)]
-pub(crate) enum PossibleFeature<'a> {
-    #[help(codegen_llvm_possible_feature)]
-    Some { rust_feature: &'a str },
-    #[help(codegen_llvm_consider_filing_feature_request)]
-    None,
-}
-
 #[derive(Diagnostic)]
 #[diag(codegen_llvm_symbol_already_defined)]
 pub(crate) struct SymbolAlreadyDefined<'a> {
diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs
index f9ebf8616344e..ee78c310b0df5 100644
--- a/compiler/rustc_codegen_llvm/src/llvm_util.rs
+++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs
@@ -7,25 +7,18 @@ use std::{ptr, slice, str};
 
 use libc::c_int;
 use rustc_codegen_ssa::base::wants_wasm_eh;
-use rustc_codegen_ssa::codegen_attrs::check_tied_features;
 use rustc_codegen_ssa::{TargetConfig, target_features};
-use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::fx::FxHashSet;
 use rustc_data_structures::small_c_str::SmallCStr;
-use rustc_data_structures::unord::UnordSet;
 use rustc_fs_util::path_to_c_string;
 use rustc_middle::bug;
 use rustc_session::Session;
 use rustc_session::config::{PrintKind, PrintRequest};
-use rustc_session::features::{StabilityExt, retpoline_features_by_flags};
 use rustc_target::spec::{MergeFunctions, PanicStrategy, SmallDataThresholdSupport};
-use rustc_target::target_features::RUSTC_SPECIFIC_FEATURES;
 use smallvec::{SmallVec, smallvec};
 
 use crate::back::write::create_informational_target_machine;
-use crate::errors::{
-    FixedX18InvalidArch, PossibleFeature, UnknownCTargetFeature, UnknownCTargetFeaturePrefix,
-};
-use crate::llvm;
+use crate::{errors, llvm};
 
 static INIT: Once = Once::new();
 
@@ -194,15 +187,6 @@ impl<'a> LLVMFeature<'a> {
     ) -> Self {
         Self { llvm_feature_name, dependencies }
     }
-
-    fn contains(&'a self, feat: &str) -> bool {
-        self.iter().any(|dep| dep == feat)
-    }
-
-    fn iter(&'a self) -> impl Iterator<Item = &'a str> {
-        let dependencies = self.dependencies.iter().map(|feat| feat.as_str());
-        std::iter::once(self.llvm_feature_name).chain(dependencies)
-    }
 }
 
 impl<'a> IntoIterator for LLVMFeature<'a> {
@@ -215,18 +199,22 @@ impl<'a> IntoIterator for LLVMFeature<'a> {
     }
 }
 
-// WARNING: the features after applying `to_llvm_features` must be known
-// to LLVM or the feature detection code will walk past the end of the feature
-// array, leading to crashes.
-//
-// To find a list of LLVM's names, see llvm-project/llvm/lib/Target/{ARCH}/*.td
-// where `{ARCH}` is the architecture name. Look for instances of `SubtargetFeature`.
-//
-// Check the current rustc fork of LLVM in the repo at https://github.com/rust-lang/llvm-project/.
-// The commit in use can be found via the `llvm-project` submodule in
-// https://github.com/rust-lang/rust/tree/master/src Though note that Rust can also be build with
-// an external precompiled version of LLVM which might lead to failures if the oldest tested /
-// supported LLVM version doesn't yet support the relevant intrinsics.
+/// Convert a Rust feature name to an LLVM feature name. Returning `None` means the
+/// feature should be skipped, usually because it is not supported by the current
+/// LLVM version.
+///
+/// WARNING: the features after applying `to_llvm_features` must be known
+/// to LLVM or the feature detection code will walk past the end of the feature
+/// array, leading to crashes.
+///
+/// To find a list of LLVM's names, see llvm-project/llvm/lib/Target/{ARCH}/*.td
+/// where `{ARCH}` is the architecture name. Look for instances of `SubtargetFeature`.
+///
+/// Check the current rustc fork of LLVM in the repo at <https://github.com/rust-lang/llvm-project/>.
+/// The commit in use can be found via the `llvm-project` submodule in
+/// <https://github.com/rust-lang/rust/tree/master/src> Though note that Rust can also be build with
+/// an external precompiled version of LLVM which might lead to failures if the oldest tested /
+/// supported LLVM version doesn't yet support the relevant intrinsics.
 pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option<LLVMFeature<'a>> {
     let arch = if sess.target.arch == "x86_64" {
         "x86"
@@ -634,10 +622,9 @@ pub(crate) fn target_cpu(sess: &Session) -> &str {
     handle_native(cpu_name)
 }
 
-fn llvm_features_by_flags(sess: &Session) -> Vec<&str> {
-    let mut features: Vec<&str> = Vec::new();
-    retpoline_features_by_flags(sess, &mut features);
-    features
+/// The target features for compiler flags other than `-Ctarget-features`.
+fn llvm_features_by_flags(sess: &Session, features: &mut Vec<String>) {
+    target_features::retpoline_features_by_flags(sess, features);
 }
 
 /// The list of LLVM features computed from CLI flags (`-Ctarget-cpu`, `-Ctarget-feature`,
@@ -704,6 +691,8 @@ pub(crate) fn global_llvm_features(
             .split(',')
             .filter(|v| !v.is_empty())
             // Drop +v8plus feature introduced in LLVM 20.
+            // (Hard-coded target features do not go through `to_llvm_feature` since they already
+            // are LLVM feature names, hence we need a special case here.)
             .filter(|v| *v != "+v8plus" || get_version() >= (20, 0, 0))
             .map(String::from),
     );
@@ -714,86 +703,23 @@ 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();
-
-        // Compute implied features
-        let mut all_rust_features = vec![];
-        for feature in sess.opts.cg.target_feature.split(',').chain(llvm_features_by_flags(sess)) {
-            if let Some(feature) = feature.strip_prefix('+') {
-                all_rust_features.extend(
-                    UnordSet::from(sess.target.implied_target_features(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_config`.
-                // 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)
-        });
-
-        // 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
-                                }
-                            });
-                        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, _)) => {
-                        stability.verify_feature_enabled_by_flag(sess, enable, feature);
-                    }
-                }
-
-                // FIXME(nagisa): figure out how to not allocate a full hashset here.
-                featsmap.insert(feature, enable);
-            }
-        }
-
-        // Translate this into LLVM features.
-        let feats = all_rust_features
-            .iter()
-            .filter_map(|&(enable, feature)| {
+        target_features::flag_to_backend_features(
+            sess,
+            diagnostics,
+            |feature| {
+                to_llvm_features(sess, feature)
+                    .map(|f| SmallVec::<[&str; 2]>::from_iter(f.into_iter()))
+                    .unwrap_or_default()
+            },
+            |feature, enable| {
                 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
                 // different names when the LLVM name and the Rust name differ.
-                let llvm_feature = to_llvm_features(sess, feature)?;
+                let Some(llvm_feature) = to_llvm_features(sess, feature) else { return };
 
-                Some(
+                features.extend(
                     std::iter::once(format!(
                         "{}{}",
                         enable_disable, llvm_feature.llvm_feature_name
@@ -808,23 +734,17 @@ pub(crate) fn global_llvm_features(
                         },
                     )),
                 )
-            })
-            .flatten();
-        features.extend(feats);
-
-        if diagnostics && let Some(f) = check_tied_features(sess, &featsmap) {
-            sess.dcx().emit_err(rustc_codegen_ssa::errors::TargetFeatureDisableOrEnable {
-                features: f,
-                span: None,
-                missing_features: None,
-            });
-        }
+            },
+        );
+
+        llvm_features_by_flags(sess, &mut features);
     }
 
     // -Zfixed-x18
+    // FIXME: merge with `llvm_features_by_flags`.
     if sess.opts.unstable_opts.fixed_x18 {
         if sess.target.arch != "aarch64" {
-            sess.dcx().emit_fatal(FixedX18InvalidArch { arch: &sess.target.arch });
+            sess.dcx().emit_fatal(errors::FixedX18InvalidArch { arch: &sess.target.arch });
         } else {
             features.push("+reserve-x18".into());
         }
diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl
index 5322fe58cf337..ffcce8b2ff4a0 100644
--- a/compiler/rustc_codegen_ssa/messages.ftl
+++ b/compiler/rustc_codegen_ssa/messages.ftl
@@ -68,6 +68,11 @@ codegen_ssa_failed_to_write = failed to write {$path}: {$error}
 
 codegen_ssa_field_associated_value_expected = associated value expected for `{$name}`
 
+codegen_ssa_forbidden_ctarget_feature =
+    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_ssa_forbidden_ctarget_feature_issue = for more information, see issue #116344 <https://github.com/rust-lang/rust/issues/116344>
+
 codegen_ssa_forbidden_target_feature_attr =
     target feature `{$feature}` cannot be enabled with `#[target_feature]`: {$reason}
 
@@ -368,8 +373,22 @@ codegen_ssa_unexpected_parameter_name = unexpected parameter name
 codegen_ssa_unknown_archive_kind =
     Don't know how to build archive of type: {$kind}
 
+codegen_ssa_unknown_ctarget_feature =
+    unknown and unstable feature specified for `-Ctarget-feature`: `{$feature}`
+    .note = it is still passed through to the codegen backend, but use of this feature might be unsound and the behavior of this feature can change in the future
+    .possible_feature = you might have meant: `{$rust_feature}`
+    .consider_filing_feature_request = consider filing a feature request
+
+codegen_ssa_unknown_ctarget_feature_prefix =
+    unknown feature specified for `-Ctarget-feature`: `{$feature}`
+    .note = features must begin with a `+` to enable or `-` to disable it
+
 codegen_ssa_unknown_reuse_kind = unknown cgu-reuse-kind `{$kind}` specified
 
+codegen_ssa_unstable_ctarget_feature =
+    unstable feature specified for `-Ctarget-feature`: `{$feature}`
+    .note = this feature is not stably supported; its behavior can change in the future
+
 codegen_ssa_unsupported_instruction_set = target does not support `#[instruction_set]`
 
 codegen_ssa_unsupported_link_self_contained = option `-C link-self-contained` is not supported on this target
diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
index 188a9a98ce7a0..78c8264eeb0d0 100644
--- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
+++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
@@ -7,7 +7,6 @@ use rustc_attr_data_structures::ReprAttr::ReprAlign;
 use rustc_attr_data_structures::{
     AttributeKind, InlineAttr, InstructionSetAttr, OptimizeAttr, find_attr,
 };
-use rustc_data_structures::fx::FxHashMap;
 use rustc_hir::def::DefKind;
 use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
 use rustc_hir::weak_lang_items::WEAK_LANG_ITEMS;
@@ -19,13 +18,15 @@ use rustc_middle::mir::mono::Linkage;
 use rustc_middle::query::Providers;
 use rustc_middle::span_bug;
 use rustc_middle::ty::{self as ty, TyCtxt};
+use rustc_session::lint;
 use rustc_session::parse::feature_err;
-use rustc_session::{Session, lint};
 use rustc_span::{Ident, Span, sym};
 use rustc_target::spec::SanitizerSet;
 
 use crate::errors;
-use crate::target_features::{check_target_feature_trait_unsafe, from_target_feature_attr};
+use crate::target_features::{
+    check_target_feature_trait_unsafe, check_tied_features, from_target_feature_attr,
+};
 
 fn linkage_by_name(tcx: TyCtxt<'_>, def_id: LocalDefId, name: &str) -> Linkage {
     use rustc_middle::mir::mono::Linkage::*;
@@ -625,25 +626,6 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
     codegen_fn_attrs
 }
 
-/// Given a map from target_features to whether they are enabled or disabled, ensure only valid
-/// combinations are allowed.
-pub fn check_tied_features(
-    sess: &Session,
-    features: &FxHashMap<&str, bool>,
-) -> Option<&'static [&'static str]> {
-    if !features.is_empty() {
-        for tied in sess.target.tied_target_features() {
-            // Tied features must be set to the same value, or not set at all
-            let mut tied_iter = tied.iter();
-            let enabled = features.get(tied_iter.next().unwrap());
-            if tied_iter.any(|f| enabled != features.get(f)) {
-                return Some(tied);
-            }
-        }
-    }
-    None
-}
-
 /// Checks if the provided DefId is a method in a trait impl for a trait which has track_caller
 /// applied to the method prototype.
 fn should_inherit_track_caller(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs
index 5387b2a7f8183..7c5d615aaff10 100644
--- a/compiler/rustc_codegen_ssa/src/errors.rs
+++ b/compiler/rustc_codegen_ssa/src/errors.rs
@@ -1217,30 +1217,6 @@ pub(crate) struct ErrorCreatingImportLibrary<'a> {
     pub error: String,
 }
 
-pub struct TargetFeatureDisableOrEnable<'a> {
-    pub features: &'a [&'a str],
-    pub span: Option<Span>,
-    pub missing_features: Option<MissingFeatures>,
-}
-
-#[derive(Subdiagnostic)]
-#[help(codegen_ssa_missing_features)]
-pub struct MissingFeatures;
-
-impl<G: EmissionGuarantee> Diagnostic<'_, G> for TargetFeatureDisableOrEnable<'_> {
-    fn into_diag(self, dcx: DiagCtxtHandle<'_>, level: Level) -> Diag<'_, G> {
-        let mut diag = Diag::new(dcx, level, fluent::codegen_ssa_target_feature_disable_or_enable);
-        if let Some(span) = self.span {
-            diag.span(span);
-        };
-        if let Some(missing_features) = self.missing_features {
-            diag.subdiagnostic(missing_features);
-        }
-        diag.arg("features", self.features.join(", "));
-        diag
-    }
-}
-
 #[derive(Diagnostic)]
 #[diag(codegen_ssa_aix_strip_not_used)]
 pub(crate) struct AixStripNotUsed;
@@ -1283,3 +1259,68 @@ pub(crate) struct XcrunSdkPathWarning {
 #[derive(LintDiagnostic)]
 #[diag(codegen_ssa_aarch64_softfloat_neon)]
 pub(crate) struct Aarch64SoftfloatNeon;
+
+#[derive(Diagnostic)]
+#[diag(codegen_ssa_unknown_ctarget_feature_prefix)]
+#[note]
+pub(crate) struct UnknownCTargetFeaturePrefix<'a> {
+    pub feature: &'a str,
+}
+
+#[derive(Subdiagnostic)]
+pub(crate) enum PossibleFeature<'a> {
+    #[help(codegen_ssa_possible_feature)]
+    Some { rust_feature: &'a str },
+    #[help(codegen_ssa_consider_filing_feature_request)]
+    None,
+}
+
+#[derive(Diagnostic)]
+#[diag(codegen_ssa_unknown_ctarget_feature)]
+#[note]
+pub(crate) struct UnknownCTargetFeature<'a> {
+    pub feature: &'a str,
+    #[subdiagnostic]
+    pub rust_feature: PossibleFeature<'a>,
+}
+
+#[derive(Diagnostic)]
+#[diag(codegen_ssa_unstable_ctarget_feature)]
+#[note]
+pub(crate) struct UnstableCTargetFeature<'a> {
+    pub feature: &'a str,
+}
+
+#[derive(Diagnostic)]
+#[diag(codegen_ssa_forbidden_ctarget_feature)]
+#[note]
+#[note(codegen_ssa_forbidden_ctarget_feature_issue)]
+pub(crate) struct ForbiddenCTargetFeature<'a> {
+    pub feature: &'a str,
+    pub enabled: &'a str,
+    pub reason: &'a str,
+}
+
+pub struct TargetFeatureDisableOrEnable<'a> {
+    pub features: &'a [&'a str],
+    pub span: Option<Span>,
+    pub missing_features: Option<MissingFeatures>,
+}
+
+#[derive(Subdiagnostic)]
+#[help(codegen_ssa_missing_features)]
+pub struct MissingFeatures;
+
+impl<G: EmissionGuarantee> Diagnostic<'_, G> for TargetFeatureDisableOrEnable<'_> {
+    fn into_diag(self, dcx: DiagCtxtHandle<'_>, level: Level) -> Diag<'_, G> {
+        let mut diag = Diag::new(dcx, level, fluent::codegen_ssa_target_feature_disable_or_enable);
+        if let Some(span) = self.span {
+            diag.span(span);
+        };
+        if let Some(missing_features) = self.missing_features {
+            diag.subdiagnostic(missing_features);
+        }
+        diag.arg("features", self.features.join(", "));
+        diag
+    }
+}
diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs
index 476671d68550a..9cca6a3c72b18 100644
--- a/compiler/rustc_codegen_ssa/src/target_features.rs
+++ b/compiler/rustc_codegen_ssa/src/target_features.rs
@@ -1,5 +1,5 @@
 use rustc_attr_data_structures::InstructionSetAttr;
-use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
+use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
 use rustc_data_structures::unord::{UnordMap, UnordSet};
 use rustc_errors::Applicability;
 use rustc_hir as hir;
@@ -9,11 +9,13 @@ use rustc_middle::middle::codegen_fn_attrs::TargetFeature;
 use rustc_middle::query::Providers;
 use rustc_middle::ty::TyCtxt;
 use rustc_session::Session;
-use rustc_session::features::StabilityExt;
 use rustc_session::lint::builtin::AARCH64_SOFTFLOAT_NEON;
 use rustc_session::parse::feature_err;
 use rustc_span::{Span, Symbol, sym};
-use rustc_target::target_features::{self, RUSTC_SPECIAL_FEATURES, Stability};
+use rustc_target::target_features::{
+    self, RUSTC_SPECIAL_FEATURES, RUSTC_SPECIFIC_FEATURES, Stability,
+};
+use smallvec::SmallVec;
 
 use crate::errors;
 
@@ -68,7 +70,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.is_toggle_permitted(tcx.sess) {
+            if let Err(reason) = stability.toggle_allowed() {
                 tcx.dcx().emit_err(errors::ForbiddenTargetFeatureAttr {
                     span: item.span(),
                     feature,
@@ -89,7 +91,7 @@ pub(crate) fn from_target_feature_attr(
                 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
+                    // Here we do assume that the backend doesn't add even more implied features
                     // we don't know about, at least no features that would have ABI effects!
                     // We skip this logic in rustdoc, where we want to allow all target features of
                     // all targets, so we can't check their ABI compatibility and anyway we are not
@@ -251,6 +253,155 @@ pub fn cfg_target_feature(
     (f(true), f(false))
 }
 
+/// Given a map from target_features to whether they are enabled or disabled, ensure only valid
+/// combinations are allowed.
+pub fn check_tied_features(
+    sess: &Session,
+    features: &FxHashMap<&str, bool>,
+) -> Option<&'static [&'static str]> {
+    if !features.is_empty() {
+        for tied in sess.target.tied_target_features() {
+            // Tied features must be set to the same value, or not set at all
+            let mut tied_iter = tied.iter();
+            let enabled = features.get(tied_iter.next().unwrap());
+            if tied_iter.any(|f| enabled != features.get(f)) {
+                return Some(tied);
+            }
+        }
+    }
+    None
+}
+
+/// Translates the `-Ctarget-feature` flag into a backend target feature list.
+///
+/// `to_backend_features` converts a Rust feature name into a list of backend feature names; this is
+/// used for diagnostic purposes only.
+///
+/// `extend_backend_features` extends the set of backend features (assumed to be in mutable state
+/// accessible by that closure) to enable/disable the given Rust feature name.
+pub fn flag_to_backend_features<'a, const N: usize>(
+    sess: &'a Session,
+    diagnostics: bool,
+    to_backend_features: impl Fn(&'a str) -> SmallVec<[&'a str; N]>,
+    mut extend_backend_features: impl FnMut(&'a str, /* enable */ bool),
+) {
+    let known_features = sess.target.rust_target_features();
+
+    // Compute implied features
+    let mut rust_features = vec![];
+    for feature in sess.opts.cg.target_feature.split(',') {
+        if let Some(feature) = feature.strip_prefix('+') {
+            rust_features.extend(
+                UnordSet::from(sess.target.implied_target_features(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_config`.
+            // See <https://github.com/rust-lang/rust/issues/134792>.
+            rust_features.push((false, feature));
+        } else if !feature.is_empty() {
+            if diagnostics {
+                sess.dcx().emit_warn(errors::UnknownCTargetFeaturePrefix { feature });
+            }
+        }
+    }
+    // Remove features that are meant for rustc, not the backend.
+    rust_features.retain(|(_, feature)| {
+        // Retain if it is not a rustc feature
+        !RUSTC_SPECIFIC_FEATURES.contains(feature)
+    });
+
+    // Check feature validity.
+    if diagnostics {
+        let mut featsmap = FxHashMap::default();
+
+        for &(enable, feature) in &rust_features {
+            let feature_state = known_features.iter().find(|&&(v, _, _)| v == feature);
+            match feature_state {
+                None => {
+                    // This is definitely not a valid Rust feature name. Maybe it is a backend feature name?
+                    // If so, give a better error message.
+                    let rust_feature = known_features.iter().find_map(|&(rust_feature, _, _)| {
+                        let backend_features = to_backend_features(rust_feature);
+                        if backend_features.contains(&feature)
+                            && !backend_features.contains(&rust_feature)
+                        {
+                            Some(rust_feature)
+                        } else {
+                            None
+                        }
+                    });
+                    let unknown_feature = if let Some(rust_feature) = rust_feature {
+                        errors::UnknownCTargetFeature {
+                            feature,
+                            rust_feature: errors::PossibleFeature::Some { rust_feature },
+                        }
+                    } else {
+                        errors::UnknownCTargetFeature {
+                            feature,
+                            rust_feature: errors::PossibleFeature::None,
+                        }
+                    };
+                    sess.dcx().emit_warn(unknown_feature);
+                }
+                Some((_, stability, _)) => {
+                    if let Err(reason) = stability.toggle_allowed() {
+                        sess.dcx().emit_warn(errors::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(errors::UnstableCTargetFeature { feature });
+                    }
+                }
+            }
+
+            // FIXME(nagisa): figure out how to not allocate a full hashset here.
+            featsmap.insert(feature, enable);
+        }
+
+        if let Some(f) = check_tied_features(sess, &featsmap) {
+            sess.dcx().emit_err(errors::TargetFeatureDisableOrEnable {
+                features: f,
+                span: None,
+                missing_features: None,
+            });
+        }
+    }
+
+    // Add this to the backend features.
+    for (enable, feature) in rust_features {
+        extend_backend_features(feature, enable);
+    }
+}
+
+/// Computes the backend target features to be added to account for retpoline flags.
+/// Used by both LLVM and GCC since their target features are, conveniently, the same.
+pub fn retpoline_features_by_flags(sess: &Session, features: &mut Vec<String>) {
+    // -Zretpoline without -Zretpoline-external-thunk enables
+    // retpoline-indirect-branches and retpoline-indirect-calls target features
+    let unstable_opts = &sess.opts.unstable_opts;
+    if unstable_opts.retpoline && !unstable_opts.retpoline_external_thunk {
+        features.push("+retpoline-indirect-branches".into());
+        features.push("+retpoline-indirect-calls".into());
+    }
+    // -Zretpoline-external-thunk (maybe, with -Zretpoline too) enables
+    // retpoline-external-thunk, retpoline-indirect-branches and
+    // retpoline-indirect-calls target features
+    if unstable_opts.retpoline_external_thunk {
+        features.push("+retpoline-external-thunk".into());
+        features.push("+retpoline-indirect-branches".into());
+        features.push("+retpoline-indirect-calls".into());
+    }
+}
+
 pub(crate) fn provide(providers: &mut Providers) {
     *providers = Providers {
         rust_target_features: |tcx, cnum| {
diff --git a/compiler/rustc_session/messages.ftl b/compiler/rustc_session/messages.ftl
index 61953614c77e1..528c52eace7cb 100644
--- a/compiler/rustc_session/messages.ftl
+++ b/compiler/rustc_session/messages.ftl
@@ -40,11 +40,6 @@ session_file_is_not_writeable = output file {$file} is not writeable -- check it
 
 session_file_write_fail = failed to write `{$path}` due to error `{$err}`
 
-session_forbidden_ctarget_feature =
-    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!
-session_forbidden_ctarget_feature_issue = for more information, see issue #116344 <https://github.com/rust-lang/rust/issues/116344>
-
 session_function_return_requires_x86_or_x86_64 = `-Zfunction-return` (except `keep`) is only supported on x86 and x86_64
 
 session_function_return_thunk_extern_requires_non_large_code_model = `-Zfunction-return=thunk-extern` is only supported on non-large code models
@@ -137,9 +132,6 @@ session_target_stack_protector_not_supported = `-Z stack-protector={$stack_prote
 session_unleashed_feature_help_named = skipping check for `{$gate}` feature
 session_unleashed_feature_help_unnamed = skipping check that does not even have a feature gate
 
-session_unstable_ctarget_feature =
-    unstable feature specified for `-Ctarget-feature`: `{$feature}`
-    .note = this feature is not stably supported; its behavior can change in the future
 session_unstable_virtual_function_elimination = `-Zvirtual-function-elimination` requires `-Clto`
 
 session_unsupported_crate_type_for_target =
diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs
index 9c591dcf619a5..bf95014843d23 100644
--- a/compiler/rustc_session/src/errors.rs
+++ b/compiler/rustc_session/src/errors.rs
@@ -501,20 +501,3 @@ pub(crate) struct SoftFloatIgnored;
 #[note]
 #[note(session_soft_float_deprecated_issue)]
 pub(crate) struct SoftFloatDeprecated;
-
-#[derive(Diagnostic)]
-#[diag(session_forbidden_ctarget_feature)]
-#[note]
-#[note(session_forbidden_ctarget_feature_issue)]
-pub(crate) struct ForbiddenCTargetFeature<'a> {
-    pub feature: &'a str,
-    pub enabled: &'a str,
-    pub reason: &'a str,
-}
-
-#[derive(Diagnostic)]
-#[diag(session_unstable_ctarget_feature)]
-#[note]
-pub(crate) struct UnstableCTargetFeature<'a> {
-    pub feature: &'a str,
-}
diff --git a/compiler/rustc_session/src/features.rs b/compiler/rustc_session/src/features.rs
deleted file mode 100644
index 70a088a236f7e..0000000000000
--- a/compiler/rustc_session/src/features.rs
+++ /dev/null
@@ -1,59 +0,0 @@
-use rustc_target::target_features::Stability;
-
-use crate::Session;
-use crate::errors::{ForbiddenCTargetFeature, UnstableCTargetFeature};
-
-pub trait StabilityExt {
-    /// Returns whether the feature may be toggled via `#[target_feature]` or `-Ctarget-feature`.
-    /// Otherwise, some features also may only be enabled by flag (target modifier).
-    /// (It might still be nightly-only even if this returns `true`, so make sure to also check
-    /// `requires_nightly`.)
-    fn is_toggle_permitted(&self, sess: &Session) -> Result<(), &'static str>;
-
-    /// Check that feature is correctly enabled/disabled by command line flag (emits warnings)
-    fn verify_feature_enabled_by_flag(&self, sess: &Session, enable: bool, feature: &str);
-}
-
-impl StabilityExt for Stability {
-    fn is_toggle_permitted(&self, sess: &Session) -> Result<(), &'static str> {
-        match self {
-            Stability::Forbidden { reason } => Err(reason),
-            Stability::TargetModifierOnly { reason, flag } => {
-                if !sess.opts.target_feature_flag_enabled(*flag) { Err(reason) } else { Ok(()) }
-            }
-            _ => Ok(()),
-        }
-    }
-    fn verify_feature_enabled_by_flag(&self, sess: &Session, enable: bool, feature: &str) {
-        if let Err(reason) = self.is_toggle_permitted(sess) {
-            sess.dcx().emit_warn(ForbiddenCTargetFeature {
-                feature,
-                enabled: if enable { "enabled" } else { "disabled" },
-                reason,
-            });
-        } else if self.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 });
-        }
-    }
-}
-
-pub fn retpoline_features_by_flags(sess: &Session, features: &mut Vec<&str>) {
-    // -Zretpoline without -Zretpoline-external-thunk enables
-    // retpoline-indirect-branches and retpoline-indirect-calls target features
-    let unstable_opts = &sess.opts.unstable_opts;
-    if unstable_opts.retpoline && !unstable_opts.retpoline_external_thunk {
-        features.push("+retpoline-indirect-branches");
-        features.push("+retpoline-indirect-calls");
-    }
-    // -Zretpoline-external-thunk (maybe, with -Zretpoline too) enables
-    // retpoline-external-thunk, retpoline-indirect-branches and
-    // retpoline-indirect-calls target features
-    if unstable_opts.retpoline_external_thunk {
-        features.push("+retpoline-external-thunk");
-        features.push("+retpoline-indirect-branches");
-        features.push("+retpoline-indirect-calls");
-    }
-}
diff --git a/compiler/rustc_session/src/lib.rs b/compiler/rustc_session/src/lib.rs
index 4added19e56e7..5e5872ee06815 100644
--- a/compiler/rustc_session/src/lib.rs
+++ b/compiler/rustc_session/src/lib.rs
@@ -29,7 +29,6 @@ pub use session::*;
 pub mod output;
 
 pub use getopts;
-pub mod features;
 
 rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
 
diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs
index 31b4237d3b2ed..7fef942525b96 100644
--- a/compiler/rustc_session/src/options.rs
+++ b/compiler/rustc_session/src/options.rs
@@ -290,14 +290,6 @@ macro_rules! top_level_options {
                 mods.sort_by(|a, b| a.opt.cmp(&b.opt));
                 mods
             }
-
-            pub fn target_feature_flag_enabled(&self, flag: &str) -> bool {
-                match flag {
-                    "retpoline" => self.unstable_opts.retpoline,
-                    "retpoline-external-thunk" => self.unstable_opts.retpoline_external_thunk,
-                    _ => false,
-                }
-            }
         }
     );
 }
diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs
index a1eac1fba25da..c18a2c1ebef5b 100644
--- a/compiler/rustc_target/src/target_features.rs
+++ b/compiler/rustc_target/src/target_features.rs
@@ -34,9 +34,6 @@ pub enum Stability {
     /// 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 },
-    /// This feature can not be set via `-Ctarget-feature` or `#[target_feature]`, it can only be set
-    /// by target modifier flag. Target modifier flags are tracked to be consistent in linked modules.
-    TargetModifierOnly { reason: &'static str, flag: &'static str },
 }
 use Stability::*;
 
@@ -52,7 +49,6 @@ impl<CTX> HashStable<CTX> for Stability {
             Stability::Forbidden { reason } => {
                 reason.hash_stable(hcx, hasher);
             }
-            Stability::TargetModifierOnly { .. } => {}
         }
     }
 }
@@ -62,7 +58,7 @@ impl Stability {
     /// (It might still be nightly-only even if this returns `true`, so make sure to also check
     /// `requires_nightly`.)
     pub fn in_cfg(&self) -> bool {
-        !matches!(self, Stability::Forbidden { .. })
+        matches!(self, Stability::Stable | Stability::Unstable { .. })
     }
 
     /// Returns the nightly feature that is required to toggle this target feature via
@@ -78,7 +74,16 @@ impl Stability {
             Stability::Unstable(nightly_feature) => Some(nightly_feature),
             Stability::Stable { .. } => None,
             Stability::Forbidden { .. } => panic!("forbidden features should not reach this far"),
-            Stability::TargetModifierOnly { .. } => None,
+        }
+    }
+
+    /// Returns whether the feature may be toggled via `#[target_feature]` or `-Ctarget-feature`.
+    /// (It might still be nightly-only even if this returns `true`, so make sure to also check
+    /// `requires_nightly`.)
+    pub fn toggle_allowed(&self) -> Result<(), &'static str> {
+        match self {
+            Stability::Unstable(_) | Stability::Stable { .. } => Ok(()),
+            Stability::Forbidden { reason } => Err(reason),
         }
     }
 }
@@ -450,26 +455,19 @@ static X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     ("rdseed", Stable, &[]),
     (
         "retpoline-external-thunk",
-        Stability::TargetModifierOnly {
+        Stability::Forbidden {
             reason: "use `retpoline-external-thunk` target modifier flag instead",
-            flag: "retpoline-external-thunk",
         },
         &[],
     ),
     (
         "retpoline-indirect-branches",
-        Stability::TargetModifierOnly {
-            reason: "use `retpoline` target modifier flag instead",
-            flag: "retpoline",
-        },
+        Stability::Forbidden { reason: "use `retpoline` target modifier flag instead" },
         &[],
     ),
     (
         "retpoline-indirect-calls",
-        Stability::TargetModifierOnly {
-            reason: "use `retpoline` target modifier flag instead",
-            flag: "retpoline",
-        },
+        Stability::Forbidden { reason: "use `retpoline` target modifier flag instead" },
         &[],
     ),
     ("rtm", Unstable(sym::rtm_target_feature), &[]),
diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs
index 2feadce26d09f..29c63a391e215 100644
--- a/src/librustdoc/json/mod.rs
+++ b/src/librustdoc/json/mod.rs
@@ -18,7 +18,6 @@ use rustc_data_structures::fx::FxHashSet;
 use rustc_hir::def_id::{DefId, DefIdSet};
 use rustc_middle::ty::TyCtxt;
 use rustc_session::Session;
-use rustc_session::features::StabilityExt;
 use rustc_span::def_id::LOCAL_CRATE;
 use rustdoc_json_types as types;
 // It's important to use the FxHashMap from rustdoc_json_types here, instead of
@@ -148,7 +147,7 @@ fn target(sess: &rustc_session::Session) -> types::Target {
             .copied()
             .filter(|(_, stability, _)| {
                 // Describe only target features which the user can toggle
-                stability.is_toggle_permitted(sess).is_ok()
+                stability.toggle_allowed().is_ok()
             })
             .map(|(name, stability, implied_features)| {
                 types::TargetFeature {
@@ -164,7 +163,7 @@ fn target(sess: &rustc_session::Session) -> types::Target {
                             // Imply only target features which the user can toggle
                             feature_stability
                                 .get(name)
-                                .map(|stability| stability.is_toggle_permitted(sess).is_ok())
+                                .map(|stability| stability.toggle_allowed().is_ok())
                                 .unwrap_or(false)
                         })
                         .map(String::from)
diff --git a/tests/ui/check-cfg/target_feature.stderr b/tests/ui/check-cfg/target_feature.stderr
index f29a41d6a8e28..ec81ba2e3d89f 100644
--- a/tests/ui/check-cfg/target_feature.stderr
+++ b/tests/ui/check-cfg/target_feature.stderr
@@ -212,9 +212,6 @@ LL |     cfg!(target_feature = "_UNEXPECTED_VALUE");
 `relax`
 `relaxed-simd`
 `reserve-x18`
-`retpoline-external-thunk`
-`retpoline-indirect-branches`
-`retpoline-indirect-calls`
 `rtm`
 `sb`
 `scq`

From e46c234ca4ccbad085f11de7ca1cd25a40431b22 Mon Sep 17 00:00:00 2001
From: Ralf Jung <post@ralfj.de>
Date: Sun, 11 May 2025 11:18:48 +0200
Subject: [PATCH 11/20] unify two -Ctarget-feature parsers

This does change the logic a bit: previously, we didn't forward reverse
implications of negated features to the backend, instead relying on the backend
to handle the implication itself.
---
 .../rustc_codegen_ssa/src/target_features.rs  | 239 ++++++++++--------
 .../target-feature-negative-implication.rs    |  20 ++
 tests/codegen/target-feature-overrides.rs     |   5 +-
 tests/codegen/tied-features-strength.rs       |  15 +-
 ...le-target-feature-flag-enable.riscv.stderr |  10 +-
 ...incompatible-target-feature-flag-enable.rs |   2 -
 6 files changed, 170 insertions(+), 121 deletions(-)
 create mode 100644 tests/codegen/target-feature-negative-implication.rs

diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs
index 9cca6a3c72b18..41d8b8d8cbfec 100644
--- a/compiler/rustc_codegen_ssa/src/target_features.rs
+++ b/compiler/rustc_codegen_ssa/src/target_features.rs
@@ -159,6 +159,58 @@ pub(crate) fn check_target_feature_trait_unsafe(tcx: TyCtxt<'_>, id: LocalDefId,
     }
 }
 
+/// Parse the value of `-Ctarget-feature`, also expanding implied features,
+/// and call the closure for each (expanded) Rust feature. If the list contains
+/// a syntactically invalid item (not starting with `+`/`-`), the error callback is invoked.
+fn parse_rust_feature_flag<'a>(
+    sess: &Session,
+    target_feature_flag: &'a str,
+    err_callback: impl Fn(&'a str),
+    mut callback: impl FnMut(
+        /* base_feature */ &'a str,
+        /* with_implied */ FxHashSet<&'a str>,
+        /* enable */ bool,
+    ),
+) {
+    // A cache for the backwards implication map.
+    let mut inverse_implied_features: Option<FxHashMap<&str, FxHashSet<&str>>> = None;
+
+    for feature in target_feature_flag.split(',') {
+        if let Some(base_feature) = feature.strip_prefix('+') {
+            callback(base_feature, sess.target.implied_target_features(base_feature), true)
+        } else if let Some(base_feature) = feature.strip_prefix('-') {
+            // If `f1` implies `f2`, then `!f2` implies `!f1` -- this is standard logical contraposition.
+            // So we have to find all the reverse implications of `base_feature` and disable them, too.
+
+            let inverse_implied_features = inverse_implied_features.get_or_insert_with(|| {
+                let mut set: FxHashMap<&str, FxHashSet<&str>> = FxHashMap::default();
+                for (f, _, is) in sess.target.rust_target_features() {
+                    for i in is.iter() {
+                        set.entry(i).or_default().insert(f);
+                    }
+                }
+                set
+            });
+
+            // Inverse mplied target features have their own inverse implied target features, so we
+            // traverse the map until there are no more features to add.
+            let mut features = FxHashSet::default();
+            let mut new_features = vec![base_feature];
+            while let Some(new_feature) = new_features.pop() {
+                if features.insert(new_feature) {
+                    if let Some(implied_features) = inverse_implied_features.get(&new_feature) {
+                        new_features.extend(implied_features)
+                    }
+                }
+            }
+
+            callback(base_feature, features, false)
+        } else if !feature.is_empty() {
+            err_callback(feature)
+        }
+    }
+}
+
 /// Utility function for a codegen backend to compute `cfg(target_feature)`, or more specifically,
 /// to populate `sess.unstable_target_features` and `sess.target_features` (these are the first and
 /// 2nd component of the return value, respectively).
@@ -175,7 +227,7 @@ pub fn cfg_target_feature(
 ) -> (Vec<Symbol>, Vec<Symbol>) {
     // Compute which of the known target features are enabled in the 'base' target machine. We only
     // consider "supported" features; "forbidden" features are not reflected in `cfg` as of now.
-    let mut features: FxHashSet<Symbol> = sess
+    let mut features: UnordSet<Symbol> = sess
         .target
         .rust_target_features()
         .iter()
@@ -190,43 +242,23 @@ pub fn cfg_target_feature(
         .collect();
 
     // Add enabled and remove disabled features.
-    for (enabled, feature) in
-        target_feature_flag.split(',').filter_map(|s| match s.chars().next() {
-            Some('+') => Some((true, Symbol::intern(&s[1..]))),
-            Some('-') => Some((false, Symbol::intern(&s[1..]))),
-            _ => None,
-        })
-    {
-        if enabled {
-            // Also add all transitively implied features.
-
-            // We don't care about the order in `features` since the only thing we use it for is the
-            // `features.contains` below.
+    parse_rust_feature_flag(
+        sess,
+        target_feature_flag,
+        /* err_callback */ |_| {},
+        |_base_feature, new_features, enabled| {
+            // Iteration order is irrelevant since this only influences an `UnordSet`.
             #[allow(rustc::potential_query_instability)]
-            features.extend(
-                sess.target
-                    .implied_target_features(feature.as_str())
-                    .iter()
-                    .map(|s| Symbol::intern(s)),
-            );
-        } else {
-            // Remove transitively reverse-implied features.
-
-            // 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.retain(|f| {
-                if sess.target.implied_target_features(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
-                } else {
-                    // We can keep `f`.
-                    true
+            if enabled {
+                features.extend(new_features.into_iter().map(|f| Symbol::intern(f)));
+            } else {
+                // Remove `new_features` from `features`.
+                for new in new_features {
+                    features.remove(&Symbol::intern(new));
                 }
-            });
-        }
-    }
+            }
+        },
+    );
 
     // Filter enabled features based on feature gates.
     let f = |allow_unstable| {
@@ -289,85 +321,82 @@ pub fn flag_to_backend_features<'a, const N: usize>(
 
     // Compute implied features
     let mut rust_features = vec![];
-    for feature in sess.opts.cg.target_feature.split(',') {
-        if let Some(feature) = feature.strip_prefix('+') {
-            rust_features.extend(
-                UnordSet::from(sess.target.implied_target_features(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_config`.
-            // See <https://github.com/rust-lang/rust/issues/134792>.
-            rust_features.push((false, feature));
-        } else if !feature.is_empty() {
+    parse_rust_feature_flag(
+        sess,
+        &sess.opts.cg.target_feature,
+        /* err_callback */
+        |feature| {
             if diagnostics {
                 sess.dcx().emit_warn(errors::UnknownCTargetFeaturePrefix { feature });
             }
-        }
-    }
-    // Remove features that are meant for rustc, not the backend.
-    rust_features.retain(|(_, feature)| {
-        // Retain if it is not a rustc feature
-        !RUSTC_SPECIFIC_FEATURES.contains(feature)
-    });
-
-    // Check feature validity.
-    if diagnostics {
-        let mut featsmap = FxHashMap::default();
+        },
+        |base_feature, new_features, enable| {
+            // Skip features that are meant for rustc, not the backend.
+            if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) {
+                return;
+            }
 
-        for &(enable, feature) in &rust_features {
-            let feature_state = known_features.iter().find(|&&(v, _, _)| v == feature);
-            match feature_state {
-                None => {
-                    // This is definitely not a valid Rust feature name. Maybe it is a backend feature name?
-                    // If so, give a better error message.
-                    let rust_feature = known_features.iter().find_map(|&(rust_feature, _, _)| {
-                        let backend_features = to_backend_features(rust_feature);
-                        if backend_features.contains(&feature)
-                            && !backend_features.contains(&rust_feature)
-                        {
-                            Some(rust_feature)
+            rust_features.extend(
+                UnordSet::from(new_features).to_sorted_stable_ord().iter().map(|&&s| (enable, s)),
+            );
+            // Check feature validity.
+            if diagnostics {
+                let feature_state = known_features.iter().find(|&&(v, _, _)| v == base_feature);
+                match feature_state {
+                    None => {
+                        // This is definitely not a valid Rust feature name. Maybe it is a backend feature name?
+                        // If so, give a better error message.
+                        let rust_feature =
+                            known_features.iter().find_map(|&(rust_feature, _, _)| {
+                                let backend_features = to_backend_features(rust_feature);
+                                if backend_features.contains(&base_feature)
+                                    && !backend_features.contains(&rust_feature)
+                                {
+                                    Some(rust_feature)
+                                } else {
+                                    None
+                                }
+                            });
+                        let unknown_feature = if let Some(rust_feature) = rust_feature {
+                            errors::UnknownCTargetFeature {
+                                feature: base_feature,
+                                rust_feature: errors::PossibleFeature::Some { rust_feature },
+                            }
                         } else {
-                            None
-                        }
-                    });
-                    let unknown_feature = if let Some(rust_feature) = rust_feature {
-                        errors::UnknownCTargetFeature {
-                            feature,
-                            rust_feature: errors::PossibleFeature::Some { rust_feature },
-                        }
-                    } else {
-                        errors::UnknownCTargetFeature {
-                            feature,
-                            rust_feature: errors::PossibleFeature::None,
+                            errors::UnknownCTargetFeature {
+                                feature: base_feature,
+                                rust_feature: errors::PossibleFeature::None,
+                            }
+                        };
+                        sess.dcx().emit_warn(unknown_feature);
+                    }
+                    Some((_, stability, _)) => {
+                        if let Err(reason) = stability.toggle_allowed() {
+                            sess.dcx().emit_warn(errors::ForbiddenCTargetFeature {
+                                feature: base_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(errors::UnstableCTargetFeature {
+                                feature: base_feature,
+                            });
                         }
-                    };
-                    sess.dcx().emit_warn(unknown_feature);
-                }
-                Some((_, stability, _)) => {
-                    if let Err(reason) = stability.toggle_allowed() {
-                        sess.dcx().emit_warn(errors::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(errors::UnstableCTargetFeature { feature });
                     }
                 }
             }
+        },
+    );
 
-            // FIXME(nagisa): figure out how to not allocate a full hashset here.
-            featsmap.insert(feature, enable);
-        }
-
-        if let Some(f) = check_tied_features(sess, &featsmap) {
+    if diagnostics {
+        // FIXME(nagisa): figure out how to not allocate a full hashmap here.
+        if let Some(f) = check_tied_features(
+            sess,
+            &FxHashMap::from_iter(rust_features.iter().map(|&(enable, feature)| (feature, enable))),
+        ) {
             sess.dcx().emit_err(errors::TargetFeatureDisableOrEnable {
                 features: f,
                 span: None,
diff --git a/tests/codegen/target-feature-negative-implication.rs b/tests/codegen/target-feature-negative-implication.rs
new file mode 100644
index 0000000000000..36cd82dd8cf5a
--- /dev/null
+++ b/tests/codegen/target-feature-negative-implication.rs
@@ -0,0 +1,20 @@
+//@ add-core-stubs
+//@ needs-llvm-components: x86
+//@ compile-flags: --target=x86_64-unknown-linux-gnu
+//@ compile-flags: -Ctarget-feature=-avx2
+
+#![feature(no_core, lang_items)]
+#![crate_type = "lib"]
+#![no_core]
+
+extern crate minicore;
+use minicore::*;
+
+#[no_mangle]
+pub unsafe fn banana() {
+    // CHECK-LABEL: @banana()
+    // CHECK-SAME: [[BANANAATTRS:#[0-9]+]] {
+}
+
+// CHECK: attributes [[BANANAATTRS]]
+// CHECK-SAME: -avx512
diff --git a/tests/codegen/target-feature-overrides.rs b/tests/codegen/target-feature-overrides.rs
index 0fc1e0136b3f3..eb19b0de2fa8a 100644
--- a/tests/codegen/target-feature-overrides.rs
+++ b/tests/codegen/target-feature-overrides.rs
@@ -1,3 +1,4 @@
+// ignore-tidy-linelength
 //@ add-core-stubs
 //@ revisions: COMPAT INCOMPAT
 //@ needs-llvm-components: x86
@@ -39,7 +40,7 @@ pub unsafe fn banana() -> u32 {
 
 // CHECK: attributes [[APPLEATTRS]]
 // COMPAT-SAME: "target-features"="+avx,+avx2,{{.*}}"
-// INCOMPAT-SAME: "target-features"="-avx2,-avx,+avx,{{.*}}"
+// INCOMPAT-SAME: "target-features"="{{(-[^,]+,)*}}-avx2{{(,-[^,]+)*}},-avx{{(,-[^,]+)*}},+avx{{(,\+[^,]+)*}}"
 // CHECK: attributes [[BANANAATTRS]]
 // COMPAT-SAME: "target-features"="+avx,+avx2,{{.*}}"
-// INCOMPAT-SAME: "target-features"="-avx2,-avx"
+// INCOMPAT-SAME: "target-features"="{{(-[^,]+,)*}}-avx2{{(,-[^,]+)*}},-avx{{(,-[^,]+)*}}"
diff --git a/tests/codegen/tied-features-strength.rs b/tests/codegen/tied-features-strength.rs
index 6be0e21e0ef3a..81499c070d19a 100644
--- a/tests/codegen/tied-features-strength.rs
+++ b/tests/codegen/tied-features-strength.rs
@@ -4,14 +4,23 @@
 //@ compile-flags: --crate-type=rlib --target=aarch64-unknown-linux-gnu
 //@ needs-llvm-components: aarch64
 
+// Rust made SVE require neon.
 //@ [ENABLE_SVE] compile-flags: -C target-feature=+sve -Copt-level=0
-// ENABLE_SVE: attributes #0 = { {{.*}} "target-features"="{{((\+outline-atomics,?)|(\+v8a,?)|(\+sve,?)|(\+neon,?)|(\+fp-armv8,?))*}}" }
+// ENABLE_SVE: attributes #0
+// ENABLE_SVE-SAME: +neon
+// ENABLE_SVE-SAME: +sve
 
+// However, disabling SVE does not disable neon.
 //@ [DISABLE_SVE] compile-flags: -C target-feature=-sve -Copt-level=0
-// DISABLE_SVE: attributes #0 = { {{.*}} "target-features"="{{((\+outline-atomics,?)|(\+v8a,?)|(-sve,?)|(\+neon,?))*}}" }
+// DISABLE_SVE: attributes #0
+// DISABLE_SVE-NOT: -neon
+// DISABLE_SVE-SAME: -sve
 
+// OTOH, neon fn `fp-armv8` are fully tied; toggling neon must toggle `fp-armv8` the same way.
 //@ [DISABLE_NEON] compile-flags: -C target-feature=-neon -Copt-level=0
-// DISABLE_NEON: attributes #0 = { {{.*}} "target-features"="{{((\+outline-atomics,?)|(\+v8a,?)|(-fp-armv8,?)|(-neon,?))*}}" }
+// DISABLE_NEON: attributes #0
+// DISABLE_NEON-SAME: -neon
+// DISABLE_NEON-SAME: -fp-armv8
 
 //@ [ENABLE_NEON] compile-flags: -C target-feature=+neon -Copt-level=0
 // ENABLE_NEON: attributes #0 = { {{.*}} "target-features"="{{((\+outline-atomics,?)|(\+v8a,?)|(\+fp-armv8,?)|(\+neon,?))*}}" }
diff --git a/tests/ui/target-feature/abi-incompatible-target-feature-flag-enable.riscv.stderr b/tests/ui/target-feature/abi-incompatible-target-feature-flag-enable.riscv.stderr
index 2dca0c220332b..0b2d71f97d0d4 100644
--- a/tests/ui/target-feature/abi-incompatible-target-feature-flag-enable.riscv.stderr
+++ b/tests/ui/target-feature/abi-incompatible-target-feature-flag-enable.riscv.stderr
@@ -7,13 +7,5 @@ warning: unstable feature specified for `-Ctarget-feature`: `d`
    |
    = note: this feature is not stably supported; its behavior can change in the future
 
-warning: unstable feature specified for `-Ctarget-feature`: `f`
-   |
-   = note: this feature is not stably supported; its behavior can change in the future
-
-warning: unstable feature specified for `-Ctarget-feature`: `zicsr`
-   |
-   = note: this feature is not stably supported; its behavior can change in the future
-
-warning: 4 warnings emitted
+warning: 2 warnings emitted
 
diff --git a/tests/ui/target-feature/abi-incompatible-target-feature-flag-enable.rs b/tests/ui/target-feature/abi-incompatible-target-feature-flag-enable.rs
index 302cceccf6939..1006b078bab36 100644
--- a/tests/ui/target-feature/abi-incompatible-target-feature-flag-enable.rs
+++ b/tests/ui/target-feature/abi-incompatible-target-feature-flag-enable.rs
@@ -24,5 +24,3 @@ pub trait Freeze {}
 
 //~? WARN must be disabled to ensure that the ABI of the current target can be implemented correctly
 //~? WARN unstable feature specified for `-Ctarget-feature`
-//[riscv]~? WARN unstable feature specified for `-Ctarget-feature`
-//[riscv]~? WARN unstable feature specified for `-Ctarget-feature`

From 8bec5bb5adaa1f5a0bc3683fee5f77a5fdac539b Mon Sep 17 00:00:00 2001
From: Ralf Jung <post@ralfj.de>
Date: Sun, 11 May 2025 12:50:48 +0200
Subject: [PATCH 12/20] cg_gcc: properly populate cfg(target_features) with
 -Ctarget-features

---
 compiler/rustc_codegen_gcc/src/lib.rs         | 39 ++++++++-----------
 compiler/rustc_codegen_llvm/src/llvm_util.rs  | 32 +++++++--------
 .../rustc_codegen_ssa/src/target_features.rs  | 13 ++-----
 3 files changed, 37 insertions(+), 47 deletions(-)

diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs
index f5ad28681c790..a912678ef2a10 100644
--- a/compiler/rustc_codegen_gcc/src/lib.rs
+++ b/compiler/rustc_codegen_gcc/src/lib.rs
@@ -102,10 +102,9 @@ use rustc_codegen_ssa::back::write::{
     CodegenContext, FatLtoInput, ModuleConfig, TargetMachineFactoryFn,
 };
 use rustc_codegen_ssa::base::codegen_crate;
+use rustc_codegen_ssa::target_features::cfg_target_feature;
 use rustc_codegen_ssa::traits::{CodegenBackend, ExtraBackendMethods, WriteBackendMethods};
-use rustc_codegen_ssa::{
-    CodegenResults, CompiledModule, ModuleCodegen, TargetConfig, target_features,
-};
+use rustc_codegen_ssa::{CodegenResults, CompiledModule, ModuleCodegen, TargetConfig};
 use rustc_data_structures::fx::FxIndexMap;
 use rustc_data_structures::sync::IntoDynSyncSend;
 use rustc_errors::DiagCtxtHandle;
@@ -478,25 +477,21 @@ fn to_gcc_opt_level(optlevel: Option<OptLevel>) -> OptimizationLevel {
 
 /// Returns the features that should be set in `cfg(target_feature)`.
 fn target_config(sess: &Session, target_info: &LockedTargetInfo) -> TargetConfig {
-    let (unstable_target_features, target_features) = target_features::cfg_target_feature(
-        sess,
-        /* FIXME: we ignore `-Ctarget-feature` */ "",
-        |feature| {
-            // TODO: we disable Neon for now since we don't support the LLVM intrinsics for it.
-            if feature == "neon" {
-                return false;
-            }
-            target_info.cpu_supports(feature)
-            // cSpell:disable
-            /*
-              adx, aes, avx, avx2, avx512bf16, avx512bitalg, avx512bw, avx512cd, avx512dq, avx512er, avx512f, avx512fp16, avx512ifma,
-              avx512pf, avx512vbmi, avx512vbmi2, avx512vl, avx512vnni, avx512vp2intersect, avx512vpopcntdq,
-              bmi1, bmi2, cmpxchg16b, ermsb, f16c, fma, fxsr, gfni, lzcnt, movbe, pclmulqdq, popcnt, rdrand, rdseed, rtm,
-              sha, sse, sse2, sse3, sse4.1, sse4.2, sse4a, ssse3, tbm, vaes, vpclmulqdq, xsave, xsavec, xsaveopt, xsaves
-            */
-            // cSpell:enable
-        },
-    );
+    let (unstable_target_features, target_features) = cfg_target_feature(sess, |feature| {
+        // TODO: we disable Neon for now since we don't support the LLVM intrinsics for it.
+        if feature == "neon" {
+            return false;
+        }
+        target_info.cpu_supports(feature)
+        // cSpell:disable
+        /*
+          adx, aes, avx, avx2, avx512bf16, avx512bitalg, avx512bw, avx512cd, avx512dq, avx512er, avx512f, avx512fp16, avx512ifma,
+          avx512pf, avx512vbmi, avx512vbmi2, avx512vl, avx512vnni, avx512vp2intersect, avx512vpopcntdq,
+          bmi1, bmi2, cmpxchg16b, ermsb, f16c, fma, fxsr, gfni, lzcnt, movbe, pclmulqdq, popcnt, rdrand, rdseed, rtm,
+          sha, sse, sse2, sse3, sse4.1, sse4.2, sse4a, ssse3, tbm, vaes, vpclmulqdq, xsave, xsavec, xsaveopt, xsaves
+        */
+        // cSpell:enable
+    });
 
     let has_reliable_f16 = target_info.supports_target_dependent_type(CType::Float16);
     let has_reliable_f128 = target_info.supports_target_dependent_type(CType::Float128);
diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs
index ee78c310b0df5..bc2165fa8290e 100644
--- a/compiler/rustc_codegen_llvm/src/llvm_util.rs
+++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs
@@ -7,6 +7,7 @@ use std::{ptr, slice, str};
 
 use libc::c_int;
 use rustc_codegen_ssa::base::wants_wasm_eh;
+use rustc_codegen_ssa::target_features::cfg_target_feature;
 use rustc_codegen_ssa::{TargetConfig, target_features};
 use rustc_data_structures::fx::FxHashSet;
 use rustc_data_structures::small_c_str::SmallCStr;
@@ -331,24 +332,23 @@ pub(crate) fn target_config(sess: &Session) -> TargetConfig {
     // by LLVM.
     let target_machine = create_informational_target_machine(sess, true);
 
-    let (unstable_target_features, target_features) =
-        target_features::cfg_target_feature(sess, &sess.opts.cg.target_feature, |feature| {
-            if let Some(feat) = to_llvm_features(sess, feature) {
-                // All the LLVM features this expands to must be enabled.
-                for llvm_feature in feat {
-                    let cstr = SmallCStr::new(llvm_feature);
-                    // `LLVMRustHasFeature` is moderately expensive. On targets with many
-                    // features (e.g. x86) these calls take a non-trivial fraction of runtime
-                    // when compiling very small programs.
-                    if !unsafe { llvm::LLVMRustHasFeature(target_machine.raw(), cstr.as_ptr()) } {
-                        return false;
-                    }
+    let (unstable_target_features, target_features) = cfg_target_feature(sess, |feature| {
+        if let Some(feat) = to_llvm_features(sess, feature) {
+            // All the LLVM features this expands to must be enabled.
+            for llvm_feature in feat {
+                let cstr = SmallCStr::new(llvm_feature);
+                // `LLVMRustHasFeature` is moderately expensive. On targets with many
+                // features (e.g. x86) these calls take a non-trivial fraction of runtime
+                // when compiling very small programs.
+                if !unsafe { llvm::LLVMRustHasFeature(target_machine.raw(), cstr.as_ptr()) } {
+                    return false;
                 }
-                true
-            } else {
-                false
             }
-        });
+            true
+        } else {
+            false
+        }
+    });
 
     let mut cfg = TargetConfig {
         target_features,
diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs
index 41d8b8d8cbfec..e29cdbb968ed2 100644
--- a/compiler/rustc_codegen_ssa/src/target_features.rs
+++ b/compiler/rustc_codegen_ssa/src/target_features.rs
@@ -163,8 +163,7 @@ pub(crate) fn check_target_feature_trait_unsafe(tcx: TyCtxt<'_>, id: LocalDefId,
 /// and call the closure for each (expanded) Rust feature. If the list contains
 /// a syntactically invalid item (not starting with `+`/`-`), the error callback is invoked.
 fn parse_rust_feature_flag<'a>(
-    sess: &Session,
-    target_feature_flag: &'a str,
+    sess: &'a Session,
     err_callback: impl Fn(&'a str),
     mut callback: impl FnMut(
         /* base_feature */ &'a str,
@@ -175,7 +174,7 @@ fn parse_rust_feature_flag<'a>(
     // A cache for the backwards implication map.
     let mut inverse_implied_features: Option<FxHashMap<&str, FxHashSet<&str>>> = None;
 
-    for feature in target_feature_flag.split(',') {
+    for feature in sess.opts.cg.target_feature.split(',') {
         if let Some(base_feature) = feature.strip_prefix('+') {
             callback(base_feature, sess.target.implied_target_features(base_feature), true)
         } else if let Some(base_feature) = feature.strip_prefix('-') {
@@ -215,15 +214,13 @@ fn parse_rust_feature_flag<'a>(
 /// to populate `sess.unstable_target_features` and `sess.target_features` (these are the first and
 /// 2nd component of the return value, respectively).
 ///
-/// `target_feature_flag` is the value of `-Ctarget-feature` (giving the caller a chance to override it).
 /// `target_base_has_feature` should check whether the given feature (a Rust feature name!) is enabled
 /// in the "base" target machine, i.e., without applying `-Ctarget-feature`.
 ///
 /// We do not have to worry about RUSTC_SPECIFIC_FEATURES here, those are handled elsewhere.
 pub fn cfg_target_feature(
     sess: &Session,
-    target_feature_flag: &str,
-    mut is_feature_enabled: impl FnMut(&str) -> bool,
+    mut target_base_has_feature: impl FnMut(&str) -> bool,
 ) -> (Vec<Symbol>, Vec<Symbol>) {
     // Compute which of the known target features are enabled in the 'base' target machine. We only
     // consider "supported" features; "forbidden" features are not reflected in `cfg` as of now.
@@ -236,7 +233,7 @@ pub fn cfg_target_feature(
             if RUSTC_SPECIAL_FEATURES.contains(feature) {
                 return true;
             }
-            is_feature_enabled(feature)
+            target_base_has_feature(feature)
         })
         .map(|(feature, _, _)| Symbol::intern(feature))
         .collect();
@@ -244,7 +241,6 @@ pub fn cfg_target_feature(
     // Add enabled and remove disabled features.
     parse_rust_feature_flag(
         sess,
-        target_feature_flag,
         /* err_callback */ |_| {},
         |_base_feature, new_features, enabled| {
             // Iteration order is irrelevant since this only influences an `UnordSet`.
@@ -323,7 +319,6 @@ pub fn flag_to_backend_features<'a, const N: usize>(
     let mut rust_features = vec![];
     parse_rust_feature_flag(
         sess,
-        &sess.opts.cg.target_feature,
         /* err_callback */
         |feature| {
             if diagnostics {

From 0c4b0f57268b1e866eb7eb720caf815efcb85aeb Mon Sep 17 00:00:00 2001
From: Ralf Jung <post@ralfj.de>
Date: Thu, 12 Jun 2025 14:04:39 +0200
Subject: [PATCH 13/20] line-wrap and extend comments, typos

---
 compiler/rustc_codegen_llvm/src/llvm_util.rs  | 10 ++++----
 .../rustc_codegen_ssa/src/target_features.rs  | 25 ++++++++++++-------
 compiler/rustc_target/src/target_features.rs  |  1 +
 3 files changed, 22 insertions(+), 14 deletions(-)

diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs
index bc2165fa8290e..676890b36ebfd 100644
--- a/compiler/rustc_codegen_llvm/src/llvm_util.rs
+++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs
@@ -211,11 +211,11 @@ impl<'a> IntoIterator for LLVMFeature<'a> {
 /// To find a list of LLVM's names, see llvm-project/llvm/lib/Target/{ARCH}/*.td
 /// where `{ARCH}` is the architecture name. Look for instances of `SubtargetFeature`.
 ///
-/// Check the current rustc fork of LLVM in the repo at <https://github.com/rust-lang/llvm-project/>.
-/// The commit in use can be found via the `llvm-project` submodule in
-/// <https://github.com/rust-lang/rust/tree/master/src> Though note that Rust can also be build with
-/// an external precompiled version of LLVM which might lead to failures if the oldest tested /
-/// supported LLVM version doesn't yet support the relevant intrinsics.
+/// Check the current rustc fork of LLVM in the repo at
+/// <https://github.com/rust-lang/llvm-project/>. The commit in use can be found via the
+/// `llvm-project` submodule in <https://github.com/rust-lang/rust/tree/master/src> Though note that
+/// Rust can also be build with an external precompiled version of LLVM which might lead to failures
+/// if the oldest tested / supported LLVM version doesn't yet support the relevant intrinsics.
 pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option<LLVMFeature<'a>> {
     let arch = if sess.target.arch == "x86_64" {
         "x86"
diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs
index e29cdbb968ed2..f4f03c1a16bd4 100644
--- a/compiler/rustc_codegen_ssa/src/target_features.rs
+++ b/compiler/rustc_codegen_ssa/src/target_features.rs
@@ -178,8 +178,9 @@ fn parse_rust_feature_flag<'a>(
         if let Some(base_feature) = feature.strip_prefix('+') {
             callback(base_feature, sess.target.implied_target_features(base_feature), true)
         } else if let Some(base_feature) = feature.strip_prefix('-') {
-            // If `f1` implies `f2`, then `!f2` implies `!f1` -- this is standard logical contraposition.
-            // So we have to find all the reverse implications of `base_feature` and disable them, too.
+            // If `f1` implies `f2`, then `!f2` implies `!f1` -- this is standard logical
+            // contraposition. So we have to find all the reverse implications of `base_feature` and
+            // disable them, too.
 
             let inverse_implied_features = inverse_implied_features.get_or_insert_with(|| {
                 let mut set: FxHashMap<&str, FxHashSet<&str>> = FxHashMap::default();
@@ -191,7 +192,7 @@ fn parse_rust_feature_flag<'a>(
                 set
             });
 
-            // Inverse mplied target features have their own inverse implied target features, so we
+            // Inverse implied target features have their own inverse implied target features, so we
             // traverse the map until there are no more features to add.
             let mut features = FxHashSet::default();
             let mut new_features = vec![base_feature];
@@ -214,8 +215,8 @@ fn parse_rust_feature_flag<'a>(
 /// to populate `sess.unstable_target_features` and `sess.target_features` (these are the first and
 /// 2nd component of the return value, respectively).
 ///
-/// `target_base_has_feature` should check whether the given feature (a Rust feature name!) is enabled
-/// in the "base" target machine, i.e., without applying `-Ctarget-feature`.
+/// `target_base_has_feature` should check whether the given feature (a Rust feature name!) is
+/// enabled in the "base" target machine, i.e., without applying `-Ctarget-feature`.
 ///
 /// We do not have to worry about RUSTC_SPECIFIC_FEATURES here, those are handled elsewhere.
 pub fn cfg_target_feature(
@@ -231,6 +232,8 @@ pub fn cfg_target_feature(
         .filter(|(feature, _, _)| {
             // Skip checking special features, those are not known to the backend.
             if RUSTC_SPECIAL_FEATURES.contains(feature) {
+                // FIXME: `true` here means we'll always think the feature is enabled.
+                // Does that really make sense?
                 return true;
             }
             target_base_has_feature(feature)
@@ -241,7 +244,10 @@ pub fn cfg_target_feature(
     // Add enabled and remove disabled features.
     parse_rust_feature_flag(
         sess,
-        /* err_callback */ |_| {},
+        /* err_callback */
+        |_| {
+            // Errors are already emitted in `flag_to_backend_features`; avoid duplicates.
+        },
         |_base_feature, new_features, enabled| {
             // Iteration order is irrelevant since this only influences an `UnordSet`.
             #[allow(rustc::potential_query_instability)]
@@ -339,8 +345,8 @@ pub fn flag_to_backend_features<'a, const N: usize>(
                 let feature_state = known_features.iter().find(|&&(v, _, _)| v == base_feature);
                 match feature_state {
                     None => {
-                        // This is definitely not a valid Rust feature name. Maybe it is a backend feature name?
-                        // If so, give a better error message.
+                        // This is definitely not a valid Rust feature name. Maybe it is a backend
+                        // feature name? If so, give a better error message.
                         let rust_feature =
                             known_features.iter().find_map(|&(rust_feature, _, _)| {
                                 let backend_features = to_backend_features(rust_feature);
@@ -452,7 +458,8 @@ pub(crate) fn provide(providers: &mut Providers) {
                                     Stability::Unstable { .. } | Stability::Forbidden { .. },
                                 )
                                 | (Stability::Forbidden { .. }, Stability::Forbidden { .. }) => {
-                                    // The stability in the entry is at least as good as the new one, just keep it.
+                                    // The stability in the entry is at least as good as the new
+                                    // one, just keep it.
                                 }
                                 _ => {
                                     // Overwrite stabilite.
diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs
index c18a2c1ebef5b..f7582f8a34409 100644
--- a/compiler/rustc_target/src/target_features.rs
+++ b/compiler/rustc_target/src/target_features.rs
@@ -730,6 +730,7 @@ static LOONGARCH_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
 #[rustfmt::skip]
 const IBMZ_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
+    // For "backchain", https://github.com/rust-lang/rust/issues/142412 is a stabilization blocker
     ("backchain", Unstable(sym::s390x_target_feature), &[]),
     ("concurrent-functions", Unstable(sym::s390x_target_feature), &[]),
     ("deflate-conversion", Unstable(sym::s390x_target_feature), &[]),

From a50a3b8e318594c41783294e440d864763e412ef Mon Sep 17 00:00:00 2001
From: Ralf Jung <post@ralfj.de>
Date: Sat, 14 Jun 2025 14:11:00 +0200
Subject: [PATCH 14/20] various minor target feature cleanups

---
 compiler/rustc_codegen_gcc/src/gcc_util.rs    |  3 +-
 compiler/rustc_codegen_llvm/src/llvm_util.rs  | 22 +++++++-------
 .../rustc_codegen_ssa/src/target_features.rs  | 29 ++++++++-----------
 compiler/rustc_target/src/target_features.rs  | 20 +++----------
 tests/assembly/s390x-backchain-toggle.rs      |  5 +++-
 tests/ui/check-cfg/target_feature.stderr      |  1 -
 ...ine-target-feature-flag.by_feature1.stderr |  2 +-
 ...ine-target-feature-flag.by_feature2.stderr |  2 +-
 ...ine-target-feature-flag.by_feature3.stderr |  2 +-
 .../retpoline-target-feature-flag.rs          |  6 ++--
 10 files changed, 38 insertions(+), 54 deletions(-)

diff --git a/compiler/rustc_codegen_gcc/src/gcc_util.rs b/compiler/rustc_codegen_gcc/src/gcc_util.rs
index 562f412488296..42ba40692b75c 100644
--- a/compiler/rustc_codegen_gcc/src/gcc_util.rs
+++ b/compiler/rustc_codegen_gcc/src/gcc_util.rs
@@ -6,6 +6,7 @@ use smallvec::{SmallVec, smallvec};
 
 fn gcc_features_by_flags(sess: &Session, features: &mut Vec<String>) {
     target_features::retpoline_features_by_flags(sess, features);
+    // FIXME: LLVM also sets +reserve-x18 here under some conditions.
 }
 
 /// The list of GCC features computed from CLI flags (`-Ctarget-cpu`, `-Ctarget-feature`,
@@ -59,8 +60,6 @@ pub(crate) fn global_gcc_features(sess: &Session, diagnostics: bool) -> Vec<Stri
 
     gcc_features_by_flags(sess, &mut features);
 
-    // FIXME: LLVM also sets +reserve-x18 here under some conditions.
-
     features
 }
 
diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs
index 676890b36ebfd..6fd07d562afd8 100644
--- a/compiler/rustc_codegen_llvm/src/llvm_util.rs
+++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs
@@ -625,6 +625,15 @@ pub(crate) fn target_cpu(sess: &Session) -> &str {
 /// The target features for compiler flags other than `-Ctarget-features`.
 fn llvm_features_by_flags(sess: &Session, features: &mut Vec<String>) {
     target_features::retpoline_features_by_flags(sess, features);
+
+    // -Zfixed-x18
+    if sess.opts.unstable_opts.fixed_x18 {
+        if sess.target.arch != "aarch64" {
+            sess.dcx().emit_fatal(errors::FixedX18InvalidArch { arch: &sess.target.arch });
+        } else {
+            features.push("+reserve-x18".into());
+        }
+    }
 }
 
 /// The list of LLVM features computed from CLI flags (`-Ctarget-cpu`, `-Ctarget-feature`,
@@ -736,19 +745,10 @@ pub(crate) fn global_llvm_features(
                 )
             },
         );
-
-        llvm_features_by_flags(sess, &mut features);
     }
 
-    // -Zfixed-x18
-    // FIXME: merge with `llvm_features_by_flags`.
-    if sess.opts.unstable_opts.fixed_x18 {
-        if sess.target.arch != "aarch64" {
-            sess.dcx().emit_fatal(errors::FixedX18InvalidArch { arch: &sess.target.arch });
-        } else {
-            features.push("+reserve-x18".into());
-        }
-    }
+    // We add this in the "base target" so that these show up in `sess.unstable_target_features`.
+    llvm_features_by_flags(sess, &mut features);
 
     features
 }
diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs
index f4f03c1a16bd4..67ac619091bea 100644
--- a/compiler/rustc_codegen_ssa/src/target_features.rs
+++ b/compiler/rustc_codegen_ssa/src/target_features.rs
@@ -12,9 +12,7 @@ use rustc_session::Session;
 use rustc_session::lint::builtin::AARCH64_SOFTFLOAT_NEON;
 use rustc_session::parse::feature_err;
 use rustc_span::{Span, Symbol, sym};
-use rustc_target::target_features::{
-    self, RUSTC_SPECIAL_FEATURES, RUSTC_SPECIFIC_FEATURES, Stability,
-};
+use rustc_target::target_features::{self, RUSTC_SPECIFIC_FEATURES, Stability};
 use smallvec::SmallVec;
 
 use crate::errors;
@@ -176,8 +174,18 @@ fn parse_rust_feature_flag<'a>(
 
     for feature in sess.opts.cg.target_feature.split(',') {
         if let Some(base_feature) = feature.strip_prefix('+') {
+            // Skip features that are not target features, but rustc features.
+            if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) {
+                return;
+            }
+
             callback(base_feature, sess.target.implied_target_features(base_feature), true)
         } else if let Some(base_feature) = feature.strip_prefix('-') {
+            // Skip features that are not target features, but rustc features.
+            if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) {
+                return;
+            }
+
             // If `f1` implies `f2`, then `!f2` implies `!f1` -- this is standard logical
             // contraposition. So we have to find all the reverse implications of `base_feature` and
             // disable them, too.
@@ -229,15 +237,7 @@ pub fn cfg_target_feature(
         .target
         .rust_target_features()
         .iter()
-        .filter(|(feature, _, _)| {
-            // Skip checking special features, those are not known to the backend.
-            if RUSTC_SPECIAL_FEATURES.contains(feature) {
-                // FIXME: `true` here means we'll always think the feature is enabled.
-                // Does that really make sense?
-                return true;
-            }
-            target_base_has_feature(feature)
-        })
+        .filter(|(feature, _, _)| target_base_has_feature(feature))
         .map(|(feature, _, _)| Symbol::intern(feature))
         .collect();
 
@@ -332,11 +332,6 @@ pub fn flag_to_backend_features<'a, const N: usize>(
             }
         },
         |base_feature, new_features, enable| {
-            // Skip features that are meant for rustc, not the backend.
-            if RUSTC_SPECIFIC_FEATURES.contains(&base_feature) {
-                return;
-            }
-
             rust_features.extend(
                 UnordSet::from(new_features).to_sorted_stable_ord().iter().map(|&&s| (enable, s)),
             );
diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs
index f7582f8a34409..3eea1e070a669 100644
--- a/compiler/rustc_target/src/target_features.rs
+++ b/compiler/rustc_target/src/target_features.rs
@@ -11,11 +11,6 @@ use crate::spec::{FloatAbi, RustcAbi, Target};
 /// These exist globally and are not in the target-specific lists below.
 pub const RUSTC_SPECIFIC_FEATURES: &[&str] = &["crt-static"];
 
-/// Features that require special handling when passing to LLVM:
-/// these are target-specific (i.e., must also be listed in the target-specific list below)
-/// but do not correspond to an LLVM target feature.
-pub const RUSTC_SPECIAL_FEATURES: &[&str] = &["backchain"];
-
 /// Stability information for target features.
 #[derive(Debug, Copy, Clone)]
 pub enum Stability {
@@ -275,12 +270,7 @@ static AARCH64_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     ("rcpc3", Unstable(sym::aarch64_unstable_target_feature), &["rcpc2"]),
     // FEAT_RDM
     ("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", Forbidden { reason: "use `-Zfixed-x18` compiler flag instead" }, &[]),
     // FEAT_SB
     ("sb", Stable, &[]),
     // FEAT_SHA1 & FEAT_SHA256
@@ -455,19 +445,17 @@ static X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     ("rdseed", Stable, &[]),
     (
         "retpoline-external-thunk",
-        Stability::Forbidden {
-            reason: "use `retpoline-external-thunk` target modifier flag instead",
-        },
+        Stability::Forbidden { reason: "use `-Zretpoline-external-thunk` compiler flag instead" },
         &[],
     ),
     (
         "retpoline-indirect-branches",
-        Stability::Forbidden { reason: "use `retpoline` target modifier flag instead" },
+        Stability::Forbidden { reason: "use `-Zretpoline` compiler flag instead" },
         &[],
     ),
     (
         "retpoline-indirect-calls",
-        Stability::Forbidden { reason: "use `retpoline` target modifier flag instead" },
+        Stability::Forbidden { reason: "use `-Zretpoline` compiler flag instead" },
         &[],
     ),
     ("rtm", Unstable(sym::rtm_target_feature), &[]),
diff --git a/tests/assembly/s390x-backchain-toggle.rs b/tests/assembly/s390x-backchain-toggle.rs
index 83c7b82d0d4b3..9bae15b7d11bb 100644
--- a/tests/assembly/s390x-backchain-toggle.rs
+++ b/tests/assembly/s390x-backchain-toggle.rs
@@ -1,5 +1,5 @@
 //@ add-core-stubs
-//@ revisions: enable-backchain disable-backchain
+//@ revisions: enable-backchain disable-backchain default-backchain
 //@ assembly-output: emit-asm
 //@ compile-flags: -Copt-level=3 --crate-type=lib --target=s390x-unknown-linux-gnu
 //@ needs-llvm-components: systemz
@@ -26,6 +26,8 @@ extern "C" fn test_backchain() -> i32 {
     // enable-backchain: stg [[REG1]], 0(%r15)
     // disable-backchain: aghi %r15, -160
     // disable-backchain-NOT: stg %r{{.*}}, 0(%r15)
+    // default-backchain: aghi %r15, -160
+    // default-backchain-NOT: stg %r{{.*}}, 0(%r15)
     unsafe {
         extern_func();
     }
@@ -35,6 +37,7 @@ extern "C" fn test_backchain() -> i32 {
     // Make sure that the expected return value is written into %r2 (return register):
     // enable-backchain-NEXT: lghi %r2, 1
     // disable-backchain: lghi %r2, 0
+    // default-backchain: lghi %r2, 0
     #[cfg(target_feature = "backchain")]
     {
         1
diff --git a/tests/ui/check-cfg/target_feature.stderr b/tests/ui/check-cfg/target_feature.stderr
index ec81ba2e3d89f..f422919983b75 100644
--- a/tests/ui/check-cfg/target_feature.stderr
+++ b/tests/ui/check-cfg/target_feature.stderr
@@ -211,7 +211,6 @@ LL |     cfg!(target_feature = "_UNEXPECTED_VALUE");
 `reference-types`
 `relax`
 `relaxed-simd`
-`reserve-x18`
 `rtm`
 `sb`
 `scq`
diff --git a/tests/ui/target-feature/retpoline-target-feature-flag.by_feature1.stderr b/tests/ui/target-feature/retpoline-target-feature-flag.by_feature1.stderr
index 2a0f5f01aef6f..79e89823c5170 100644
--- a/tests/ui/target-feature/retpoline-target-feature-flag.by_feature1.stderr
+++ b/tests/ui/target-feature/retpoline-target-feature-flag.by_feature1.stderr
@@ -1,4 +1,4 @@
-warning: target feature `retpoline-external-thunk` cannot be enabled with `-Ctarget-feature`: use `retpoline-external-thunk` target modifier flag instead
+warning: target feature `retpoline-external-thunk` cannot be enabled with `-Ctarget-feature`: use `-Zretpoline-external-thunk` compiler flag instead
    |
    = 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/retpoline-target-feature-flag.by_feature2.stderr b/tests/ui/target-feature/retpoline-target-feature-flag.by_feature2.stderr
index f7b6cb1644778..f5ff15df63299 100644
--- a/tests/ui/target-feature/retpoline-target-feature-flag.by_feature2.stderr
+++ b/tests/ui/target-feature/retpoline-target-feature-flag.by_feature2.stderr
@@ -1,4 +1,4 @@
-warning: target feature `retpoline-indirect-branches` cannot be enabled with `-Ctarget-feature`: use `retpoline` target modifier flag instead
+warning: target feature `retpoline-indirect-branches` cannot be enabled with `-Ctarget-feature`: use `-Zretpoline` compiler flag instead
    |
    = 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/retpoline-target-feature-flag.by_feature3.stderr b/tests/ui/target-feature/retpoline-target-feature-flag.by_feature3.stderr
index 4f2cd1d1a522f..158cca08a7621 100644
--- a/tests/ui/target-feature/retpoline-target-feature-flag.by_feature3.stderr
+++ b/tests/ui/target-feature/retpoline-target-feature-flag.by_feature3.stderr
@@ -1,4 +1,4 @@
-warning: target feature `retpoline-indirect-calls` cannot be enabled with `-Ctarget-feature`: use `retpoline` target modifier flag instead
+warning: target feature `retpoline-indirect-calls` cannot be enabled with `-Ctarget-feature`: use `-Zretpoline` compiler flag instead
    |
    = 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/retpoline-target-feature-flag.rs b/tests/ui/target-feature/retpoline-target-feature-flag.rs
index de3c44c3ed0eb..05c85860385a7 100644
--- a/tests/ui/target-feature/retpoline-target-feature-flag.rs
+++ b/tests/ui/target-feature/retpoline-target-feature-flag.rs
@@ -16,6 +16,6 @@
 #![no_core]
 extern crate minicore;
 
-//[by_feature1]~? WARN target feature `retpoline-external-thunk` cannot be enabled with `-Ctarget-feature`: use `retpoline-external-thunk` target modifier flag instead
-//[by_feature2]~? WARN target feature `retpoline-indirect-branches` cannot be enabled with `-Ctarget-feature`: use `retpoline` target modifier flag instead
-//[by_feature3]~? WARN target feature `retpoline-indirect-calls` cannot be enabled with `-Ctarget-feature`: use `retpoline` target modifier flag instead
+//[by_feature1]~? WARN target feature `retpoline-external-thunk` cannot be enabled with `-Ctarget-feature`
+//[by_feature2]~? WARN target feature `retpoline-indirect-branches` cannot be enabled with `-Ctarget-feature`
+//[by_feature3]~? WARN target feature `retpoline-indirect-calls` cannot be enabled with `-Ctarget-feature`

From f45ab4f742e39cd963b2d926f188c35049575859 Mon Sep 17 00:00:00 2001
From: rustbot <47979223+rustbot@users.noreply.github.com>
Date: Thu, 19 Jun 2025 04:45:42 +0200
Subject: [PATCH 15/20] Update books

---
 src/doc/book            | 2 +-
 src/doc/reference       | 2 +-
 src/doc/rust-by-example | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/doc/book b/src/doc/book
index 4433c9f0cad84..8a6d44e45b7b5 160000
--- a/src/doc/book
+++ b/src/doc/book
@@ -1 +1 @@
-Subproject commit 4433c9f0cad8460bee05ede040587f8a1fa3f1de
+Subproject commit 8a6d44e45b7b564eeb6bae30507e1fbac439d72d
diff --git a/src/doc/reference b/src/doc/reference
index d4c66b346f4b7..50fc1628f3656 160000
--- a/src/doc/reference
+++ b/src/doc/reference
@@ -1 +1 @@
-Subproject commit d4c66b346f4b72d29e70390a3fa3ea7d4e064db1
+Subproject commit 50fc1628f36563958399123829c73755fa7a8421
diff --git a/src/doc/rust-by-example b/src/doc/rust-by-example
index 9baa9e863116c..05c7d8bae65f2 160000
--- a/src/doc/rust-by-example
+++ b/src/doc/rust-by-example
@@ -1 +1 @@
-Subproject commit 9baa9e863116cb9524a177d5a5c475baac18928a
+Subproject commit 05c7d8bae65f23a1837430c5a19be129d414f5ec

From 7760f8ec4297fc9a8bf319ffefff02ba9b17458c Mon Sep 17 00:00:00 2001
From: Deadbeef <ent3rm4n@gmail.com>
Date: Thu, 19 Jun 2025 18:31:56 +0800
Subject: [PATCH 16/20] add comment to `src/bootstrap/build.rs`

---
 src/bootstrap/build.rs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/bootstrap/build.rs b/src/bootstrap/build.rs
index e0e32d3135354..d9810e899a043 100644
--- a/src/bootstrap/build.rs
+++ b/src/bootstrap/build.rs
@@ -1,6 +1,7 @@
 use std::env;
 
 fn main() {
+    // this is needed because `HOST` is only available to build scripts.
     let host = env::var("HOST").unwrap();
     println!("cargo:rerun-if-changed=build.rs");
     println!("cargo:rustc-env=BUILD_TRIPLE={host}");

From ede48910fdb8956f4a23a3bb29ded754206a3ef2 Mon Sep 17 00:00:00 2001
From: Camille Gillot <gillot.camille@gmail.com>
Date: Thu, 19 Jun 2025 13:18:33 +0200
Subject: [PATCH 17/20] Update compiler/rustc_interface/src/passes.rs

Co-authored-by: bjorn3 <17426603+bjorn3@users.noreply.github.com>
---
 compiler/rustc_interface/src/passes.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs
index d29656fedd5b4..21bb8b1527114 100644
--- a/compiler/rustc_interface/src/passes.rs
+++ b/compiler/rustc_interface/src/passes.rs
@@ -1010,7 +1010,7 @@ fn run_required_analyses(tcx: TyCtxt<'_>) {
 
     // Prefetch this to prevent multiple threads from blocking on it later.
     // This is needed since the `hir_id_validator::check_crate` call above is not guaranteed
-    // to use `hir_crate`.
+    // to use `hir_crate_items`.
     tcx.ensure_done().hir_crate_items(());
 
     let sess = tcx.sess;

From ecdf220dbcd19ba15a2707231df4f25eef498906 Mon Sep 17 00:00:00 2001
From: Marijn Schouten <mhkbst@gmail.com>
Date: Thu, 19 Jun 2025 11:39:21 +0000
Subject: [PATCH 18/20] vec tests: remove static mut

---
 library/alloctests/testing/macros.rs    |  24 ++++-
 library/alloctests/tests/testing/mod.rs |   2 +
 library/alloctests/tests/vec.rs         | 119 ++++++------------------
 3 files changed, 53 insertions(+), 92 deletions(-)

diff --git a/library/alloctests/testing/macros.rs b/library/alloctests/testing/macros.rs
index 37cf59bc1693f..2433e53ca8954 100644
--- a/library/alloctests/testing/macros.rs
+++ b/library/alloctests/testing/macros.rs
@@ -1,13 +1,33 @@
 macro_rules! struct_with_counted_drop {
-    ($struct_name:ident$(($elt_ty:ty))?, $drop_counter:ident $(=> $drop_stmt:expr)?) => {
+    ($struct_name:ident $(( $( $elt_ty:ty ),+ ))?, $drop_counter:ident $( => $drop_stmt:expr )? ) => {
         thread_local! {static $drop_counter: ::core::cell::Cell<u32> = ::core::cell::Cell::new(0);}
 
-        struct $struct_name$(($elt_ty))?;
+        #[derive(Clone, Debug, PartialEq)]
+        struct $struct_name $(( $( $elt_ty ),+ ))?;
 
         impl ::std::ops::Drop for $struct_name {
             fn drop(&mut self) {
                 $drop_counter.set($drop_counter.get() + 1);
 
+                $($drop_stmt(self))?
+            }
+        }
+    };
+    ($struct_name:ident $(( $( $elt_ty:ty ),+ ))?, $drop_counter:ident[ $drop_key:expr,$key_ty:ty ] $( => $drop_stmt:expr )? ) => {
+        thread_local! {
+            static $drop_counter: ::core::cell::RefCell<::std::collections::HashMap<$key_ty, u32>> =
+                ::core::cell::RefCell::new(::std::collections::HashMap::new());
+        }
+
+        #[derive(Clone, Debug, PartialEq)]
+        struct $struct_name $(( $( $elt_ty ),+ ))?;
+
+        impl ::std::ops::Drop for $struct_name {
+            fn drop(&mut self) {
+                $drop_counter.with_borrow_mut(|counter| {
+                    *counter.entry($drop_key(self)).or_default() += 1;
+                });
+
                 $($drop_stmt(self))?
             }
         }
diff --git a/library/alloctests/tests/testing/mod.rs b/library/alloctests/tests/testing/mod.rs
index 0a3dd191dc891..f0911406cf990 100644
--- a/library/alloctests/tests/testing/mod.rs
+++ b/library/alloctests/tests/testing/mod.rs
@@ -1 +1,3 @@
 pub mod crash_test;
+#[path = "../../testing/macros.rs"]
+pub mod macros;
diff --git a/library/alloctests/tests/vec.rs b/library/alloctests/tests/vec.rs
index 51b49b8edb3f0..00f640cd17ea4 100644
--- a/library/alloctests/tests/vec.rs
+++ b/library/alloctests/tests/vec.rs
@@ -1,6 +1,3 @@
-// FIXME(static_mut_refs): Do not allow `static_mut_refs` lint
-#![allow(static_mut_refs)]
-
 use core::alloc::{Allocator, Layout};
 use core::num::NonZero;
 use core::ptr::NonNull;
@@ -20,6 +17,8 @@ use std::rc::Rc;
 use std::sync::atomic::{AtomicU32, Ordering};
 use std::vec::{Drain, IntoIter};
 
+use crate::testing::macros::struct_with_counted_drop;
+
 struct DropCounter<'a> {
     count: &'a mut u32,
 }
@@ -548,32 +547,25 @@ fn test_cmp() {
 
 #[test]
 fn test_vec_truncate_drop() {
-    static mut DROPS: u32 = 0;
-    struct Elem(#[allow(dead_code)] i32);
-    impl Drop for Elem {
-        fn drop(&mut self) {
-            unsafe {
-                DROPS += 1;
-            }
-        }
-    }
+    struct_with_counted_drop!(Elem(i32), DROPS);
 
     let mut v = vec![Elem(1), Elem(2), Elem(3), Elem(4), Elem(5)];
-    assert_eq!(unsafe { DROPS }, 0);
+
+    assert_eq!(DROPS.get(), 0);
     v.truncate(3);
-    assert_eq!(unsafe { DROPS }, 2);
+    assert_eq!(DROPS.get(), 2);
     v.truncate(0);
-    assert_eq!(unsafe { DROPS }, 5);
+    assert_eq!(DROPS.get(), 5);
 }
 
 #[test]
 #[should_panic]
 fn test_vec_truncate_fail() {
     struct BadElem(i32);
+
     impl Drop for BadElem {
         fn drop(&mut self) {
-            let BadElem(ref mut x) = *self;
-            if *x == 0xbadbeef {
+            if let BadElem(0xbadbeef) = self {
                 panic!("BadElem panic: 0xbadbeef")
             }
         }
@@ -812,22 +804,7 @@ fn test_drain_end_overflow() {
 #[test]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn test_drain_leak() {
-    static mut DROPS: i32 = 0;
-
-    #[derive(Debug, PartialEq)]
-    struct D(u32, bool);
-
-    impl Drop for D {
-        fn drop(&mut self) {
-            unsafe {
-                DROPS += 1;
-            }
-
-            if self.1 {
-                panic!("panic in `drop`");
-            }
-        }
-    }
+    struct_with_counted_drop!(D(u32, bool), DROPS => |this: &D| if this.1 { panic!("panic in `drop`"); });
 
     let mut v = vec![
         D(0, false),
@@ -844,7 +821,7 @@ fn test_drain_leak() {
     }))
     .ok();
 
-    assert_eq!(unsafe { DROPS }, 4);
+    assert_eq!(DROPS.get(), 4);
     assert_eq!(v, vec![D(0, false), D(1, false), D(6, false),]);
 }
 
@@ -1057,27 +1034,13 @@ fn test_into_iter_clone() {
 #[test]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn test_into_iter_leak() {
-    static mut DROPS: i32 = 0;
-
-    struct D(bool);
-
-    impl Drop for D {
-        fn drop(&mut self) {
-            unsafe {
-                DROPS += 1;
-            }
-
-            if self.0 {
-                panic!("panic in `drop`");
-            }
-        }
-    }
+    struct_with_counted_drop!(D(bool), DROPS => |this: &D| if this.0 { panic!("panic in `drop`"); });
 
     let v = vec![D(false), D(true), D(false)];
 
     catch_unwind(move || drop(v.into_iter())).ok();
 
-    assert_eq!(unsafe { DROPS }, 3);
+    assert_eq!(DROPS.get(), 3);
 }
 
 #[test]
@@ -1274,55 +1237,31 @@ fn test_from_iter_specialization_panic_during_iteration_drops() {
 
 #[test]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
-// FIXME(static_mut_refs): Do not allow `static_mut_refs` lint
-#[allow(static_mut_refs)]
 fn test_from_iter_specialization_panic_during_drop_doesnt_leak() {
-    static mut DROP_COUNTER_OLD: [usize; 5] = [0; 5];
-    static mut DROP_COUNTER_NEW: [usize; 2] = [0; 2];
-
-    #[derive(Debug)]
-    struct Old(usize);
-
-    impl Drop for Old {
-        fn drop(&mut self) {
-            unsafe {
-                DROP_COUNTER_OLD[self.0] += 1;
-            }
-
-            if self.0 == 3 {
-                panic!();
-            }
-
-            println!("Dropped Old: {}", self.0);
-        }
-    }
-
-    #[derive(Debug)]
-    struct New(usize);
-
-    impl Drop for New {
-        fn drop(&mut self) {
-            unsafe {
-                DROP_COUNTER_NEW[self.0] += 1;
+    struct_with_counted_drop!(
+        Old(usize), DROP_COUNTER_OLD[|this: &Old| this.0, usize] =>
+            |this: &Old| {
+                if this.0 == 3 { panic!(); } println!("Dropped Old: {}", this.0)
             }
-
-            println!("Dropped New: {}", self.0);
-        }
-    }
+    );
+    struct_with_counted_drop!(
+        New(usize), DROP_COUNTER_NEW[|this: &New| this.0, usize] =>
+            |this: &New| println!("Dropped New: {}", this.0)
+    );
 
     let _ = std::panic::catch_unwind(AssertUnwindSafe(|| {
         let v = vec![Old(0), Old(1), Old(2), Old(3), Old(4)];
         let _ = v.into_iter().map(|x| New(x.0)).take(2).collect::<Vec<_>>();
     }));
 
-    assert_eq!(unsafe { DROP_COUNTER_OLD[0] }, 1);
-    assert_eq!(unsafe { DROP_COUNTER_OLD[1] }, 1);
-    assert_eq!(unsafe { DROP_COUNTER_OLD[2] }, 1);
-    assert_eq!(unsafe { DROP_COUNTER_OLD[3] }, 1);
-    assert_eq!(unsafe { DROP_COUNTER_OLD[4] }, 1);
+    DROP_COUNTER_OLD.with_borrow(|c| assert_eq!(c.get(&0), Some(&1)));
+    DROP_COUNTER_OLD.with_borrow(|c| assert_eq!(c.get(&1), Some(&1)));
+    DROP_COUNTER_OLD.with_borrow(|c| assert_eq!(c.get(&2), Some(&1)));
+    DROP_COUNTER_OLD.with_borrow(|c| assert_eq!(c.get(&3), Some(&1)));
+    DROP_COUNTER_OLD.with_borrow(|c| assert_eq!(c.get(&4), Some(&1)));
 
-    assert_eq!(unsafe { DROP_COUNTER_NEW[0] }, 1);
-    assert_eq!(unsafe { DROP_COUNTER_NEW[1] }, 1);
+    DROP_COUNTER_NEW.with_borrow(|c| assert_eq!(c.get(&0), Some(&1)));
+    DROP_COUNTER_NEW.with_borrow(|c| assert_eq!(c.get(&1), Some(&1)));
 }
 
 // regression test for issue #85322. Peekable previously implemented InPlaceIterable,

From 456c9da45a7ccb0903f4d462d93ecffcb51b9d65 Mon Sep 17 00:00:00 2001
From: Marijn Schouten <mhkbst@gmail.com>
Date: Thu, 19 Jun 2025 11:51:47 +0000
Subject: [PATCH 19/20] vec_deque alloctests: remove static mut

---
 library/alloctests/tests/vec_deque.rs | 119 ++++----------------------
 1 file changed, 18 insertions(+), 101 deletions(-)

diff --git a/library/alloctests/tests/vec_deque.rs b/library/alloctests/tests/vec_deque.rs
index b77ea3a312bef..a82906d55e5d0 100644
--- a/library/alloctests/tests/vec_deque.rs
+++ b/library/alloctests/tests/vec_deque.rs
@@ -1,6 +1,3 @@
-// FIXME(static_mut_refs): Do not allow `static_mut_refs` lint
-#![allow(static_mut_refs)]
-
 use core::num::NonZero;
 use std::assert_matches::assert_matches;
 use std::collections::TryReserveErrorKind::*;
@@ -14,6 +11,7 @@ use Taggy::*;
 use Taggypar::*;
 
 use crate::hash;
+use crate::testing::macros::struct_with_counted_drop;
 
 #[test]
 fn test_simple() {
@@ -719,15 +717,7 @@ fn test_show() {
 
 #[test]
 fn test_drop() {
-    static mut DROPS: i32 = 0;
-    struct Elem;
-    impl Drop for Elem {
-        fn drop(&mut self) {
-            unsafe {
-                DROPS += 1;
-            }
-        }
-    }
+    struct_with_counted_drop!(Elem, DROPS);
 
     let mut ring = VecDeque::new();
     ring.push_back(Elem);
@@ -736,20 +726,12 @@ fn test_drop() {
     ring.push_front(Elem);
     drop(ring);
 
-    assert_eq!(unsafe { DROPS }, 4);
+    assert_eq!(DROPS.get(), 4);
 }
 
 #[test]
 fn test_drop_with_pop() {
-    static mut DROPS: i32 = 0;
-    struct Elem;
-    impl Drop for Elem {
-        fn drop(&mut self) {
-            unsafe {
-                DROPS += 1;
-            }
-        }
-    }
+    struct_with_counted_drop!(Elem, DROPS);
 
     let mut ring = VecDeque::new();
     ring.push_back(Elem);
@@ -759,23 +741,15 @@ fn test_drop_with_pop() {
 
     drop(ring.pop_back());
     drop(ring.pop_front());
-    assert_eq!(unsafe { DROPS }, 2);
+    assert_eq!(DROPS.get(), 2);
 
     drop(ring);
-    assert_eq!(unsafe { DROPS }, 4);
+    assert_eq!(DROPS.get(), 4);
 }
 
 #[test]
 fn test_drop_clear() {
-    static mut DROPS: i32 = 0;
-    struct Elem;
-    impl Drop for Elem {
-        fn drop(&mut self) {
-            unsafe {
-                DROPS += 1;
-            }
-        }
-    }
+    struct_with_counted_drop!(Elem, DROPS);
 
     let mut ring = VecDeque::new();
     ring.push_back(Elem);
@@ -783,30 +757,16 @@ fn test_drop_clear() {
     ring.push_back(Elem);
     ring.push_front(Elem);
     ring.clear();
-    assert_eq!(unsafe { DROPS }, 4);
+    assert_eq!(DROPS.get(), 4);
 
     drop(ring);
-    assert_eq!(unsafe { DROPS }, 4);
+    assert_eq!(DROPS.get(), 4);
 }
 
 #[test]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn test_drop_panic() {
-    static mut DROPS: i32 = 0;
-
-    struct D(bool);
-
-    impl Drop for D {
-        fn drop(&mut self) {
-            unsafe {
-                DROPS += 1;
-            }
-
-            if self.0 {
-                panic!("panic in `drop`");
-            }
-        }
-    }
+    struct_with_counted_drop!(D(bool), DROPS => |this: &D| if this.0 { panic!("panic in `drop`"); } );
 
     let mut q = VecDeque::new();
     q.push_back(D(false));
@@ -820,7 +780,7 @@ fn test_drop_panic() {
 
     catch_unwind(move || drop(q)).ok();
 
-    assert_eq!(unsafe { DROPS }, 8);
+    assert_eq!(DROPS.get(), 8);
 }
 
 #[test]
@@ -1655,21 +1615,7 @@ fn test_try_rfold_moves_iter() {
 #[test]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn truncate_leak() {
-    static mut DROPS: i32 = 0;
-
-    struct D(bool);
-
-    impl Drop for D {
-        fn drop(&mut self) {
-            unsafe {
-                DROPS += 1;
-            }
-
-            if self.0 {
-                panic!("panic in `drop`");
-            }
-        }
-    }
+    struct_with_counted_drop!(D(bool), DROPS => |this: &D| if this.0 { panic!("panic in `drop`"); } );
 
     let mut q = VecDeque::new();
     q.push_back(D(false));
@@ -1683,27 +1629,13 @@ fn truncate_leak() {
 
     catch_unwind(AssertUnwindSafe(|| q.truncate(1))).ok();
 
-    assert_eq!(unsafe { DROPS }, 7);
+    assert_eq!(DROPS.get(), 7);
 }
 
 #[test]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn truncate_front_leak() {
-    static mut DROPS: i32 = 0;
-
-    struct D(bool);
-
-    impl Drop for D {
-        fn drop(&mut self) {
-            unsafe {
-                DROPS += 1;
-            }
-
-            if self.0 {
-                panic!("panic in `drop`");
-            }
-        }
-    }
+    struct_with_counted_drop!(D(bool), DROPS => |this: &D| if this.0 { panic!("panic in `drop`"); } );
 
     let mut q = VecDeque::new();
     q.push_back(D(false));
@@ -1717,28 +1649,13 @@ fn truncate_front_leak() {
 
     catch_unwind(AssertUnwindSafe(|| q.truncate_front(1))).ok();
 
-    assert_eq!(unsafe { DROPS }, 7);
+    assert_eq!(DROPS.get(), 7);
 }
 
 #[test]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn test_drain_leak() {
-    static mut DROPS: i32 = 0;
-
-    #[derive(Debug, PartialEq)]
-    struct D(u32, bool);
-
-    impl Drop for D {
-        fn drop(&mut self) {
-            unsafe {
-                DROPS += 1;
-            }
-
-            if self.1 {
-                panic!("panic in `drop`");
-            }
-        }
-    }
+    struct_with_counted_drop!(D(u32, bool), DROPS => |this: &D| if this.1 { panic!("panic in `drop`"); } );
 
     let mut v = VecDeque::new();
     v.push_back(D(4, false));
@@ -1754,10 +1671,10 @@ fn test_drain_leak() {
     }))
     .ok();
 
-    assert_eq!(unsafe { DROPS }, 4);
+    assert_eq!(DROPS.get(), 4);
     assert_eq!(v.len(), 3);
     drop(v);
-    assert_eq!(unsafe { DROPS }, 7);
+    assert_eq!(DROPS.get(), 7);
 }
 
 #[test]

From 9c2276818369d20e683497459b85e93d9c5aee84 Mon Sep 17 00:00:00 2001
From: Marijn Schouten <mhkbst@gmail.com>
Date: Thu, 19 Jun 2025 12:08:24 +0000
Subject: [PATCH 20/20] atomic tests: remove static mut

---
 library/coretests/tests/atomic.rs | 30 +++++++++++++-----------------
 1 file changed, 13 insertions(+), 17 deletions(-)

diff --git a/library/coretests/tests/atomic.rs b/library/coretests/tests/atomic.rs
index e0c0fe4790c04..b1ab443aa6e5e 100644
--- a/library/coretests/tests/atomic.rs
+++ b/library/coretests/tests/atomic.rs
@@ -228,24 +228,20 @@ fn static_init() {
 }
 
 #[test]
-// FIXME(static_mut_refs): Do not allow `static_mut_refs` lint
-#[allow(static_mut_refs)]
 fn atomic_access_bool() {
-    static mut ATOMIC: AtomicBool = AtomicBool::new(false);
-
-    unsafe {
-        assert_eq!(*ATOMIC.get_mut(), false);
-        ATOMIC.store(true, SeqCst);
-        assert_eq!(*ATOMIC.get_mut(), true);
-        ATOMIC.fetch_or(false, SeqCst);
-        assert_eq!(*ATOMIC.get_mut(), true);
-        ATOMIC.fetch_and(false, SeqCst);
-        assert_eq!(*ATOMIC.get_mut(), false);
-        ATOMIC.fetch_nand(true, SeqCst);
-        assert_eq!(*ATOMIC.get_mut(), true);
-        ATOMIC.fetch_xor(true, SeqCst);
-        assert_eq!(*ATOMIC.get_mut(), false);
-    }
+    let mut atom = AtomicBool::new(false);
+
+    assert_eq!(*atom.get_mut(), false);
+    atom.store(true, SeqCst);
+    assert_eq!(*atom.get_mut(), true);
+    atom.fetch_or(false, SeqCst);
+    assert_eq!(*atom.get_mut(), true);
+    atom.fetch_and(false, SeqCst);
+    assert_eq!(*atom.get_mut(), false);
+    atom.fetch_nand(true, SeqCst);
+    assert_eq!(*atom.get_mut(), true);
+    atom.fetch_xor(true, SeqCst);
+    assert_eq!(*atom.get_mut(), false);
 }
 
 #[test]