about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock9
-rw-r--r--compiler/rustc_driver/src/lib.rs8
-rw-r--r--compiler/rustc_errors/src/lib.rs30
-rw-r--r--compiler/rustc_feature/src/active.rs2
-rw-r--r--compiler/rustc_lint/src/lib.rs3
-rw-r--r--compiler/rustc_lint/src/multiple_supertrait_upcastable.rs63
-rw-r--r--compiler/rustc_span/src/symbol.rs1
-rw-r--r--compiler/rustc_symbol_mangling/src/typeid/typeid_itanium_cxx_abi.rs5
-rw-r--r--library/alloc/src/lib.rs2
-rw-r--r--library/core/src/error.rs1
-rw-r--r--library/core/src/lib.rs2
-rw-r--r--library/std/src/sys/sgx/mod.rs2
-rw-r--r--library/std/src/sys/sgx/thread.rs8
-rw-r--r--library/std/src/sys/sgx/thread_parker.rs107
-rw-r--r--library/std/src/sys/sgx/thread_parking.rs23
-rw-r--r--library/std/src/sys/unix/mod.rs2
-rw-r--r--library/std/src/sys/unix/thread_parker/netbsd.rs113
-rw-r--r--library/std/src/sys/unix/thread_parking/darwin.rs (renamed from library/std/src/sys/unix/thread_parker/darwin.rs)2
-rw-r--r--library/std/src/sys/unix/thread_parking/mod.rs (renamed from library/std/src/sys/unix/thread_parker/mod.rs)2
-rw-r--r--library/std/src/sys/unix/thread_parking/netbsd.rs52
-rw-r--r--library/std/src/sys/unix/thread_parking/pthread.rs (renamed from library/std/src/sys/unix/thread_parker/pthread.rs)2
-rw-r--r--library/std/src/sys/windows/mod.rs2
-rw-r--r--library/std/src/sys/windows/thread_parking.rs (renamed from library/std/src/sys/windows/thread_parker.rs)2
-rw-r--r--library/std/src/sys_common/mod.rs2
-rw-r--r--library/std/src/sys_common/thread_parking/futex.rs (renamed from library/std/src/sys_common/thread_parker/futex.rs)2
-rw-r--r--library/std/src/sys_common/thread_parking/generic.rs (renamed from library/std/src/sys_common/thread_parker/generic.rs)2
-rw-r--r--library/std/src/sys_common/thread_parking/id.rs108
-rw-r--r--library/std/src/sys_common/thread_parking/mod.rs (renamed from library/std/src/sys_common/thread_parker/mod.rs)10
-rw-r--r--library/std/src/sys_common/thread_parking/wait_flag.rs (renamed from library/std/src/sys_common/thread_parker/wait_flag.rs)2
-rw-r--r--library/std/src/thread/mod.rs4
-rw-r--r--src/bootstrap/builder.rs22
-rw-r--r--src/bootstrap/check.rs43
-rw-r--r--src/bootstrap/clean.rs22
-rw-r--r--src/bootstrap/compile.rs57
-rw-r--r--src/bootstrap/doc.rs4
-rw-r--r--src/bootstrap/flags.rs44
-rw-r--r--src/bootstrap/format.rs23
-rw-r--r--src/bootstrap/test.rs9
-rw-r--r--src/bootstrap/tool.rs2
-rw-r--r--src/test/codegen/sanitizer-cfi-emit-type-metadata-id-itanium-cxx-abi.rs22
-rw-r--r--src/test/rustdoc-gui/scrape-examples-layout.goml14
-rw-r--r--src/test/ui/feature-gates/feature-gate-multiple_supertrait_upcastable.rs12
-rw-r--r--src/test/ui/feature-gates/feature-gate-multiple_supertrait_upcastable.stderr57
-rw-r--r--src/test/ui/object-safety/issue-106247.rs9
-rw-r--r--src/test/ui/traits/trait-upcasting/multiple_supertrait_upcastable.rs10
-rw-r--r--src/test/ui/traits/trait-upcasting/multiple_supertrait_upcastable.stderr14
-rw-r--r--src/tools/tidy/Cargo.toml1
-rw-r--r--src/tools/tidy/src/error_codes_check.rs37
-rw-r--r--src/tools/tidy/src/lib.rs31
49 files changed, 428 insertions, 578 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 9581099c210..f99e58e59b8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2675,9 +2675,9 @@ dependencies = [
 
 [[package]]
 name = "owo-colors"
-version = "3.4.0"
+version = "3.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b"
+checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
 
 [[package]]
 name = "packed_simd_2"
@@ -5203,9 +5203,9 @@ dependencies = [
 
 [[package]]
 name = "termcolor"
-version = "1.1.2"
+version = "1.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
 dependencies = [
  "winapi-util",
 ]
@@ -5309,6 +5309,7 @@ dependencies = [
  "lazy_static",
  "miropt-test-tools",
  "regex",
+ "termcolor",
  "walkdir",
 ]
 
diff --git a/compiler/rustc_driver/src/lib.rs b/compiler/rustc_driver/src/lib.rs
index 31e08c44d29..30179e97872 100644
--- a/compiler/rustc_driver/src/lib.rs
+++ b/compiler/rustc_driver/src/lib.rs
@@ -1199,8 +1199,8 @@ static DEFAULT_HOOK: LazyLock<Box<dyn Fn(&panic::PanicInfo<'_>) + Sync + Send +
             };
 
             // Invoke the default handler, which prints the actual panic message and optionally a backtrace
-            // Don't do this for `ExplicitBug`, which has an unhelpful message and backtrace.
-            if !info.payload().is::<rustc_errors::ExplicitBug>() {
+            // Don't do this for `GoodPathBug`, which already emits its own more useful backtrace.
+            if !info.payload().is::<rustc_errors::GoodPathBug>() {
                 (*DEFAULT_HOOK)(info);
 
                 // Separate the output with an empty line
@@ -1237,7 +1237,9 @@ pub fn report_ice(info: &panic::PanicInfo<'_>, bug_report_url: &str) {
 
     // a .span_bug or .bug call has already printed what
     // it wants to print.
-    if !info.payload().is::<rustc_errors::ExplicitBug>() {
+    if !info.payload().is::<rustc_errors::ExplicitBug>()
+        && !info.payload().is::<rustc_errors::GoodPathBug>()
+    {
         let mut d = rustc_errors::Diagnostic::new(rustc_errors::Level::Bug, "unexpected panic");
         handler.emit_diagnostic(&mut d);
     }
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index b03352d5fec..0455f0d7383 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -40,12 +40,13 @@ use rustc_span::source_map::SourceMap;
 use rustc_span::HashStableContext;
 use rustc_span::{Loc, Span};
 
+use std::any::Any;
 use std::borrow::Cow;
+use std::fmt;
 use std::hash::Hash;
 use std::num::NonZeroUsize;
 use std::panic;
 use std::path::Path;
-use std::{error, fmt};
 
 use termcolor::{Color, ColorSpec};
 
@@ -361,16 +362,11 @@ pub use rustc_span::fatal_error::{FatalError, FatalErrorMarker};
 
 /// Signifies that the compiler died with an explicit call to `.bug`
 /// or `.span_bug` rather than a failed assertion, etc.
-#[derive(Copy, Clone, Debug)]
 pub struct ExplicitBug;
 
-impl fmt::Display for ExplicitBug {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "parser internal bug")
-    }
-}
-
-impl error::Error for ExplicitBug {}
+/// Signifies that the compiler died with an explicit call to `.delay_good_path_bug`
+/// rather than a failed assertion, etc.
+pub struct GoodPathBug;
 
 pub use diagnostic::{
     AddToDiagnostic, DecorateLint, Diagnostic, DiagnosticArg, DiagnosticArgValue, DiagnosticId,
@@ -507,7 +503,11 @@ impl Drop for HandlerInner {
 
         if !self.has_errors() {
             let bugs = std::mem::replace(&mut self.delayed_span_bugs, Vec::new());
-            self.flush_delayed(bugs, "no errors encountered even though `delay_span_bug` issued");
+            self.flush_delayed(
+                bugs,
+                "no errors encountered even though `delay_span_bug` issued",
+                ExplicitBug,
+            );
         }
 
         // FIXME(eddyb) this explains what `delayed_good_path_bugs` are!
@@ -520,6 +520,7 @@ impl Drop for HandlerInner {
             self.flush_delayed(
                 bugs.into_iter().map(DelayedDiagnostic::decorate),
                 "no warnings or errors encountered even though `delayed_good_path_bugs` issued",
+                GoodPathBug,
             );
         }
 
@@ -1203,7 +1204,11 @@ impl Handler {
     pub fn flush_delayed(&self) {
         let mut inner = self.inner.lock();
         let bugs = std::mem::replace(&mut inner.delayed_span_bugs, Vec::new());
-        inner.flush_delayed(bugs, "no errors encountered even though `delay_span_bug` issued");
+        inner.flush_delayed(
+            bugs,
+            "no errors encountered even though `delay_span_bug` issued",
+            ExplicitBug,
+        );
     }
 }
 
@@ -1580,6 +1585,7 @@ impl HandlerInner {
         &mut self,
         bugs: impl IntoIterator<Item = Diagnostic>,
         explanation: impl Into<DiagnosticMessage> + Copy,
+        panic_with: impl Any + Send + 'static,
     ) {
         let mut no_bugs = true;
         for mut bug in bugs {
@@ -1607,7 +1613,7 @@ impl HandlerInner {
 
         // Panic with `ExplicitBug` to avoid "unexpected panic" messages.
         if !no_bugs {
-            panic::panic_any(ExplicitBug);
+            panic::panic_any(panic_with);
         }
     }
 
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index 34fc62e0549..deb37bdebda 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -160,8 +160,6 @@ declare_features! (
     (active, intrinsics, "1.0.0", None, None),
     /// Allows using `#[lang = ".."]` attribute for linking items to special compiler logic.
     (active, lang_items, "1.0.0", None, None),
-    /// Allows the `multiple_supertrait_upcastable` lint.
-    (active, multiple_supertrait_upcastable, "CURRENT_RUSTC_VERSION", None, None),
     /// Allows using `#[omit_gdb_pretty_printer_section]`.
     (active, omit_gdb_pretty_printer_section, "1.5.0", None, None),
     /// Allows using `#[prelude_import]` on glob `use` items.
diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs
index 44ee4172675..1275d6f223c 100644
--- a/compiler/rustc_lint/src/lib.rs
+++ b/compiler/rustc_lint/src/lib.rs
@@ -61,7 +61,6 @@ mod late;
 mod let_underscore;
 mod levels;
 mod methods;
-mod multiple_supertrait_upcastable;
 mod non_ascii_idents;
 mod non_fmt_panic;
 mod nonstandard_style;
@@ -96,7 +95,6 @@ use hidden_unicode_codepoints::*;
 use internal::*;
 use let_underscore::*;
 use methods::*;
-use multiple_supertrait_upcastable::*;
 use non_ascii_idents::*;
 use non_fmt_panic::NonPanicFmt;
 use nonstandard_style::*;
@@ -231,7 +229,6 @@ late_lint_methods!(
             InvalidAtomicOrdering: InvalidAtomicOrdering,
             NamedAsmLabels: NamedAsmLabels,
             OpaqueHiddenInferredBound: OpaqueHiddenInferredBound,
-            MultipleSupertraitUpcastable: MultipleSupertraitUpcastable,
         ]
     ]
 );
diff --git a/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs b/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs
deleted file mode 100644
index 5861b826b1c..00000000000
--- a/compiler/rustc_lint/src/multiple_supertrait_upcastable.rs
+++ /dev/null
@@ -1,63 +0,0 @@
-use crate::{LateContext, LateLintPass, LintContext};
-
-use rustc_errors::DelayDm;
-use rustc_hir as hir;
-use rustc_span::sym;
-
-declare_lint! {
-    /// The `multiple_supertrait_upcastable` lint detects when an object-safe trait has multiple
-    /// supertraits.
-    ///
-    /// ### Example
-    ///
-    /// ```rust
-    /// trait A {}
-    /// trait B {}
-    ///
-    /// #[warn(multiple_supertrait_upcastable)]
-    /// trait C: A + B {}
-    /// ```
-    ///
-    /// {{produces}}
-    ///
-    /// ### Explanation
-    ///
-    /// To support upcasting with multiple supertraits, we need to store multiple vtables and this
-    /// can result in extra space overhead, even if no code actually uses upcasting.
-    /// This lint allows users to identify when such scenarios occur and to decide whether the
-    /// additional overhead is justified.
-    pub MULTIPLE_SUPERTRAIT_UPCASTABLE,
-    Allow,
-    "detect when an object-safe trait has multiple supertraits",
-    @feature_gate = sym::multiple_supertrait_upcastable;
-}
-
-declare_lint_pass!(MultipleSupertraitUpcastable => [MULTIPLE_SUPERTRAIT_UPCASTABLE]);
-
-impl<'tcx> LateLintPass<'tcx> for MultipleSupertraitUpcastable {
-    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'tcx>) {
-        let def_id = item.owner_id.to_def_id();
-        if let hir::ItemKind::Trait(_, _, _, _, _) = item.kind
-            && cx.tcx.is_object_safe(def_id)
-        {
-            let direct_super_traits_iter = cx.tcx
-                    .super_predicates_of(def_id)
-                    .predicates
-                    .into_iter()
-                    .filter_map(|(pred, _)| pred.to_opt_poly_trait_pred());
-            if direct_super_traits_iter.count() > 1 {
-                cx.struct_span_lint(
-                    MULTIPLE_SUPERTRAIT_UPCASTABLE,
-                    cx.tcx.def_span(def_id),
-                    DelayDm(|| {
-                        format!(
-                            "`{}` is object-safe and has multiple supertraits",
-                            item.ident,
-                        )
-                    }),
-                    |diag| diag,
-                );
-            }
-        }
-    }
-}
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index c450c4da9a8..85510fa2c66 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -944,7 +944,6 @@ symbols! {
         mul,
         mul_assign,
         mul_with_overflow,
-        multiple_supertrait_upcastable,
         must_not_suspend,
         must_use,
         naked,
diff --git a/compiler/rustc_symbol_mangling/src/typeid/typeid_itanium_cxx_abi.rs b/compiler/rustc_symbol_mangling/src/typeid/typeid_itanium_cxx_abi.rs
index 493e31a688f..e9b85705086 100644
--- a/compiler/rustc_symbol_mangling/src/typeid/typeid_itanium_cxx_abi.rs
+++ b/compiler/rustc_symbol_mangling/src/typeid/typeid_itanium_cxx_abi.rs
@@ -164,6 +164,7 @@ fn encode_const<'tcx>(
 
 /// Encodes a FnSig using the Itanium C++ ABI with vendor extended type qualifiers and types for
 /// Rust types that are not used at the FFI boundary.
+#[instrument(level = "trace", skip(tcx, dict))]
 fn encode_fnsig<'tcx>(
     tcx: TyCtxt<'tcx>,
     fn_sig: &FnSig<'tcx>,
@@ -653,6 +654,7 @@ fn encode_ty<'tcx>(
 // Transforms a ty:Ty for being encoded and used in the substitution dictionary. It transforms all
 // c_void types into unit types unconditionally, and generalizes all pointers if
 // TransformTyOptions::GENERALIZE_POINTERS option is set.
+#[instrument(level = "trace", skip(tcx))]
 fn transform_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, options: TransformTyOptions) -> Ty<'tcx> {
     let mut ty = ty;
 
@@ -698,7 +700,7 @@ fn transform_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, options: TransformTyOptio
                     !is_zst
                 });
                 if let Some(field) = field {
-                    let ty0 = tcx.type_of(field.did);
+                    let ty0 = tcx.bound_type_of(field.did).subst(tcx, substs);
                     // Generalize any repr(transparent) user-defined type that is either a pointer
                     // or reference, and either references itself or any other type that contains or
                     // references itself, to avoid a reference cycle.
@@ -827,6 +829,7 @@ fn transform_substs<'tcx>(
 
 /// Returns a type metadata identifier for the specified FnAbi using the Itanium C++ ABI with vendor
 /// extended type qualifiers and types for Rust types that are not used at the FFI boundary.
+#[instrument(level = "trace", skip(tcx))]
 pub fn typeid_for_fnabi<'tcx>(
     tcx: TyCtxt<'tcx>,
     fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
diff --git a/library/alloc/src/lib.rs b/library/alloc/src/lib.rs
index f6cbac005ff..9857f0516ba 100644
--- a/library/alloc/src/lib.rs
+++ b/library/alloc/src/lib.rs
@@ -87,7 +87,6 @@
 #![warn(missing_debug_implementations)]
 #![warn(missing_docs)]
 #![allow(explicit_outlives_requirements)]
-#![cfg_attr(not(bootstrap), warn(multiple_supertrait_upcastable))]
 //
 // Library features:
 #![feature(alloc_layout_extra)]
@@ -192,7 +191,6 @@
 #![feature(unsized_fn_params)]
 #![feature(c_unwind)]
 #![feature(with_negative_coherence)]
-#![cfg_attr(not(bootstrap), feature(multiple_supertrait_upcastable))]
 //
 // Rustdoc features:
 #![feature(doc_cfg)]
diff --git a/library/core/src/error.rs b/library/core/src/error.rs
index d2fac23ff18..7152300abcb 100644
--- a/library/core/src/error.rs
+++ b/library/core/src/error.rs
@@ -28,7 +28,6 @@ use crate::fmt::{Debug, Display};
 #[stable(feature = "rust1", since = "1.0.0")]
 #[cfg_attr(not(test), rustc_diagnostic_item = "Error")]
 #[rustc_has_incoherent_inherent_impls]
-#[cfg_attr(not(bootstrap), allow(multiple_supertrait_upcastable))]
 pub trait Error: Debug + Display {
     /// The lower-level source of this error, if any.
     ///
diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs
index 890d7df8e17..0e3fef4ead3 100644
--- a/library/core/src/lib.rs
+++ b/library/core/src/lib.rs
@@ -95,7 +95,6 @@
 #![warn(missing_docs)]
 #![allow(explicit_outlives_requirements)]
 #![allow(incomplete_features)]
-#![cfg_attr(not(bootstrap), warn(multiple_supertrait_upcastable))]
 //
 // Library features:
 #![feature(const_align_offset)]
@@ -232,7 +231,6 @@
 #![feature(unsized_fn_params)]
 #![feature(asm_const)]
 #![feature(const_transmute_copy)]
-#![cfg_attr(not(bootstrap), feature(multiple_supertrait_upcastable))]
 //
 // Target features:
 #![feature(arm_target_feature)]
diff --git a/library/std/src/sys/sgx/mod.rs b/library/std/src/sys/sgx/mod.rs
index 63e070207cd..9865a945bad 100644
--- a/library/std/src/sys/sgx/mod.rs
+++ b/library/std/src/sys/sgx/mod.rs
@@ -34,7 +34,7 @@ pub mod process;
 pub mod stdio;
 pub mod thread;
 pub mod thread_local_key;
-pub mod thread_parker;
+pub mod thread_parking;
 pub mod time;
 
 mod condvar;
diff --git a/library/std/src/sys/sgx/thread.rs b/library/std/src/sys/sgx/thread.rs
index 579f758c6cc..1608b8cb642 100644
--- a/library/std/src/sys/sgx/thread.rs
+++ b/library/std/src/sys/sgx/thread.rs
@@ -65,9 +65,9 @@ mod task_queue {
 /// execution. The signal is sent once all TLS destructors have finished at
 /// which point no new thread locals should be created.
 pub mod wait_notify {
-    use super::super::thread_parker::Parker;
     use crate::pin::Pin;
     use crate::sync::Arc;
+    use crate::sys_common::thread_parking::Parker;
 
     pub struct Notifier(Arc<Parker>);
 
@@ -87,14 +87,14 @@ pub mod wait_notify {
         /// called, this will return immediately, otherwise the current thread
         /// is blocked until notified.
         pub fn wait(self) {
-            // This is not actually `unsafe`, but it uses the `Parker` API,
-            // which needs `unsafe` on some platforms.
+            // SAFETY:
+            // This is only ever called on one thread.
             unsafe { Pin::new(&*self.0).park() }
         }
     }
 
     pub fn new() -> (Notifier, Waiter) {
-        let inner = Arc::new(Parker::new_internal());
+        let inner = Arc::new(Parker::new());
         (Notifier(inner.clone()), Waiter(inner))
     }
 }
diff --git a/library/std/src/sys/sgx/thread_parker.rs b/library/std/src/sys/sgx/thread_parker.rs
deleted file mode 100644
index 1c55bcffb1e..00000000000
--- a/library/std/src/sys/sgx/thread_parker.rs
+++ /dev/null
@@ -1,107 +0,0 @@
-//! Thread parking based on SGX events.
-
-use super::abi::{thread, usercalls};
-use crate::io::ErrorKind;
-use crate::pin::Pin;
-use crate::ptr::{self, NonNull};
-use crate::sync::atomic::AtomicPtr;
-use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release};
-use crate::time::Duration;
-use fortanix_sgx_abi::{EV_UNPARK, WAIT_INDEFINITE};
-
-// The TCS structure must be page-aligned (this is checked by EENTER), so these cannot
-// be valid pointers
-const EMPTY: *mut u8 = ptr::invalid_mut(1);
-const NOTIFIED: *mut u8 = ptr::invalid_mut(2);
-
-pub struct Parker {
-    /// The park state. One of EMPTY, NOTIFIED or a TCS address.
-    /// A state change to NOTIFIED must be done with release ordering
-    /// and be observed with acquire ordering so that operations after
-    /// `thread::park` returns will not occur before the unpark message
-    /// was sent.
-    state: AtomicPtr<u8>,
-}
-
-impl Parker {
-    /// Construct the thread parker. The UNIX parker implementation
-    /// requires this to happen in-place.
-    pub unsafe fn new(parker: *mut Parker) {
-        unsafe { parker.write(Parker::new_internal()) }
-    }
-
-    pub(super) fn new_internal() -> Parker {
-        Parker { state: AtomicPtr::new(EMPTY) }
-    }
-
-    // This implementation doesn't require `unsafe` and `Pin`, but other implementations do.
-    pub unsafe fn park(self: Pin<&Self>) {
-        if self.state.load(Acquire) != NOTIFIED {
-            let mut prev = EMPTY;
-            loop {
-                // Guard against changing TCS addresses by always setting the state to
-                // the current value.
-                let tcs = thread::current().as_ptr();
-                if self.state.compare_exchange(prev, tcs, Relaxed, Acquire).is_ok() {
-                    let event = usercalls::wait(EV_UNPARK, WAIT_INDEFINITE).unwrap();
-                    assert!(event & EV_UNPARK == EV_UNPARK);
-                    prev = tcs;
-                } else {
-                    // The state was definitely changed by another thread at this point.
-                    // The only time this occurs is when the state is changed to NOTIFIED.
-                    // We observed this change with acquire ordering, so we can simply
-                    // change the state to EMPTY with a relaxed store.
-                    break;
-                }
-            }
-        }
-
-        // At this point, the token was definately read with acquire ordering,
-        // so this can be a relaxed store.
-        self.state.store(EMPTY, Relaxed);
-    }
-
-    // This implementation doesn't require `unsafe` and `Pin`, but other implementations do.
-    pub unsafe fn park_timeout(self: Pin<&Self>, dur: Duration) {
-        let timeout = u128::min(dur.as_nanos(), WAIT_INDEFINITE as u128 - 1) as u64;
-        let tcs = thread::current().as_ptr();
-
-        if self.state.load(Acquire) != NOTIFIED {
-            if self.state.compare_exchange(EMPTY, tcs, Relaxed, Acquire).is_ok() {
-                match usercalls::wait(EV_UNPARK, timeout) {
-                    Ok(event) => assert!(event & EV_UNPARK == EV_UNPARK),
-                    Err(e) => {
-                        assert!(matches!(e.kind(), ErrorKind::TimedOut | ErrorKind::WouldBlock))
-                    }
-                }
-
-                // Swap to provide acquire ordering even if the timeout occurred
-                // before the token was set. This situation can result in spurious
-                // wakeups on the next call to `park_timeout`, but it is better to let
-                // those be handled by the user than do some perhaps unnecessary, but
-                // always expensive guarding.
-                self.state.swap(EMPTY, Acquire);
-                return;
-            }
-        }
-
-        // The token was already read with `acquire` ordering, this can be a store.
-        self.state.store(EMPTY, Relaxed);
-    }
-
-    // This implementation doesn't require `Pin`, but other implementations do.
-    pub fn unpark(self: Pin<&Self>) {
-        let state = self.state.swap(NOTIFIED, Release);
-
-        if !matches!(state, EMPTY | NOTIFIED) {
-            // There is a thread waiting, wake it up.
-            let tcs = NonNull::new(state).unwrap();
-            // This will fail if the thread has already terminated or its TCS is destroyed
-            // by the time the signal is sent, but that is fine. If another thread receives
-            // the same TCS, it will receive this notification as a spurious wakeup, but
-            // all users of `wait` should and (internally) do guard against those where
-            // necessary.
-            let _ = usercalls::send(EV_UNPARK, Some(tcs));
-        }
-    }
-}
diff --git a/library/std/src/sys/sgx/thread_parking.rs b/library/std/src/sys/sgx/thread_parking.rs
new file mode 100644
index 00000000000..0006cd4f1be
--- /dev/null
+++ b/library/std/src/sys/sgx/thread_parking.rs
@@ -0,0 +1,23 @@
+use super::abi::usercalls;
+use crate::io::ErrorKind;
+use crate::time::Duration;
+use fortanix_sgx_abi::{EV_UNPARK, WAIT_INDEFINITE};
+
+pub type ThreadId = fortanix_sgx_abi::Tcs;
+
+pub use super::abi::thread::current;
+
+pub fn park(_hint: usize) {
+    usercalls::wait(EV_UNPARK, WAIT_INDEFINITE).unwrap();
+}
+
+pub fn park_timeout(dur: Duration, _hint: usize) {
+    let timeout = u128::min(dur.as_nanos(), WAIT_INDEFINITE as u128 - 1) as u64;
+    if let Err(e) = usercalls::wait(EV_UNPARK, timeout) {
+        assert!(matches!(e.kind(), ErrorKind::TimedOut | ErrorKind::WouldBlock))
+    }
+}
+
+pub fn unpark(tid: ThreadId, _hint: usize) {
+    let _ = usercalls::send(EV_UNPARK, Some(tid));
+}
diff --git a/library/std/src/sys/unix/mod.rs b/library/std/src/sys/unix/mod.rs
index 9d7a06852b7..30a96be1430 100644
--- a/library/std/src/sys/unix/mod.rs
+++ b/library/std/src/sys/unix/mod.rs
@@ -40,7 +40,7 @@ pub mod stdio;
 pub mod thread;
 pub mod thread_local_dtor;
 pub mod thread_local_key;
-pub mod thread_parker;
+pub mod thread_parking;
 pub mod time;
 
 #[cfg(target_os = "espidf")]
diff --git a/library/std/src/sys/unix/thread_parker/netbsd.rs b/library/std/src/sys/unix/thread_parker/netbsd.rs
deleted file mode 100644
index 7657605b52f..00000000000
--- a/library/std/src/sys/unix/thread_parker/netbsd.rs
+++ /dev/null
@@ -1,113 +0,0 @@
-use crate::ffi::{c_int, c_void};
-use crate::pin::Pin;
-use crate::ptr::{null, null_mut};
-use crate::sync::atomic::{
-    AtomicU64,
-    Ordering::{Acquire, Relaxed, Release},
-};
-use crate::time::Duration;
-use libc::{_lwp_self, clockid_t, lwpid_t, time_t, timespec, CLOCK_MONOTONIC};
-
-extern "C" {
-    fn ___lwp_park60(
-        clock_id: clockid_t,
-        flags: c_int,
-        ts: *mut timespec,
-        unpark: lwpid_t,
-        hint: *const c_void,
-        unparkhint: *const c_void,
-    ) -> c_int;
-    fn _lwp_unpark(lwp: lwpid_t, hint: *const c_void) -> c_int;
-}
-
-/// The thread is not parked and the token is not available.
-///
-/// Zero cannot be a valid LWP id, since it is used as empty value for the unpark
-/// argument in _lwp_park.
-const EMPTY: u64 = 0;
-/// The token is available. Do not park anymore.
-const NOTIFIED: u64 = u64::MAX;
-
-pub struct Parker {
-    /// The parker state. Contains either one of the two state values above or the LWP
-    /// id of the parked thread.
-    state: AtomicU64,
-}
-
-impl Parker {
-    pub unsafe fn new(parker: *mut Parker) {
-        parker.write(Parker { state: AtomicU64::new(EMPTY) })
-    }
-
-    // Does not actually need `unsafe` or `Pin`, but the pthread implementation does.
-    pub unsafe fn park(self: Pin<&Self>) {
-        // If the token has already been made available, we can skip
-        // a bit of work, so check for it here.
-        if self.state.load(Acquire) != NOTIFIED {
-            let parked = _lwp_self() as u64;
-            let hint = self.state.as_mut_ptr().cast();
-            if self.state.compare_exchange(EMPTY, parked, Relaxed, Acquire).is_ok() {
-                // Loop to guard against spurious wakeups.
-                loop {
-                    ___lwp_park60(0, 0, null_mut(), 0, hint, null());
-                    if self.state.load(Acquire) == NOTIFIED {
-                        break;
-                    }
-                }
-            }
-        }
-
-        // At this point, the change to NOTIFIED has always been observed with acquire
-        // ordering, so we can just use a relaxed store here (instead of a swap).
-        self.state.store(EMPTY, Relaxed);
-    }
-
-    // Does not actually need `unsafe` or `Pin`, but the pthread implementation does.
-    pub unsafe fn park_timeout(self: Pin<&Self>, dur: Duration) {
-        if self.state.load(Acquire) != NOTIFIED {
-            let parked = _lwp_self() as u64;
-            let hint = self.state.as_mut_ptr().cast();
-            let mut timeout = timespec {
-                // Saturate so that the operation will definitely time out
-                // (even if it is after the heat death of the universe).
-                tv_sec: dur.as_secs().try_into().ok().unwrap_or(time_t::MAX),
-                tv_nsec: dur.subsec_nanos().into(),
-            };
-
-            if self.state.compare_exchange(EMPTY, parked, Relaxed, Acquire).is_ok() {
-                // Timeout needs to be mutable since it is modified on NetBSD 9.0 and
-                // above.
-                ___lwp_park60(CLOCK_MONOTONIC, 0, &mut timeout, 0, hint, null());
-                // Use a swap to get acquire ordering even if the token was set after
-                // the timeout occurred.
-                self.state.swap(EMPTY, Acquire);
-                return;
-            }
-        }
-
-        self.state.store(EMPTY, Relaxed);
-    }
-
-    // Does not actually need `Pin`, but the pthread implementation does.
-    pub fn unpark(self: Pin<&Self>) {
-        let state = self.state.swap(NOTIFIED, Release);
-        if !matches!(state, EMPTY | NOTIFIED) {
-            let lwp = state as lwpid_t;
-            let hint = self.state.as_mut_ptr().cast();
-
-            // If the parking thread terminated and did not actually park, this will
-            // probably return an error, which is OK. In the worst case, another
-            // thread has received the same LWP id. It will then receive a spurious
-            // wakeup, but those are allowable per the API contract. The same reasoning
-            // applies if a timeout occurred before this call, but the state was not
-            // yet reset.
-
-            // SAFETY:
-            // The syscall has no invariants to hold. Only unsafe because it is an
-            // extern function.
-            unsafe {
-                _lwp_unpark(lwp, hint);
-            }
-        }
-    }
-}
diff --git a/library/std/src/sys/unix/thread_parker/darwin.rs b/library/std/src/sys/unix/thread_parking/darwin.rs
index 2f5356fe227..b709fada3b4 100644
--- a/library/std/src/sys/unix/thread_parker/darwin.rs
+++ b/library/std/src/sys/unix/thread_parking/darwin.rs
@@ -46,7 +46,7 @@ unsafe impl Sync for Parker {}
 unsafe impl Send for Parker {}
 
 impl Parker {
-    pub unsafe fn new(parker: *mut Parker) {
+    pub unsafe fn new_in_place(parker: *mut Parker) {
         let semaphore = dispatch_semaphore_create(0);
         assert!(
             !semaphore.is_null(),
diff --git a/library/std/src/sys/unix/thread_parker/mod.rs b/library/std/src/sys/unix/thread_parking/mod.rs
index 35f1e68a87e..185333c072f 100644
--- a/library/std/src/sys/unix/thread_parker/mod.rs
+++ b/library/std/src/sys/unix/thread_parking/mod.rs
@@ -24,7 +24,7 @@ cfg_if::cfg_if! {
         pub use darwin::Parker;
     } else if #[cfg(target_os = "netbsd")] {
         mod netbsd;
-        pub use netbsd::Parker;
+        pub use netbsd::{current, park, park_timeout, unpark, ThreadId};
     } else {
         mod pthread;
         pub use pthread::Parker;
diff --git a/library/std/src/sys/unix/thread_parking/netbsd.rs b/library/std/src/sys/unix/thread_parking/netbsd.rs
new file mode 100644
index 00000000000..3be08122138
--- /dev/null
+++ b/library/std/src/sys/unix/thread_parking/netbsd.rs
@@ -0,0 +1,52 @@
+use crate::ffi::{c_int, c_void};
+use crate::ptr;
+use crate::time::Duration;
+use libc::{_lwp_self, clockid_t, lwpid_t, time_t, timespec, CLOCK_MONOTONIC};
+
+extern "C" {
+    fn ___lwp_park60(
+        clock_id: clockid_t,
+        flags: c_int,
+        ts: *mut timespec,
+        unpark: lwpid_t,
+        hint: *const c_void,
+        unparkhint: *const c_void,
+    ) -> c_int;
+    fn _lwp_unpark(lwp: lwpid_t, hint: *const c_void) -> c_int;
+}
+
+pub type ThreadId = lwpid_t;
+
+#[inline]
+pub fn current() -> ThreadId {
+    unsafe { _lwp_self() }
+}
+
+#[inline]
+pub fn park(hint: usize) {
+    unsafe {
+        ___lwp_park60(0, 0, ptr::null_mut(), 0, ptr::invalid(hint), ptr::null());
+    }
+}
+
+pub fn park_timeout(dur: Duration, hint: usize) {
+    let mut timeout = timespec {
+        // Saturate so that the operation will definitely time out
+        // (even if it is after the heat death of the universe).
+        tv_sec: dur.as_secs().try_into().ok().unwrap_or(time_t::MAX),
+        tv_nsec: dur.subsec_nanos().into(),
+    };
+
+    // Timeout needs to be mutable since it is modified on NetBSD 9.0 and
+    // above.
+    unsafe {
+        ___lwp_park60(CLOCK_MONOTONIC, 0, &mut timeout, 0, ptr::invalid(hint), ptr::null());
+    }
+}
+
+#[inline]
+pub fn unpark(tid: ThreadId, hint: usize) {
+    unsafe {
+        _lwp_unpark(tid, ptr::invalid(hint));
+    }
+}
diff --git a/library/std/src/sys/unix/thread_parker/pthread.rs b/library/std/src/sys/unix/thread_parking/pthread.rs
index 510168a010f..082d25e68f5 100644
--- a/library/std/src/sys/unix/thread_parker/pthread.rs
+++ b/library/std/src/sys/unix/thread_parking/pthread.rs
@@ -99,7 +99,7 @@ impl Parker {
     ///
     /// # Safety
     /// The constructed parker must never be moved.
-    pub unsafe fn new(parker: *mut Parker) {
+    pub unsafe fn new_in_place(parker: *mut Parker) {
         // Use the default mutex implementation to allow for simpler initialization.
         // This could lead to undefined behaviour when deadlocking. This is avoided
         // by not deadlocking. Note in particular the unlocking operation before any
diff --git a/library/std/src/sys/windows/mod.rs b/library/std/src/sys/windows/mod.rs
index e67411e1686..77359abe429 100644
--- a/library/std/src/sys/windows/mod.rs
+++ b/library/std/src/sys/windows/mod.rs
@@ -33,7 +33,7 @@ pub mod stdio;
 pub mod thread;
 pub mod thread_local_dtor;
 pub mod thread_local_key;
-pub mod thread_parker;
+pub mod thread_parking;
 pub mod time;
 cfg_if::cfg_if! {
     if #[cfg(not(target_vendor = "uwp"))] {
diff --git a/library/std/src/sys/windows/thread_parker.rs b/library/std/src/sys/windows/thread_parking.rs
index 2f7ae863b6a..5d43676adbb 100644
--- a/library/std/src/sys/windows/thread_parker.rs
+++ b/library/std/src/sys/windows/thread_parking.rs
@@ -97,7 +97,7 @@ const NOTIFIED: i8 = 1;
 impl Parker {
     /// Construct the Windows parker. The UNIX parker implementation
     /// requires this to happen in-place.
-    pub unsafe fn new(parker: *mut Parker) {
+    pub unsafe fn new_in_place(parker: *mut Parker) {
         parker.write(Self { state: AtomicI8::new(EMPTY) });
     }
 
diff --git a/library/std/src/sys_common/mod.rs b/library/std/src/sys_common/mod.rs
index 73da1ce066c..6b24b0e9aa8 100644
--- a/library/std/src/sys_common/mod.rs
+++ b/library/std/src/sys_common/mod.rs
@@ -30,7 +30,7 @@ pub mod process;
 pub mod thread;
 pub mod thread_info;
 pub mod thread_local_dtor;
-pub mod thread_parker;
+pub mod thread_parking;
 pub mod wstr;
 pub mod wtf8;
 
diff --git a/library/std/src/sys_common/thread_parker/futex.rs b/library/std/src/sys_common/thread_parking/futex.rs
index d9e2f39e345..588e7b27826 100644
--- a/library/std/src/sys_common/thread_parker/futex.rs
+++ b/library/std/src/sys_common/thread_parking/futex.rs
@@ -35,7 +35,7 @@ pub struct Parker {
 impl Parker {
     /// Construct the futex parker. The UNIX parker implementation
     /// requires this to happen in-place.
-    pub unsafe fn new(parker: *mut Parker) {
+    pub unsafe fn new_in_place(parker: *mut Parker) {
         parker.write(Self { state: AtomicU32::new(EMPTY) });
     }
 
diff --git a/library/std/src/sys_common/thread_parker/generic.rs b/library/std/src/sys_common/thread_parking/generic.rs
index f3d8b34d3fd..3209bffe353 100644
--- a/library/std/src/sys_common/thread_parker/generic.rs
+++ b/library/std/src/sys_common/thread_parking/generic.rs
@@ -19,7 +19,7 @@ pub struct Parker {
 impl Parker {
     /// Construct the generic parker. The UNIX parker implementation
     /// requires this to happen in-place.
-    pub unsafe fn new(parker: *mut Parker) {
+    pub unsafe fn new_in_place(parker: *mut Parker) {
         parker.write(Parker {
             state: AtomicUsize::new(EMPTY),
             lock: Mutex::new(()),
diff --git a/library/std/src/sys_common/thread_parking/id.rs b/library/std/src/sys_common/thread_parking/id.rs
new file mode 100644
index 00000000000..e98169597c3
--- /dev/null
+++ b/library/std/src/sys_common/thread_parking/id.rs
@@ -0,0 +1,108 @@
+//! Thread parking using thread ids.
+//!
+//! Some platforms (notably NetBSD) have thread parking primitives whose semantics
+//! match those offered by `thread::park`, with the difference that the thread to
+//! be unparked is referenced by a platform-specific thread id. Since the thread
+//! parker is constructed before that id is known, an atomic state variable is used
+//! to manage the park state and propagate the thread id. This also avoids platform
+//! calls in the case where `unpark` is called before `park`.
+
+use crate::cell::UnsafeCell;
+use crate::pin::Pin;
+use crate::sync::atomic::{
+    fence, AtomicI8,
+    Ordering::{Acquire, Relaxed, Release},
+};
+use crate::sys::thread_parking::{current, park, park_timeout, unpark, ThreadId};
+use crate::time::Duration;
+
+pub struct Parker {
+    state: AtomicI8,
+    tid: UnsafeCell<Option<ThreadId>>,
+}
+
+const PARKED: i8 = -1;
+const EMPTY: i8 = 0;
+const NOTIFIED: i8 = 1;
+
+impl Parker {
+    pub fn new() -> Parker {
+        Parker { state: AtomicI8::new(EMPTY), tid: UnsafeCell::new(None) }
+    }
+
+    /// Create a new thread parker. UNIX requires this to happen in-place.
+    pub unsafe fn new_in_place(parker: *mut Parker) {
+        parker.write(Parker::new())
+    }
+
+    /// # Safety
+    /// * must always be called from the same thread
+    /// * must be called before the state is set to PARKED
+    unsafe fn init_tid(&self) {
+        // The field is only ever written to from this thread, so we don't need
+        // synchronization to read it here.
+        if self.tid.get().read().is_none() {
+            // Because this point is only reached once, before the state is set
+            // to PARKED for the first time, the non-atomic write here can not
+            // conflict with reads by other threads.
+            self.tid.get().write(Some(current()));
+            // Ensure that the write can be observed by all threads reading the
+            // state. Synchronizes with the acquire barrier in `unpark`.
+            fence(Release);
+        }
+    }
+
+    pub unsafe fn park(self: Pin<&Self>) {
+        self.init_tid();
+
+        // Changes NOTIFIED to EMPTY and EMPTY to PARKED.
+        let mut state = self.state.fetch_sub(1, Acquire).wrapping_sub(1);
+        if state == PARKED {
+            // Loop to guard against spurious wakeups.
+            while state == PARKED {
+                park(self.state.as_mut_ptr().addr());
+                state = self.state.load(Acquire);
+            }
+
+            // Since the state change has already been observed with acquire
+            // ordering, the state can be reset with a relaxed store instead
+            // of a swap.
+            self.state.store(EMPTY, Relaxed);
+        }
+    }
+
+    pub unsafe fn park_timeout(self: Pin<&Self>, dur: Duration) {
+        self.init_tid();
+
+        let state = self.state.fetch_sub(1, Acquire).wrapping_sub(1);
+        if state == PARKED {
+            park_timeout(dur, self.state.as_mut_ptr().addr());
+            // Swap to ensure that we observe all state changes with acquire
+            // ordering, even if the state has been changed after the timeout
+            // occured.
+            self.state.swap(EMPTY, Acquire);
+        }
+    }
+
+    pub fn unpark(self: Pin<&Self>) {
+        let state = self.state.swap(NOTIFIED, Release);
+        if state == PARKED {
+            // Synchronize with the release fence in `init_tid` to observe the
+            // write to `tid`.
+            fence(Acquire);
+            // # Safety
+            // The thread id is initialized before the state is set to `PARKED`
+            // for the first time and is not written to from that point on
+            // (negating the need for an atomic read).
+            let tid = unsafe { self.tid.get().read().unwrap_unchecked() };
+            // It is possible that the waiting thread woke up because of a timeout
+            // and terminated before this call is made. This call then returns an
+            // error or wakes up an unrelated thread. The platform API and
+            // environment does allow this, however.
+            unpark(tid, self.state.as_mut_ptr().addr());
+        }
+    }
+}
+
+unsafe impl Send for Parker {}
+unsafe impl Sync for Parker {}
diff --git a/library/std/src/sys_common/thread_parker/mod.rs b/library/std/src/sys_common/thread_parking/mod.rs
index 08a2bdd8229..0ead6633c35 100644
--- a/library/std/src/sys_common/thread_parker/mod.rs
+++ b/library/std/src/sys_common/thread_parking/mod.rs
@@ -11,13 +11,17 @@ cfg_if::cfg_if! {
     ))] {
         mod futex;
         pub use futex::Parker;
+    } else if #[cfg(any(
+        target_os = "netbsd",
+        all(target_vendor = "fortanix", target_env = "sgx"),
+    ))] {
+        mod id;
+        pub use id::Parker;
     } else if #[cfg(target_os = "solid_asp3")] {
         mod wait_flag;
         pub use wait_flag::Parker;
     } else if #[cfg(any(windows, target_family = "unix"))] {
-        pub use crate::sys::thread_parker::Parker;
-    } else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] {
-        pub use crate::sys::thread_parker::Parker;
+        pub use crate::sys::thread_parking::Parker;
     } else {
         mod generic;
         pub use generic::Parker;
diff --git a/library/std/src/sys_common/thread_parker/wait_flag.rs b/library/std/src/sys_common/thread_parking/wait_flag.rs
index 6561c186655..d0f8899a94e 100644
--- a/library/std/src/sys_common/thread_parker/wait_flag.rs
+++ b/library/std/src/sys_common/thread_parking/wait_flag.rs
@@ -41,7 +41,7 @@ pub struct Parker {
 impl Parker {
     /// Construct a parker for the current thread. The UNIX parker
     /// implementation requires this to happen in-place.
-    pub unsafe fn new(parker: *mut Parker) {
+    pub unsafe fn new_in_place(parker: *mut Parker) {
         parker.write(Parker { state: AtomicI8::new(EMPTY), wait_flag: WaitFlag::new() })
     }
 
diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs
index 34bdb8bd461..7acda8e98f1 100644
--- a/library/std/src/thread/mod.rs
+++ b/library/std/src/thread/mod.rs
@@ -173,7 +173,7 @@ use crate::sync::Arc;
 use crate::sys::thread as imp;
 use crate::sys_common::thread;
 use crate::sys_common::thread_info;
-use crate::sys_common::thread_parker::Parker;
+use crate::sys_common::thread_parking::Parker;
 use crate::sys_common::{AsInner, IntoInner};
 use crate::time::Duration;
 
@@ -1216,7 +1216,7 @@ impl Thread {
             let ptr = Arc::get_mut_unchecked(&mut arc).as_mut_ptr();
             addr_of_mut!((*ptr).name).write(name);
             addr_of_mut!((*ptr).id).write(ThreadId::new());
-            Parker::new(addr_of_mut!((*ptr).parker));
+            Parker::new_in_place(addr_of_mut!((*ptr).parker));
             Pin::new_unchecked(arc.assume_init())
         };
 
diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs
index 707e4169002..66bc0f023b6 100644
--- a/src/bootstrap/builder.rs
+++ b/src/bootstrap/builder.rs
@@ -97,18 +97,36 @@ impl RunConfig<'_> {
         self.builder.build.build
     }
 
-    /// Return a `-p=x -p=y` string suitable for passing to a cargo invocation.
+    /// Return a list of crate names selected by `run.paths`.
     pub fn cargo_crates_in_set(&self) -> Interned<Vec<String>> {
         let mut crates = Vec::new();
         for krate in &self.paths {
             let path = krate.assert_single_path();
             let crate_name = self.builder.crate_paths[&path.path];
-            crates.push(format!("-p={crate_name}"));
+            crates.push(crate_name.to_string());
         }
         INTERNER.intern_list(crates)
     }
 }
 
+/// A description of the crates in this set, suitable for passing to `builder.info`.
+///
+/// `crates` should be generated by [`RunConfig::cargo_crates_in_set`].
+pub fn crate_description(crates: &[impl AsRef<str>]) -> String {
+    if crates.is_empty() {
+        return "".into();
+    }
+
+    let mut descr = String::from(" {");
+    descr.push_str(crates[0].as_ref());
+    for krate in &crates[1..] {
+        descr.push_str(", ");
+        descr.push_str(krate.as_ref());
+    }
+    descr.push('}');
+    descr
+}
+
 struct StepDescription {
     default: bool,
     only_hosts: bool,
diff --git a/src/bootstrap/check.rs b/src/bootstrap/check.rs
index 2e1bd8d6d1f..32e5d414061 100644
--- a/src/bootstrap/check.rs
+++ b/src/bootstrap/check.rs
@@ -99,19 +99,13 @@ impl Step for Std {
             cargo_subcommand(builder.kind),
         );
         std_cargo(builder, target, compiler.stage, &mut cargo);
+        cargo.args(args(builder));
 
         builder.info(&format!(
-            "Checking stage{} std artifacts ({} -> {})",
+            "Checking stage{} library artifacts ({} -> {})",
             builder.top_stage, &compiler.host, target
         ));
-        run_cargo(
-            builder,
-            cargo,
-            args(builder),
-            &libstd_stamp(builder, compiler, target),
-            vec![],
-            true,
-        );
+        run_cargo(builder, cargo, &libstd_stamp(builder, compiler, target), vec![], true);
 
         // We skip populating the sysroot in non-zero stage because that'll lead
         // to rlib/rmeta conflicts if std gets built during this session.
@@ -155,19 +149,13 @@ impl Step for Std {
         for krate in builder.in_tree_crates("test", Some(target)) {
             cargo.arg("-p").arg(krate.name);
         }
+        cargo.args(args(builder));
 
         builder.info(&format!(
-            "Checking stage{} std test/bench/example targets ({} -> {})",
+            "Checking stage{} library test/bench/example targets ({} -> {})",
             builder.top_stage, &compiler.host, target
         ));
-        run_cargo(
-            builder,
-            cargo,
-            args(builder),
-            &libstd_test_stamp(builder, compiler, target),
-            vec![],
-            true,
-        );
+        run_cargo(builder, cargo, &libstd_test_stamp(builder, compiler, target), vec![], true);
     }
 }
 
@@ -231,19 +219,13 @@ impl Step for Rustc {
         for krate in builder.in_tree_crates("rustc-main", Some(target)) {
             cargo.arg("-p").arg(krate.name);
         }
+        cargo.args(args(builder));
 
         builder.info(&format!(
             "Checking stage{} compiler artifacts ({} -> {})",
             builder.top_stage, &compiler.host, target
         ));
-        run_cargo(
-            builder,
-            cargo,
-            args(builder),
-            &librustc_stamp(builder, compiler, target),
-            vec![],
-            true,
-        );
+        run_cargo(builder, cargo, &librustc_stamp(builder, compiler, target), vec![], true);
 
         let libdir = builder.sysroot_libdir(compiler, target);
         let hostdir = builder.sysroot_libdir(compiler, compiler.host);
@@ -290,6 +272,7 @@ impl Step for CodegenBackend {
             .arg("--manifest-path")
             .arg(builder.src.join(format!("compiler/rustc_codegen_{}/Cargo.toml", backend)));
         rustc_cargo_env(builder, &mut cargo, target);
+        cargo.args(args(builder));
 
         builder.info(&format!(
             "Checking stage{} {} artifacts ({} -> {})",
@@ -299,7 +282,6 @@ impl Step for CodegenBackend {
         run_cargo(
             builder,
             cargo,
-            args(builder),
             &codegen_backend_stamp(builder, compiler, target, backend),
             vec![],
             true,
@@ -355,11 +337,13 @@ impl Step for RustAnalyzer {
             cargo.arg("--benches");
         }
 
+        cargo.args(args(builder));
+
         builder.info(&format!(
             "Checking stage{} {} artifacts ({} -> {})",
             compiler.stage, "rust-analyzer", &compiler.host.triple, target.triple
         ));
-        run_cargo(builder, cargo, args(builder), &stamp(builder, compiler, target), vec![], true);
+        run_cargo(builder, cargo, &stamp(builder, compiler, target), vec![], true);
 
         /// Cargo's output path in a given stage, compiled by a particular
         /// compiler for the specified target.
@@ -413,6 +397,8 @@ macro_rules! tool_check_step {
                     cargo.arg("--all-targets");
                 }
 
+                cargo.args(args(builder));
+
                 // Enable internal lints for clippy and rustdoc
                 // NOTE: this doesn't enable lints for any other tools unless they explicitly add `#![warn(rustc::internal)]`
                 // See https://github.com/rust-lang/rust/pull/80573#issuecomment-754010776
@@ -428,7 +414,6 @@ macro_rules! tool_check_step {
                 run_cargo(
                     builder,
                     cargo,
-                    args(builder),
                     &stamp(builder, compiler, target),
                     vec![],
                     true,
diff --git a/src/bootstrap/clean.rs b/src/bootstrap/clean.rs
index 8e363ee1290..468efc1114c 100644
--- a/src/bootstrap/clean.rs
+++ b/src/bootstrap/clean.rs
@@ -9,11 +9,10 @@ use std::fs;
 use std::io::{self, ErrorKind};
 use std::path::Path;
 
-use crate::builder::{Builder, RunConfig, ShouldRun, Step};
+use crate::builder::{crate_description, Builder, RunConfig, ShouldRun, Step};
 use crate::cache::Interned;
-use crate::config::TargetSelection;
 use crate::util::t;
-use crate::{Build, Mode, Subcommand};
+use crate::{Build, Compiler, Mode, Subcommand};
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub struct CleanAll {}
@@ -40,7 +39,7 @@ macro_rules! clean_crate_tree {
     ( $( $name:ident, $mode:path, $root_crate:literal);+ $(;)? ) => { $(
         #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
         pub struct $name {
-            target: TargetSelection,
+            compiler: Compiler,
             crates: Interned<Vec<String>>,
         }
 
@@ -54,22 +53,21 @@ macro_rules! clean_crate_tree {
 
             fn make_run(run: RunConfig<'_>) {
                 let builder = run.builder;
-                if builder.top_stage != 0 {
-                    panic!("non-stage-0 clean not supported for individual crates");
-                }
-                builder.ensure(Self { crates: run.cargo_crates_in_set(), target: run.target });
+                let compiler = builder.compiler(builder.top_stage, run.target);
+                builder.ensure(Self { crates: run.cargo_crates_in_set(), compiler });
             }
 
             fn run(self, builder: &Builder<'_>) -> Self::Output {
-                let compiler = builder.compiler(0, self.target);
-                let mut cargo = builder.bare_cargo(compiler, $mode, self.target, "clean");
+                let compiler = self.compiler;
+                let target = compiler.host;
+                let mut cargo = builder.bare_cargo(compiler, $mode, target, "clean");
                 for krate in &*self.crates {
                     cargo.arg(krate);
                 }
 
                 builder.info(&format!(
-                    "Cleaning stage{} {} artifacts ({} -> {})",
-                    compiler.stage, stringify!($name).to_lowercase(), &compiler.host, self.target
+                    "Cleaning{} stage{} {} artifacts ({} -> {})",
+                    crate_description(&self.crates), compiler.stage, stringify!($name).to_lowercase(), &compiler.host, target,
                 ));
 
                 // NOTE: doesn't use `run_cargo` because we don't want to save a stamp file,
diff --git a/src/bootstrap/compile.rs b/src/bootstrap/compile.rs
index 1030247b890..f9a04f2e91d 100644
--- a/src/bootstrap/compile.rs
+++ b/src/bootstrap/compile.rs
@@ -18,6 +18,7 @@ use std::str;
 
 use serde::Deserialize;
 
+use crate::builder::crate_description;
 use crate::builder::Cargo;
 use crate::builder::{Builder, Kind, RunConfig, ShouldRun, Step};
 use crate::cache::{Interned, INTERNER};
@@ -110,7 +111,10 @@ impl Step for Std {
         let compiler_to_use = builder.compiler_for(compiler.stage, compiler.host, target);
         if compiler_to_use != compiler {
             builder.ensure(Std::new(compiler_to_use, target));
-            builder.info(&format!("Uplifting stage1 std ({} -> {})", compiler_to_use.host, target));
+            builder.info(&format!(
+                "Uplifting stage1 library ({} -> {})",
+                compiler_to_use.host, target
+            ));
 
             // Even if we're not building std this stage, the new sysroot must
             // still contain the third party objects needed by various targets.
@@ -126,19 +130,18 @@ impl Step for Std {
 
         let mut cargo = builder.cargo(compiler, Mode::Std, SourceType::InTree, target, "build");
         std_cargo(builder, target, compiler.stage, &mut cargo);
+        for krate in &*self.crates {
+            cargo.arg("-p").arg(krate);
+        }
 
         builder.info(&format!(
-            "Building stage{} std artifacts ({} -> {})",
-            compiler.stage, &compiler.host, target
+            "Building{} stage{} library artifacts ({} -> {})",
+            crate_description(&self.crates),
+            compiler.stage,
+            &compiler.host,
+            target,
         ));
-        run_cargo(
-            builder,
-            cargo,
-            self.crates.to_vec(),
-            &libstd_stamp(builder, compiler, target),
-            target_deps,
-            false,
-        );
+        run_cargo(builder, cargo, &libstd_stamp(builder, compiler, target), target_deps, false);
 
         builder.ensure(StdLink::from_std(
             self,
@@ -425,7 +428,7 @@ impl Step for StdLink {
         let target_compiler = self.target_compiler;
         let target = self.target;
         builder.info(&format!(
-            "Copying stage{} std from stage{} ({} -> {} / {})",
+            "Copying stage{} library from stage{} ({} -> {} / {})",
             target_compiler.stage, compiler.stage, &compiler.host, target_compiler.host, target
         ));
         let libdir = builder.sysroot_libdir(target_compiler, target);
@@ -714,18 +717,18 @@ impl Step for Rustc {
             }
         }
 
+        for krate in &*self.crates {
+            cargo.arg("-p").arg(krate);
+        }
+
         builder.info(&format!(
-            "Building stage{} compiler artifacts ({} -> {})",
-            compiler.stage, &compiler.host, target
+            "Building{} stage{} compiler artifacts ({} -> {})",
+            crate_description(&self.crates),
+            compiler.stage,
+            &compiler.host,
+            target,
         ));
-        run_cargo(
-            builder,
-            cargo,
-            self.crates.to_vec(),
-            &librustc_stamp(builder, compiler, target),
-            vec![],
-            false,
-        );
+        run_cargo(builder, cargo, &librustc_stamp(builder, compiler, target), vec![], false);
 
         builder.ensure(RustcLink::from_rustc(
             self,
@@ -981,7 +984,7 @@ impl Step for CodegenBackend {
             "Building stage{} codegen backend {} ({} -> {})",
             compiler.stage, backend, &compiler.host, target
         ));
-        let files = run_cargo(builder, cargo, vec![], &tmp_stamp, vec![], false);
+        let files = run_cargo(builder, cargo, &tmp_stamp, vec![], false);
         if builder.config.dry_run() {
             return;
         }
@@ -1405,7 +1408,6 @@ pub fn add_to_sysroot(
 pub fn run_cargo(
     builder: &Builder<'_>,
     cargo: Cargo,
-    tail_args: Vec<String>,
     stamp: &Path,
     additional_target_deps: Vec<(PathBuf, DependencyType)>,
     is_check: bool,
@@ -1431,7 +1433,7 @@ pub fn run_cargo(
     // files we need to probe for later.
     let mut deps = Vec::new();
     let mut toplevel = Vec::new();
-    let ok = stream_cargo(builder, cargo, tail_args, &mut |msg| {
+    let ok = stream_cargo(builder, cargo, &mut |msg| {
         let (filenames, crate_types) = match msg {
             CargoMessage::CompilerArtifact {
                 filenames,
@@ -1546,7 +1548,6 @@ pub fn run_cargo(
 pub fn stream_cargo(
     builder: &Builder<'_>,
     cargo: Cargo,
-    tail_args: Vec<String>,
     cb: &mut dyn FnMut(CargoMessage<'_>),
 ) -> bool {
     let mut cargo = Command::from(cargo);
@@ -1566,10 +1567,6 @@ pub fn stream_cargo(
     }
     cargo.arg("--message-format").arg(message_format).stdout(Stdio::piped());
 
-    for arg in tail_args {
-        cargo.arg(arg);
-    }
-
     builder.verbose(&format!("running: {:?}", cargo));
     let mut child = match cargo.spawn() {
         Ok(child) => child,
diff --git a/src/bootstrap/doc.rs b/src/bootstrap/doc.rs
index 3f43c68d2e0..9bad9046ecc 100644
--- a/src/bootstrap/doc.rs
+++ b/src/bootstrap/doc.rs
@@ -12,6 +12,7 @@ use std::fs;
 use std::io;
 use std::path::{Path, PathBuf};
 
+use crate::builder::crate_description;
 use crate::builder::{Builder, Compiler, Kind, RunConfig, ShouldRun, Step};
 use crate::cache::{Interned, INTERNER};
 use crate::compile;
@@ -558,7 +559,8 @@ fn doc_std(
     requested_crates: &[String],
 ) {
     builder.info(&format!(
-        "Documenting stage{} std ({}) in {} format",
+        "Documenting{} stage{} library ({}) in {} format",
+        crate_description(requested_crates),
         stage,
         target,
         format.as_str()
diff --git a/src/bootstrap/flags.rs b/src/bootstrap/flags.rs
index 3be7ad4075e..2c6d201d18f 100644
--- a/src/bootstrap/flags.rs
+++ b/src/bootstrap/flags.rs
@@ -352,32 +352,32 @@ To learn more about a subcommand, run `./x.py <subcommand> -h`",
 
         // fn usage()
         let usage = |exit_code: i32, opts: &Options, verbose: bool, subcommand_help: &str| -> ! {
-            // We have an unfortunate situation here: some Steps use `builder.in_tree_crates` to determine their paths.
-            // To determine those crates, we need to run `cargo metadata`, which means we need all submodules to be checked out.
-            // That takes a while to run, so only do it when paths were explicitly requested, not on all CLI errors.
-            // `Build::new` won't load submodules for the `setup` command.
-            let cmd = if verbose {
-                println!("note: updating submodules before printing available paths");
-                "build"
-            } else {
-                "setup"
-            };
-            let config = Config::parse(&[cmd.to_string()]);
-            let build = Build::new(config);
-            let paths = Builder::get_help(&build, subcommand);
-
             println!("{}", opts.usage(subcommand_help));
-            if let Some(s) = paths {
-                if verbose {
+            if verbose {
+                // We have an unfortunate situation here: some Steps use `builder.in_tree_crates` to determine their paths.
+                // To determine those crates, we need to run `cargo metadata`, which means we need all submodules to be checked out.
+                // That takes a while to run, so only do it when paths were explicitly requested, not on all CLI errors.
+                // `Build::new` won't load submodules for the `setup` command.
+                let cmd = if verbose {
+                    println!("note: updating submodules before printing available paths");
+                    "build"
+                } else {
+                    "setup"
+                };
+                let config = Config::parse(&[cmd.to_string()]);
+                let build = Build::new(config);
+                let paths = Builder::get_help(&build, subcommand);
+
+                if let Some(s) = paths {
                     println!("{}", s);
                 } else {
-                    println!(
-                        "Run `./x.py {} -h -v` to see a list of available paths.",
-                        subcommand.as_str()
-                    );
+                    panic!("No paths available for subcommand `{}`", subcommand.as_str());
                 }
-            } else if verbose {
-                panic!("No paths available for subcommand `{}`", subcommand.as_str());
+            } else {
+                println!(
+                    "Run `./x.py {} -h -v` to see a list of available paths.",
+                    subcommand.as_str()
+                );
             }
             crate::detail_exit(exit_code);
         };
diff --git a/src/bootstrap/format.rs b/src/bootstrap/format.rs
index 1d57c6ecbbb..84e46118959 100644
--- a/src/bootstrap/format.rs
+++ b/src/bootstrap/format.rs
@@ -79,24 +79,19 @@ fn update_rustfmt_version(build: &Builder<'_>) {
 ///
 /// Returns `None` if all files should be formatted.
 fn get_modified_rs_files(build: &Builder<'_>) -> Option<Vec<String>> {
-    let Ok(remote) = get_rust_lang_rust_remote() else {return None;};
+    let Ok(remote) = get_rust_lang_rust_remote() else { return None; };
     if !verify_rustfmt_version(build) {
         return None;
     }
+
+    let merge_base =
+        output(build.config.git().arg("merge-base").arg(&format!("{remote}/master")).arg("HEAD"));
     Some(
-        output(
-            build
-                .config
-                .git()
-                .arg("diff-index")
-                .arg("--name-only")
-                .arg("--merge-base")
-                .arg(&format!("{remote}/master")),
-        )
-        .lines()
-        .map(|s| s.trim().to_owned())
-        .filter(|f| Path::new(f).extension().map_or(false, |ext| ext == "rs"))
-        .collect(),
+        output(build.config.git().arg("diff-index").arg("--name-only").arg(merge_base.trim()))
+            .lines()
+            .map(|s| s.trim().to_owned())
+            .filter(|f| Path::new(f).extension().map_or(false, |ext| ext == "rs"))
+            .collect(),
     )
 }
 
diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs
index c8b4134391e..d5bec268a45 100644
--- a/src/bootstrap/test.rs
+++ b/src/bootstrap/test.rs
@@ -11,6 +11,7 @@ use std::iter;
 use std::path::{Path, PathBuf};
 use std::process::{Command, Stdio};
 
+use crate::builder::crate_description;
 use crate::builder::{Builder, Compiler, Kind, RunConfig, ShouldRun, Step};
 use crate::cache::Interned;
 use crate::compile;
@@ -2154,8 +2155,12 @@ impl Step for Crate {
         }
 
         builder.info(&format!(
-            "{} {:?} stage{} ({} -> {})",
-            test_kind, self.crates, compiler.stage, &compiler.host, target
+            "{}{} stage{} ({} -> {})",
+            test_kind,
+            crate_description(&self.crates),
+            compiler.stage,
+            &compiler.host,
+            target
         ));
         let _time = util::timeit(&builder);
         try_run(builder, &mut cargo.into());
diff --git a/src/bootstrap/tool.rs b/src/bootstrap/tool.rs
index 24b033cc0dc..63026bd44d4 100644
--- a/src/bootstrap/tool.rs
+++ b/src/bootstrap/tool.rs
@@ -72,7 +72,7 @@ impl Step for ToolBuild {
 
         builder.info(&format!("Building stage{} tool {} ({})", compiler.stage, tool, target));
         let mut duplicates = Vec::new();
-        let is_expected = compile::stream_cargo(builder, cargo, vec![], &mut |msg| {
+        let is_expected = compile::stream_cargo(builder, cargo, &mut |msg| {
             // Only care about big things like the RLS/Cargo for now
             match tool {
                 "rls" | "cargo" | "clippy-driver" | "miri" | "rustfmt" => {}
diff --git a/src/test/codegen/sanitizer-cfi-emit-type-metadata-id-itanium-cxx-abi.rs b/src/test/codegen/sanitizer-cfi-emit-type-metadata-id-itanium-cxx-abi.rs
index ece2adbdf43..b9c33914360 100644
--- a/src/test/codegen/sanitizer-cfi-emit-type-metadata-id-itanium-cxx-abi.rs
+++ b/src/test/codegen/sanitizer-cfi-emit-type-metadata-id-itanium-cxx-abi.rs
@@ -131,6 +131,13 @@ pub struct Type13<'a> {
     member3: &'a Type13<'a>,
 }
 
+// Helper type to allow `Type14<Bar>` to be a unique ID
+pub struct Bar;
+
+// repr(transparent) parameterized type
+#[repr(transparent)]
+pub struct Type14<T>(T);
+
 pub fn foo0(_: ()) { }
 // CHECK: define{{.*}}foo0{{.*}}!type ![[TYPE0:[0-9]+]]
 pub fn foo1(_: c_void, _: ()) { }
@@ -425,6 +432,12 @@ pub fn foo145(_: Type13, _: Type13) { }
 // CHECK: define{{.*}}foo145{{.*}}!type ![[TYPE145:[0-9]+]]
 pub fn foo146(_: Type13, _: Type13, _: Type13) { }
 // CHECK: define{{.*}}foo146{{.*}}!type ![[TYPE146:[0-9]+]]
+pub fn foo147(_: Type14<Bar>) { }
+// CHECK: define{{.*}}foo147{{.*}}!type ![[TYPE147:[0-9]+]]
+pub fn foo148(_: Type14<Bar>, _: Type14<Bar>) { }
+// CHECK: define{{.*}}foo148{{.*}}!type ![[TYPE148:[0-9]+]]
+pub fn foo149(_: Type14<Bar>, _: Type14<Bar>, _: Type14<Bar>) { }
+// CHECK: define{{.*}}foo149{{.*}}!type ![[TYPE149:[0-9]+]]
 
 // CHECK: ![[TYPE0]] = !{i64 0, !"_ZTSFvvE"}
 // CHECK: ![[TYPE1]] = !{i64 0, !"_ZTSFvvvE"}
@@ -570,6 +583,9 @@ pub fn foo146(_: Type13, _: Type13, _: Type13) { }
 // CHECK: ![[TYPE141]] = !{i64 0, !"_ZTSFvu{{[0-9]+}}NtC{{[[:print:]]+}}_51sanitizer_cfi_emit_type_metadata_id_itanium_cxx_abi3FooE"}
 // CHECK: ![[TYPE142]] = !{i64 0, !"_ZTSFvu{{[0-9]+}}NtC{{[[:print:]]+}}_51sanitizer_cfi_emit_type_metadata_id_itanium_cxx_abi3FooS_E"}
 // CHECK: ![[TYPE143]] = !{i64 0, !"_ZTSFvu{{[0-9]+}}NtC{{[[:print:]]+}}_51sanitizer_cfi_emit_type_metadata_id_itanium_cxx_abi3FooS_S_E"}
-// CHECK: ![[TYPE144]] = !{i64 0, !"_ZTSFvu3refIu3refIvEEE"}
-// CHECK: ![[TYPE145]] = !{i64 0, !"_ZTSFvu3refIu3refIvEES0_E"}
-// CHECK: ![[TYPE146]] = !{i64 0, !"_ZTSFvu3refIu3refIvEES0_S0_E"}
+// CHECK: ![[TYPE144]] = !{i64 0, !"_ZTSFvu3refIvEE"}
+// CHECK: ![[TYPE145]] = !{i64 0, !"_ZTSFvu3refIvES_E"}
+// CHECK: ![[TYPE146]] = !{i64 0, !"_ZTSFvu3refIvES_S_E"}
+// CHECK: ![[TYPE147]] = !{i64 0, !"_ZTSFvu{{[0-9]+}}NtC{{[[:print:]]+}}_51sanitizer_cfi_emit_type_metadata_id_itanium_cxx_abi3BarE
+// CHECK: ![[TYPE148]] = !{i64 0, !"_ZTSFvu{{[0-9]+}}NtC{{[[:print:]]+}}_51sanitizer_cfi_emit_type_metadata_id_itanium_cxx_abi3BarS_E
+// CHECK: ![[TYPE149]] = !{i64 0, !"_ZTSFvu{{[0-9]+}}NtC{{[[:print:]]+}}_51sanitizer_cfi_emit_type_metadata_id_itanium_cxx_abi3BarS_S_E
diff --git a/src/test/rustdoc-gui/scrape-examples-layout.goml b/src/test/rustdoc-gui/scrape-examples-layout.goml
index fde9a0ab0bc..95102528ec1 100644
--- a/src/test/rustdoc-gui/scrape-examples-layout.goml
+++ b/src/test/rustdoc-gui/scrape-examples-layout.goml
@@ -33,3 +33,17 @@ assert-property: (
     ".more-scraped-examples .scraped-example:nth-child(6) .code-wrapper .src-line-numbers",
     {"clientWidth": |clientWidth|}
 )
+
+// Check that for both mobile and desktop sizes, the buttons in scraped examples are displayed
+// correctly.
+
+store-value: (offset_y, 4)
+
+// First with desktop
+assert-position: (".scraped-example .code-wrapper", {"y": 255})
+assert-position: (".scraped-example .code-wrapper .prev", {"y": 255 + |offset_y|})
+
+// Then with mobile
+size: (600, 600)
+assert-position: (".scraped-example .code-wrapper", {"y": 314})
+assert-position: (".scraped-example .code-wrapper .prev", {"y": 314 + |offset_y|})
diff --git a/src/test/ui/feature-gates/feature-gate-multiple_supertrait_upcastable.rs b/src/test/ui/feature-gates/feature-gate-multiple_supertrait_upcastable.rs
deleted file mode 100644
index 0467dea621b..00000000000
--- a/src/test/ui/feature-gates/feature-gate-multiple_supertrait_upcastable.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-// check-pass
-
-#![deny(multiple_supertrait_upcastable)]
-//~^ WARNING unknown lint: `multiple_supertrait_upcastable`
-//~| WARNING unknown lint: `multiple_supertrait_upcastable`
-//~| WARNING unknown lint: `multiple_supertrait_upcastable`
-#![warn(multiple_supertrait_upcastable)]
-//~^ WARNING unknown lint: `multiple_supertrait_upcastable`
-//~| WARNING unknown lint: `multiple_supertrait_upcastable`
-//~| WARNING unknown lint: `multiple_supertrait_upcastable`
-
-fn main() {}
diff --git a/src/test/ui/feature-gates/feature-gate-multiple_supertrait_upcastable.stderr b/src/test/ui/feature-gates/feature-gate-multiple_supertrait_upcastable.stderr
deleted file mode 100644
index 1f725f35417..00000000000
--- a/src/test/ui/feature-gates/feature-gate-multiple_supertrait_upcastable.stderr
+++ /dev/null
@@ -1,57 +0,0 @@
-warning: unknown lint: `multiple_supertrait_upcastable`
-  --> $DIR/feature-gate-multiple_supertrait_upcastable.rs:3:1
-   |
-LL | #![deny(multiple_supertrait_upcastable)]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: the `multiple_supertrait_upcastable` lint is unstable
-   = help: add `#![feature(multiple_supertrait_upcastable)]` to the crate attributes to enable
-   = note: `#[warn(unknown_lints)]` on by default
-
-warning: unknown lint: `multiple_supertrait_upcastable`
-  --> $DIR/feature-gate-multiple_supertrait_upcastable.rs:7:1
-   |
-LL | #![warn(multiple_supertrait_upcastable)]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: the `multiple_supertrait_upcastable` lint is unstable
-   = help: add `#![feature(multiple_supertrait_upcastable)]` to the crate attributes to enable
-
-warning: unknown lint: `multiple_supertrait_upcastable`
-  --> $DIR/feature-gate-multiple_supertrait_upcastable.rs:3:1
-   |
-LL | #![deny(multiple_supertrait_upcastable)]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: the `multiple_supertrait_upcastable` lint is unstable
-   = help: add `#![feature(multiple_supertrait_upcastable)]` to the crate attributes to enable
-
-warning: unknown lint: `multiple_supertrait_upcastable`
-  --> $DIR/feature-gate-multiple_supertrait_upcastable.rs:7:1
-   |
-LL | #![warn(multiple_supertrait_upcastable)]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: the `multiple_supertrait_upcastable` lint is unstable
-   = help: add `#![feature(multiple_supertrait_upcastable)]` to the crate attributes to enable
-
-warning: unknown lint: `multiple_supertrait_upcastable`
-  --> $DIR/feature-gate-multiple_supertrait_upcastable.rs:3:1
-   |
-LL | #![deny(multiple_supertrait_upcastable)]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: the `multiple_supertrait_upcastable` lint is unstable
-   = help: add `#![feature(multiple_supertrait_upcastable)]` to the crate attributes to enable
-
-warning: unknown lint: `multiple_supertrait_upcastable`
-  --> $DIR/feature-gate-multiple_supertrait_upcastable.rs:7:1
-   |
-LL | #![warn(multiple_supertrait_upcastable)]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = note: the `multiple_supertrait_upcastable` lint is unstable
-   = help: add `#![feature(multiple_supertrait_upcastable)]` to the crate attributes to enable
-
-warning: 6 warnings emitted
-
diff --git a/src/test/ui/object-safety/issue-106247.rs b/src/test/ui/object-safety/issue-106247.rs
new file mode 100644
index 00000000000..64bf59e5d3a
--- /dev/null
+++ b/src/test/ui/object-safety/issue-106247.rs
@@ -0,0 +1,9 @@
+// check-pass
+
+#![deny(where_clauses_object_safety)]
+
+pub trait Trait {
+    fn method(&self) where Self: Sync;
+}
+
+fn main() {}
diff --git a/src/test/ui/traits/trait-upcasting/multiple_supertrait_upcastable.rs b/src/test/ui/traits/trait-upcasting/multiple_supertrait_upcastable.rs
deleted file mode 100644
index 3c6ab86e4c6..00000000000
--- a/src/test/ui/traits/trait-upcasting/multiple_supertrait_upcastable.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-#![feature(multiple_supertrait_upcastable)]
-#![deny(multiple_supertrait_upcastable)]
-
-trait A {}
-trait B {}
-
-trait C: A + B {}
-//~^ ERROR `C` is object-safe and has multiple supertraits
-
-fn main() {}
diff --git a/src/test/ui/traits/trait-upcasting/multiple_supertrait_upcastable.stderr b/src/test/ui/traits/trait-upcasting/multiple_supertrait_upcastable.stderr
deleted file mode 100644
index ad80a009ece..00000000000
--- a/src/test/ui/traits/trait-upcasting/multiple_supertrait_upcastable.stderr
+++ /dev/null
@@ -1,14 +0,0 @@
-error: `C` is object-safe and has multiple supertraits
-  --> $DIR/multiple_supertrait_upcastable.rs:7:1
-   |
-LL | trait C: A + B {}
-   | ^^^^^^^^^^^^^^
-   |
-note: the lint level is defined here
-  --> $DIR/multiple_supertrait_upcastable.rs:2:9
-   |
-LL | #![deny(multiple_supertrait_upcastable)]
-   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-error: aborting due to previous error
-
diff --git a/src/tools/tidy/Cargo.toml b/src/tools/tidy/Cargo.toml
index 97d038da702..fff83a1d097 100644
--- a/src/tools/tidy/Cargo.toml
+++ b/src/tools/tidy/Cargo.toml
@@ -11,6 +11,7 @@ miropt-test-tools = { path = "../miropt-test-tools" }
 lazy_static = "1"
 walkdir = "2"
 ignore = "0.4.18"
+termcolor = "1.1.3"
 
 [[bin]]
 name = "rust-tidy"
diff --git a/src/tools/tidy/src/error_codes_check.rs b/src/tools/tidy/src/error_codes_check.rs
index fd870b0997c..40a46c630d7 100644
--- a/src/tools/tidy/src/error_codes_check.rs
+++ b/src/tools/tidy/src/error_codes_check.rs
@@ -80,15 +80,6 @@ fn check_if_error_code_is_test_in_explanation(f: &str, err_code: &str) -> bool {
     ignore_found
 }
 
-macro_rules! some_or_continue {
-    ($e:expr) => {
-        match $e {
-            Some(e) => e,
-            None => continue,
-        }
-    };
-}
-
 fn extract_error_codes(
     f: &str,
     error_codes: &mut HashMap<String, ErrorCodeStatus>,
@@ -122,10 +113,16 @@ fn extract_error_codes(
                     Some((file_name, _)) => file_name,
                 },
             };
-            let path = some_or_continue!(path.parent())
+
+            let Some(parent) = path.parent() else {
+                continue;
+            };
+
+            let path = parent
                 .join(md_file_name)
                 .canonicalize()
                 .expect("failed to canonicalize error explanation file path");
+
             match read_to_string(&path) {
                 Ok(content) => {
                     let has_test = check_if_error_code_is_test_in_explanation(&content, &err_code);
@@ -215,8 +212,6 @@ pub fn check(paths: &[&Path], bad: &mut bool) {
     // * #[error = "E0111"]
     let regex = Regex::new(r#"[(,"\s](E\d{4})[,)"]"#).unwrap();
 
-    println!("Checking which error codes lack tests...");
-
     for path in paths {
         walk(path, &mut filter_dirs, &mut |entry, contents| {
             let file_name = entry.file_name();
@@ -245,20 +240,15 @@ pub fn check(paths: &[&Path], bad: &mut bool) {
         });
     }
     if found_explanations == 0 {
-        eprintln!("No error code explanation was tested!");
-        *bad = true;
+        tidy_error!(bad, "No error code explanation was tested!");
     }
     if found_tests == 0 {
-        eprintln!("No error code was found in compilation errors!");
-        *bad = true;
+        tidy_error!(bad, "No error code was found in compilation errors!");
     }
     if explanations.is_empty() {
-        eprintln!("No error code explanation was found!");
-        *bad = true;
+        tidy_error!(bad, "No error code explanation was found!");
     }
     if errors.is_empty() {
-        println!("Found {} error codes", error_codes.len());
-
         for (err_code, error_status) in &error_codes {
             if !error_status.has_test && !EXEMPTED_FROM_TEST.contains(&err_code.as_str()) {
                 errors.push(format!("Error code {err_code} needs to have at least one UI test!"));
@@ -310,11 +300,6 @@ pub fn check(paths: &[&Path], bad: &mut bool) {
     }
     errors.sort();
     for err in &errors {
-        eprintln!("{err}");
-    }
-    println!("Found {} error(s) in error codes", errors.len());
-    if !errors.is_empty() {
-        *bad = true;
+        tidy_error!(bad, "{err}");
     }
-    println!("Done!");
 }
diff --git a/src/tools/tidy/src/lib.rs b/src/tools/tidy/src/lib.rs
index 698e4850bea..ce7e7ac5cd4 100644
--- a/src/tools/tidy/src/lib.rs
+++ b/src/tools/tidy/src/lib.rs
@@ -3,6 +3,10 @@
 //! This library contains the tidy lints and exposes it
 //! to be used by tools.
 
+use std::fmt::Display;
+
+use termcolor::WriteColor;
+
 /// A helper macro to `unwrap` a result except also print out details like:
 ///
 /// * The expression that failed
@@ -26,18 +30,27 @@ macro_rules! t {
 }
 
 macro_rules! tidy_error {
-    ($bad:expr, $fmt:expr) => ({
-        *$bad = true;
-        eprint!("tidy error: ");
-        eprintln!($fmt);
-    });
-    ($bad:expr, $fmt:expr, $($arg:tt)*) => ({
-        *$bad = true;
-        eprint!("tidy error: ");
-        eprintln!($fmt, $($arg)*);
+    ($bad:expr, $($fmt:tt)*) => ({
+        $crate::tidy_error($bad, format_args!($($fmt)*)).expect("failed to output error");
     });
 }
 
+fn tidy_error(bad: &mut bool, args: impl Display) -> std::io::Result<()> {
+    use std::io::Write;
+    use termcolor::{Color, ColorChoice, ColorSpec, StandardStream};
+
+    *bad = true;
+
+    let mut stderr = StandardStream::stdout(ColorChoice::Auto);
+    stderr.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
+
+    write!(&mut stderr, "tidy error")?;
+    stderr.set_color(&ColorSpec::new())?;
+
+    writeln!(&mut stderr, ": {args}")?;
+    Ok(())
+}
+
 pub mod alphabetical;
 pub mod bins;
 pub mod debug_artifacts;