From 981dc02eaf876a25b95581411e841ba664dc9e97 Mon Sep 17 00:00:00 2001
From: Nicholas Nethercote <n.nethercote@gmail.com>
Date: Mon, 4 Nov 2024 15:41:32 +1100
Subject: [PATCH 01/15] Revert "Avoid nested replacement ranges" from #129346.

It caused a test regression in the `cfg_eval.rs` crate. (The bugfix
in #129346 was in a different commit; this commit was just a code
simplification.)
---
 .../rustc_parse/src/parser/attr_wrapper.rs    | 28 ++++++++++++++-----
 1 file changed, 21 insertions(+), 7 deletions(-)

diff --git a/compiler/rustc_parse/src/parser/attr_wrapper.rs b/compiler/rustc_parse/src/parser/attr_wrapper.rs
index 8bd615e6d7913..c85d0bd05cbd6 100644
--- a/compiler/rustc_parse/src/parser/attr_wrapper.rs
+++ b/compiler/rustc_parse/src/parser/attr_wrapper.rs
@@ -136,8 +136,9 @@ impl ToAttrTokenStream for LazyAttrTokenStreamImpl {
                 node_replacements.array_windows()
             {
                 assert!(
-                    node_range.0.end <= next_node_range.0.start,
-                    "Node ranges should be disjoint: ({:?}, {:?}) ({:?}, {:?})",
+                    node_range.0.end <= next_node_range.0.start
+                        || node_range.0.end >= next_node_range.0.end,
+                    "Node ranges should be disjoint or nested: ({:?}, {:?}) ({:?}, {:?})",
                     node_range,
                     tokens,
                     next_node_range,
@@ -145,8 +146,20 @@ impl ToAttrTokenStream for LazyAttrTokenStreamImpl {
                 );
             }
 
-            // Process the replace ranges.
-            for (node_range, target) in node_replacements.into_iter() {
+            // Process the replace ranges, starting from the highest start
+            // position and working our way back. If have tokens like:
+            //
+            // `#[cfg(FALSE)] struct Foo { #[cfg(FALSE)] field: bool }`
+            //
+            // Then we will generate replace ranges for both
+            // the `#[cfg(FALSE)] field: bool` and the entire
+            // `#[cfg(FALSE)] struct Foo { #[cfg(FALSE)] field: bool }`
+            //
+            // By starting processing from the replace range with the greatest
+            // start position, we ensure that any (outer) replace range which
+            // encloses another (inner) replace range will fully overwrite the
+            // inner range's replacement.
+            for (node_range, target) in node_replacements.into_iter().rev() {
                 assert!(
                     !node_range.0.is_empty(),
                     "Cannot replace an empty node range: {:?}",
@@ -383,9 +396,10 @@ impl<'a> Parser<'a> {
             // from `ParserRange` form to `NodeRange` form. We will perform the actual
             // replacement only when we convert the `LazyAttrTokenStream` to an
             // `AttrTokenStream`.
-            self.capture_state
-                .parser_replacements
-                .drain(parser_replacements_start..parser_replacements_end)
+            self.capture_state.parser_replacements
+                [parser_replacements_start..parser_replacements_end]
+                .iter()
+                .cloned()
                 .chain(inner_attr_parser_replacements)
                 .map(|(parser_range, data)| {
                     (NodeRange::new(parser_range, collect_pos.start_pos), data)

From 560556827cd0d64ea45209a76f0cd7f9bc463949 Mon Sep 17 00:00:00 2001
From: Ralf Jung <post@ralfj.de>
Date: Sun, 3 Nov 2024 19:58:57 +0100
Subject: [PATCH 02/15] add const_eval_select macro to reduce redundancy

---
 library/core/src/ffi/c_str.rs     |  99 +++++++++++------------
 library/core/src/intrinsics.rs    |  80 ++++++++++++++++---
 library/core/src/macros/mod.rs    |  23 +++---
 library/core/src/ptr/const_ptr.rs | 128 ++++++++++++++----------------
 library/core/src/ub_checks.rs     |  65 ++++++++-------
 5 files changed, 219 insertions(+), 176 deletions(-)

diff --git a/library/core/src/ffi/c_str.rs b/library/core/src/ffi/c_str.rs
index 4ea5cbf862645..b57ead374720e 100644
--- a/library/core/src/ffi/c_str.rs
+++ b/library/core/src/ffi/c_str.rs
@@ -3,11 +3,12 @@
 use crate::cmp::Ordering;
 use crate::error::Error;
 use crate::ffi::c_char;
+use crate::intrinsics::const_eval_select;
 use crate::iter::FusedIterator;
 use crate::marker::PhantomData;
 use crate::ptr::NonNull;
 use crate::slice::memchr;
-use crate::{fmt, intrinsics, ops, slice, str};
+use crate::{fmt, ops, slice, str};
 
 // FIXME: because this is doc(inline)d, we *have* to use intra-doc links because the actual link
 //   depends on where the item is being documented. however, since this is libcore, we can't
@@ -411,37 +412,35 @@ impl CStr {
     #[rustc_const_stable(feature = "const_cstr_unchecked", since = "1.59.0")]
     #[rustc_allow_const_fn_unstable(const_eval_select)]
     pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> &CStr {
-        #[inline]
-        fn rt_impl(bytes: &[u8]) -> &CStr {
-            // Chance at catching some UB at runtime with debug builds.
-            debug_assert!(!bytes.is_empty() && bytes[bytes.len() - 1] == 0);
-
-            // SAFETY: Casting to CStr is safe because its internal representation
-            // is a [u8] too (safe only inside std).
-            // Dereferencing the obtained pointer is safe because it comes from a
-            // reference. Making a reference is then safe because its lifetime
-            // is bound by the lifetime of the given `bytes`.
-            unsafe { &*(bytes as *const [u8] as *const CStr) }
-        }
-
-        const fn const_impl(bytes: &[u8]) -> &CStr {
-            // Saturating so that an empty slice panics in the assert with a good
-            // message, not here due to underflow.
-            let mut i = bytes.len().saturating_sub(1);
-            assert!(!bytes.is_empty() && bytes[i] == 0, "input was not nul-terminated");
-
-            // Ending nul byte exists, skip to the rest.
-            while i != 0 {
-                i -= 1;
-                let byte = bytes[i];
-                assert!(byte != 0, "input contained interior nul");
+        const_eval_select!(
+            (bytes: &[u8]) -> &CStr:
+            if const {
+                // Saturating so that an empty slice panics in the assert with a good
+                // message, not here due to underflow.
+                let mut i = bytes.len().saturating_sub(1);
+                assert!(!bytes.is_empty() && bytes[i] == 0, "input was not nul-terminated");
+
+                // Ending nul byte exists, skip to the rest.
+                while i != 0 {
+                    i -= 1;
+                    let byte = bytes[i];
+                    assert!(byte != 0, "input contained interior nul");
+                }
+
+                // SAFETY: See runtime cast comment below.
+                unsafe { &*(bytes as *const [u8] as *const CStr) }
+            } else #[inline] {
+                // Chance at catching some UB at runtime with debug builds.
+                debug_assert!(!bytes.is_empty() && bytes[bytes.len() - 1] == 0);
+
+                // SAFETY: Casting to CStr is safe because its internal representation
+                // is a [u8] too (safe only inside std).
+                // Dereferencing the obtained pointer is safe because it comes from a
+                // reference. Making a reference is then safe because its lifetime
+                // is bound by the lifetime of the given `bytes`.
+                unsafe { &*(bytes as *const [u8] as *const CStr) }
             }
-
-            // SAFETY: See `rt_impl` cast.
-            unsafe { &*(bytes as *const [u8] as *const CStr) }
-        }
-
-        intrinsics::const_eval_select((bytes,), const_impl, rt_impl)
+        )
     }
 
     /// Returns the inner pointer to this C string.
@@ -735,29 +734,27 @@ impl AsRef<CStr> for CStr {
 #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_cstr_from_ptr", since = "1.81.0"))]
 #[rustc_allow_const_fn_unstable(const_eval_select)]
 const unsafe fn strlen(ptr: *const c_char) -> usize {
-    const fn strlen_ct(s: *const c_char) -> usize {
-        let mut len = 0;
-
-        // SAFETY: Outer caller has provided a pointer to a valid C string.
-        while unsafe { *s.add(len) } != 0 {
-            len += 1;
-        }
+    const_eval_select!(
+        (s: *const c_char = ptr) -> usize:
+        if const {
+            let mut len = 0;
+
+            // SAFETY: Outer caller has provided a pointer to a valid C string.
+            while unsafe { *s.add(len) } != 0 {
+                len += 1;
+            }
 
-        len
-    }
+            len
+        } else #[inline] {
+            extern "C" {
+                /// Provided by libc or compiler_builtins.
+                fn strlen(s: *const c_char) -> usize;
+            }
 
-    #[inline]
-    fn strlen_rt(s: *const c_char) -> usize {
-        extern "C" {
-            /// Provided by libc or compiler_builtins.
-            fn strlen(s: *const c_char) -> usize;
+            // SAFETY: Outer caller has provided a pointer to a valid C string.
+            unsafe { strlen(s) }
         }
-
-        // SAFETY: Outer caller has provided a pointer to a valid C string.
-        unsafe { strlen(s) }
-    }
-
-    intrinsics::const_eval_select((ptr,), strlen_ct, strlen_rt)
+    )
 }
 
 /// An iterator over the bytes of a [`CStr`], without the nul terminator.
diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs
index fc09da7bcbc65..d82d911426427 100644
--- a/library/core/src/intrinsics.rs
+++ b/library/core/src/intrinsics.rs
@@ -2788,6 +2788,65 @@ where
     unreachable!()
 }
 
+/// A macro to make it easier to invoke const_eval_select. Use as follows:
+/// ```rust,ignore (just a macro example)
+/// const_eval_select!(
+///     #[inline]
+///     (arg1: i32 = some_expr, arg2: T = other_expr) -> U:
+///     if const {
+///         // Compile-time code goes here.
+///     } else {
+///         // Run-time code goes here.
+///     }
+/// )
+/// ```
+pub(crate) macro const_eval_select {
+    (
+        $(#[$attr:meta])*
+        ($($arg:ident : $ty:ty = $val:expr),* $(,)?) $( -> $ret:ty )?:
+        if const
+            $(#[$compiletime_attr:meta])* $compiletime:block
+        else
+            $(#[$runtime_attr:meta])* $runtime:block
+    ) => {{
+        $(#[$attr])*
+        $(#[$runtime_attr])*
+        fn runtime($($arg: $ty),*) $( -> $ret )? {
+            $runtime
+        }
+
+        $(#[$attr])*
+        $(#[$compiletime_attr])*
+        const fn compiletime($($arg: $ty),*) $( -> $ret )? {
+            // Don't warn if one of the arguments is unused.
+            $(let _ = $arg;)*
+
+            $compiletime
+        }
+
+        const_eval_select(($($val,)*), compiletime, runtime)
+    }},
+    // We support leaving away the `val` expressions for *all* arguments
+    // (but not for *some* arguments, that's too tricky).
+    (
+        $(#[$attr:meta])*
+        ($($arg:ident : $ty:ty),* $(,)?) -> $ret:ty:
+        if const
+            $(#[$compiletime_attr:meta])* $compiletime:block
+        else
+            $(#[$runtime_attr:meta])* $runtime:block
+    ) => {
+        $crate::intrinsics::const_eval_select!(
+            $(#[$attr])*
+            ($($arg : $ty = $arg),*) -> $ret:
+            if const
+                $(#[$compiletime_attr])* $compiletime
+            else
+                $(#[$runtime_attr])* $runtime
+        )
+    },
+}
+
 /// Returns whether the argument's value is statically known at
 /// compile-time.
 ///
@@ -2830,7 +2889,7 @@ where
 /// # Stability concerns
 ///
 /// While it is safe to call, this intrinsic may behave differently in
-/// a `const` context than otherwise. See the [`const_eval_select`]
+/// a `const` context than otherwise. See the [`const_eval_select()`]
 /// documentation for an explanation of the issues this can cause. Unlike
 /// `const_eval_select`, this intrinsic isn't guaranteed to behave
 /// deterministically even in a `const` context.
@@ -3734,14 +3793,15 @@ pub(crate) const fn miri_promise_symbolic_alignment(ptr: *const (), align: usize
         fn miri_promise_symbolic_alignment(ptr: *const (), align: usize);
     }
 
-    fn runtime(ptr: *const (), align: usize) {
-        // SAFETY: this call is always safe.
-        unsafe {
-            miri_promise_symbolic_alignment(ptr, align);
+    const_eval_select!(
+        (ptr: *const (), align: usize):
+        if const {
+            // Do nothing.
+        } else {
+            // SAFETY: this call is always safe.
+            unsafe {
+                miri_promise_symbolic_alignment(ptr, align);
+            }
         }
-    }
-
-    const fn compiletime(_ptr: *const (), _align: usize) {}
-
-    const_eval_select((ptr, align), compiletime, runtime);
+    )
 }
diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs
index 9a91ff82acd7c..c0bf798fb7069 100644
--- a/library/core/src/macros/mod.rs
+++ b/library/core/src/macros/mod.rs
@@ -24,18 +24,6 @@ macro_rules! panic {
 #[doc(hidden)]
 pub macro const_panic {
     ($const_msg:literal, $runtime_msg:literal, $($arg:ident : $ty:ty = $val:expr),* $(,)?) => {{
-        #[inline]
-        #[track_caller]
-        fn runtime($($arg: $ty),*) -> ! {
-            $crate::panic!($runtime_msg);
-        }
-
-        #[inline]
-        #[track_caller]
-        const fn compiletime($(_: $ty),*) -> ! {
-            $crate::panic!($const_msg);
-        }
-
         // Wrap call to `const_eval_select` in a function so that we can
         // add the `rustc_allow_const_fn_unstable`. This is okay to do
         // because both variants will panic, just with different messages.
@@ -44,7 +32,16 @@ pub macro const_panic {
         #[track_caller]
         #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_panic", since = "CURRENT_RUSTC_VERSION"))]
         const fn do_panic($($arg: $ty),*) -> ! {
-            $crate::intrinsics::const_eval_select(($($arg),* ,), compiletime, runtime)
+            $crate::intrinsics::const_eval_select!(
+                #[inline]
+                #[track_caller]
+                ($($arg: $ty),*) -> !:
+                if const {
+                    $crate::panic!($const_msg)
+                } else {
+                    $crate::panic!($runtime_msg)
+                }
+            )
         }
 
         do_panic($($val),*)
diff --git a/library/core/src/ptr/const_ptr.rs b/library/core/src/ptr/const_ptr.rs
index a4e8e373e041e..7c5fa6b4e9072 100644
--- a/library/core/src/ptr/const_ptr.rs
+++ b/library/core/src/ptr/const_ptr.rs
@@ -33,26 +33,24 @@ impl<T: ?Sized> *const T {
     #[rustc_diagnostic_item = "ptr_const_is_null"]
     #[inline]
     pub const fn is_null(self) -> bool {
-        #[inline]
-        fn runtime_impl(ptr: *const u8) -> bool {
-            ptr.addr() == 0
-        }
-
-        #[inline]
-        #[rustc_const_unstable(feature = "const_ptr_is_null", issue = "74939")]
-        const fn const_impl(ptr: *const u8) -> bool {
-            match (ptr).guaranteed_eq(null_mut()) {
-                Some(res) => res,
-                // To remain maximally convervative, we stop execution when we don't
-                // know whether the pointer is null or not.
-                // We can *not* return `false` here, that would be unsound in `NonNull::new`!
-                None => panic!("null-ness of this pointer cannot be determined in const context"),
-            }
-        }
-
         // Compare via a cast to a thin pointer, so fat pointers are only
         // considering their "data" part for null-ness.
-        const_eval_select((self as *const u8,), const_impl, runtime_impl)
+        let ptr = self as *const u8;
+        const_eval_select!(
+            #[inline]
+            (ptr: *const u8) -> bool:
+            if const #[rustc_const_unstable(feature = "const_ptr_is_null", issue = "74939")] {
+                match (ptr).guaranteed_eq(null_mut()) {
+                    Some(res) => res,
+                    // To remain maximally convervative, we stop execution when we don't
+                    // know whether the pointer is null or not.
+                    // We can *not* return `false` here, that would be unsound in `NonNull::new`!
+                    None => panic!("null-ness of this pointer cannot be determined in const context"),
+                }
+            } else {
+                ptr.addr() == 0
+            }
+        )
     }
 
     /// Casts to a pointer of another type.
@@ -410,22 +408,20 @@ impl<T: ?Sized> *const T {
         #[inline]
         #[rustc_allow_const_fn_unstable(const_eval_select)]
         const fn runtime_offset_nowrap(this: *const (), count: isize, size: usize) -> bool {
-            #[inline]
-            fn runtime(this: *const (), count: isize, size: usize) -> bool {
-                // We know `size <= isize::MAX` so the `as` cast here is not lossy.
-                let Some(byte_offset) = count.checked_mul(size as isize) else {
-                    return false;
-                };
-                let (_, overflow) = this.addr().overflowing_add_signed(byte_offset);
-                !overflow
-            }
-
-            const fn comptime(_: *const (), _: isize, _: usize) -> bool {
-                true
-            }
-
             // We can use const_eval_select here because this is only for UB checks.
-            intrinsics::const_eval_select((this, count, size), comptime, runtime)
+            const_eval_select!(
+                (this: *const (), count: isize, size: usize) -> bool:
+                if const {
+                    true
+                } else #[inline] {
+                    // We know `size <= isize::MAX` so the `as` cast here is not lossy.
+                    let Some(byte_offset) = count.checked_mul(size as isize) else {
+                        return false;
+                    };
+                    let (_, overflow) = this.addr().overflowing_add_signed(byte_offset);
+                    !overflow
+                }
+            )
         }
 
         ub_checks::assert_unsafe_precondition!(
@@ -763,14 +759,14 @@ impl<T: ?Sized> *const T {
     {
         #[rustc_allow_const_fn_unstable(const_eval_select)]
         const fn runtime_ptr_ge(this: *const (), origin: *const ()) -> bool {
-            fn runtime(this: *const (), origin: *const ()) -> bool {
-                this >= origin
-            }
-            const fn comptime(_: *const (), _: *const ()) -> bool {
-                true
-            }
-
-            intrinsics::const_eval_select((this, origin), comptime, runtime)
+            const_eval_select!(
+                (this: *const (), origin: *const ()) -> bool:
+                if const {
+                    true
+                } else {
+                    this >= origin
+                }
+            )
         }
 
         ub_checks::assert_unsafe_precondition!(
@@ -924,20 +920,18 @@ impl<T: ?Sized> *const T {
         #[inline]
         #[rustc_allow_const_fn_unstable(const_eval_select)]
         const fn runtime_add_nowrap(this: *const (), count: usize, size: usize) -> bool {
-            #[inline]
-            fn runtime(this: *const (), count: usize, size: usize) -> bool {
-                let Some(byte_offset) = count.checked_mul(size) else {
-                    return false;
-                };
-                let (_, overflow) = this.addr().overflowing_add(byte_offset);
-                byte_offset <= (isize::MAX as usize) && !overflow
-            }
-
-            const fn comptime(_: *const (), _: usize, _: usize) -> bool {
-                true
-            }
-
-            intrinsics::const_eval_select((this, count, size), comptime, runtime)
+            const_eval_select!(
+                (this: *const (), count: usize, size: usize) -> bool:
+                if const {
+                    true
+                } else #[inline] {
+                    let Some(byte_offset) = count.checked_mul(size) else {
+                        return false;
+                    };
+                    let (_, overflow) = this.addr().overflowing_add(byte_offset);
+                    byte_offset <= (isize::MAX as usize) && !overflow
+                }
+            )
         }
 
         #[cfg(debug_assertions)] // Expensive, and doesn't catch much in the wild.
@@ -1033,19 +1027,17 @@ impl<T: ?Sized> *const T {
         #[inline]
         #[rustc_allow_const_fn_unstable(const_eval_select)]
         const fn runtime_sub_nowrap(this: *const (), count: usize, size: usize) -> bool {
-            #[inline]
-            fn runtime(this: *const (), count: usize, size: usize) -> bool {
-                let Some(byte_offset) = count.checked_mul(size) else {
-                    return false;
-                };
-                byte_offset <= (isize::MAX as usize) && this.addr() >= byte_offset
-            }
-
-            const fn comptime(_: *const (), _: usize, _: usize) -> bool {
-                true
-            }
-
-            intrinsics::const_eval_select((this, count, size), comptime, runtime)
+            const_eval_select!(
+                (this: *const (), count: usize, size: usize) -> bool:
+                if const {
+                    true
+                } else #[inline] {
+                    let Some(byte_offset) = count.checked_mul(size) else {
+                        return false;
+                    };
+                    byte_offset <= (isize::MAX as usize) && this.addr() >= byte_offset
+                }
+            )
         }
 
         #[cfg(debug_assertions)] // Expensive, and doesn't catch much in the wild.
diff --git a/library/core/src/ub_checks.rs b/library/core/src/ub_checks.rs
index dd1454f401eaf..972d9ae26d2bf 100644
--- a/library/core/src/ub_checks.rs
+++ b/library/core/src/ub_checks.rs
@@ -95,20 +95,19 @@ pub use intrinsics::ub_checks as check_library_ub;
 #[inline]
 #[rustc_allow_const_fn_unstable(const_eval_select)]
 pub(crate) const fn check_language_ub() -> bool {
-    #[inline]
-    fn runtime() -> bool {
-        // Disable UB checks in Miri.
-        !cfg!(miri)
-    }
-
-    #[inline]
-    const fn comptime() -> bool {
-        // Always disable UB checks.
-        false
-    }
-
     // Only used for UB checks so we may const_eval_select.
-    intrinsics::ub_checks() && const_eval_select((), comptime, runtime)
+    intrinsics::ub_checks()
+        && const_eval_select!(
+            #[inline]
+            () -> bool:
+            if const {
+                // Always disable UB checks.
+                false
+            } else {
+                // Disable UB checks in Miri.
+                !cfg!(miri)
+            }
+        )
 }
 
 /// Checks whether `ptr` is properly aligned with respect to the given alignment, and
@@ -154,26 +153,24 @@ pub(crate) const fn is_nonoverlapping(
     size: usize,
     count: usize,
 ) -> bool {
-    #[inline]
-    fn runtime(src: *const (), dst: *const (), size: usize, count: usize) -> bool {
-        let src_usize = src.addr();
-        let dst_usize = dst.addr();
-        let Some(size) = size.checked_mul(count) else {
-            crate::panicking::panic_nounwind(
-                "is_nonoverlapping: `size_of::<T>() * count` overflows a usize",
-            )
-        };
-        let diff = src_usize.abs_diff(dst_usize);
-        // If the absolute distance between the ptrs is at least as big as the size of the buffer,
-        // they do not overlap.
-        diff >= size
-    }
-
-    #[inline]
-    const fn comptime(_: *const (), _: *const (), _: usize, _: usize) -> bool {
-        true
-    }
-
     // This is just for safety checks so we can const_eval_select.
-    const_eval_select((src, dst, size, count), comptime, runtime)
+    const_eval_select!(
+        #[inline]
+        (src: *const (), dst: *const (), size: usize, count: usize) -> bool:
+        if const {
+            true
+        } else {
+            let src_usize = src.addr();
+            let dst_usize = dst.addr();
+            let Some(size) = size.checked_mul(count) else {
+                crate::panicking::panic_nounwind(
+                    "is_nonoverlapping: `size_of::<T>() * count` overflows a usize",
+                )
+            };
+            let diff = src_usize.abs_diff(dst_usize);
+            // If the absolute distance between the ptrs is at least as big as the size of the buffer,
+            // they do not overlap.
+            diff >= size
+        }
+    )
 }

From 7faa84e20e02c8b7a4afb7c5741e8b59819d7b34 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gabriel=20Bj=C3=B8rnager=20Jensen?= <gabriel@achernar.io>
Date: Mon, 4 Nov 2024 12:28:26 +0100
Subject: [PATCH 03/15] Stabilise 'const_char_encode_utf16';

---
 library/core/src/char/methods.rs | 7 +++++--
 library/core/src/lib.rs          | 1 -
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/library/core/src/char/methods.rs b/library/core/src/char/methods.rs
index 701e34b135e23..39f421df22747 100644
--- a/library/core/src/char/methods.rs
+++ b/library/core/src/char/methods.rs
@@ -711,7 +711,7 @@ impl char {
     /// '𝕊'.encode_utf16(&mut b);
     /// ```
     #[stable(feature = "unicode_encode_char", since = "1.15.0")]
-    #[rustc_const_unstable(feature = "const_char_encode_utf16", issue = "130660")]
+    #[rustc_const_stable(feature = "const_char_encode_utf16", since = "CURRENT_RUSTC_VERSION")]
     #[inline]
     pub const fn encode_utf16(self, dst: &mut [u16]) -> &mut [u16] {
         encode_utf16_raw(self as u32, dst)
@@ -1819,7 +1819,10 @@ pub const fn encode_utf8_raw(code: u32, dst: &mut [u8]) -> &mut [u8] {
 /// Panics if the buffer is not large enough.
 /// A buffer of length 2 is large enough to encode any `char`.
 #[unstable(feature = "char_internals", reason = "exposed only for libstd", issue = "none")]
-#[rustc_const_unstable(feature = "const_char_encode_utf16", issue = "130660")]
+#[cfg_attr(
+    bootstrap,
+    rustc_const_stable(feature = "const_char_encode_utf16", since = "CURRENT_RUSTC_VERSION")
+)]
 #[doc(hidden)]
 #[inline]
 pub const fn encode_utf16_raw(mut code: u32, dst: &mut [u16]) -> &mut [u16] {
diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs
index 12f6997dbeae7..ecebddaa0aef2 100644
--- a/library/core/src/lib.rs
+++ b/library/core/src/lib.rs
@@ -114,7 +114,6 @@
 #![feature(const_align_of_val_raw)]
 #![feature(const_alloc_layout)]
 #![feature(const_black_box)]
-#![feature(const_char_encode_utf16)]
 #![feature(const_eval_select)]
 #![feature(const_exact_div)]
 #![feature(const_float_methods)]

From e9161db5b55ee5398e10a6806cb62d67e725278c Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume1.gomez@gmail.com>
Date: Mon, 4 Nov 2024 14:28:35 +0100
Subject: [PATCH 04/15] Fix invalid coverage computation when
 `--output-format=json` is enabled

---
 src/librustdoc/clean/mod.rs                     | 4 ++--
 src/librustdoc/core.rs                          | 7 +++++++
 src/librustdoc/passes/calculate_doc_coverage.rs | 1 +
 src/librustdoc/passes/strip_hidden.rs           | 2 +-
 src/librustdoc/passes/strip_priv_imports.rs     | 2 +-
 src/librustdoc/passes/strip_private.rs          | 2 +-
 src/librustdoc/visit_ast.rs                     | 2 +-
 7 files changed, 14 insertions(+), 6 deletions(-)

diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index c367eed53e07b..bd4704e8b28ed 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -2907,7 +2907,7 @@ fn clean_extern_crate<'tcx>(
                     None => false,
                 }
         })
-        && !cx.output_format.is_json();
+        && !cx.is_json();
 
     let krate_owner_def_id = krate.owner_id.def_id;
     if please_inline {
@@ -3000,7 +3000,7 @@ fn clean_use_statement_inner<'tcx>(
     // forcefully don't inline if this is not public or if the
     // #[doc(no_inline)] attribute is present.
     // Don't inline doc(hidden) imports so they can be stripped at a later stage.
-    let mut denied = cx.output_format.is_json()
+    let mut denied = cx.is_json()
         || !(visibility.is_public()
             || (cx.render_options.document_private && is_visible_from_parent_mod))
         || pub_underscore
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 0f7d4d3e8f318..274b61ae1d3c0 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -121,6 +121,13 @@ impl<'tcx> DocContext<'tcx> {
             _ => None,
         }
     }
+
+    /// Returns `true` if the JSON output format is enabled for generating the crate content.
+    ///
+    /// If another option like `--show-coverage` is enabled, it will return false.
+    pub(crate) fn is_json(&self) -> bool {
+        self.output_format.is_json() && !self.show_coverage
+    }
 }
 
 /// Creates a new `DiagCtxt` that can be used to emit warnings and errors.
diff --git a/src/librustdoc/passes/calculate_doc_coverage.rs b/src/librustdoc/passes/calculate_doc_coverage.rs
index d27e737764dcf..9f9a093da8a38 100644
--- a/src/librustdoc/passes/calculate_doc_coverage.rs
+++ b/src/librustdoc/passes/calculate_doc_coverage.rs
@@ -132,6 +132,7 @@ impl<'a, 'b> CoverageCalculator<'a, 'b> {
 
     fn print_results(&self) {
         let output_format = self.ctx.output_format;
+        // In this case we want to ensure that the `OutputFormat` is JSON and NOT the `DocContext`.
         if output_format.is_json() {
             println!("{}", self.to_json());
             return;
diff --git a/src/librustdoc/passes/strip_hidden.rs b/src/librustdoc/passes/strip_hidden.rs
index aba04283e59dc..2998289fd273a 100644
--- a/src/librustdoc/passes/strip_hidden.rs
+++ b/src/librustdoc/passes/strip_hidden.rs
@@ -23,7 +23,7 @@ pub(crate) const STRIP_HIDDEN: Pass = Pass {
 /// Strip items marked `#[doc(hidden)]`
 pub(crate) fn strip_hidden(krate: clean::Crate, cx: &mut DocContext<'_>) -> clean::Crate {
     let mut retained = ItemIdSet::default();
-    let is_json_output = cx.output_format.is_json() && !cx.show_coverage;
+    let is_json_output = cx.is_json();
 
     // strip all #[doc(hidden)] items
     let krate = {
diff --git a/src/librustdoc/passes/strip_priv_imports.rs b/src/librustdoc/passes/strip_priv_imports.rs
index 2e9f06bd0a30c..20659d5e8c69c 100644
--- a/src/librustdoc/passes/strip_priv_imports.rs
+++ b/src/librustdoc/passes/strip_priv_imports.rs
@@ -13,7 +13,7 @@ pub(crate) const STRIP_PRIV_IMPORTS: Pass = Pass {
 };
 
 pub(crate) fn strip_priv_imports(krate: clean::Crate, cx: &mut DocContext<'_>) -> clean::Crate {
-    let is_json_output = cx.output_format.is_json() && !cx.show_coverage;
+    let is_json_output = cx.is_json();
     ImportStripper {
         tcx: cx.tcx,
         is_json_output,
diff --git a/src/librustdoc/passes/strip_private.rs b/src/librustdoc/passes/strip_private.rs
index 78f0ad277408b..916c2f0657e47 100644
--- a/src/librustdoc/passes/strip_private.rs
+++ b/src/librustdoc/passes/strip_private.rs
@@ -18,7 +18,7 @@ pub(crate) const STRIP_PRIVATE: Pass = Pass {
 pub(crate) fn strip_private(mut krate: clean::Crate, cx: &mut DocContext<'_>) -> clean::Crate {
     // This stripper collects all *retained* nodes.
     let mut retained = ItemIdSet::default();
-    let is_json_output = cx.output_format.is_json() && !cx.show_coverage;
+    let is_json_output = cx.is_json();
 
     // strip all private items
     {
diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs
index f789aca73784d..00f5251596b60 100644
--- a/src/librustdoc/visit_ast.rs
+++ b/src/librustdoc/visit_ast.rs
@@ -235,7 +235,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
             return false;
         }
 
-        if self.cx.output_format.is_json() {
+        if self.cx.is_json() {
             return false;
         }
 

From 0eff07ee4ec22e7f83462b4b1a42dc04e9d5570b Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume1.gomez@gmail.com>
Date: Mon, 4 Nov 2024 14:46:04 +0100
Subject: [PATCH 05/15] Add UI regressions tests for rustdoc `--show-coverage`
 option

---
 tests/rustdoc-ui/show-coverage-json.rs     | 13 +++++++++++++
 tests/rustdoc-ui/show-coverage-json.stdout |  1 +
 tests/rustdoc-ui/show-coverage.rs          | 13 +++++++++++++
 tests/rustdoc-ui/show-coverage.stdout      |  7 +++++++
 4 files changed, 34 insertions(+)
 create mode 100644 tests/rustdoc-ui/show-coverage-json.rs
 create mode 100644 tests/rustdoc-ui/show-coverage-json.stdout
 create mode 100644 tests/rustdoc-ui/show-coverage.rs
 create mode 100644 tests/rustdoc-ui/show-coverage.stdout

diff --git a/tests/rustdoc-ui/show-coverage-json.rs b/tests/rustdoc-ui/show-coverage-json.rs
new file mode 100644
index 0000000000000..3851e34fe3599
--- /dev/null
+++ b/tests/rustdoc-ui/show-coverage-json.rs
@@ -0,0 +1,13 @@
+//@ compile-flags: -Z unstable-options --show-coverage --output-format=json
+//@ check-pass
+
+mod bar {
+    /// a
+    ///
+    /// ```
+    /// let x = 0;
+    /// ```
+    pub struct Foo;
+}
+
+pub use bar::Foo;
diff --git a/tests/rustdoc-ui/show-coverage-json.stdout b/tests/rustdoc-ui/show-coverage-json.stdout
new file mode 100644
index 0000000000000..ed5b5a60212eb
--- /dev/null
+++ b/tests/rustdoc-ui/show-coverage-json.stdout
@@ -0,0 +1 @@
+{"$DIR/show-coverage-json.rs":{"total":2,"with_docs":1,"total_examples":2,"with_examples":1}}
diff --git a/tests/rustdoc-ui/show-coverage.rs b/tests/rustdoc-ui/show-coverage.rs
new file mode 100644
index 0000000000000..00bb1606a82cb
--- /dev/null
+++ b/tests/rustdoc-ui/show-coverage.rs
@@ -0,0 +1,13 @@
+//@ compile-flags: -Z unstable-options --show-coverage
+//@ check-pass
+
+mod bar {
+    /// a
+    ///
+    /// ```
+    /// let x = 0;
+    /// ```
+    pub struct Foo;
+}
+
+pub use bar::Foo;
diff --git a/tests/rustdoc-ui/show-coverage.stdout b/tests/rustdoc-ui/show-coverage.stdout
new file mode 100644
index 0000000000000..b3b7679771f22
--- /dev/null
+++ b/tests/rustdoc-ui/show-coverage.stdout
@@ -0,0 +1,7 @@
++-------------------------------------+------------+------------+------------+------------+
+| File                                | Documented | Percentage |   Examples | Percentage |
++-------------------------------------+------------+------------+------------+------------+
+| ...ests/rustdoc-ui/show-coverage.rs |          1 |      50.0% |          1 |      50.0% |
++-------------------------------------+------------+------------+------------+------------+
+| Total                               |          1 |      50.0% |          1 |      50.0% |
++-------------------------------------+------------+------------+------------+------------+

From 1916bbfc6ebbde010f759888e318f6606741e42a Mon Sep 17 00:00:00 2001
From: Ralf Jung <post@ralfj.de>
Date: Mon, 4 Nov 2024 10:49:05 +0100
Subject: [PATCH 06/15] move const_panic/assert macros into core::panic module
 (since they are just internal helpers)

---
 library/core/src/char/methods.rs         |   2 +-
 library/core/src/ffi/c_str.rs            |   8 +-
 library/core/src/intrinsics.rs           |  28 +++--
 library/core/src/macros/mod.rs           |  58 ---------
 library/core/src/num/f128.rs             |   2 +-
 library/core/src/num/f16.rs              |   2 +-
 library/core/src/num/f32.rs              |   2 +-
 library/core/src/num/f64.rs              |   2 +-
 library/core/src/num/mod.rs              |   2 +-
 library/core/src/panic.rs                |  56 +++++++++
 library/core/src/panicking.rs            |  62 +++++----
 library/core/src/ptr/const_ptr.rs        |  20 +--
 library/core/src/ptr/mut_ptr.rs          |  81 ++++++------
 library/core/src/slice/ascii.rs          | 152 +++++++++++------------
 library/core/src/slice/index.rs          |   2 +-
 library/core/src/slice/memchr.rs         |  98 +++++++--------
 library/core/src/str/validations.rs      |  17 ++-
 library/core/src/ub_checks.rs            |  26 ++--
 tests/ui/consts/const-ptr-is-null.stderr |   4 +-
 19 files changed, 301 insertions(+), 323 deletions(-)

diff --git a/library/core/src/char/methods.rs b/library/core/src/char/methods.rs
index 701e34b135e23..3915afb49d641 100644
--- a/library/core/src/char/methods.rs
+++ b/library/core/src/char/methods.rs
@@ -1,7 +1,7 @@
 //! impl char {}
 
 use super::*;
-use crate::macros::const_panic;
+use crate::panic::const_panic;
 use crate::slice;
 use crate::str::from_utf8_unchecked_mut;
 use crate::unicode::printable::is_printable;
diff --git a/library/core/src/ffi/c_str.rs b/library/core/src/ffi/c_str.rs
index b57ead374720e..85571222b5cd2 100644
--- a/library/core/src/ffi/c_str.rs
+++ b/library/core/src/ffi/c_str.rs
@@ -413,7 +413,7 @@ impl CStr {
     #[rustc_allow_const_fn_unstable(const_eval_select)]
     pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> &CStr {
         const_eval_select!(
-            (bytes: &[u8]) -> &CStr:
+            @capture { bytes: &[u8] } -> &CStr:
             if const {
                 // Saturating so that an empty slice panics in the assert with a good
                 // message, not here due to underflow.
@@ -429,7 +429,7 @@ impl CStr {
 
                 // SAFETY: See runtime cast comment below.
                 unsafe { &*(bytes as *const [u8] as *const CStr) }
-            } else #[inline] {
+            } else {
                 // Chance at catching some UB at runtime with debug builds.
                 debug_assert!(!bytes.is_empty() && bytes[bytes.len() - 1] == 0);
 
@@ -735,7 +735,7 @@ impl AsRef<CStr> for CStr {
 #[rustc_allow_const_fn_unstable(const_eval_select)]
 const unsafe fn strlen(ptr: *const c_char) -> usize {
     const_eval_select!(
-        (s: *const c_char = ptr) -> usize:
+        @capture { s: *const c_char = ptr } -> usize:
         if const {
             let mut len = 0;
 
@@ -745,7 +745,7 @@ const unsafe fn strlen(ptr: *const c_char) -> usize {
             }
 
             len
-        } else #[inline] {
+        } else {
             extern "C" {
                 /// Provided by libc or compiler_builtins.
                 fn strlen(s: *const c_char) -> usize;
diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs
index d82d911426427..09c76fdad9368 100644
--- a/library/core/src/intrinsics.rs
+++ b/library/core/src/intrinsics.rs
@@ -2791,31 +2791,35 @@ where
 /// A macro to make it easier to invoke const_eval_select. Use as follows:
 /// ```rust,ignore (just a macro example)
 /// const_eval_select!(
-///     #[inline]
-///     (arg1: i32 = some_expr, arg2: T = other_expr) -> U:
-///     if const {
+///     @capture { arg1: i32 = some_expr, arg2: T = other_expr } -> U:
+///     if const #[attributes_for_const_arm] {
 ///         // Compile-time code goes here.
-///     } else {
+///     } else #[attributes_for_runtime_arm] {
 ///         // Run-time code goes here.
 ///     }
 /// )
 /// ```
+/// The `@capture` block declares which surrounding variables / expressions can be
+/// used inside the `if const`.
+/// Note that the two arms of this `if` really each become their own function, which is why the
+/// macro supports setting attributes for those functions. The runtime function is always
+/// markes as `#[inline]`.
+///
+/// See [`const_eval_select()`] for the rules and requirements around that intrinsic.
 pub(crate) macro const_eval_select {
     (
-        $(#[$attr:meta])*
-        ($($arg:ident : $ty:ty = $val:expr),* $(,)?) $( -> $ret:ty )?:
+        @capture { $($arg:ident : $ty:ty = $val:expr),* $(,)? } $( -> $ret:ty )? :
         if const
             $(#[$compiletime_attr:meta])* $compiletime:block
         else
             $(#[$runtime_attr:meta])* $runtime:block
     ) => {{
-        $(#[$attr])*
         $(#[$runtime_attr])*
+        #[inline]
         fn runtime($($arg: $ty),*) $( -> $ret )? {
             $runtime
         }
 
-        $(#[$attr])*
         $(#[$compiletime_attr])*
         const fn compiletime($($arg: $ty),*) $( -> $ret )? {
             // Don't warn if one of the arguments is unused.
@@ -2829,16 +2833,14 @@ pub(crate) macro const_eval_select {
     // We support leaving away the `val` expressions for *all* arguments
     // (but not for *some* arguments, that's too tricky).
     (
-        $(#[$attr:meta])*
-        ($($arg:ident : $ty:ty),* $(,)?) -> $ret:ty:
+        @capture { $($arg:ident : $ty:ty),* $(,)? } $( -> $ret:ty )? :
         if const
             $(#[$compiletime_attr:meta])* $compiletime:block
         else
             $(#[$runtime_attr:meta])* $runtime:block
     ) => {
         $crate::intrinsics::const_eval_select!(
-            $(#[$attr])*
-            ($($arg : $ty = $arg),*) -> $ret:
+            @capture { $($arg : $ty = $arg),* } $(-> $ret)? :
             if const
                 $(#[$compiletime_attr])* $compiletime
             else
@@ -3794,7 +3796,7 @@ pub(crate) const fn miri_promise_symbolic_alignment(ptr: *const (), align: usize
     }
 
     const_eval_select!(
-        (ptr: *const (), align: usize):
+        @capture { ptr: *const (), align: usize}:
         if const {
             // Do nothing.
         } else {
diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs
index c0bf798fb7069..771c2d31b60e0 100644
--- a/library/core/src/macros/mod.rs
+++ b/library/core/src/macros/mod.rs
@@ -12,51 +12,6 @@ macro_rules! panic {
     };
 }
 
-/// Helper macro for panicking in a `const fn`.
-/// Invoke as:
-/// ```rust,ignore (just an example)
-/// core::macros::const_panic!("boring message", "flavored message {a} {b:?}", a: u32 = foo.len(), b: Something = bar);
-/// ```
-/// where the first message will be printed in const-eval,
-/// and the second message will be printed at runtime.
-// All uses of this macro are FIXME(const-hack).
-#[unstable(feature = "panic_internals", issue = "none")]
-#[doc(hidden)]
-pub macro const_panic {
-    ($const_msg:literal, $runtime_msg:literal, $($arg:ident : $ty:ty = $val:expr),* $(,)?) => {{
-        // Wrap call to `const_eval_select` in a function so that we can
-        // add the `rustc_allow_const_fn_unstable`. This is okay to do
-        // because both variants will panic, just with different messages.
-        #[rustc_allow_const_fn_unstable(const_eval_select)]
-        #[inline(always)]
-        #[track_caller]
-        #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_panic", since = "CURRENT_RUSTC_VERSION"))]
-        const fn do_panic($($arg: $ty),*) -> ! {
-            $crate::intrinsics::const_eval_select!(
-                #[inline]
-                #[track_caller]
-                ($($arg: $ty),*) -> !:
-                if const {
-                    $crate::panic!($const_msg)
-                } else {
-                    $crate::panic!($runtime_msg)
-                }
-            )
-        }
-
-        do_panic($($val),*)
-    }},
-    // We support leaving away the `val` expressions for *all* arguments
-    // (but not for *some* arguments, that's too tricky).
-    ($const_msg:literal, $runtime_msg:literal, $($arg:ident : $ty:ty),* $(,)?) => {
-        $crate::macros::const_panic!(
-            $const_msg,
-            $runtime_msg,
-            $($arg: $ty = $arg),*
-        )
-    },
-}
-
 /// Asserts that two expressions are equal to each other (using [`PartialEq`]).
 ///
 /// Assertions are always checked in both debug and release builds, and cannot
@@ -241,19 +196,6 @@ pub macro assert_matches {
     },
 }
 
-/// A version of `assert` that prints a non-formatting message in const contexts.
-///
-/// See [`const_panic!`].
-#[unstable(feature = "panic_internals", issue = "none")]
-#[doc(hidden)]
-pub macro const_assert {
-    ($condition: expr, $const_msg:literal, $runtime_msg:literal, $($arg:tt)*) => {{
-        if !$crate::intrinsics::likely($condition) {
-            $crate::macros::const_panic!($const_msg, $runtime_msg, $($arg)*)
-        }
-    }}
-}
-
 /// A macro for defining `#[cfg]` match-like statements.
 ///
 /// It is similar to the `if/elif` C preprocessor macro by allowing definition of a cascade of
diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs
index 7709e7de01b0b..0484611958d87 100644
--- a/library/core/src/num/f128.rs
+++ b/library/core/src/num/f128.rs
@@ -14,9 +14,9 @@
 use crate::convert::FloatToInt;
 #[cfg(not(test))]
 use crate::intrinsics;
-use crate::macros::const_assert;
 use crate::mem;
 use crate::num::FpCategory;
+use crate::panic::const_assert;
 
 /// Basic mathematical constants.
 #[unstable(feature = "f128", issue = "116909")]
diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs
index eb0225c58b837..898caf835bfd3 100644
--- a/library/core/src/num/f16.rs
+++ b/library/core/src/num/f16.rs
@@ -14,9 +14,9 @@
 use crate::convert::FloatToInt;
 #[cfg(not(test))]
 use crate::intrinsics;
-use crate::macros::const_assert;
 use crate::mem;
 use crate::num::FpCategory;
+use crate::panic::const_assert;
 
 /// Basic mathematical constants.
 #[unstable(feature = "f16", issue = "116909")]
diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs
index 686a6c5092792..20ece883da60b 100644
--- a/library/core/src/num/f32.rs
+++ b/library/core/src/num/f32.rs
@@ -14,9 +14,9 @@
 use crate::convert::FloatToInt;
 #[cfg(not(test))]
 use crate::intrinsics;
-use crate::macros::const_assert;
 use crate::mem;
 use crate::num::FpCategory;
+use crate::panic::const_assert;
 
 /// The radix or base of the internal representation of `f32`.
 /// Use [`f32::RADIX`] instead.
diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs
index 798cb4b1b5ccd..5640e71788b85 100644
--- a/library/core/src/num/f64.rs
+++ b/library/core/src/num/f64.rs
@@ -14,9 +14,9 @@
 use crate::convert::FloatToInt;
 #[cfg(not(test))]
 use crate::intrinsics;
-use crate::macros::const_assert;
 use crate::mem;
 use crate::num::FpCategory;
+use crate::panic::const_assert;
 
 /// The radix or base of the internal representation of `f64`.
 /// Use [`f64::RADIX`] instead.
diff --git a/library/core/src/num/mod.rs b/library/core/src/num/mod.rs
index f4930ca5c7dbd..5a69dc0c7242b 100644
--- a/library/core/src/num/mod.rs
+++ b/library/core/src/num/mod.rs
@@ -2,7 +2,7 @@
 
 #![stable(feature = "rust1", since = "1.0.0")]
 
-use crate::macros::const_panic;
+use crate::panic::const_panic;
 use crate::str::FromStr;
 use crate::ub_checks::assert_unsafe_precondition;
 use crate::{ascii, intrinsics, mem};
diff --git a/library/core/src/panic.rs b/library/core/src/panic.rs
index c95a000561c35..f8f3962ce55ac 100644
--- a/library/core/src/panic.rs
+++ b/library/core/src/panic.rs
@@ -189,3 +189,59 @@ pub unsafe trait PanicPayload: crate::fmt::Display {
         None
     }
 }
+
+/// Helper macro for panicking in a `const fn`.
+/// Invoke as:
+/// ```rust,ignore (just an example)
+/// core::macros::const_panic!("boring message", "flavored message {a} {b:?}", a: u32 = foo.len(), b: Something = bar);
+/// ```
+/// where the first message will be printed in const-eval,
+/// and the second message will be printed at runtime.
+// All uses of this macro are FIXME(const-hack).
+#[unstable(feature = "panic_internals", issue = "none")]
+#[doc(hidden)]
+pub macro const_panic {
+    ($const_msg:literal, $runtime_msg:literal, $($arg:ident : $ty:ty = $val:expr),* $(,)?) => {{
+        // Wrap call to `const_eval_select` in a function so that we can
+        // add the `rustc_allow_const_fn_unstable`. This is okay to do
+        // because both variants will panic, just with different messages.
+        #[rustc_allow_const_fn_unstable(const_eval_select)]
+        #[inline(always)]
+        #[track_caller]
+        #[cfg_attr(bootstrap, rustc_const_stable(feature = "const_panic", since = "CURRENT_RUSTC_VERSION"))]
+        const fn do_panic($($arg: $ty),*) -> ! {
+            $crate::intrinsics::const_eval_select!(
+                @capture { $($arg: $ty),* } -> !:
+                if const #[track_caller] {
+                    $crate::panic!($const_msg)
+                } else #[track_caller] {
+                    $crate::panic!($runtime_msg)
+                }
+            )
+        }
+
+        do_panic($($val),*)
+    }},
+    // We support leaving away the `val` expressions for *all* arguments
+    // (but not for *some* arguments, that's too tricky).
+    ($const_msg:literal, $runtime_msg:literal, $($arg:ident : $ty:ty),* $(,)?) => {
+        $crate::panic::const_panic!(
+            $const_msg,
+            $runtime_msg,
+            $($arg: $ty = $arg),*
+        )
+    },
+}
+
+/// A version of `assert` that prints a non-formatting message in const contexts.
+///
+/// See [`const_panic!`].
+#[unstable(feature = "panic_internals", issue = "none")]
+#[doc(hidden)]
+pub macro const_assert {
+    ($condition: expr, $const_msg:literal, $runtime_msg:literal, $($arg:tt)*) => {{
+        if !$crate::intrinsics::likely($condition) {
+            $crate::panic::const_panic!($const_msg, $runtime_msg, $($arg)*)
+        }
+    }}
+}
diff --git a/library/core/src/panicking.rs b/library/core/src/panicking.rs
index 9071d6719a30e..f603eb2971f6d 100644
--- a/library/core/src/panicking.rs
+++ b/library/core/src/panicking.rs
@@ -29,6 +29,7 @@
 )]
 
 use crate::fmt;
+use crate::intrinsics::const_eval_select;
 use crate::panic::{Location, PanicInfo};
 
 #[cfg(feature = "panic_immediate_abort")]
@@ -89,40 +90,35 @@ pub const fn panic_fmt(fmt: fmt::Arguments<'_>) -> ! {
 #[cfg_attr(not(bootstrap), rustc_const_stable_indirect)] // must follow stable const rules since it is exposed to stable
 #[rustc_allow_const_fn_unstable(const_eval_select)]
 pub const fn panic_nounwind_fmt(fmt: fmt::Arguments<'_>, force_no_backtrace: bool) -> ! {
-    #[inline] // this should always be inlined into `panic_nounwind_fmt`
-    #[track_caller]
-    fn runtime(fmt: fmt::Arguments<'_>, force_no_backtrace: bool) -> ! {
-        if cfg!(feature = "panic_immediate_abort") {
-            super::intrinsics::abort()
+    const_eval_select!(
+        @capture { fmt: fmt::Arguments<'_>, force_no_backtrace: bool } -> !:
+        if const #[track_caller] {
+            // We don't unwind anyway at compile-time so we can call the regular `panic_fmt`.
+            panic_fmt(fmt)
+        } else #[track_caller] {
+            if cfg!(feature = "panic_immediate_abort") {
+                super::intrinsics::abort()
+            }
+
+            // NOTE This function never crosses the FFI boundary; it's a Rust-to-Rust call
+            // that gets resolved to the `#[panic_handler]` function.
+            extern "Rust" {
+                #[lang = "panic_impl"]
+                fn panic_impl(pi: &PanicInfo<'_>) -> !;
+            }
+
+            // PanicInfo with the `can_unwind` flag set to false forces an abort.
+            let pi = PanicInfo::new(
+                &fmt,
+                Location::caller(),
+                /* can_unwind */ false,
+                force_no_backtrace,
+            );
+
+            // SAFETY: `panic_impl` is defined in safe Rust code and thus is safe to call.
+            unsafe { panic_impl(&pi) }
         }
-
-        // NOTE This function never crosses the FFI boundary; it's a Rust-to-Rust call
-        // that gets resolved to the `#[panic_handler]` function.
-        extern "Rust" {
-            #[lang = "panic_impl"]
-            fn panic_impl(pi: &PanicInfo<'_>) -> !;
-        }
-
-        // PanicInfo with the `can_unwind` flag set to false forces an abort.
-        let pi = PanicInfo::new(
-            &fmt,
-            Location::caller(),
-            /* can_unwind */ false,
-            force_no_backtrace,
-        );
-
-        // SAFETY: `panic_impl` is defined in safe Rust code and thus is safe to call.
-        unsafe { panic_impl(&pi) }
-    }
-
-    #[inline]
-    #[track_caller]
-    const fn comptime(fmt: fmt::Arguments<'_>, _force_no_backtrace: bool) -> ! {
-        // We don't unwind anyway at compile-time so we can call the regular `panic_fmt`.
-        panic_fmt(fmt);
-    }
-
-    super::intrinsics::const_eval_select((fmt, force_no_backtrace), comptime, runtime);
+    )
 }
 
 // Next we define a bunch of higher-level wrappers that all bottom out in the two core functions
diff --git a/library/core/src/ptr/const_ptr.rs b/library/core/src/ptr/const_ptr.rs
index 7c5fa6b4e9072..2d7507e2d53ee 100644
--- a/library/core/src/ptr/const_ptr.rs
+++ b/library/core/src/ptr/const_ptr.rs
@@ -37,8 +37,7 @@ impl<T: ?Sized> *const T {
         // considering their "data" part for null-ness.
         let ptr = self as *const u8;
         const_eval_select!(
-            #[inline]
-            (ptr: *const u8) -> bool:
+            @capture { ptr: *const u8 } -> bool:
             if const #[rustc_const_unstable(feature = "const_ptr_is_null", issue = "74939")] {
                 match (ptr).guaranteed_eq(null_mut()) {
                     Some(res) => res,
@@ -410,11 +409,12 @@ impl<T: ?Sized> *const T {
         const fn runtime_offset_nowrap(this: *const (), count: isize, size: usize) -> bool {
             // We can use const_eval_select here because this is only for UB checks.
             const_eval_select!(
-                (this: *const (), count: isize, size: usize) -> bool:
+                @capture { this: *const (), count: isize, size: usize } -> bool:
                 if const {
                     true
-                } else #[inline] {
-                    // We know `size <= isize::MAX` so the `as` cast here is not lossy.
+                } else {
+                    // `size` is the size of a Rust type, so we know that
+                    // `size <= isize::MAX` and thus `as` cast here is not lossy.
                     let Some(byte_offset) = count.checked_mul(size as isize) else {
                         return false;
                     };
@@ -760,7 +760,7 @@ impl<T: ?Sized> *const T {
         #[rustc_allow_const_fn_unstable(const_eval_select)]
         const fn runtime_ptr_ge(this: *const (), origin: *const ()) -> bool {
             const_eval_select!(
-                (this: *const (), origin: *const ()) -> bool:
+                @capture { this: *const (), origin: *const () } -> bool:
                 if const {
                     true
                 } else {
@@ -921,10 +921,10 @@ impl<T: ?Sized> *const T {
         #[rustc_allow_const_fn_unstable(const_eval_select)]
         const fn runtime_add_nowrap(this: *const (), count: usize, size: usize) -> bool {
             const_eval_select!(
-                (this: *const (), count: usize, size: usize) -> bool:
+                @capture { this: *const (), count: usize, size: usize } -> bool:
                 if const {
                     true
-                } else #[inline] {
+                } else {
                     let Some(byte_offset) = count.checked_mul(size) else {
                         return false;
                     };
@@ -1028,10 +1028,10 @@ impl<T: ?Sized> *const T {
         #[rustc_allow_const_fn_unstable(const_eval_select)]
         const fn runtime_sub_nowrap(this: *const (), count: usize, size: usize) -> bool {
             const_eval_select!(
-                (this: *const (), count: usize, size: usize) -> bool:
+                @capture { this: *const (), count: usize, size: usize } -> bool:
                 if const {
                     true
-                } else #[inline] {
+                } else {
                     let Some(byte_offset) = count.checked_mul(size) else {
                         return false;
                     };
diff --git a/library/core/src/ptr/mut_ptr.rs b/library/core/src/ptr/mut_ptr.rs
index 0d94a7f491c21..344ba46a50e20 100644
--- a/library/core/src/ptr/mut_ptr.rs
+++ b/library/core/src/ptr/mut_ptr.rs
@@ -1,5 +1,6 @@
 use super::*;
 use crate::cmp::Ordering::{Equal, Greater, Less};
+use crate::intrinsics::const_eval_select;
 use crate::mem::SizedTypeProperties;
 use crate::slice::{self, SliceIndex};
 
@@ -404,23 +405,21 @@ impl<T: ?Sized> *mut T {
         #[inline]
         #[rustc_allow_const_fn_unstable(const_eval_select)]
         const fn runtime_offset_nowrap(this: *const (), count: isize, size: usize) -> bool {
-            #[inline]
-            fn runtime(this: *const (), count: isize, size: usize) -> bool {
-                // `size` is the size of a Rust type, so we know that
-                // `size <= isize::MAX` and thus `as` cast here is not lossy.
-                let Some(byte_offset) = count.checked_mul(size as isize) else {
-                    return false;
-                };
-                let (_, overflow) = this.addr().overflowing_add_signed(byte_offset);
-                !overflow
-            }
-
-            const fn comptime(_: *const (), _: isize, _: usize) -> bool {
-                true
-            }
-
             // We can use const_eval_select here because this is only for UB checks.
-            intrinsics::const_eval_select((this, count, size), comptime, runtime)
+            const_eval_select!(
+                @capture { this: *const (), count: isize, size: usize } -> bool:
+                if const {
+                    true
+                } else {
+                    // `size` is the size of a Rust type, so we know that
+                    // `size <= isize::MAX` and thus `as` cast here is not lossy.
+                    let Some(byte_offset) = count.checked_mul(size as isize) else {
+                        return false;
+                    };
+                    let (_, overflow) = this.addr().overflowing_add_signed(byte_offset);
+                    !overflow
+                }
+            )
         }
 
         ub_checks::assert_unsafe_precondition!(
@@ -1002,20 +1001,18 @@ impl<T: ?Sized> *mut T {
         #[inline]
         #[rustc_allow_const_fn_unstable(const_eval_select)]
         const fn runtime_add_nowrap(this: *const (), count: usize, size: usize) -> bool {
-            #[inline]
-            fn runtime(this: *const (), count: usize, size: usize) -> bool {
-                let Some(byte_offset) = count.checked_mul(size) else {
-                    return false;
-                };
-                let (_, overflow) = this.addr().overflowing_add(byte_offset);
-                byte_offset <= (isize::MAX as usize) && !overflow
-            }
-
-            const fn comptime(_: *const (), _: usize, _: usize) -> bool {
-                true
-            }
-
-            intrinsics::const_eval_select((this, count, size), comptime, runtime)
+            const_eval_select!(
+                @capture { this: *const (), count: usize, size: usize } -> bool:
+                if const {
+                    true
+                } else {
+                    let Some(byte_offset) = count.checked_mul(size) else {
+                        return false;
+                    };
+                    let (_, overflow) = this.addr().overflowing_add(byte_offset);
+                    byte_offset <= (isize::MAX as usize) && !overflow
+                }
+            )
         }
 
         #[cfg(debug_assertions)] // Expensive, and doesn't catch much in the wild.
@@ -1111,19 +1108,17 @@ impl<T: ?Sized> *mut T {
         #[inline]
         #[rustc_allow_const_fn_unstable(const_eval_select)]
         const fn runtime_sub_nowrap(this: *const (), count: usize, size: usize) -> bool {
-            #[inline]
-            fn runtime(this: *const (), count: usize, size: usize) -> bool {
-                let Some(byte_offset) = count.checked_mul(size) else {
-                    return false;
-                };
-                byte_offset <= (isize::MAX as usize) && this.addr() >= byte_offset
-            }
-
-            const fn comptime(_: *const (), _: usize, _: usize) -> bool {
-                true
-            }
-
-            intrinsics::const_eval_select((this, count, size), comptime, runtime)
+            const_eval_select!(
+                @capture { this: *const (), count: usize, size: usize } -> bool:
+                if const {
+                    true
+                } else {
+                    let Some(byte_offset) = count.checked_mul(size) else {
+                        return false;
+                    };
+                    byte_offset <= (isize::MAX as usize) && this.addr() >= byte_offset
+                }
+            )
         }
 
         #[cfg(debug_assertions)] // Expensive, and doesn't catch much in the wild.
diff --git a/library/core/src/slice/ascii.rs b/library/core/src/slice/ascii.rs
index 8dcd34929e18d..58ba3a1573a81 100644
--- a/library/core/src/slice/ascii.rs
+++ b/library/core/src/slice/ascii.rs
@@ -351,89 +351,87 @@ pub const fn is_ascii_simple(mut bytes: &[u8]) -> bool {
 const fn is_ascii(s: &[u8]) -> bool {
     // The runtime version behaves the same as the compiletime version, it's
     // just more optimized.
-    return const_eval_select((s,), compiletime, runtime);
-
-    const fn compiletime(s: &[u8]) -> bool {
-        is_ascii_simple(s)
-    }
-
-    #[inline]
-    fn runtime(s: &[u8]) -> bool {
-        const USIZE_SIZE: usize = mem::size_of::<usize>();
-
-        let len = s.len();
-        let align_offset = s.as_ptr().align_offset(USIZE_SIZE);
-
-        // If we wouldn't gain anything from the word-at-a-time implementation, fall
-        // back to a scalar loop.
-        //
-        // We also do this for architectures where `size_of::<usize>()` isn't
-        // sufficient alignment for `usize`, because it's a weird edge case.
-        if len < USIZE_SIZE || len < align_offset || USIZE_SIZE < mem::align_of::<usize>() {
-            return is_ascii_simple(s);
-        }
+    const_eval_select!(
+        @capture { s: &[u8] } -> bool:
+        if const {
+            is_ascii_simple(s)
+        } else {
+            const USIZE_SIZE: usize = mem::size_of::<usize>();
+
+            let len = s.len();
+            let align_offset = s.as_ptr().align_offset(USIZE_SIZE);
+
+            // If we wouldn't gain anything from the word-at-a-time implementation, fall
+            // back to a scalar loop.
+            //
+            // We also do this for architectures where `size_of::<usize>()` isn't
+            // sufficient alignment for `usize`, because it's a weird edge case.
+            if len < USIZE_SIZE || len < align_offset || USIZE_SIZE < mem::align_of::<usize>() {
+                return is_ascii_simple(s);
+            }
 
-        // We always read the first word unaligned, which means `align_offset` is
-        // 0, we'd read the same value again for the aligned read.
-        let offset_to_aligned = if align_offset == 0 { USIZE_SIZE } else { align_offset };
+            // We always read the first word unaligned, which means `align_offset` is
+            // 0, we'd read the same value again for the aligned read.
+            let offset_to_aligned = if align_offset == 0 { USIZE_SIZE } else { align_offset };
 
-        let start = s.as_ptr();
-        // SAFETY: We verify `len < USIZE_SIZE` above.
-        let first_word = unsafe { (start as *const usize).read_unaligned() };
+            let start = s.as_ptr();
+            // SAFETY: We verify `len < USIZE_SIZE` above.
+            let first_word = unsafe { (start as *const usize).read_unaligned() };
 
-        if contains_nonascii(first_word) {
-            return false;
-        }
-        // We checked this above, somewhat implicitly. Note that `offset_to_aligned`
-        // is either `align_offset` or `USIZE_SIZE`, both of are explicitly checked
-        // above.
-        debug_assert!(offset_to_aligned <= len);
-
-        // SAFETY: word_ptr is the (properly aligned) usize ptr we use to read the
-        // middle chunk of the slice.
-        let mut word_ptr = unsafe { start.add(offset_to_aligned) as *const usize };
-
-        // `byte_pos` is the byte index of `word_ptr`, used for loop end checks.
-        let mut byte_pos = offset_to_aligned;
-
-        // Paranoia check about alignment, since we're about to do a bunch of
-        // unaligned loads. In practice this should be impossible barring a bug in
-        // `align_offset` though.
-        // While this method is allowed to spuriously fail in CTFE, if it doesn't
-        // have alignment information it should have given a `usize::MAX` for
-        // `align_offset` earlier, sending things through the scalar path instead of
-        // this one, so this check should pass if it's reachable.
-        debug_assert!(word_ptr.is_aligned_to(mem::align_of::<usize>()));
-
-        // Read subsequent words until the last aligned word, excluding the last
-        // aligned word by itself to be done in tail check later, to ensure that
-        // tail is always one `usize` at most to extra branch `byte_pos == len`.
-        while byte_pos < len - USIZE_SIZE {
-            // Sanity check that the read is in bounds
-            debug_assert!(byte_pos + USIZE_SIZE <= len);
-            // And that our assumptions about `byte_pos` hold.
-            debug_assert!(word_ptr.cast::<u8>() == start.wrapping_add(byte_pos));
-
-            // SAFETY: We know `word_ptr` is properly aligned (because of
-            // `align_offset`), and we know that we have enough bytes between `word_ptr` and the end
-            let word = unsafe { word_ptr.read() };
-            if contains_nonascii(word) {
+            if contains_nonascii(first_word) {
                 return false;
             }
+            // We checked this above, somewhat implicitly. Note that `offset_to_aligned`
+            // is either `align_offset` or `USIZE_SIZE`, both of are explicitly checked
+            // above.
+            debug_assert!(offset_to_aligned <= len);
+
+            // SAFETY: word_ptr is the (properly aligned) usize ptr we use to read the
+            // middle chunk of the slice.
+            let mut word_ptr = unsafe { start.add(offset_to_aligned) as *const usize };
+
+            // `byte_pos` is the byte index of `word_ptr`, used for loop end checks.
+            let mut byte_pos = offset_to_aligned;
+
+            // Paranoia check about alignment, since we're about to do a bunch of
+            // unaligned loads. In practice this should be impossible barring a bug in
+            // `align_offset` though.
+            // While this method is allowed to spuriously fail in CTFE, if it doesn't
+            // have alignment information it should have given a `usize::MAX` for
+            // `align_offset` earlier, sending things through the scalar path instead of
+            // this one, so this check should pass if it's reachable.
+            debug_assert!(word_ptr.is_aligned_to(mem::align_of::<usize>()));
+
+            // Read subsequent words until the last aligned word, excluding the last
+            // aligned word by itself to be done in tail check later, to ensure that
+            // tail is always one `usize` at most to extra branch `byte_pos == len`.
+            while byte_pos < len - USIZE_SIZE {
+                // Sanity check that the read is in bounds
+                debug_assert!(byte_pos + USIZE_SIZE <= len);
+                // And that our assumptions about `byte_pos` hold.
+                debug_assert!(word_ptr.cast::<u8>() == start.wrapping_add(byte_pos));
+
+                // SAFETY: We know `word_ptr` is properly aligned (because of
+                // `align_offset`), and we know that we have enough bytes between `word_ptr` and the end
+                let word = unsafe { word_ptr.read() };
+                if contains_nonascii(word) {
+                    return false;
+                }
+
+                byte_pos += USIZE_SIZE;
+                // SAFETY: We know that `byte_pos <= len - USIZE_SIZE`, which means that
+                // after this `add`, `word_ptr` will be at most one-past-the-end.
+                word_ptr = unsafe { word_ptr.add(1) };
+            }
 
-            byte_pos += USIZE_SIZE;
-            // SAFETY: We know that `byte_pos <= len - USIZE_SIZE`, which means that
-            // after this `add`, `word_ptr` will be at most one-past-the-end.
-            word_ptr = unsafe { word_ptr.add(1) };
-        }
-
-        // Sanity check to ensure there really is only one `usize` left. This should
-        // be guaranteed by our loop condition.
-        debug_assert!(byte_pos <= len && len - byte_pos <= USIZE_SIZE);
+            // Sanity check to ensure there really is only one `usize` left. This should
+            // be guaranteed by our loop condition.
+            debug_assert!(byte_pos <= len && len - byte_pos <= USIZE_SIZE);
 
-        // SAFETY: This relies on `len >= USIZE_SIZE`, which we check at the start.
-        let last_word = unsafe { (start.add(len - USIZE_SIZE) as *const usize).read_unaligned() };
+            // SAFETY: This relies on `len >= USIZE_SIZE`, which we check at the start.
+            let last_word = unsafe { (start.add(len - USIZE_SIZE) as *const usize).read_unaligned() };
 
-        !contains_nonascii(last_word)
-    }
+            !contains_nonascii(last_word)
+        }
+    )
 }
diff --git a/library/core/src/slice/index.rs b/library/core/src/slice/index.rs
index ebb4bdb144909..aafa19c0dd3d3 100644
--- a/library/core/src/slice/index.rs
+++ b/library/core/src/slice/index.rs
@@ -1,6 +1,6 @@
 //! Indexing implementations for `[T]`.
 
-use crate::macros::const_panic;
+use crate::panic::const_panic;
 use crate::ub_checks::assert_unsafe_precondition;
 use crate::{ops, range};
 
diff --git a/library/core/src/slice/memchr.rs b/library/core/src/slice/memchr.rs
index b7c4a1f6f08b1..339adad1b17bf 100644
--- a/library/core/src/slice/memchr.rs
+++ b/library/core/src/slice/memchr.rs
@@ -56,61 +56,59 @@ const fn memchr_naive(x: u8, text: &[u8]) -> Option<usize> {
 const fn memchr_aligned(x: u8, text: &[u8]) -> Option<usize> {
     // The runtime version behaves the same as the compiletime version, it's
     // just more optimized.
-    return const_eval_select((x, text), compiletime, runtime);
-
-    const fn compiletime(x: u8, text: &[u8]) -> Option<usize> {
-        memchr_naive(x, text)
-    }
-
-    #[inline]
-    fn runtime(x: u8, text: &[u8]) -> Option<usize> {
-        // Scan for a single byte value by reading two `usize` words at a time.
-        //
-        // Split `text` in three parts
-        // - unaligned initial part, before the first word aligned address in text
-        // - body, scan by 2 words at a time
-        // - the last remaining part, < 2 word size
-
-        // search up to an aligned boundary
-        let len = text.len();
-        let ptr = text.as_ptr();
-        let mut offset = ptr.align_offset(USIZE_BYTES);
-
-        if offset > 0 {
-            offset = offset.min(len);
-            let slice = &text[..offset];
-            if let Some(index) = memchr_naive(x, slice) {
-                return Some(index);
+    const_eval_select!(
+        @capture { x: u8, text: &[u8] } -> Option<usize>:
+        if const {
+            memchr_naive(x, text)
+        } else {
+            // Scan for a single byte value by reading two `usize` words at a time.
+            //
+            // Split `text` in three parts
+            // - unaligned initial part, before the first word aligned address in text
+            // - body, scan by 2 words at a time
+            // - the last remaining part, < 2 word size
+
+            // search up to an aligned boundary
+            let len = text.len();
+            let ptr = text.as_ptr();
+            let mut offset = ptr.align_offset(USIZE_BYTES);
+
+            if offset > 0 {
+                offset = offset.min(len);
+                let slice = &text[..offset];
+                if let Some(index) = memchr_naive(x, slice) {
+                    return Some(index);
+                }
             }
-        }
 
-        // search the body of the text
-        let repeated_x = usize::repeat_u8(x);
-        while offset <= len - 2 * USIZE_BYTES {
-            // SAFETY: the while's predicate guarantees a distance of at least 2 * usize_bytes
-            // between the offset and the end of the slice.
-            unsafe {
-                let u = *(ptr.add(offset) as *const usize);
-                let v = *(ptr.add(offset + USIZE_BYTES) as *const usize);
-
-                // break if there is a matching byte
-                let zu = contains_zero_byte(u ^ repeated_x);
-                let zv = contains_zero_byte(v ^ repeated_x);
-                if zu || zv {
-                    break;
+            // search the body of the text
+            let repeated_x = usize::repeat_u8(x);
+            while offset <= len - 2 * USIZE_BYTES {
+                // SAFETY: the while's predicate guarantees a distance of at least 2 * usize_bytes
+                // between the offset and the end of the slice.
+                unsafe {
+                    let u = *(ptr.add(offset) as *const usize);
+                    let v = *(ptr.add(offset + USIZE_BYTES) as *const usize);
+
+                    // break if there is a matching byte
+                    let zu = contains_zero_byte(u ^ repeated_x);
+                    let zv = contains_zero_byte(v ^ repeated_x);
+                    if zu || zv {
+                        break;
+                    }
                 }
+                offset += USIZE_BYTES * 2;
             }
-            offset += USIZE_BYTES * 2;
-        }
 
-        // Find the byte after the point the body loop stopped.
-        // FIXME(const-hack): Use `?` instead.
-        // FIXME(const-hack, fee1-dead): use range slicing
-        let slice =
-        // SAFETY: offset is within bounds
-            unsafe { super::from_raw_parts(text.as_ptr().add(offset), text.len() - offset) };
-        if let Some(i) = memchr_naive(x, slice) { Some(offset + i) } else { None }
-    }
+            // Find the byte after the point the body loop stopped.
+            // FIXME(const-hack): Use `?` instead.
+            // FIXME(const-hack, fee1-dead): use range slicing
+            let slice =
+            // SAFETY: offset is within bounds
+                unsafe { super::from_raw_parts(text.as_ptr().add(offset), text.len() - offset) };
+            if let Some(i) = memchr_naive(x, slice) { Some(offset + i) } else { None }
+        }
+    )
 }
 
 /// Returns the last index matching the byte `x` in `text`.
diff --git a/library/core/src/str/validations.rs b/library/core/src/str/validations.rs
index 6095b589e18c3..0f724dd961329 100644
--- a/library/core/src/str/validations.rs
+++ b/library/core/src/str/validations.rs
@@ -132,19 +132,16 @@ pub(super) const fn run_utf8_validation(v: &[u8]) -> Result<(), Utf8Error> {
 
     let ascii_block_size = 2 * USIZE_BYTES;
     let blocks_end = if len >= ascii_block_size { len - ascii_block_size + 1 } else { 0 };
-    let align = {
-        const fn compiletime(_v: &[u8]) -> usize {
+    // Below, we safely fall back to a slower codepath if the offset is `usize::MAX`,
+    // so the end-to-end behavior is the same at compiletime and runtime.
+    let align = const_eval_select!(
+        @capture { v: &[u8] } -> usize:
+        if const {
             usize::MAX
-        }
-
-        fn runtime(v: &[u8]) -> usize {
+        } else {
             v.as_ptr().align_offset(USIZE_BYTES)
         }
-
-        // Below, we safely fall back to a slower codepath if the offset is `usize::MAX`,
-        // so the end-to-end behavior is the same at compiletime and runtime.
-        const_eval_select((v,), compiletime, runtime)
-    };
+    );
 
     while index < len {
         let old_offset = index;
diff --git a/library/core/src/ub_checks.rs b/library/core/src/ub_checks.rs
index 972d9ae26d2bf..8fcbda141dab7 100644
--- a/library/core/src/ub_checks.rs
+++ b/library/core/src/ub_checks.rs
@@ -98,8 +98,7 @@ pub(crate) const fn check_language_ub() -> bool {
     // Only used for UB checks so we may const_eval_select.
     intrinsics::ub_checks()
         && const_eval_select!(
-            #[inline]
-            () -> bool:
+            @capture { } -> bool:
             if const {
                 // Always disable UB checks.
                 false
@@ -119,19 +118,15 @@ pub(crate) const fn check_language_ub() -> bool {
 #[inline]
 #[rustc_const_unstable(feature = "const_ub_checks", issue = "none")]
 pub(crate) const fn is_aligned_and_not_null(ptr: *const (), align: usize, is_zst: bool) -> bool {
-    #[inline]
-    fn runtime(ptr: *const (), align: usize, is_zst: bool) -> bool {
-        ptr.is_aligned_to(align) && (is_zst || !ptr.is_null())
-    }
-
-    #[inline]
-    #[rustc_const_unstable(feature = "const_ub_checks", issue = "none")]
-    const fn comptime(ptr: *const (), _align: usize, is_zst: bool) -> bool {
-        is_zst || !ptr.is_null()
-    }
-
     // This is just for safety checks so we can const_eval_select.
-    const_eval_select((ptr, align, is_zst), comptime, runtime)
+    const_eval_select!(
+        @capture { ptr: *const (), align: usize, is_zst: bool } -> bool:
+        if const #[rustc_const_unstable(feature = "const_ub_checks", issue = "none")] {
+            is_zst || !ptr.is_null()
+        } else {
+            ptr.is_aligned_to(align) && (is_zst || !ptr.is_null())
+        }
+    )
 }
 
 #[inline]
@@ -155,8 +150,7 @@ pub(crate) const fn is_nonoverlapping(
 ) -> bool {
     // This is just for safety checks so we can const_eval_select.
     const_eval_select!(
-        #[inline]
-        (src: *const (), dst: *const (), size: usize, count: usize) -> bool:
+        @capture { src: *const (), dst: *const (), size: usize, count: usize } -> bool:
         if const {
             true
         } else {
diff --git a/tests/ui/consts/const-ptr-is-null.stderr b/tests/ui/consts/const-ptr-is-null.stderr
index 20e44a1401f9e..5fd3514281832 100644
--- a/tests/ui/consts/const-ptr-is-null.stderr
+++ b/tests/ui/consts/const-ptr-is-null.stderr
@@ -3,7 +3,7 @@ error[E0080]: evaluation of constant value failed
    |
    = note: the evaluated program panicked at 'null-ness of this pointer cannot be determined in const context', $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL
    |
-note: inside `std::ptr::const_ptr::<impl *const T>::is_null::const_impl`
+note: inside `std::ptr::const_ptr::<impl *const T>::is_null::compiletime`
   --> $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL
 note: inside `std::ptr::const_ptr::<impl *const i32>::is_null`
   --> $SRC_DIR/core/src/ptr/const_ptr.rs:LL:COL
@@ -12,7 +12,7 @@ note: inside `MAYBE_NULL`
    |
 LL |     assert!(!ptr.wrapping_sub(512).is_null());
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
+   = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `const_eval_select` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error: aborting due to 1 previous error
 

From 5dfbc0383d6e7ad27f0bdae5542109dafc9cd4f1 Mon Sep 17 00:00:00 2001
From: Guillaume Gomez <guillaume1.gomez@gmail.com>
Date: Mon, 4 Nov 2024 17:15:28 +0100
Subject: [PATCH 07/15] Rename `DocContext::is_json` into
 `DocContext::is_json_output`

---
 src/librustdoc/clean/mod.rs                 | 4 ++--
 src/librustdoc/core.rs                      | 4 ++--
 src/librustdoc/passes/strip_hidden.rs       | 2 +-
 src/librustdoc/passes/strip_priv_imports.rs | 2 +-
 src/librustdoc/passes/strip_private.rs      | 2 +-
 src/librustdoc/visit_ast.rs                 | 2 +-
 6 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index bd4704e8b28ed..992be651dbe08 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -2907,7 +2907,7 @@ fn clean_extern_crate<'tcx>(
                     None => false,
                 }
         })
-        && !cx.is_json();
+        && !cx.is_json_output();
 
     let krate_owner_def_id = krate.owner_id.def_id;
     if please_inline {
@@ -3000,7 +3000,7 @@ fn clean_use_statement_inner<'tcx>(
     // forcefully don't inline if this is not public or if the
     // #[doc(no_inline)] attribute is present.
     // Don't inline doc(hidden) imports so they can be stripped at a later stage.
-    let mut denied = cx.is_json()
+    let mut denied = cx.is_json_output()
         || !(visibility.is_public()
             || (cx.render_options.document_private && is_visible_from_parent_mod))
         || pub_underscore
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 274b61ae1d3c0..7ba3cfb66bd23 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -124,8 +124,8 @@ impl<'tcx> DocContext<'tcx> {
 
     /// Returns `true` if the JSON output format is enabled for generating the crate content.
     ///
-    /// If another option like `--show-coverage` is enabled, it will return false.
-    pub(crate) fn is_json(&self) -> bool {
+    /// If another option like `--show-coverage` is enabled, it will return `false`.
+    pub(crate) fn is_json_output(&self) -> bool {
         self.output_format.is_json() && !self.show_coverage
     }
 }
diff --git a/src/librustdoc/passes/strip_hidden.rs b/src/librustdoc/passes/strip_hidden.rs
index 2998289fd273a..4ef5f7f20a917 100644
--- a/src/librustdoc/passes/strip_hidden.rs
+++ b/src/librustdoc/passes/strip_hidden.rs
@@ -23,7 +23,7 @@ pub(crate) const STRIP_HIDDEN: Pass = Pass {
 /// Strip items marked `#[doc(hidden)]`
 pub(crate) fn strip_hidden(krate: clean::Crate, cx: &mut DocContext<'_>) -> clean::Crate {
     let mut retained = ItemIdSet::default();
-    let is_json_output = cx.is_json();
+    let is_json_output = cx.is_json_output();
 
     // strip all #[doc(hidden)] items
     let krate = {
diff --git a/src/librustdoc/passes/strip_priv_imports.rs b/src/librustdoc/passes/strip_priv_imports.rs
index 20659d5e8c69c..b9b2431f06f2b 100644
--- a/src/librustdoc/passes/strip_priv_imports.rs
+++ b/src/librustdoc/passes/strip_priv_imports.rs
@@ -13,7 +13,7 @@ pub(crate) const STRIP_PRIV_IMPORTS: Pass = Pass {
 };
 
 pub(crate) fn strip_priv_imports(krate: clean::Crate, cx: &mut DocContext<'_>) -> clean::Crate {
-    let is_json_output = cx.is_json();
+    let is_json_output = cx.is_json_output();
     ImportStripper {
         tcx: cx.tcx,
         is_json_output,
diff --git a/src/librustdoc/passes/strip_private.rs b/src/librustdoc/passes/strip_private.rs
index 916c2f0657e47..1bd8a7838ec08 100644
--- a/src/librustdoc/passes/strip_private.rs
+++ b/src/librustdoc/passes/strip_private.rs
@@ -18,7 +18,7 @@ pub(crate) const STRIP_PRIVATE: Pass = Pass {
 pub(crate) fn strip_private(mut krate: clean::Crate, cx: &mut DocContext<'_>) -> clean::Crate {
     // This stripper collects all *retained* nodes.
     let mut retained = ItemIdSet::default();
-    let is_json_output = cx.is_json();
+    let is_json_output = cx.is_json_output();
 
     // strip all private items
     {
diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs
index 00f5251596b60..31c33fbf49737 100644
--- a/src/librustdoc/visit_ast.rs
+++ b/src/librustdoc/visit_ast.rs
@@ -235,7 +235,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> {
             return false;
         }
 
-        if self.cx.is_json() {
+        if self.cx.is_json_output() {
             return false;
         }
 

From 75c943ed2d9bdddac892fd2a1825954d65d6b7ef Mon Sep 17 00:00:00 2001
From: rustbot <47979223+rustbot@users.noreply.github.com>
Date: Mon, 4 Nov 2024 12:01:29 -0500
Subject: [PATCH 08/15] Update books

---
 src/doc/edition-guide   | 2 +-
 src/doc/reference       | 2 +-
 src/doc/rust-by-example | 2 +-
 src/doc/rustc-dev-guide | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/doc/edition-guide b/src/doc/edition-guide
index 1f07c242f8162..2d482e203eb6d 160000
--- a/src/doc/edition-guide
+++ b/src/doc/edition-guide
@@ -1 +1 @@
-Subproject commit 1f07c242f8162a711a5ac5a4ea8fa7ec884ee7a9
+Subproject commit 2d482e203eb6d6e353814cf1415c5f94e590b9e0
diff --git a/src/doc/reference b/src/doc/reference
index 23ce619966541..da0f6dad76767 160000
--- a/src/doc/reference
+++ b/src/doc/reference
@@ -1 +1 @@
-Subproject commit 23ce619966541bf2c80d45fdfeecf3393e360a13
+Subproject commit da0f6dad767670da0e8cd5af8a7090db3272f626
diff --git a/src/doc/rust-by-example b/src/doc/rust-by-example
index 8bede1b919a81..9db78608b17d5 160000
--- a/src/doc/rust-by-example
+++ b/src/doc/rust-by-example
@@ -1 +1 @@
-Subproject commit 8bede1b919a81ab7d0c961f6bbf68d3efa297bd2
+Subproject commit 9db78608b17d5f4a6c033b8a3038466b87d63206
diff --git a/src/doc/rustc-dev-guide b/src/doc/rustc-dev-guide
index 59d94ea75a0b1..6a5accdaf1025 160000
--- a/src/doc/rustc-dev-guide
+++ b/src/doc/rustc-dev-guide
@@ -1 +1 @@
-Subproject commit 59d94ea75a0b157e148af14c73c2dd60efb7b60a
+Subproject commit 6a5accdaf10255882b1e6c59dfe5f1c79ac95484

From 02a1ab807191891e888c19b4b8b54de33dba081b Mon Sep 17 00:00:00 2001
From: Eugene Shamis <eugene.shamis@amd.com>
Date: Fri, 1 Nov 2024 15:33:07 -0400
Subject: [PATCH 09/15] Replace checked slice indexing by unchecked to support
 panic-free code

Fixes #126425

Replace the potentially panicking `[]` indexing with `get_unchecked()`
to prevent linking with panic-related code.
---
 library/core/src/fmt/num.rs | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/library/core/src/fmt/num.rs b/library/core/src/fmt/num.rs
index f1540803f978d..aaf429bac8e7f 100644
--- a/library/core/src/fmt/num.rs
+++ b/library/core/src/fmt/num.rs
@@ -88,7 +88,9 @@ unsafe trait GenericRadix: Sized {
                 };
             }
         }
-        let buf = &buf[curr..];
+        // SAFETY: `curr` is initialized to `buf.len()` and is only decremented,
+        // so it is always in bounds.
+        let buf = unsafe { buf.get_unchecked(curr..) };
         // SAFETY: The only chars in `buf` are created by `Self::digit` which are assumed to be
         // valid UTF-8
         let buf = unsafe {

From 37f48da802f2f76e210c82731fc7483d7ae96bfd Mon Sep 17 00:00:00 2001
From: Eugene Shamis <eugene.shamis@amd.com>
Date: Mon, 4 Nov 2024 09:50:08 -0500
Subject: [PATCH 10/15] Updated SAFETY comment to address underflow

---
 library/core/src/fmt/num.rs | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/library/core/src/fmt/num.rs b/library/core/src/fmt/num.rs
index aaf429bac8e7f..5a5c4d600745f 100644
--- a/library/core/src/fmt/num.rs
+++ b/library/core/src/fmt/num.rs
@@ -88,8 +88,9 @@ unsafe trait GenericRadix: Sized {
                 };
             }
         }
-        // SAFETY: `curr` is initialized to `buf.len()` and is only decremented,
-        // so it is always in bounds.
+        // SAFETY: `curr` is initialized to `buf.len()` and is only decremented, so it can't overflow. It is
+        // decremented exactly once for each digit. Since u128 is the widest fixed width integer format dupported,
+        // the maximum number of digits (bits) is 128 for base-2, so `curr` won't underflow as well.
         let buf = unsafe { buf.get_unchecked(curr..) };
         // SAFETY: The only chars in `buf` are created by `Self::digit` which are assumed to be
         // valid UTF-8

From 65d8f1b8bfe79a3b88048ce24629456b007f8f4a Mon Sep 17 00:00:00 2001
From: Eugene Shamis <eugene.shamis@amd.com>
Date: Mon, 4 Nov 2024 12:43:57 -0500
Subject: [PATCH 11/15] Fixed typo, rebased

---
 library/core/src/fmt/num.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/library/core/src/fmt/num.rs b/library/core/src/fmt/num.rs
index 5a5c4d600745f..d43f25d9fd129 100644
--- a/library/core/src/fmt/num.rs
+++ b/library/core/src/fmt/num.rs
@@ -89,7 +89,7 @@ unsafe trait GenericRadix: Sized {
             }
         }
         // SAFETY: `curr` is initialized to `buf.len()` and is only decremented, so it can't overflow. It is
-        // decremented exactly once for each digit. Since u128 is the widest fixed width integer format dupported,
+        // decremented exactly once for each digit. Since u128 is the widest fixed width integer format supported,
         // the maximum number of digits (bits) is 128 for base-2, so `curr` won't underflow as well.
         let buf = unsafe { buf.get_unchecked(curr..) };
         // SAFETY: The only chars in `buf` are created by `Self::digit` which are assumed to be

From 4872b6bcbd8f2800b370b7e05b1ca1e18054a166 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Eduardo=20S=C3=A1nchez=20Mu=C3=B1oz?= <eduardosm-dev@e64.io>
Date: Mon, 4 Nov 2024 19:08:28 +0100
Subject: [PATCH 12/15] Improve example of `impl Pattern for &[char]`

The previous version used `['l', 'l']` as pattern, which would suggest that it matches the `ll` of `Hello world` as a whole.
---
 library/core/src/str/pattern.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/library/core/src/str/pattern.rs b/library/core/src/str/pattern.rs
index 665c9fc67d01e..52e2364893eb1 100644
--- a/library/core/src/str/pattern.rs
+++ b/library/core/src/str/pattern.rs
@@ -886,8 +886,8 @@ impl<'a, 'b> DoubleEndedSearcher<'a> for CharSliceSearcher<'a, 'b> {}
 /// # Examples
 ///
 /// ```
-/// assert_eq!("Hello world".find(&['l', 'l'] as &[_]), Some(2));
-/// assert_eq!("Hello world".find(&['l', 'l'][..]), Some(2));
+/// assert_eq!("Hello world".find(&['o', 'l'][..]), Some(2));
+/// assert_eq!("Hello world".find(&['h', 'w'][..]), Some(6));
 /// ```
 impl<'b> Pattern for &'b [char] {
     pattern_methods!('a, CharSliceSearcher<'a, 'b>, MultiCharEqPattern, CharSliceSearcher);

From 107b4fdba21d437e7e36b2a62bc803d6a8f31021 Mon Sep 17 00:00:00 2001
From: NotWearingPants <26556598+NotWearingPants@users.noreply.github.com>
Date: Mon, 4 Nov 2024 20:42:21 +0200
Subject: [PATCH 13/15] docs: fix grammar in doc comment at unix/process.rs

---
 library/std/src/os/unix/process.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/library/std/src/os/unix/process.rs b/library/std/src/os/unix/process.rs
index ef5adaf229088..7c3fa7d6507e7 100644
--- a/library/std/src/os/unix/process.rs
+++ b/library/std/src/os/unix/process.rs
@@ -143,7 +143,7 @@ pub trait CommandExt: Sealed {
     ///
     /// This function, unlike `spawn`, will **not** `fork` the process to create
     /// a new child. Like spawn, however, the default behavior for the stdio
-    /// descriptors will be to inherited from the current process.
+    /// descriptors will be to inherit them from the current process.
     ///
     /// # Notes
     ///

From f4b72dcff01f03b0479b737c007c49c97039d3a4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonathan=20D=C3=B6nszelmann?= <jonathan@donsz.nl>
Date: Mon, 4 Nov 2024 14:55:07 +0100
Subject: [PATCH 14/15] Move two attribute lints to be early pass (post
 expansion)

---
 .../src/attrs/allow_attributes.rs             |   4 +-
 .../attrs/allow_attributes_without_reason.rs  |   4 +-
 .../attrs/blanket_clippy_restriction_lints.rs |   6 +-
 .../src/attrs/deprecated_semver.rs            |   4 +-
 .../src/attrs/duplicated_attributes.rs        |   8 +-
 .../src/attrs/mixed_attributes_style.rs       |   6 +-
 .../clippy/clippy_lints/src/attrs/mod.rs      | 128 +++++++++++-------
 .../src/attrs/should_panic_without_expect.rs  |   6 +-
 .../src/attrs/useless_attribute.rs            |   8 +-
 src/tools/clippy/clippy_lints/src/lib.rs      |   2 +
 .../clippy_utils/src/check_proc_macro.rs      |   5 +-
 .../clippy/tests/ui/allow_attributes.stderr   |  10 +-
 .../ui/allow_attributes_without_reason.stderr |  11 +-
 src/tools/clippy/tests/ui/attrs.stderr        |  18 +--
 .../blanket_clippy_restriction_lints.stderr   |  14 +-
 15 files changed, 122 insertions(+), 112 deletions(-)

diff --git a/src/tools/clippy/clippy_lints/src/attrs/allow_attributes.rs b/src/tools/clippy/clippy_lints/src/attrs/allow_attributes.rs
index a5a7b9f74a693..1879391ec290b 100644
--- a/src/tools/clippy/clippy_lints/src/attrs/allow_attributes.rs
+++ b/src/tools/clippy/clippy_lints/src/attrs/allow_attributes.rs
@@ -3,11 +3,11 @@ use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::is_from_proc_macro;
 use rustc_ast::{AttrStyle, Attribute};
 use rustc_errors::Applicability;
-use rustc_lint::{LateContext, LintContext};
+use rustc_lint::{EarlyContext, LintContext};
 use rustc_middle::lint::in_external_macro;
 
 // Separate each crate's features.
-pub fn check<'cx>(cx: &LateContext<'cx>, attr: &'cx Attribute) {
+pub fn check<'cx>(cx: &EarlyContext<'cx>, attr: &'cx Attribute) {
     if !in_external_macro(cx.sess(), attr.span)
         && let AttrStyle::Outer = attr.style
         && let Some(ident) = attr.ident()
diff --git a/src/tools/clippy/clippy_lints/src/attrs/allow_attributes_without_reason.rs b/src/tools/clippy/clippy_lints/src/attrs/allow_attributes_without_reason.rs
index 5d4e864b9b0b5..788377fe83ce4 100644
--- a/src/tools/clippy/clippy_lints/src/attrs/allow_attributes_without_reason.rs
+++ b/src/tools/clippy/clippy_lints/src/attrs/allow_attributes_without_reason.rs
@@ -2,12 +2,12 @@ use super::{ALLOW_ATTRIBUTES_WITHOUT_REASON, Attribute};
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::is_from_proc_macro;
 use rustc_ast::{MetaItemInner, MetaItemKind};
-use rustc_lint::{LateContext, LintContext};
+use rustc_lint::{EarlyContext, LintContext};
 use rustc_middle::lint::in_external_macro;
 use rustc_span::sym;
 use rustc_span::symbol::Symbol;
 
-pub(super) fn check<'cx>(cx: &LateContext<'cx>, name: Symbol, items: &[MetaItemInner], attr: &'cx Attribute) {
+pub(super) fn check<'cx>(cx: &EarlyContext<'cx>, name: Symbol, items: &[MetaItemInner], attr: &'cx Attribute) {
     // Check if the reason is present
     if let Some(item) = items.last().and_then(MetaItemInner::meta_item)
         && let MetaItemKind::NameValue(_) = &item.kind
diff --git a/src/tools/clippy/clippy_lints/src/attrs/blanket_clippy_restriction_lints.rs b/src/tools/clippy/clippy_lints/src/attrs/blanket_clippy_restriction_lints.rs
index 0baf889faa076..fecf316640636 100644
--- a/src/tools/clippy/clippy_lints/src/attrs/blanket_clippy_restriction_lints.rs
+++ b/src/tools/clippy/clippy_lints/src/attrs/blanket_clippy_restriction_lints.rs
@@ -2,11 +2,11 @@ use super::BLANKET_CLIPPY_RESTRICTION_LINTS;
 use super::utils::extract_clippy_lint;
 use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
 use rustc_ast::MetaItemInner;
-use rustc_lint::{LateContext, Level, LintContext};
+use rustc_lint::{EarlyContext, Level, LintContext};
 use rustc_span::symbol::Symbol;
 use rustc_span::{DUMMY_SP, sym};
 
-pub(super) fn check(cx: &LateContext<'_>, name: Symbol, items: &[MetaItemInner]) {
+pub(super) fn check(cx: &EarlyContext<'_>, name: Symbol, items: &[MetaItemInner]) {
     for lint in items {
         if let Some(lint_name) = extract_clippy_lint(lint) {
             if lint_name.as_str() == "restriction" && name != sym::allow {
@@ -23,7 +23,7 @@ pub(super) fn check(cx: &LateContext<'_>, name: Symbol, items: &[MetaItemInner])
     }
 }
 
-pub(super) fn check_command_line(cx: &LateContext<'_>) {
+pub(super) fn check_command_line(cx: &EarlyContext<'_>) {
     for (name, level) in &cx.sess().opts.lint_opts {
         if name == "clippy::restriction" && *level > Level::Allow {
             span_lint_and_then(
diff --git a/src/tools/clippy/clippy_lints/src/attrs/deprecated_semver.rs b/src/tools/clippy/clippy_lints/src/attrs/deprecated_semver.rs
index 1898c145c76a9..d3153ec6613b5 100644
--- a/src/tools/clippy/clippy_lints/src/attrs/deprecated_semver.rs
+++ b/src/tools/clippy/clippy_lints/src/attrs/deprecated_semver.rs
@@ -1,11 +1,11 @@
 use super::DEPRECATED_SEMVER;
 use clippy_utils::diagnostics::span_lint;
 use rustc_ast::{LitKind, MetaItemLit};
-use rustc_lint::LateContext;
+use rustc_lint::EarlyContext;
 use rustc_span::Span;
 use semver::Version;
 
-pub(super) fn check(cx: &LateContext<'_>, span: Span, lit: &MetaItemLit) {
+pub(super) fn check(cx: &EarlyContext<'_>, span: Span, lit: &MetaItemLit) {
     if let LitKind::Str(is, _) = lit.kind {
         if is.as_str() == "TBD" || Version::parse(is.as_str()).is_ok() {
             return;
diff --git a/src/tools/clippy/clippy_lints/src/attrs/duplicated_attributes.rs b/src/tools/clippy/clippy_lints/src/attrs/duplicated_attributes.rs
index 55f8e1072db74..2ddbc7a6a76dc 100644
--- a/src/tools/clippy/clippy_lints/src/attrs/duplicated_attributes.rs
+++ b/src/tools/clippy/clippy_lints/src/attrs/duplicated_attributes.rs
@@ -2,12 +2,12 @@ use super::DUPLICATED_ATTRIBUTES;
 use clippy_utils::diagnostics::span_lint_and_then;
 use rustc_ast::{Attribute, MetaItem};
 use rustc_data_structures::fx::FxHashMap;
-use rustc_lint::LateContext;
+use rustc_lint::EarlyContext;
 use rustc_span::{Span, sym};
 use std::collections::hash_map::Entry;
 
 fn emit_if_duplicated(
-    cx: &LateContext<'_>,
+    cx: &EarlyContext<'_>,
     attr: &MetaItem,
     attr_paths: &mut FxHashMap<String, Span>,
     complete_path: String,
@@ -26,7 +26,7 @@ fn emit_if_duplicated(
 }
 
 fn check_duplicated_attr(
-    cx: &LateContext<'_>,
+    cx: &EarlyContext<'_>,
     attr: &MetaItem,
     attr_paths: &mut FxHashMap<String, Span>,
     parent: &mut Vec<String>,
@@ -65,7 +65,7 @@ fn check_duplicated_attr(
     }
 }
 
-pub fn check(cx: &LateContext<'_>, attrs: &[Attribute]) {
+pub fn check(cx: &EarlyContext<'_>, attrs: &[Attribute]) {
     let mut attr_paths = FxHashMap::default();
 
     for attr in attrs {
diff --git a/src/tools/clippy/clippy_lints/src/attrs/mixed_attributes_style.rs b/src/tools/clippy/clippy_lints/src/attrs/mixed_attributes_style.rs
index 5d2ea36b366c1..32c28c09c3602 100644
--- a/src/tools/clippy/clippy_lints/src/attrs/mixed_attributes_style.rs
+++ b/src/tools/clippy/clippy_lints/src/attrs/mixed_attributes_style.rs
@@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint;
 use rustc_ast::{AttrKind, AttrStyle, Attribute};
 use rustc_data_structures::fx::FxHashSet;
 use rustc_data_structures::sync::Lrc;
-use rustc_lint::{LateContext, LintContext};
+use rustc_lint::{EarlyContext, LintContext};
 use rustc_span::source_map::SourceMap;
 use rustc_span::{SourceFile, Span, Symbol};
 
@@ -32,7 +32,7 @@ impl From<&AttrKind> for SimpleAttrKind {
     }
 }
 
-pub(super) fn check(cx: &LateContext<'_>, item_span: Span, attrs: &[Attribute]) {
+pub(super) fn check(cx: &EarlyContext<'_>, item_span: Span, attrs: &[Attribute]) {
     let mut inner_attr_kind: FxHashSet<SimpleAttrKind> = FxHashSet::default();
     let mut outer_attr_kind: FxHashSet<SimpleAttrKind> = FxHashSet::default();
 
@@ -64,7 +64,7 @@ pub(super) fn check(cx: &LateContext<'_>, item_span: Span, attrs: &[Attribute])
     }
 }
 
-fn lint_mixed_attrs(cx: &LateContext<'_>, attrs: &[Attribute]) {
+fn lint_mixed_attrs(cx: &EarlyContext<'_>, attrs: &[Attribute]) {
     let mut attrs_iter = attrs.iter().filter(|attr| !attr.span.from_expansion());
     let span = if let (Some(first), Some(last)) = (attrs_iter.next(), attrs_iter.last()) {
         first.span.with_hi(last.span.hi())
diff --git a/src/tools/clippy/clippy_lints/src/attrs/mod.rs b/src/tools/clippy/clippy_lints/src/attrs/mod.rs
index 1a34ca99fc2b9..684756ce87f1d 100644
--- a/src/tools/clippy/clippy_lints/src/attrs/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/attrs/mod.rs
@@ -14,8 +14,8 @@ mod utils;
 
 use clippy_config::Conf;
 use clippy_config::msrvs::{self, Msrv};
-use rustc_ast::{Attribute, MetaItemInner, MetaItemKind};
-use rustc_hir::{ImplItem, Item, ItemKind, TraitItem};
+use rustc_ast::{Attribute, MetaItemInner, MetaItemKind, self as ast};
+use rustc_hir::{ImplItem, Item, TraitItem};
 use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass};
 use rustc_session::impl_lint_pass;
 use rustc_span::sym;
@@ -414,15 +414,7 @@ pub struct Attributes {
 }
 
 impl_lint_pass!(Attributes => [
-    ALLOW_ATTRIBUTES,
-    ALLOW_ATTRIBUTES_WITHOUT_REASON,
     INLINE_ALWAYS,
-    DEPRECATED_SEMVER,
-    USELESS_ATTRIBUTE,
-    BLANKET_CLIPPY_RESTRICTION_LINTS,
-    SHOULD_PANIC_WITHOUT_EXPECT,
-    MIXED_ATTRIBUTES_STYLE,
-    DUPLICATED_ATTRIBUTES,
 ]);
 
 impl Attributes {
@@ -434,53 +426,11 @@ impl Attributes {
 }
 
 impl<'tcx> LateLintPass<'tcx> for Attributes {
-    fn check_crate(&mut self, cx: &LateContext<'tcx>) {
-        blanket_clippy_restriction_lints::check_command_line(cx);
-        duplicated_attributes::check(cx, cx.tcx.hir().krate_attrs());
-    }
-
-    fn check_attribute(&mut self, cx: &LateContext<'tcx>, attr: &'tcx Attribute) {
-        if let Some(items) = &attr.meta_item_list() {
-            if let Some(ident) = attr.ident() {
-                if is_lint_level(ident.name, attr.id) {
-                    blanket_clippy_restriction_lints::check(cx, ident.name, items);
-                }
-                if matches!(ident.name, sym::allow) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) {
-                    allow_attributes::check(cx, attr);
-                }
-                if matches!(ident.name, sym::allow | sym::expect) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION)
-                {
-                    allow_attributes_without_reason::check(cx, ident.name, items, attr);
-                }
-                if items.is_empty() || !attr.has_name(sym::deprecated) {
-                    return;
-                }
-                for item in items {
-                    if let MetaItemInner::MetaItem(mi) = &item
-                        && let MetaItemKind::NameValue(lit) = &mi.kind
-                        && mi.has_name(sym::since)
-                    {
-                        deprecated_semver::check(cx, item.span(), lit);
-                    }
-                }
-            }
-        }
-        if attr.has_name(sym::should_panic) {
-            should_panic_without_expect::check(cx, attr);
-        }
-    }
-
     fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
         let attrs = cx.tcx.hir().attrs(item.hir_id());
         if is_relevant_item(cx, item) {
             inline_always::check(cx, item.span, item.ident.name, attrs);
         }
-        match item.kind {
-            ItemKind::ExternCrate(..) | ItemKind::Use(..) => useless_attribute::check(cx, item, attrs),
-            _ => {},
-        }
-        mixed_attributes_style::check(cx, item.span, attrs);
-        duplicated_attributes::check(cx, attrs);
     }
 
     fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
@@ -526,3 +476,77 @@ impl EarlyLintPass for EarlyAttributes {
 
     extract_msrv_attr!(EarlyContext);
 }
+
+pub struct PostExpansionEarlyAttributes {
+    msrv: Msrv,
+}
+
+impl PostExpansionEarlyAttributes {
+    pub fn new(conf: &'static Conf) -> Self {
+        Self {
+            msrv: conf.msrv.clone(),
+        }
+    }
+}
+
+impl_lint_pass!(PostExpansionEarlyAttributes => [
+    ALLOW_ATTRIBUTES,
+    ALLOW_ATTRIBUTES_WITHOUT_REASON,
+    DEPRECATED_SEMVER,
+    USELESS_ATTRIBUTE,
+    BLANKET_CLIPPY_RESTRICTION_LINTS,
+    SHOULD_PANIC_WITHOUT_EXPECT,
+    MIXED_ATTRIBUTES_STYLE,
+    DUPLICATED_ATTRIBUTES,
+]);
+
+impl EarlyLintPass for PostExpansionEarlyAttributes {
+    fn check_crate(&mut self, cx: &EarlyContext<'_>, krate: &ast::Crate) {
+        blanket_clippy_restriction_lints::check_command_line(cx);
+        duplicated_attributes::check(cx, &krate.attrs);
+    }
+
+    fn check_attribute(&mut self, cx: &EarlyContext<'_>, attr: &Attribute) {
+        if let Some(items) = &attr.meta_item_list() {
+            if let Some(ident) = attr.ident() {
+                if matches!(ident.name, sym::allow) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) {
+                    allow_attributes::check(cx, attr);
+                }
+                if matches!(ident.name, sym::allow | sym::expect) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION)
+                {
+                    allow_attributes_without_reason::check(cx, ident.name, items, attr);
+                }
+                if is_lint_level(ident.name, attr.id) {
+                    blanket_clippy_restriction_lints::check(cx, ident.name, items);
+                }
+                if items.is_empty() || !attr.has_name(sym::deprecated) {
+                    return;
+                }
+                for item in items {
+                    if let MetaItemInner::MetaItem(mi) = &item
+                        && let MetaItemKind::NameValue(lit) = &mi.kind
+                        && mi.has_name(sym::since)
+                    {
+                        deprecated_semver::check(cx, item.span(), lit);
+                    }
+                }
+            }
+        }
+
+        if attr.has_name(sym::should_panic) {
+            should_panic_without_expect::check(cx, attr);
+        }
+    }
+
+    fn check_item(&mut self, cx: &EarlyContext<'_>, item: &'_ ast::Item) {
+        match item.kind {
+            ast::ItemKind::ExternCrate(..) | ast::ItemKind::Use(..) => useless_attribute::check(cx, item, &item.attrs),
+            _ => {},
+        }
+
+        mixed_attributes_style::check(cx, item.span, &item.attrs);
+        duplicated_attributes::check(cx, &item.attrs);
+    }
+
+    extract_msrv_attr!(EarlyContext);
+}
diff --git a/src/tools/clippy/clippy_lints/src/attrs/should_panic_without_expect.rs b/src/tools/clippy/clippy_lints/src/attrs/should_panic_without_expect.rs
index 2d45cbbf621f4..fadd527288028 100644
--- a/src/tools/clippy/clippy_lints/src/attrs/should_panic_without_expect.rs
+++ b/src/tools/clippy/clippy_lints/src/attrs/should_panic_without_expect.rs
@@ -4,12 +4,12 @@ use rustc_ast::token::{Token, TokenKind};
 use rustc_ast::tokenstream::TokenTree;
 use rustc_ast::{AttrArgs, AttrArgsEq, AttrKind};
 use rustc_errors::Applicability;
-use rustc_lint::LateContext;
+use rustc_lint::EarlyContext;
 use rustc_span::sym;
 
-pub(super) fn check(cx: &LateContext<'_>, attr: &Attribute) {
+pub(super) fn check(cx: &EarlyContext<'_>, attr: &Attribute) {
     if let AttrKind::Normal(normal_attr) = &attr.kind {
-        if let AttrArgs::Eq(_, AttrArgsEq::Hir(_)) = &normal_attr.item.args {
+        if let AttrArgs::Eq(_, AttrArgsEq::Ast(_)) = &normal_attr.item.args {
             // `#[should_panic = ".."]` found, good
             return;
         }
diff --git a/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs b/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs
index 72e6ce59d5980..92b9f9cba5251 100644
--- a/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs
+++ b/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs
@@ -1,15 +1,15 @@
 use super::utils::{extract_clippy_lint, is_lint_level, is_word};
-use super::{Attribute, USELESS_ATTRIBUTE};
+use super::USELESS_ATTRIBUTE;
 use clippy_utils::diagnostics::span_lint_and_then;
 use clippy_utils::source::{SpanRangeExt, first_line_of_span};
 use rustc_ast::MetaItemInner;
 use rustc_errors::Applicability;
-use rustc_hir::{Item, ItemKind};
-use rustc_lint::{LateContext, LintContext};
+use rustc_ast::{Item, ItemKind, Attribute};
+use rustc_lint::{EarlyContext, LintContext};
 use rustc_middle::lint::in_external_macro;
 use rustc_span::sym;
 
-pub(super) fn check(cx: &LateContext<'_>, item: &Item<'_>, attrs: &[Attribute]) {
+pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) {
     let skip_unused_imports = attrs.iter().any(|attr| attr.has_name(sym::macro_use));
 
     for attr in attrs {
diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs
index 14110539709d6..3fd07ced0e4ae 100644
--- a/src/tools/clippy/clippy_lints/src/lib.rs
+++ b/src/tools/clippy/clippy_lints/src/lib.rs
@@ -412,6 +412,8 @@ use rustc_lint::{Lint, LintId};
 pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
     // NOTE: Do not add any more pre-expansion passes. These should be removed eventually.
     store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes::new(conf)));
+
+    store.register_early_pass(move || Box::new(attrs::PostExpansionEarlyAttributes::new(conf)));
 }
 
 #[derive(Default)]
diff --git a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs
index bfb3a76ad251c..c5e2c8c09a277 100644
--- a/src/tools/clippy/clippy_utils/src/check_proc_macro.rs
+++ b/src/tools/clippy/clippy_utils/src/check_proc_macro.rs
@@ -21,7 +21,7 @@ use rustc_hir::{
     ImplItem, ImplItemKind, IsAuto, Item, ItemKind, Lit, LoopSource, MatchSource, MutTy, Node, Path, QPath, Safety,
     TraitItem, TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData, YieldSource,
 };
-use rustc_lint::{LateContext, LintContext};
+use rustc_lint::{LateContext, LintContext, EarlyContext};
 use rustc_middle::ty::TyCtxt;
 use rustc_session::Session;
 use rustc_span::symbol::{Ident, kw};
@@ -429,11 +429,12 @@ impl_with_search_pat!((_cx: LateContext<'tcx>, self: ImplItem<'_>) => impl_item_
 impl_with_search_pat!((_cx: LateContext<'tcx>, self: FieldDef<'_>) => field_def_search_pat(self));
 impl_with_search_pat!((_cx: LateContext<'tcx>, self: Variant<'_>) => variant_search_pat(self));
 impl_with_search_pat!((_cx: LateContext<'tcx>, self: Ty<'_>) => ty_search_pat(self));
-impl_with_search_pat!((_cx: LateContext<'tcx>, self: Attribute) => attr_search_pat(self));
 impl_with_search_pat!((_cx: LateContext<'tcx>, self: Ident) => ident_search_pat(*self));
 impl_with_search_pat!((_cx: LateContext<'tcx>, self: Lit) => lit_search_pat(&self.node));
 impl_with_search_pat!((_cx: LateContext<'tcx>, self: Path<'_>) => path_search_pat(self));
 
+impl_with_search_pat!((_cx: EarlyContext<'tcx>, self: Attribute) => attr_search_pat(self));
+
 impl<'cx> WithSearchPat<'cx> for (&FnKind<'cx>, &Body<'cx>, HirId, Span) {
     type Context = LateContext<'cx>;
 
diff --git a/src/tools/clippy/tests/ui/allow_attributes.stderr b/src/tools/clippy/tests/ui/allow_attributes.stderr
index 10dac0bc80808..023b4d7e40439 100644
--- a/src/tools/clippy/tests/ui/allow_attributes.stderr
+++ b/src/tools/clippy/tests/ui/allow_attributes.stderr
@@ -19,13 +19,5 @@ error: #[allow] attribute found
 LL |     #[allow(unused)]
    |       ^^^^^ help: replace it with: `expect`
 
-error: #[allow] attribute found
-  --> tests/ui/allow_attributes.rs:52:7
-   |
-LL |     #[allow(unused)]
-   |       ^^^^^ help: replace it with: `expect`
-   |
-   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
-
-error: aborting due to 4 previous errors
+error: aborting due to 3 previous errors
 
diff --git a/src/tools/clippy/tests/ui/allow_attributes_without_reason.stderr b/src/tools/clippy/tests/ui/allow_attributes_without_reason.stderr
index 86d7845df0416..9c1ac5af91b06 100644
--- a/src/tools/clippy/tests/ui/allow_attributes_without_reason.stderr
+++ b/src/tools/clippy/tests/ui/allow_attributes_without_reason.stderr
@@ -43,14 +43,5 @@ LL |     #[allow(unused)]
    |
    = help: try adding a reason at the end with `, reason = ".."`
 
-error: `allow` attribute without specifying a reason
-  --> tests/ui/allow_attributes_without_reason.rs:46:5
-   |
-LL |     #[allow(unused)]
-   |     ^^^^^^^^^^^^^^^^
-   |
-   = help: try adding a reason at the end with `, reason = ".."`
-   = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
-
-error: aborting due to 6 previous errors
+error: aborting due to 5 previous errors
 
diff --git a/src/tools/clippy/tests/ui/attrs.stderr b/src/tools/clippy/tests/ui/attrs.stderr
index cd409fc8701b6..a7fdceaba6f39 100644
--- a/src/tools/clippy/tests/ui/attrs.stderr
+++ b/src/tools/clippy/tests/ui/attrs.stderr
@@ -1,12 +1,3 @@
-error: you have declared `#[inline(always)]` on `test_attr_lint`. This is usually a bad idea
-  --> tests/ui/attrs.rs:5:1
-   |
-LL | #[inline(always)]
-   | ^^^^^^^^^^^^^^^^^
-   |
-   = note: `-D clippy::inline-always` implied by `-D warnings`
-   = help: to override `-D warnings` add `#[allow(clippy::inline_always)]`
-
 error: the since field must contain a semver-compliant version
   --> tests/ui/attrs.rs:27:14
    |
@@ -22,5 +13,14 @@ error: the since field must contain a semver-compliant version
 LL | #[deprecated(since = "1")]
    |              ^^^^^^^^^^^
 
+error: you have declared `#[inline(always)]` on `test_attr_lint`. This is usually a bad idea
+  --> tests/ui/attrs.rs:5:1
+   |
+LL | #[inline(always)]
+   | ^^^^^^^^^^^^^^^^^
+   |
+   = note: `-D clippy::inline-always` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::inline_always)]`
+
 error: aborting due to 3 previous errors
 
diff --git a/src/tools/clippy/tests/ui/blanket_clippy_restriction_lints.stderr b/src/tools/clippy/tests/ui/blanket_clippy_restriction_lints.stderr
index d410f25b2c274..1bad259b09a36 100644
--- a/src/tools/clippy/tests/ui/blanket_clippy_restriction_lints.stderr
+++ b/src/tools/clippy/tests/ui/blanket_clippy_restriction_lints.stderr
@@ -1,3 +1,10 @@
+error: `clippy::restriction` is not meant to be enabled as a group
+   |
+   = note: because of the command line `--warn clippy::restriction`
+   = help: enable the restriction lints you need individually
+   = note: `-D clippy::blanket-clippy-restriction-lints` implied by `-D warnings`
+   = help: to override `-D warnings` add `#[allow(clippy::blanket_clippy_restriction_lints)]`
+
 error: `clippy::restriction` is not meant to be enabled as a group
   --> tests/ui/blanket_clippy_restriction_lints.rs:6:9
    |
@@ -5,8 +12,6 @@ LL | #![warn(clippy::restriction)]
    |         ^^^^^^^^^^^^^^^^^^^
    |
    = help: enable the restriction lints you need individually
-   = note: `-D clippy::blanket-clippy-restriction-lints` implied by `-D warnings`
-   = help: to override `-D warnings` add `#[allow(clippy::blanket_clippy_restriction_lints)]`
 
 error: `clippy::restriction` is not meant to be enabled as a group
   --> tests/ui/blanket_clippy_restriction_lints.rs:8:9
@@ -24,10 +29,5 @@ LL | #![forbid(clippy::restriction)]
    |
    = help: enable the restriction lints you need individually
 
-error: `clippy::restriction` is not meant to be enabled as a group
-   |
-   = note: because of the command line `--warn clippy::restriction`
-   = help: enable the restriction lints you need individually
-
 error: aborting due to 4 previous errors
 

From ffad9aac27ff8a78f5d751bf88250470e2e9d790 Mon Sep 17 00:00:00 2001
From: Ralf Jung <post@ralfj.de>
Date: Mon, 2 Sep 2024 11:45:59 +0200
Subject: [PATCH 15/15] mark some target features as 'forbidden' so they cannot
 be (un)set

For now, this is just a warning, but should become a hard error in the future
---
 compiler/rustc_codegen_gcc/messages.ftl       |  11 +-
 compiler/rustc_codegen_gcc/src/errors.rs      |  13 ++
 compiler/rustc_codegen_gcc/src/gcc_util.rs    |  65 +++++---
 compiler/rustc_codegen_gcc/src/lib.rs         |   3 +-
 compiler/rustc_codegen_llvm/messages.ftl      |   5 +
 compiler/rustc_codegen_llvm/src/errors.rs     |   9 ++
 compiler/rustc_codegen_llvm/src/llvm_util.rs  | 119 +++++++++------
 compiler/rustc_codegen_ssa/messages.ftl       |   3 +
 .../rustc_codegen_ssa/src/codegen_attrs.rs    |  14 +-
 compiler/rustc_codegen_ssa/src/errors.rs      |   9 ++
 .../rustc_codegen_ssa/src/target_features.rs  |  54 ++++---
 compiler/rustc_middle/src/query/mod.rs        |   5 +-
 compiler/rustc_session/src/config/cfg.rs      |   5 +-
 compiler/rustc_target/src/target_features.rs  | 142 +++++++++++-------
 .../using-target-feature-unstable.rs          |   0
 .../forbidden-target-feature-attribute.rs     |  12 ++
 .../forbidden-target-feature-attribute.stderr |   8 +
 .../forbidden-target-feature-cfg.rs           |  15 ++
 .../forbidden-target-feature-flag-disable.rs  |  11 ++
 ...rbidden-target-feature-flag-disable.stderr |   7 +
 .../forbidden-target-feature-flag.rs          |  11 ++
 .../forbidden-target-feature-flag.stderr      |   7 +
 .../using-target-feature-unstable.rs          |   0
 23 files changed, 371 insertions(+), 157 deletions(-)
 rename tests/ui/{ => target-feature}/auxiliary/using-target-feature-unstable.rs (100%)
 create mode 100644 tests/ui/target-feature/forbidden-target-feature-attribute.rs
 create mode 100644 tests/ui/target-feature/forbidden-target-feature-attribute.stderr
 create mode 100644 tests/ui/target-feature/forbidden-target-feature-cfg.rs
 create mode 100644 tests/ui/target-feature/forbidden-target-feature-flag-disable.rs
 create mode 100644 tests/ui/target-feature/forbidden-target-feature-flag-disable.stderr
 create mode 100644 tests/ui/target-feature/forbidden-target-feature-flag.rs
 create mode 100644 tests/ui/target-feature/forbidden-target-feature-flag.stderr
 rename tests/ui/{ => target-feature}/using-target-feature-unstable.rs (100%)

diff --git a/compiler/rustc_codegen_gcc/messages.ftl b/compiler/rustc_codegen_gcc/messages.ftl
index bbae59ea7a55a..26ddc5732dd0b 100644
--- a/compiler/rustc_codegen_gcc/messages.ftl
+++ b/compiler/rustc_codegen_gcc/messages.ftl
@@ -8,6 +8,9 @@ codegen_gcc_invalid_minimum_alignment =
 codegen_gcc_lto_not_supported =
     LTO is not supported. You may get a linker error.
 
+codegen_gcc_forbidden_ctarget_feature =
+    target feature `{$feature}` cannot be toggled with `-Ctarget-feature`
+
 codegen_gcc_unwinding_inline_asm =
     GCC backend does not support unwinding from inline asm
 
@@ -24,11 +27,15 @@ codegen_gcc_lto_dylib = lto cannot be used for `dylib` crate type without `-Zdyl
 codegen_gcc_lto_bitcode_from_rlib = failed to get bitcode from object file for LTO ({$gcc_err})
 
 codegen_gcc_unknown_ctarget_feature =
-    unknown feature specified for `-Ctarget-feature`: `{$feature}`
-    .note = it is still passed through to the codegen backend
+    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_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_gcc_missing_features =
     add the missing features in a `target_feature` attribute
 
diff --git a/compiler/rustc_codegen_gcc/src/errors.rs b/compiler/rustc_codegen_gcc/src/errors.rs
index dc1895f437b52..7a586b5b04c52 100644
--- a/compiler/rustc_codegen_gcc/src/errors.rs
+++ b/compiler/rustc_codegen_gcc/src/errors.rs
@@ -17,6 +17,19 @@ pub(crate) struct UnknownCTargetFeature<'a> {
     pub rust_feature: PossibleFeature<'a>,
 }
 
+#[derive(Diagnostic)]
+#[diag(codegen_gcc_unstable_ctarget_feature)]
+#[note]
+pub(crate) struct UnstableCTargetFeature<'a> {
+    pub feature: &'a str,
+}
+
+#[derive(Diagnostic)]
+#[diag(codegen_gcc_forbidden_ctarget_feature)]
+pub(crate) struct ForbiddenCTargetFeature<'a> {
+    pub feature: &'a str,
+}
+
 #[derive(Subdiagnostic)]
 pub(crate) enum PossibleFeature<'a> {
     #[help(codegen_gcc_possible_feature)]
diff --git a/compiler/rustc_codegen_gcc/src/gcc_util.rs b/compiler/rustc_codegen_gcc/src/gcc_util.rs
index 3104088e0d5e9..65279c9495a3d 100644
--- a/compiler/rustc_codegen_gcc/src/gcc_util.rs
+++ b/compiler/rustc_codegen_gcc/src/gcc_util.rs
@@ -5,10 +5,13 @@ use rustc_codegen_ssa::errors::TargetFeatureDisableOrEnable;
 use rustc_data_structures::fx::FxHashMap;
 use rustc_middle::bug;
 use rustc_session::Session;
-use rustc_target::target_features::RUSTC_SPECIFIC_FEATURES;
+use rustc_target::target_features::{RUSTC_SPECIFIC_FEATURES, Stability};
 use smallvec::{SmallVec, smallvec};
 
-use crate::errors::{PossibleFeature, UnknownCTargetFeature, UnknownCTargetFeaturePrefix};
+use crate::errors::{
+    ForbiddenCTargetFeature, PossibleFeature, UnknownCTargetFeature, UnknownCTargetFeaturePrefix,
+    UnstableCTargetFeature,
+};
 
 /// The list of GCC features computed from CLI flags (`-Ctarget-cpu`, `-Ctarget-feature`,
 /// `--target` and similar).
@@ -43,7 +46,7 @@ pub(crate) fn global_gcc_features(sess: &Session, diagnostics: bool) -> Vec<Stri
     );
 
     // -Ctarget-features
-    let supported_features = sess.target.supported_target_features();
+    let known_features = sess.target.rust_target_features();
     let mut featsmap = FxHashMap::default();
     let feats = sess
         .opts
@@ -62,37 +65,49 @@ pub(crate) fn global_gcc_features(sess: &Session, diagnostics: bool) -> Vec<Stri
                 }
             };
 
+            // Get the backend feature name, if any.
+            // This excludes rustc-specific features, that do not get passed down to GCC.
             let feature = backend_feature_name(s)?;
             // Warn against use of GCC specific feature names on the CLI.
-            if diagnostics && !supported_features.iter().any(|&(v, _, _)| v == feature) {
-                let rust_feature = supported_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
+            if diagnostics {
+                let feature_state = known_features.iter().find(|&&(v, _, _)| v == feature);
+                match feature_state {
+                    None => {
+                        let rust_feature =
+                            known_features.iter().find_map(|&(rust_feature, _, _)| {
+                                let gcc_features = to_gcc_features(sess, rust_feature);
+                                if gcc_features.contains(&feature)
+                                    && !gcc_features.contains(&rust_feature)
+                                {
+                                    Some(rust_feature)
+                                } else {
+                                    None
+                                }
+                            });
+                        let unknown_feature = if let Some(rust_feature) = rust_feature {
+                            UnknownCTargetFeature {
+                                feature,
+                                rust_feature: PossibleFeature::Some { rust_feature },
+                            }
+                        } else {
+                            UnknownCTargetFeature { feature, rust_feature: PossibleFeature::None }
+                        };
+                        sess.dcx().emit_warn(unknown_feature);
                     }
-                });
-                let unknown_feature = if let Some(rust_feature) = rust_feature {
-                    UnknownCTargetFeature {
-                        feature,
-                        rust_feature: PossibleFeature::Some { rust_feature },
+                    Some((_, Stability::Stable, _)) => {}
+                    Some((_, Stability::Unstable(_), _)) => {
+                        // An unstable feature. Warn about using it.
+                        sess.dcx().emit_warn(UnstableCTargetFeature { feature });
                     }
-                } else {
-                    UnknownCTargetFeature { feature, rust_feature: PossibleFeature::None }
-                };
-                sess.dcx().emit_warn(unknown_feature);
-            }
+                    Some((_, Stability::Forbidden { .. }, _)) => {
+                        sess.dcx().emit_err(ForbiddenCTargetFeature { feature });
+                    }
+                }
 
-            if diagnostics {
                 // FIXME(nagisa): figure out how to not allocate a full hashset here.
                 featsmap.insert(feature, enable_disable == '+');
             }
 
-            // rustc-specific features do not get passed down to GCC…
-            if RUSTC_SPECIFIC_FEATURES.contains(&feature) {
-                return None;
-            }
             // ... otherwise though 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
diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs
index 7486eefeb85a7..f70dc94b26741 100644
--- a/compiler/rustc_codegen_gcc/src/lib.rs
+++ b/compiler/rustc_codegen_gcc/src/lib.rs
@@ -491,8 +491,9 @@ pub fn target_features(
 ) -> Vec<Symbol> {
     // TODO(antoyo): use global_gcc_features.
     sess.target
-        .supported_target_features()
+        .rust_target_features()
         .iter()
+        .filter(|(_, gate, _)| gate.is_supported())
         .filter_map(|&(feature, gate, _)| {
             if sess.is_nightly_build() || allow_unstable || gate.is_stable() {
                 Some(feature)
diff --git a/compiler/rustc_codegen_llvm/messages.ftl b/compiler/rustc_codegen_llvm/messages.ftl
index 0950e4bb26bac..63c64269eb805 100644
--- a/compiler/rustc_codegen_llvm/messages.ftl
+++ b/compiler/rustc_codegen_llvm/messages.ftl
@@ -7,6 +7,11 @@ codegen_llvm_dynamic_linking_with_lto =
 
 codegen_llvm_fixed_x18_invalid_arch = the `-Zfixed-x18` flag is not supported on the `{$arch}` architecture
 
+codegen_llvm_forbidden_ctarget_feature =
+    target feature `{$feature}` cannot be toggled with `-Ctarget-feature`: {$reason}
+    .note = this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+codegen_llvm_forbidden_ctarget_feature_issue = for more information, see issue #116344 <https://github.com/rust-lang/rust/issues/116344>
+
 codegen_llvm_from_llvm_diag = {$message}
 
 codegen_llvm_from_llvm_optimization_diag = {$filename}:{$line}:{$column} {$pass_name} ({$kind}): {$message}
diff --git a/compiler/rustc_codegen_llvm/src/errors.rs b/compiler/rustc_codegen_llvm/src/errors.rs
index 0d436e1891ece..3cdb5b971d908 100644
--- a/compiler/rustc_codegen_llvm/src/errors.rs
+++ b/compiler/rustc_codegen_llvm/src/errors.rs
@@ -31,6 +31,15 @@ pub(crate) struct UnstableCTargetFeature<'a> {
     pub feature: &'a str,
 }
 
+#[derive(Diagnostic)]
+#[diag(codegen_llvm_forbidden_ctarget_feature)]
+#[note]
+#[note(codegen_llvm_forbidden_ctarget_feature_issue)]
+pub(crate) struct ForbiddenCTargetFeature<'a> {
+    pub feature: &'a str,
+    pub reason: &'a str,
+}
+
 #[derive(Subdiagnostic)]
 pub(crate) enum PossibleFeature<'a> {
     #[help(codegen_llvm_possible_feature)]
diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs
index 9adb1299b3d31..8b27a6a66777b 100644
--- a/compiler/rustc_codegen_llvm/src/llvm_util.rs
+++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs
@@ -16,12 +16,12 @@ use rustc_session::Session;
 use rustc_session::config::{PrintKind, PrintRequest};
 use rustc_span::symbol::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_SPECIAL_FEATURES, RUSTC_SPECIFIC_FEATURES, Stability};
 
 use crate::back::write::create_informational_target_machine;
 use crate::errors::{
-    FixedX18InvalidArch, InvalidTargetFeaturePrefix, PossibleFeature, UnknownCTargetFeature,
-    UnknownCTargetFeaturePrefix, UnstableCTargetFeature,
+    FixedX18InvalidArch, ForbiddenCTargetFeature, InvalidTargetFeaturePrefix, PossibleFeature,
+    UnknownCTargetFeature, UnknownCTargetFeaturePrefix, UnstableCTargetFeature,
 };
 use crate::llvm;
 
@@ -280,19 +280,29 @@ pub(crate) fn to_llvm_features<'a>(sess: &Session, s: &'a str) -> Option<LLVMFea
     }
 }
 
-/// Used to generate cfg variables and apply features
-/// Must express features in the way Rust understands them
+/// Used to generate cfg variables and apply features.
+/// Must express features in the way Rust understands them.
+///
+/// We do not have to worry about RUSTC_SPECIFIC_FEATURES here, those are handled outside codegen.
 pub fn target_features(sess: &Session, allow_unstable: bool) -> Vec<Symbol> {
-    let mut features = vec![];
-
-    // Add base features for the target
+    let mut features: FxHashSet<Symbol> = Default::default();
+
+    // Add base features for the target.
+    // We do *not* add the -Ctarget-features there, and instead duplicate the logic for that below.
+    // The reason is that if LLVM considers a feature implied but we do not, we don't want that to
+    // show up in `cfg`. That way, `cfg` is entirely under our control -- except for the handling of
+    // 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.
     features.extend(
         sess.target
-            .supported_target_features()
+            .rust_target_features()
             .iter()
+            .filter(|(_, gate, _)| gate.is_supported())
             .filter(|(feature, _, _)| {
-                // skip checking special features, as LLVM may not understands them
+                // skip checking special features, as LLVM may not understand them
                 if RUSTC_SPECIAL_FEATURES.contains(feature) {
                     return true;
                 }
@@ -323,7 +333,12 @@ pub fn target_features(sess: &Session, allow_unstable: bool) -> Vec<Symbol> {
         if enabled {
             features.extend(sess.target.implied_target_features(std::iter::once(feature)));
         } else {
+            // 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| {
+                // Keep a feature if it does not imply `feature`. Or, equivalently,
+                // remove the reverse-dependencies of `feature`.
                 !sess.target.implied_target_features(std::iter::once(*f)).contains(&feature)
             });
         }
@@ -331,8 +346,9 @@ pub fn target_features(sess: &Session, allow_unstable: bool) -> Vec<Symbol> {
 
     // Filter enabled features based on feature gates
     sess.target
-        .supported_target_features()
+        .rust_target_features()
         .iter()
+        .filter(|(_, gate, _)| gate.is_supported())
         .filter_map(|&(feature, gate, _)| {
             if sess.is_nightly_build() || allow_unstable || gate.is_stable() {
                 Some(feature)
@@ -392,9 +408,13 @@ fn print_target_features(out: &mut String, sess: &Session, tm: &llvm::TargetMach
     let mut known_llvm_target_features = FxHashSet::<&'static str>::default();
     let mut rustc_target_features = sess
         .target
-        .supported_target_features()
+        .rust_target_features()
         .iter()
-        .filter_map(|(feature, _gate, _implied)| {
+        .filter_map(|(feature, gate, _implied)| {
+            if !gate.is_supported() {
+                // Only list (experimentally) supported features.
+                return None;
+            }
             // LLVM asserts that these are sorted. LLVM and Rust both use byte comparison for these
             // strings.
             let llvm_feature = to_llvm_features(sess, *feature)?.llvm_feature_name;
@@ -567,7 +587,7 @@ pub(crate) fn global_llvm_features(
 
     // -Ctarget-features
     if !only_base_features {
-        let supported_features = sess.target.supported_target_features();
+        let known_features = sess.target.rust_target_features();
         let mut featsmap = FxHashMap::default();
 
         // insert implied features
@@ -601,50 +621,53 @@ pub(crate) fn global_llvm_features(
                     }
                 };
 
+                // Get the backend feature name, if any.
+                // This excludes rustc-specific features, which do not get passed to LLVM.
                 let feature = backend_feature_name(sess, s)?;
                 // Warn against use of LLVM specific feature names and unstable features on the CLI.
                 if diagnostics {
-                    let feature_state = supported_features.iter().find(|&&(v, _, _)| v == feature);
-                    if feature_state.is_none() {
-                        let rust_feature =
-                            supported_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 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 },
                                 }
-                            });
-                        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);
-                    } else if feature_state
-                        .is_some_and(|(_name, feature_gate, _implied)| !feature_gate.is_stable())
-                    {
-                        // An unstable feature. Warn about using it.
-                        sess.dcx().emit_warn(UnstableCTargetFeature { feature });
+                            } else {
+                                UnknownCTargetFeature {
+                                    feature,
+                                    rust_feature: PossibleFeature::None,
+                                }
+                            };
+                            sess.dcx().emit_warn(unknown_feature);
+                        }
+                        Some((_, Stability::Stable, _)) => {}
+                        Some((_, Stability::Unstable(_), _)) => {
+                            // An unstable feature. Warn about using it.
+                            sess.dcx().emit_warn(UnstableCTargetFeature { feature });
+                        }
+                        Some((_, Stability::Forbidden { reason }, _)) => {
+                            sess.dcx().emit_warn(ForbiddenCTargetFeature { feature, reason });
+                        }
                     }
-                }
 
-                if diagnostics {
                     // FIXME(nagisa): figure out how to not allocate a full hashset here.
                     featsmap.insert(feature, enable_disable == '+');
                 }
 
-                // rustc-specific features do not get passed down to LLVM…
-                if RUSTC_SPECIFIC_FEATURES.contains(&feature) {
-                    return None;
-                }
-
-                // ... otherwise though we run through `to_llvm_features` when
+                // 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.
diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl
index d07274920feaf..bb74698a060af 100644
--- a/compiler/rustc_codegen_ssa/messages.ftl
+++ b/compiler/rustc_codegen_ssa/messages.ftl
@@ -58,6 +58,9 @@ codegen_ssa_failed_to_write = failed to write {$path}: {$error}
 
 codegen_ssa_field_associated_value_expected = associated value expected for `{$name}`
 
+codegen_ssa_forbidden_target_feature_attr =
+    target feature `{$feature}` cannot be toggled with `#[target_feature]`: {$reason}
+
 codegen_ssa_ignoring_emit_path = ignoring emit path because multiple .{$extension} files were produced
 
 codegen_ssa_ignoring_output = ignoring -o because multiple .{$extension} files were produced
diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
index a5bd3adbcddc9..31f8d479f7eb8 100644
--- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
+++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
@@ -20,8 +20,8 @@ use rustc_span::symbol::Ident;
 use rustc_span::{Span, sym};
 use rustc_target::spec::{SanitizerSet, abi};
 
-use crate::errors::{self, MissingFeatures, TargetFeatureDisableOrEnable};
-use crate::target_features::{check_target_feature_trait_unsafe, from_target_feature};
+use crate::errors;
+use crate::target_features::{check_target_feature_trait_unsafe, from_target_feature_attr};
 
 fn linkage_by_name(tcx: TyCtxt<'_>, def_id: LocalDefId, name: &str) -> Linkage {
     use rustc_middle::mir::mono::Linkage::*;
@@ -73,7 +73,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
         codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_BUILTINS;
     }
 
-    let supported_target_features = tcx.supported_target_features(LOCAL_CRATE);
+    let rust_target_features = tcx.rust_target_features(LOCAL_CRATE);
 
     let mut inline_span = None;
     let mut link_ordinal_span = None;
@@ -281,10 +281,10 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
                         check_target_feature_trait_unsafe(tcx, did, attr.span);
                     }
                 }
-                from_target_feature(
+                from_target_feature_attr(
                     tcx,
                     attr,
-                    supported_target_features,
+                    rust_target_features,
                     &mut codegen_fn_attrs.target_features,
                 );
             }
@@ -676,10 +676,10 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: LocalDefId) -> CodegenFnAttrs {
             .next()
             .map_or_else(|| tcx.def_span(did), |a| a.span);
         tcx.dcx()
-            .create_err(TargetFeatureDisableOrEnable {
+            .create_err(errors::TargetFeatureDisableOrEnable {
                 features,
                 span: Some(span),
-                missing_features: Some(MissingFeatures),
+                missing_features: Some(errors::MissingFeatures),
             })
             .emit();
     }
diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs
index d67cf0e3a6d5f..670ad39d811e6 100644
--- a/compiler/rustc_codegen_ssa/src/errors.rs
+++ b/compiler/rustc_codegen_ssa/src/errors.rs
@@ -1018,6 +1018,15 @@ pub(crate) struct TargetFeatureSafeTrait {
     pub def: Span,
 }
 
+#[derive(Diagnostic)]
+#[diag(codegen_ssa_forbidden_target_feature_attr)]
+pub struct ForbiddenTargetFeatureAttr<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub feature: &'a str,
+    pub reason: &'a str,
+}
+
 #[derive(Diagnostic)]
 #[diag(codegen_ssa_failed_to_get_layout)]
 pub struct FailedToGetLayout<'tcx> {
diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs
index 0845bcc5749f4..eee7cc75400d9 100644
--- a/compiler/rustc_codegen_ssa/src/target_features.rs
+++ b/compiler/rustc_codegen_ssa/src/target_features.rs
@@ -11,13 +11,16 @@ use rustc_middle::ty::TyCtxt;
 use rustc_session::parse::feature_err;
 use rustc_span::Span;
 use rustc_span::symbol::{Symbol, sym};
+use rustc_target::target_features::{self, Stability};
 
 use crate::errors;
 
-pub(crate) fn from_target_feature(
+/// Compute the enabled target features from the `#[target_feature]` function attribute.
+/// Enabled target features are added to `target_features`.
+pub(crate) fn from_target_feature_attr(
     tcx: TyCtxt<'_>,
     attr: &ast::Attribute,
-    supported_target_features: &UnordMap<String, Option<Symbol>>,
+    rust_target_features: &UnordMap<String, target_features::Stability>,
     target_features: &mut Vec<TargetFeature>,
 ) {
     let Some(list) = attr.meta_item_list() else { return };
@@ -46,12 +49,12 @@ pub(crate) fn from_target_feature(
 
         // We allow comma separation to enable multiple features.
         added_target_features.extend(value.as_str().split(',').filter_map(|feature| {
-            let Some(feature_gate) = supported_target_features.get(feature) else {
+            let Some(stability) = rust_target_features.get(feature) else {
                 let msg = format!("the feature named `{feature}` is not valid for this target");
                 let mut err = tcx.dcx().struct_span_err(item.span(), msg);
                 err.span_label(item.span(), format!("`{feature}` is not valid for this target"));
                 if let Some(stripped) = feature.strip_prefix('+') {
-                    let valid = supported_target_features.contains_key(stripped);
+                    let valid = rust_target_features.contains_key(stripped);
                     if valid {
                         err.help("consider removing the leading `+` in the feature name");
                     }
@@ -61,18 +64,31 @@ pub(crate) fn from_target_feature(
             };
 
             // Only allow target features whose feature gates have been enabled.
-            let allowed = match feature_gate.as_ref().copied() {
-                Some(name) => rust_features.enabled(name),
-                None => true,
+            let allowed = match stability {
+                Stability::Forbidden { .. } => false,
+                Stability::Stable => true,
+                Stability::Unstable(name) => rust_features.enabled(*name),
             };
             if !allowed {
-                feature_err(
-                    &tcx.sess,
-                    feature_gate.unwrap(),
-                    item.span(),
-                    format!("the target feature `{feature}` is currently unstable"),
-                )
-                .emit();
+                match stability {
+                    Stability::Stable => unreachable!(),
+                    &Stability::Unstable(lang_feature_name) => {
+                        feature_err(
+                            &tcx.sess,
+                            lang_feature_name,
+                            item.span(),
+                            format!("the target feature `{feature}` is currently unstable"),
+                        )
+                        .emit();
+                    }
+                    Stability::Forbidden { reason } => {
+                        tcx.dcx().emit_err(errors::ForbiddenTargetFeatureAttr {
+                            span: item.span(),
+                            feature,
+                            reason,
+                        });
+                    }
+                }
             }
             Some(Symbol::intern(feature))
         }));
@@ -138,20 +154,20 @@ pub(crate) fn check_target_feature_trait_unsafe(tcx: TyCtxt<'_>, id: LocalDefId,
 
 pub(crate) fn provide(providers: &mut Providers) {
     *providers = Providers {
-        supported_target_features: |tcx, cnum| {
+        rust_target_features: |tcx, cnum| {
             assert_eq!(cnum, LOCAL_CRATE);
             if tcx.sess.opts.actually_rustdoc {
                 // rustdoc needs to be able to document functions that use all the features, so
                 // whitelist them all
-                rustc_target::target_features::all_known_features()
-                    .map(|(a, b)| (a.to_string(), b.as_feature_name()))
+                rustc_target::target_features::all_rust_features()
+                    .map(|(a, b)| (a.to_string(), b))
                     .collect()
             } else {
                 tcx.sess
                     .target
-                    .supported_target_features()
+                    .rust_target_features()
                     .iter()
-                    .map(|&(a, b, _)| (a.to_string(), b.as_feature_name()))
+                    .map(|&(a, b, _)| (a.to_string(), b))
                     .collect()
             }
         },
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index d7a60a843b717..0f7f727212cf0 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -2185,10 +2185,11 @@ rustc_queries! {
         desc { "computing autoderef types for `{}`", goal.canonical.value.value }
     }
 
-    query supported_target_features(_: CrateNum) -> &'tcx UnordMap<String, Option<Symbol>> {
+    /// Returns the Rust target features for the current target. These are not always the same as LLVM target features!
+    query rust_target_features(_: CrateNum) -> &'tcx UnordMap<String, rustc_target::target_features::Stability> {
         arena_cache
         eval_always
-        desc { "looking up supported target features" }
+        desc { "looking up Rust target features" }
     }
 
     query implied_target_features(feature: Symbol) -> &'tcx Vec<Symbol> {
diff --git a/compiler/rustc_session/src/config/cfg.rs b/compiler/rustc_session/src/config/cfg.rs
index 31ef2bda4f1e5..f30da4fbfc64b 100644
--- a/compiler/rustc_session/src/config/cfg.rs
+++ b/compiler/rustc_session/src/config/cfg.rs
@@ -370,8 +370,9 @@ impl CheckCfg {
         ins!(sym::sanitizer_cfi_normalize_integers, no_values);
 
         ins!(sym::target_feature, empty_values).extend(
-            rustc_target::target_features::all_known_features()
-                .map(|(f, _sb)| f)
+            rustc_target::target_features::all_rust_features()
+                .filter(|(_, s)| s.is_supported())
+                .map(|(f, _s)| f)
                 .chain(rustc_target::target_features::RUSTC_SPECIFIC_FEATURES.iter().cloned())
                 .map(Symbol::intern),
         );
diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs
index 3df8f0590a354..eec07a8c3517e 100644
--- a/compiler/rustc_target/src/target_features.rs
+++ b/compiler/rustc_target/src/target_features.rs
@@ -1,10 +1,17 @@
+//! Declares Rust's target feature names for each target.
+//! Note that these are similar to but not always identical to LLVM's feature names,
+//! and Rust adds some features that do not correspond to LLVM features at all.
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
 use rustc_span::symbol::{Symbol, sym};
 
 /// Features that control behaviour of rustc, rather than the codegen.
+/// These exist globally and are not in the target-specific lists below.
 pub const RUSTC_SPECIFIC_FEATURES: &[&str] = &["crt-static"];
 
-/// Features that require special handling when passing to LLVM.
+/// 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.
@@ -16,26 +23,47 @@ pub enum Stability {
     /// This target feature is unstable; using it in `#[target_feature]` or `#[cfg(target_feature)]`
     /// requires enabling the given nightly feature.
     Unstable(Symbol),
+    /// This feature can not be set via `-Ctarget-feature` or `#[target_feature]`, it can only be set in the basic
+    /// target definition. Used in particular for features that change the floating-point ABI.
+    Forbidden { reason: &'static str },
 }
 use Stability::*;
 
-impl Stability {
-    pub fn as_feature_name(self) -> Option<Symbol> {
+impl<CTX> HashStable<CTX> for Stability {
+    #[inline]
+    fn hash_stable(&self, hcx: &mut CTX, hasher: &mut StableHasher) {
+        std::mem::discriminant(self).hash_stable(hcx, hasher);
         match self {
-            Stable => None,
-            Unstable(s) => Some(s),
+            Stable => {}
+            Unstable(sym) => {
+                sym.hash_stable(hcx, hasher);
+            }
+            Forbidden { .. } => {}
         }
     }
+}
 
+impl Stability {
     pub fn is_stable(self) -> bool {
         matches!(self, Stable)
     }
+
+    /// Forbidden features are not supported.
+    pub fn is_supported(self) -> bool {
+        !matches!(self, Forbidden { .. })
+    }
 }
 
 // Here we list target features that rustc "understands": they can be used in `#[target_feature]`
 // and `#[cfg(target_feature)]`. They also do not trigger any warnings when used with
 // `-Ctarget-feature`.
 //
+// Note that even unstable (and even entirely unlisted) features can be used with `-Ctarget-feature`
+// on stable. Using a feature not on the list of Rust target features only emits a warning.
+// Only `cfg(target_feature)` and `#[target_feature]` actually do any stability gating.
+// `cfg(target_feature)` for unstable features just works on nightly without any feature gate.
+// `#[target_feature]` requires a feature gate.
+//
 // When adding features to the below lists
 // check whether they're named already elsewhere in rust
 // e.g. in stdarch and whether the given name matches LLVM's
@@ -46,17 +74,27 @@ impl Stability {
 // per-function level, since we would then allow safe calls from functions with `+soft-float` to
 // functions without that feature!
 //
-// When adding a new feature, be particularly mindful of features that affect function ABIs. Those
-// need to be treated very carefully to avoid introducing unsoundness! This often affects features
-// that enable/disable hardfloat support (see https://github.com/rust-lang/rust/issues/116344 for an
-// example of this going wrong), but features enabling new SIMD registers are also a concern (see
-// https://github.com/rust-lang/rust/issues/116558 for an example of this going wrong).
+// It is important for soundness that features allowed here do *not* change the function call ABI.
+// For example, disabling the `x87` feature on x86 changes how scalar floats are passed as
+// arguments, so enabling toggling that feature would be unsound. In fact, since `-Ctarget-feature`
+// will just allow unknown features (with a warning), we have to explicitly list features that change
+// the ABI as `Forbidden` to ensure using them causes an error. Note that this is only effective if
+// such features can never be toggled via `-Ctarget-cpu`! If that is ever a possibility, we will need
+// extra checks ensuring that the LLVM-computed target features for a CPU did not (un)set a
+// `Forbidden` feature. See https://github.com/rust-lang/rust/issues/116344 for some more context.
+// FIXME: add such "forbidden" features for non-x86 targets.
+//
+// The one exception to features that change the ABI is features that enable larger vector
+// registers. Those are permitted to be listed here. This is currently unsound (see
+// https://github.com/rust-lang/rust/issues/116558); in the future we will have to ensure that
+// functions can only use such vectors as arguments/return types if the corresponding target feature
+// is enabled.
 //
 // Stabilizing a target feature requires t-lang approval.
 
 type ImpliedFeatures = &'static [&'static str];
 
-const ARM_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const ARM_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
     ("aclass", Unstable(sym::arm_target_feature), &[]),
     ("aes", Unstable(sym::arm_target_feature), &["neon"]),
@@ -70,6 +108,7 @@ const ARM_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     ("neon", Unstable(sym::arm_target_feature), &["vfp3"]),
     ("rclass", Unstable(sym::arm_target_feature), &[]),
     ("sha2", Unstable(sym::arm_target_feature), &["neon"]),
+    ("soft-float", Forbidden { reason: "unsound because it changes float ABI" }, &[]),
     // This is needed for inline assembly, but shouldn't be stabilized as-is
     // since it should be enabled per-function using #[instruction_set], not
     // #[target_feature].
@@ -87,9 +126,10 @@ const ARM_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     ("vfp4", Unstable(sym::arm_target_feature), &["vfp3"]),
     ("virtualization", Unstable(sym::arm_target_feature), &[]),
     // tidy-alphabetical-end
+    // FIXME: need to also forbid turning off `fpregs` on hardfloat targets
 ];
 
-const AARCH64_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const AARCH64_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
     // FEAT_AES & FEAT_PMULL
     ("aes", Stable, &["neon"]),
@@ -277,7 +317,7 @@ const AARCH64_TIED_FEATURES: &[&[&str]] = &[
     &["paca", "pacg"], // Together these represent `pauth` in LLVM
 ];
 
-const X86_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const X86_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
     ("adx", Stable, &[]),
     ("aes", Stable, &["sse2"]),
@@ -328,6 +368,7 @@ const X86_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     ("sha512", Unstable(sym::sha512_sm_x86), &["avx2"]),
     ("sm3", Unstable(sym::sha512_sm_x86), &["avx"]),
     ("sm4", Unstable(sym::sha512_sm_x86), &["avx2"]),
+    ("soft-float", Forbidden { reason: "unsound because it changes float ABI" }, &[]),
     ("sse", Stable, &[]),
     ("sse2", Stable, &["sse"]),
     ("sse3", Stable, &["sse2"]),
@@ -344,16 +385,17 @@ const X86_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     ("xsaveopt", Stable, &["xsave"]),
     ("xsaves", Stable, &["xsave"]),
     // tidy-alphabetical-end
+    // FIXME: need to also forbid turning off `x87` on hardfloat targets
 ];
 
-const HEXAGON_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const HEXAGON_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
     ("hvx", Unstable(sym::hexagon_target_feature), &[]),
     ("hvx-length128b", Unstable(sym::hexagon_target_feature), &["hvx"]),
     // tidy-alphabetical-end
 ];
 
-const POWERPC_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const POWERPC_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
     ("altivec", Unstable(sym::powerpc_target_feature), &[]),
     ("partword-atomics", Unstable(sym::powerpc_target_feature), &[]),
@@ -367,7 +409,7 @@ const POWERPC_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-end
 ];
 
-const MIPS_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const MIPS_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
     ("fp64", Unstable(sym::mips_target_feature), &[]),
     ("msa", Unstable(sym::mips_target_feature), &[]),
@@ -375,7 +417,7 @@ const MIPS_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-end
 ];
 
-const RISCV_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const RISCV_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
     ("a", Stable, &["zaamo", "zalrsc"]),
     ("c", Stable, &[]),
@@ -415,7 +457,7 @@ const RISCV_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-end
 ];
 
-const WASM_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const WASM_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
     ("atomics", Unstable(sym::wasm_target_feature), &[]),
     ("bulk-memory", Stable, &[]),
@@ -431,10 +473,10 @@ const WASM_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-end
 ];
 
-const BPF_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] =
+const BPF_FEATURES: &[(&str, Stability, ImpliedFeatures)] =
     &[("alu32", Unstable(sym::bpf_target_feature), &[])];
 
-const CSKY_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const CSKY_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
     ("10e60", Unstable(sym::csky_target_feature), &["7e10"]),
     ("2e3", Unstable(sym::csky_target_feature), &["e2"]),
@@ -481,7 +523,7 @@ const CSKY_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-end
 ];
 
-const LOONGARCH_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const LOONGARCH_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
     ("d", Unstable(sym::loongarch_target_feature), &["f"]),
     ("f", Unstable(sym::loongarch_target_feature), &[]),
@@ -495,7 +537,7 @@ const LOONGARCH_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-end
 ];
 
-const IBMZ_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
+const IBMZ_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
     // tidy-alphabetical-start
     ("backchain", Unstable(sym::s390x_target_feature), &[]),
     ("vector", Unstable(sym::s390x_target_feature), &[]),
@@ -506,41 +548,39 @@ const IBMZ_ALLOWED_FEATURES: &[(&str, Stability, ImpliedFeatures)] = &[
 /// primitives may be documented.
 ///
 /// IMPORTANT: If you're adding another feature list above, make sure to add it to this iterator!
-pub fn all_known_features() -> impl Iterator<Item = (&'static str, Stability)> {
+pub fn all_rust_features() -> impl Iterator<Item = (&'static str, Stability)> {
     std::iter::empty()
-        .chain(ARM_ALLOWED_FEATURES.iter())
-        .chain(AARCH64_ALLOWED_FEATURES.iter())
-        .chain(X86_ALLOWED_FEATURES.iter())
-        .chain(HEXAGON_ALLOWED_FEATURES.iter())
-        .chain(POWERPC_ALLOWED_FEATURES.iter())
-        .chain(MIPS_ALLOWED_FEATURES.iter())
-        .chain(RISCV_ALLOWED_FEATURES.iter())
-        .chain(WASM_ALLOWED_FEATURES.iter())
-        .chain(BPF_ALLOWED_FEATURES.iter())
-        .chain(CSKY_ALLOWED_FEATURES)
-        .chain(LOONGARCH_ALLOWED_FEATURES)
-        .chain(IBMZ_ALLOWED_FEATURES)
+        .chain(ARM_FEATURES.iter())
+        .chain(AARCH64_FEATURES.iter())
+        .chain(X86_FEATURES.iter())
+        .chain(HEXAGON_FEATURES.iter())
+        .chain(POWERPC_FEATURES.iter())
+        .chain(MIPS_FEATURES.iter())
+        .chain(RISCV_FEATURES.iter())
+        .chain(WASM_FEATURES.iter())
+        .chain(BPF_FEATURES.iter())
+        .chain(CSKY_FEATURES)
+        .chain(LOONGARCH_FEATURES)
+        .chain(IBMZ_FEATURES)
         .cloned()
         .map(|(f, s, _)| (f, s))
 }
 
 impl super::spec::Target {
-    pub fn supported_target_features(
-        &self,
-    ) -> &'static [(&'static str, Stability, ImpliedFeatures)] {
+    pub fn rust_target_features(&self) -> &'static [(&'static str, Stability, ImpliedFeatures)] {
         match &*self.arch {
-            "arm" => ARM_ALLOWED_FEATURES,
-            "aarch64" | "arm64ec" => AARCH64_ALLOWED_FEATURES,
-            "x86" | "x86_64" => X86_ALLOWED_FEATURES,
-            "hexagon" => HEXAGON_ALLOWED_FEATURES,
-            "mips" | "mips32r6" | "mips64" | "mips64r6" => MIPS_ALLOWED_FEATURES,
-            "powerpc" | "powerpc64" => POWERPC_ALLOWED_FEATURES,
-            "riscv32" | "riscv64" => RISCV_ALLOWED_FEATURES,
-            "wasm32" | "wasm64" => WASM_ALLOWED_FEATURES,
-            "bpf" => BPF_ALLOWED_FEATURES,
-            "csky" => CSKY_ALLOWED_FEATURES,
-            "loongarch64" => LOONGARCH_ALLOWED_FEATURES,
-            "s390x" => IBMZ_ALLOWED_FEATURES,
+            "arm" => ARM_FEATURES,
+            "aarch64" | "arm64ec" => AARCH64_FEATURES,
+            "x86" | "x86_64" => X86_FEATURES,
+            "hexagon" => HEXAGON_FEATURES,
+            "mips" | "mips32r6" | "mips64" | "mips64r6" => MIPS_FEATURES,
+            "powerpc" | "powerpc64" => POWERPC_FEATURES,
+            "riscv32" | "riscv64" => RISCV_FEATURES,
+            "wasm32" | "wasm64" => WASM_FEATURES,
+            "bpf" => BPF_FEATURES,
+            "csky" => CSKY_FEATURES,
+            "loongarch64" => LOONGARCH_FEATURES,
+            "s390x" => IBMZ_FEATURES,
             _ => &[],
         }
     }
@@ -557,7 +597,7 @@ impl super::spec::Target {
         base_features: impl Iterator<Item = Symbol>,
     ) -> FxHashSet<Symbol> {
         let implied_features = self
-            .supported_target_features()
+            .rust_target_features()
             .iter()
             .map(|(f, _, i)| (Symbol::intern(f), i))
             .collect::<FxHashMap<_, _>>();
diff --git a/tests/ui/auxiliary/using-target-feature-unstable.rs b/tests/ui/target-feature/auxiliary/using-target-feature-unstable.rs
similarity index 100%
rename from tests/ui/auxiliary/using-target-feature-unstable.rs
rename to tests/ui/target-feature/auxiliary/using-target-feature-unstable.rs
diff --git a/tests/ui/target-feature/forbidden-target-feature-attribute.rs b/tests/ui/target-feature/forbidden-target-feature-attribute.rs
new file mode 100644
index 0000000000000..91c56b43689e1
--- /dev/null
+++ b/tests/ui/target-feature/forbidden-target-feature-attribute.rs
@@ -0,0 +1,12 @@
+//@ compile-flags: --target=x86_64-unknown-linux-gnu --crate-type=lib
+//@ needs-llvm-components: x86
+#![feature(no_core, lang_items)]
+#![no_std]
+#![no_core]
+
+#[lang = "sized"]
+pub trait Sized {}
+
+#[target_feature(enable = "soft-float")]
+//~^ERROR: cannot be toggled with
+pub unsafe fn my_fun() {}
diff --git a/tests/ui/target-feature/forbidden-target-feature-attribute.stderr b/tests/ui/target-feature/forbidden-target-feature-attribute.stderr
new file mode 100644
index 0000000000000..fb318531f7edc
--- /dev/null
+++ b/tests/ui/target-feature/forbidden-target-feature-attribute.stderr
@@ -0,0 +1,8 @@
+error: target feature `soft-float` cannot be toggled with `#[target_feature]`: unsound because it changes float ABI
+  --> $DIR/forbidden-target-feature-attribute.rs:10:18
+   |
+LL | #[target_feature(enable = "soft-float")]
+   |                  ^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui/target-feature/forbidden-target-feature-cfg.rs b/tests/ui/target-feature/forbidden-target-feature-cfg.rs
new file mode 100644
index 0000000000000..5df26e26793d9
--- /dev/null
+++ b/tests/ui/target-feature/forbidden-target-feature-cfg.rs
@@ -0,0 +1,15 @@
+//@ compile-flags: --target=x86_64-unknown-none --crate-type=lib
+//@ needs-llvm-components: x86
+//@ check-pass
+#![feature(no_core, lang_items)]
+#![no_std]
+#![no_core]
+#![allow(unexpected_cfgs)]
+
+#[lang = "sized"]
+pub trait Sized {}
+
+// The compile_error macro does not exist, so if the `cfg` evaluates to `true` this
+// complains about the missing macro rather than showing the error... but that's good enough.
+#[cfg(target_feature = "soft-float")]
+compile_error!("the soft-float feature should not be exposed in `cfg`");
diff --git a/tests/ui/target-feature/forbidden-target-feature-flag-disable.rs b/tests/ui/target-feature/forbidden-target-feature-flag-disable.rs
new file mode 100644
index 0000000000000..b27e8a10afee7
--- /dev/null
+++ b/tests/ui/target-feature/forbidden-target-feature-flag-disable.rs
@@ -0,0 +1,11 @@
+//@ compile-flags: --target=x86_64-unknown-linux-gnu --crate-type=lib
+//@ needs-llvm-components: x86
+//@ compile-flags: -Ctarget-feature=-soft-float
+// For now this is just a warning.
+//@ build-pass
+#![feature(no_core, lang_items)]
+#![no_std]
+#![no_core]
+
+#[lang = "sized"]
+pub trait Sized {}
diff --git a/tests/ui/target-feature/forbidden-target-feature-flag-disable.stderr b/tests/ui/target-feature/forbidden-target-feature-flag-disable.stderr
new file mode 100644
index 0000000000000..508e1fe0cf476
--- /dev/null
+++ b/tests/ui/target-feature/forbidden-target-feature-flag-disable.stderr
@@ -0,0 +1,7 @@
+warning: target feature `soft-float` cannot be toggled with `-Ctarget-feature`: unsound because it changes float ABI
+   |
+   = note: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #116344 <https://github.com/rust-lang/rust/issues/116344>
+
+warning: 1 warning emitted
+
diff --git a/tests/ui/target-feature/forbidden-target-feature-flag.rs b/tests/ui/target-feature/forbidden-target-feature-flag.rs
new file mode 100644
index 0000000000000..93cebc6b53690
--- /dev/null
+++ b/tests/ui/target-feature/forbidden-target-feature-flag.rs
@@ -0,0 +1,11 @@
+//@ compile-flags: --target=x86_64-unknown-linux-gnu --crate-type=lib
+//@ needs-llvm-components: x86
+//@ compile-flags: -Ctarget-feature=+soft-float
+// For now this is just a warning.
+//@ build-pass
+#![feature(no_core, lang_items)]
+#![no_std]
+#![no_core]
+
+#[lang = "sized"]
+pub trait Sized {}
diff --git a/tests/ui/target-feature/forbidden-target-feature-flag.stderr b/tests/ui/target-feature/forbidden-target-feature-flag.stderr
new file mode 100644
index 0000000000000..508e1fe0cf476
--- /dev/null
+++ b/tests/ui/target-feature/forbidden-target-feature-flag.stderr
@@ -0,0 +1,7 @@
+warning: target feature `soft-float` cannot be toggled with `-Ctarget-feature`: unsound because it changes float ABI
+   |
+   = note: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #116344 <https://github.com/rust-lang/rust/issues/116344>
+
+warning: 1 warning emitted
+
diff --git a/tests/ui/using-target-feature-unstable.rs b/tests/ui/target-feature/using-target-feature-unstable.rs
similarity index 100%
rename from tests/ui/using-target-feature-unstable.rs
rename to tests/ui/target-feature/using-target-feature-unstable.rs