about summary refs log tree commit diff
path: root/src/tools
diff options
context:
space:
mode:
authorThe Miri Cronjob Bot <miri@cron.bot>2025-04-17 17:59:31 +0000
committerThe Miri Cronjob Bot <miri@cron.bot>2025-04-17 17:59:31 +0000
commit2602dd93a6bb8e4959d2162b85a12a754a5668b8 (patch)
treee6f5d5fcca22a403353ea679b53546931738c028 /src/tools
parentedb3aaf655f1a7a766c5d37eacbf7931c087130b (diff)
parentaa8670f6f81dac097d24277e7559ce5abf5d5183 (diff)
downloadrust-2602dd93a6bb8e4959d2162b85a12a754a5668b8.tar.gz
rust-2602dd93a6bb8e4959d2162b85a12a754a5668b8.zip
Merge from rustc
Diffstat (limited to 'src/tools')
-rw-r--r--src/tools/bump-stage0/src/main.rs25
m---------src/tools/cargo0
-rw-r--r--src/tools/clippy/clippy_lints/src/assigning_clones.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/bool_assert_comparison.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/format_args.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/implied_bounds_in_impls.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/len_zero.rs13
-rw-r--r--src/tools/clippy/clippy_lints/src/lifetimes.rs14
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/double_ended_iterator_last.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/needless_collect.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/unnecessary_iter_cloned.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/missing_trait_methods.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/needless_borrows_for_generic_args.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/ptr.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/same_name_method.rs5
-rw-r--r--src/tools/clippy/clippy_lints/src/unconditional_recursion.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/unused_self.rs2
-rw-r--r--src/tools/clippy/clippy_utils/src/hir_utils.rs8
-rw-r--r--src/tools/clippy/clippy_utils/src/ty/mod.rs8
-rw-r--r--src/tools/compiletest/Cargo.toml1
-rw-r--r--src/tools/compiletest/src/common.rs76
-rw-r--r--src/tools/compiletest/src/compute_diff.rs17
-rw-r--r--src/tools/compiletest/src/debuggers.rs27
-rw-r--r--src/tools/compiletest/src/errors.rs6
-rw-r--r--src/tools/compiletest/src/executor.rs345
-rw-r--r--src/tools/compiletest/src/executor/deadline.rs78
-rw-r--r--src/tools/compiletest/src/executor/json.rs111
-rw-r--r--src/tools/compiletest/src/executor/libtest.rs111
-rw-r--r--src/tools/compiletest/src/header.rs121
-rw-r--r--src/tools/compiletest/src/header/tests.rs26
-rw-r--r--src/tools/compiletest/src/lib.rs169
-rw-r--r--src/tools/compiletest/src/runtest.rs237
-rw-r--r--src/tools/compiletest/src/runtest/assembly.rs4
-rw-r--r--src/tools/compiletest/src/runtest/codegen_units.rs4
-rw-r--r--src/tools/compiletest/src/runtest/coverage.rs21
-rw-r--r--src/tools/compiletest/src/runtest/debugger.rs19
-rw-r--r--src/tools/compiletest/src/runtest/debuginfo.rs51
-rw-r--r--src/tools/compiletest/src/runtest/js_doc.rs3
-rw-r--r--src/tools/compiletest/src/runtest/mir_opt.rs43
-rw-r--r--src/tools/compiletest/src/runtest/run_make.rs32
-rw-r--r--src/tools/compiletest/src/runtest/ui.rs10
-rw-r--r--src/tools/compiletest/src/tests.rs12
-rw-r--r--src/tools/compiletest/src/util.rs38
-rw-r--r--src/tools/compiletest/src/util/tests.rs10
-rw-r--r--src/tools/generate-windows-sys/Cargo.toml2
-rw-r--r--src/tools/miri/test_dependencies/Cargo.lock4
-rw-r--r--src/tools/opt-dist/src/main.rs2
-rw-r--r--src/tools/opt-dist/src/training.rs4
-rw-r--r--src/tools/opt-dist/src/utils/mod.rs4
-rw-r--r--src/tools/run-make-support/src/artifact_names.rs53
-rw-r--r--src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs29
-rw-r--r--src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs29
-rw-r--r--src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs22
-rw-r--r--src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs2
-rw-r--r--src/tools/rustbook/Cargo.lock14
-rw-r--r--src/tools/rustdoc-gui-test/Cargo.toml1
-rw-r--r--src/tools/rustdoc-gui-test/src/main.rs6
-rw-r--r--src/tools/rustfmt/src/macros.rs14
-rw-r--r--src/tools/tidy/src/deps.rs16
62 files changed, 1145 insertions, 744 deletions
diff --git a/src/tools/bump-stage0/src/main.rs b/src/tools/bump-stage0/src/main.rs
index f51072718a3..d679084ae44 100644
--- a/src/tools/bump-stage0/src/main.rs
+++ b/src/tools/bump-stage0/src/main.rs
@@ -65,32 +65,33 @@ impl Tool {
             nightly_branch,
         } = &self.config;
 
-        file_content.push_str(&format!("dist_server={}", dist_server));
-        file_content.push_str(&format!("\nartifacts_server={}", artifacts_server));
+        file_content.push_str(&format!("dist_server={}\n", dist_server));
+        file_content.push_str(&format!("artifacts_server={}\n", artifacts_server));
         file_content.push_str(&format!(
-            "\nartifacts_with_llvm_assertions_server={}",
+            "artifacts_with_llvm_assertions_server={}\n",
             artifacts_with_llvm_assertions_server
         ));
-        file_content.push_str(&format!("\ngit_merge_commit_email={}", git_merge_commit_email));
-        file_content.push_str(&format!("\ngit_repository={}", git_repository));
-        file_content.push_str(&format!("\nnightly_branch={}", nightly_branch));
+        file_content.push_str(&format!("git_merge_commit_email={}\n", git_merge_commit_email));
+        file_content.push_str(&format!("git_repository={}\n", git_repository));
+        file_content.push_str(&format!("nightly_branch={}\n", nightly_branch));
 
-        file_content.push_str("\n\n");
+        file_content.push_str("\n");
         file_content.push_str(COMMENTS);
+        file_content.push_str("\n");
 
         let compiler = self.detect_compiler()?;
-        file_content.push_str(&format!("\ncompiler_date={}", compiler.date));
-        file_content.push_str(&format!("\ncompiler_version={}", compiler.version));
+        file_content.push_str(&format!("compiler_date={}\n", compiler.date));
+        file_content.push_str(&format!("compiler_version={}\n", compiler.version));
 
         if let Some(rustfmt) = self.detect_rustfmt()? {
-            file_content.push_str(&format!("\nrustfmt_date={}", rustfmt.date));
-            file_content.push_str(&format!("\nrustfmt_version={}", rustfmt.version));
+            file_content.push_str(&format!("rustfmt_date={}\n", rustfmt.date));
+            file_content.push_str(&format!("rustfmt_version={}\n", rustfmt.version));
         }
 
         file_content.push_str("\n");
 
         for (key, value) in self.checksums {
-            file_content.push_str(&format!("\n{}={}", key, value));
+            file_content.push_str(&format!("{}={}\n", key, value));
         }
 
         std::fs::write(PATH, file_content)?;
diff --git a/src/tools/cargo b/src/tools/cargo
-Subproject 0e93c5bf6a1d5ee7bc2af63d1afb16cd2879360
+Subproject d811228b14ae2707323f37346aee3f4147e247e
diff --git a/src/tools/clippy/clippy_lints/src/assigning_clones.rs b/src/tools/clippy/clippy_lints/src/assigning_clones.rs
index ab34af7c317..e5439a6d401 100644
--- a/src/tools/clippy/clippy_lints/src/assigning_clones.rs
+++ b/src/tools/clippy/clippy_lints/src/assigning_clones.rs
@@ -111,8 +111,8 @@ impl<'tcx> LateLintPass<'tcx> for AssigningClones {
             // Only suggest if `clone_from`/`clone_into` is explicitly implemented
             && resolved_assoc_items.in_definition_order().any(|assoc|
                 match which_trait {
-                    CloneTrait::Clone => assoc.name == sym::clone_from,
-                    CloneTrait::ToOwned => assoc.name.as_str() == "clone_into",
+                    CloneTrait::Clone => assoc.name() == sym::clone_from,
+                    CloneTrait::ToOwned => assoc.name().as_str() == "clone_into",
                 }
             )
             && !clone_source_borrows_from_dest(cx, lhs, rhs.span)
diff --git a/src/tools/clippy/clippy_lints/src/bool_assert_comparison.rs b/src/tools/clippy/clippy_lints/src/bool_assert_comparison.rs
index ad18c7039ee..4a876b85416 100644
--- a/src/tools/clippy/clippy_lints/src/bool_assert_comparison.rs
+++ b/src/tools/clippy/clippy_lints/src/bool_assert_comparison.rs
@@ -56,7 +56,7 @@ fn is_impl_not_trait_with_bool_out<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -
             cx.tcx.associated_items(trait_id).find_by_ident_and_kind(
                 cx.tcx,
                 Ident::from_str("Output"),
-                ty::AssocKind::Type,
+                ty::AssocTag::Type,
                 trait_id,
             )
         })
diff --git a/src/tools/clippy/clippy_lints/src/format_args.rs b/src/tools/clippy/clippy_lints/src/format_args.rs
index 3862ff7921d..06224f57c5c 100644
--- a/src/tools/clippy/clippy_lints/src/format_args.rs
+++ b/src/tools/clippy/clippy_lints/src/format_args.rs
@@ -550,7 +550,7 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
         // a `Target` that is in `self.ty_msrv_map`.
         if let Some(deref_trait_id) = self.cx.tcx.lang_items().deref_trait()
             && implements_trait(self.cx, ty, deref_trait_id, &[])
-            && let Some(target_ty) = self.cx.get_associated_type(ty, deref_trait_id, "Target")
+            && let Some(target_ty) = self.cx.get_associated_type(ty, deref_trait_id, sym::Target)
             && let Some(msrv) = self.ty_msrv_map.get(&target_ty)
             && msrv.is_none_or(|msrv| self.msrv.meets(self.cx, msrv))
         {
diff --git a/src/tools/clippy/clippy_lints/src/implied_bounds_in_impls.rs b/src/tools/clippy/clippy_lints/src/implied_bounds_in_impls.rs
index d02d9b2102b..430f2411183 100644
--- a/src/tools/clippy/clippy_lints/src/implied_bounds_in_impls.rs
+++ b/src/tools/clippy/clippy_lints/src/implied_bounds_in_impls.rs
@@ -315,7 +315,7 @@ fn check<'tcx>(cx: &LateContext<'tcx>, bounds: GenericBounds<'tcx>) {
                 assocs
                     .filter_by_name_unhygienic(constraint.ident.name)
                     .next()
-                    .is_some_and(|assoc| assoc.kind == ty::AssocKind::Type)
+                    .is_some_and(|assoc| assoc.is_type())
                 })
         {
             emit_lint(cx, poly_trait, bounds, index, implied_constraints, bound);
diff --git a/src/tools/clippy/clippy_lints/src/len_zero.rs b/src/tools/clippy/clippy_lints/src/len_zero.rs
index 72e22ae59d8..77085d52a32 100644
--- a/src/tools/clippy/clippy_lints/src/len_zero.rs
+++ b/src/tools/clippy/clippy_lints/src/len_zero.rs
@@ -13,7 +13,7 @@ use rustc_hir::{
     QPath, TraitItemRef, TyKind,
 };
 use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::ty::{self, AssocKind, FnSig, Ty};
+use rustc_middle::ty::{self, FnSig, Ty};
 use rustc_session::declare_lint_pass;
 use rustc_span::source_map::Spanned;
 use rustc_span::symbol::sym;
@@ -288,8 +288,7 @@ fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, ident: Iden
             .items()
             .flat_map(|&i| cx.tcx.associated_items(i).filter_by_name_unhygienic(is_empty))
             .any(|i| {
-                i.kind == AssocKind::Fn
-                    && i.fn_has_self_parameter
+                i.is_method()
                     && cx.tcx.fn_sig(i.def_id).skip_binder().inputs().skip_binder().len() == 1
             });
 
@@ -466,7 +465,7 @@ fn check_for_is_empty(
         .inherent_impls(impl_ty)
         .iter()
         .flat_map(|&id| cx.tcx.associated_items(id).filter_by_name_unhygienic(is_empty))
-        .find(|item| item.kind == AssocKind::Fn);
+        .find(|item| item.is_fn());
 
     let (msg, is_empty_span, self_kind) = match is_empty {
         None => (
@@ -486,7 +485,7 @@ fn check_for_is_empty(
             None,
         ),
         Some(is_empty)
-            if !(is_empty.fn_has_self_parameter
+            if !(is_empty.is_method()
                 && check_is_empty_sig(
                     cx,
                     cx.tcx.fn_sig(is_empty.def_id).instantiate_identity().skip_binder(),
@@ -608,7 +607,7 @@ fn is_empty_array(expr: &Expr<'_>) -> bool {
 fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
     /// Gets an `AssocItem` and return true if it matches `is_empty(self)`.
     fn is_is_empty(cx: &LateContext<'_>, item: &ty::AssocItem) -> bool {
-        if item.kind == AssocKind::Fn {
+        if item.is_fn() {
             let sig = cx.tcx.fn_sig(item.def_id).skip_binder();
             let ty = sig.skip_binder();
             ty.inputs().len() == 1
@@ -644,7 +643,7 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
                         && cx.tcx.get_diagnostic_item(sym::Deref).is_some_and(|deref_id| {
                             implements_trait(cx, ty, deref_id, &[])
                                 && cx
-                                    .get_associated_type(ty, deref_id, "Target")
+                                    .get_associated_type(ty, deref_id, sym::Target)
                                     .is_some_and(|deref_ty| ty_has_is_empty(cx, deref_ty, depth + 1))
                         }))
             },
diff --git a/src/tools/clippy/clippy_lints/src/lifetimes.rs b/src/tools/clippy/clippy_lints/src/lifetimes.rs
index 8d47c756fc5..dabef18b98a 100644
--- a/src/tools/clippy/clippy_lints/src/lifetimes.rs
+++ b/src/tools/clippy/clippy_lints/src/lifetimes.rs
@@ -14,7 +14,7 @@ use rustc_hir::intravisit::{
 };
 use rustc_hir::{
     AmbigArg, BareFnTy, BodyId, FnDecl, FnSig, GenericArg, GenericArgs, GenericBound, GenericParam, GenericParamKind,
-    Generics, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, Lifetime, LifetimeName, LifetimeParamKind, Node,
+    Generics, HirId, Impl, ImplItem, ImplItemKind, Item, ItemKind, Lifetime, LifetimeKind, LifetimeParamKind, Node,
     PolyTraitRef, PredicateOrigin, TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WhereBoundPredicate, WherePredicate,
     WherePredicateKind, lang_items,
 };
@@ -218,7 +218,7 @@ fn check_fn_inner<'tcx>(
             for bound in pred.bounds {
                 let mut visitor = RefVisitor::new(cx);
                 walk_param_bound(&mut visitor, bound);
-                if visitor.lts.iter().any(|lt| matches!(lt.res, LifetimeName::Param(_))) {
+                if visitor.lts.iter().any(|lt| matches!(lt.kind, LifetimeKind::Param(_))) {
                     return;
                 }
                 if let GenericBound::Trait(ref trait_ref) = *bound {
@@ -235,7 +235,7 @@ fn check_fn_inner<'tcx>(
                             _ => None,
                         });
                         for bound in lifetimes {
-                            if bound.res != LifetimeName::Static && !bound.is_elided() {
+                            if bound.kind != LifetimeKind::Static && !bound.is_elided() {
                                 return;
                             }
                         }
@@ -421,8 +421,8 @@ fn named_lifetime_occurrences(lts: &[Lifetime]) -> Vec<(LocalDefId, usize)> {
 }
 
 fn named_lifetime(lt: &Lifetime) -> Option<LocalDefId> {
-    match lt.res {
-        LifetimeName::Param(id) if !lt.is_anonymous() => Some(id),
+    match lt.kind {
+        LifetimeKind::Param(id) if !lt.is_anonymous() => Some(id),
         _ => None,
     }
 }
@@ -614,7 +614,7 @@ where
 
     // for lifetimes as parameters of generics
     fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) {
-        if let LifetimeName::Param(def_id) = lifetime.res
+        if let LifetimeKind::Param(def_id) = lifetime.kind
             && let Some(usages) = self.map.get_mut(&def_id)
         {
             usages.push(Usage {
@@ -826,7 +826,7 @@ fn report_elidable_lifetimes(
             .iter()
             .map(|&lt| cx.tcx.def_span(lt))
             .chain(usages.iter().filter_map(|usage| {
-                if let LifetimeName::Param(def_id) = usage.res
+                if let LifetimeKind::Param(def_id) = usage.kind
                     && elidable_lts.contains(&def_id)
                 {
                     return Some(usage.ident.span);
diff --git a/src/tools/clippy/clippy_lints/src/methods/double_ended_iterator_last.rs b/src/tools/clippy/clippy_lints/src/methods/double_ended_iterator_last.rs
index e82211bbf3e..b5adc69e9a7 100644
--- a/src/tools/clippy/clippy_lints/src/methods/double_ended_iterator_last.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/double_ended_iterator_last.rs
@@ -24,7 +24,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, self_expr: &'_ Exp
         && let Ok(Some(fn_def)) = Instance::try_resolve(cx.tcx, cx.typing_env(), id, args)
         // find the provided definition of Iterator::last
         && let Some(item) = cx.tcx.get_diagnostic_item(sym::Iterator)
-        && let Some(last_def) = cx.tcx.provided_trait_methods(item).find(|m| m.name.as_str() == "last")
+        && let Some(last_def) = cx.tcx.provided_trait_methods(item).find(|m| m.name().as_str() == "last")
         // if the resolved method is the same as the provided definition
         && fn_def.def_id() == last_def.def_id
     {
diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs b/src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs
index f51bdc78f8a..7bb625222ec 100644
--- a/src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/iter_overeager_cloned.rs
@@ -48,7 +48,7 @@ pub(super) fn check<'tcx>(
         && let Some(method_id) = typeck.type_dependent_def_id(cloned_call.hir_id)
         && cx.tcx.trait_of_item(method_id) == Some(iter_id)
         && let cloned_recv_ty = typeck.expr_ty_adjusted(cloned_recv)
-        && let Some(iter_assoc_ty) = cx.get_associated_type(cloned_recv_ty, iter_id, "Item")
+        && let Some(iter_assoc_ty) = cx.get_associated_type(cloned_recv_ty, iter_id, sym::Item)
         && matches!(*iter_assoc_ty.kind(), ty::Ref(_, ty, _) if !is_copy(cx, ty))
     {
         if needs_into_iter
diff --git a/src/tools/clippy/clippy_lints/src/methods/needless_collect.rs b/src/tools/clippy/clippy_lints/src/methods/needless_collect.rs
index 239ee6c729f..e4a29b6560e 100644
--- a/src/tools/clippy/clippy_lints/src/methods/needless_collect.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/needless_collect.rs
@@ -17,7 +17,7 @@ use rustc_hir::{
 };
 use rustc_lint::LateContext;
 use rustc_middle::hir::nested_filter;
-use rustc_middle::ty::{self, AssocKind, ClauseKind, EarlyBinder, GenericArg, GenericArgKind, Ty};
+use rustc_middle::ty::{self, AssocTag, ClauseKind, EarlyBinder, GenericArg, GenericArgKind, Ty};
 use rustc_span::symbol::Ident;
 use rustc_span::{Span, sym};
 
@@ -241,7 +241,7 @@ fn is_contains_sig(cx: &LateContext<'_>, call_id: HirId, iter_expr: &Expr<'_>) -
         && let Some(iter_item) = cx.tcx.associated_items(iter_trait).find_by_ident_and_kind(
             cx.tcx,
             Ident::with_dummy_span(sym::Item),
-            AssocKind::Type,
+            AssocTag::Type,
             iter_trait,
         )
         && let args = cx.tcx.mk_args(&[GenericArg::from(typeck.expr_ty_adjusted(iter_expr))])
diff --git a/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs b/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs
index c03420a5143..6eeeea5d77c 100644
--- a/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs
@@ -78,7 +78,7 @@ pub(super) fn check<'tcx>(
                 .iter()
                 .flat_map(|impl_id| cx.tcx.associated_items(impl_id).filter_by_name_unhygienic(sugg))
                 .find_map(|assoc| {
-                    if assoc.fn_has_self_parameter
+                    if assoc.is_method()
                         && cx.tcx.fn_sig(assoc.def_id).skip_binder().inputs().skip_binder().len() == 1
                     {
                         Some(assoc.def_id)
diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_iter_cloned.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_iter_cloned.rs
index c0e01568588..20cf35363d1 100644
--- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_iter_cloned.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_iter_cloned.rs
@@ -99,7 +99,7 @@ pub fn check_for_loop_iter(
             && let Some(into_iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator)
             && let collection_ty = cx.typeck_results().expr_ty(collection)
             && implements_trait(cx, collection_ty, into_iterator_trait_id, &[])
-            && let Some(into_iter_item_ty) = cx.get_associated_type(collection_ty, into_iterator_trait_id, "Item")
+            && let Some(into_iter_item_ty) = cx.get_associated_type(collection_ty, into_iterator_trait_id, sym::Item)
             && iter_item_ty == into_iter_item_ty
             && let Some(collection_snippet) = collection.span.get_source_text(cx)
         {
diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
index 62ba3012643..206b0a8ae3c 100644
--- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
@@ -153,7 +153,7 @@ fn check_addr_of_expr(
         }
         if let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref)
             && implements_trait(cx, receiver_ty, deref_trait_id, &[])
-            && cx.get_associated_type(receiver_ty, deref_trait_id, "Target") == Some(target_ty)
+            && cx.get_associated_type(receiver_ty, deref_trait_id, sym::Target) == Some(target_ty)
             // Make sure that it's actually calling the right `.to_string()`, (#10033)
             // *or* this is a `Cow::into_owned()` call (which would be the wrong into_owned receiver (str != Cow)
             // but that's ok for Cow::into_owned specifically)
@@ -322,7 +322,7 @@ fn check_split_call_arg(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symb
         // add `.as_ref()` to the suggestion.
         let as_ref = if is_type_lang_item(cx, cx.typeck_results().expr_ty(expr), LangItem::String)
             && let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref)
-            && cx.get_associated_type(cx.typeck_results().expr_ty(receiver), deref_trait_id, "Target")
+            && cx.get_associated_type(cx.typeck_results().expr_ty(receiver), deref_trait_id, sym::Target)
                 != Some(cx.tcx.types.str_)
         {
             ".as_ref()"
@@ -648,7 +648,7 @@ fn is_to_string_on_string_like<'a>(
         && let GenericArgKind::Type(ty) = generic_arg.unpack()
         && let Some(deref_trait_id) = cx.tcx.get_diagnostic_item(sym::Deref)
         && let Some(as_ref_trait_id) = cx.tcx.get_diagnostic_item(sym::AsRef)
-        && (cx.get_associated_type(ty, deref_trait_id, "Target") == Some(cx.tcx.types.str_)
+        && (cx.get_associated_type(ty, deref_trait_id, sym::Target) == Some(cx.tcx.types.str_)
             || implements_trait(cx, ty, as_ref_trait_id, &[cx.tcx.types.str_.into()]))
     {
         true
diff --git a/src/tools/clippy/clippy_lints/src/missing_trait_methods.rs b/src/tools/clippy/clippy_lints/src/missing_trait_methods.rs
index 7ee746365d1..e266c36b6e7 100644
--- a/src/tools/clippy/clippy_lints/src/missing_trait_methods.rs
+++ b/src/tools/clippy/clippy_lints/src/missing_trait_methods.rs
@@ -81,7 +81,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingTraitMethods {
                     cx,
                     MISSING_TRAIT_METHODS,
                     cx.tcx.def_span(item.owner_id),
-                    format!("missing trait method provided by default: `{}`", assoc.name),
+                    format!("missing trait method provided by default: `{}`", assoc.name()),
                     |diag| {
                         diag.span_help(cx.tcx.def_span(assoc.def_id), "implement the method");
                     },
diff --git a/src/tools/clippy/clippy_lints/src/needless_borrows_for_generic_args.rs b/src/tools/clippy/clippy_lints/src/needless_borrows_for_generic_args.rs
index f686cc912dd..e579dd5947d 100644
--- a/src/tools/clippy/clippy_lints/src/needless_borrows_for_generic_args.rs
+++ b/src/tools/clippy/clippy_lints/src/needless_borrows_for_generic_args.rs
@@ -299,7 +299,7 @@ fn has_ref_mut_self_method(cx: &LateContext<'_>, trait_def_id: DefId) -> bool {
         .associated_items(trait_def_id)
         .in_definition_order()
         .any(|assoc_item| {
-            if assoc_item.fn_has_self_parameter {
+            if assoc_item.is_method() {
                 let self_ty = cx
                     .tcx
                     .fn_sig(assoc_item.def_id)
diff --git a/src/tools/clippy/clippy_lints/src/ptr.rs b/src/tools/clippy/clippy_lints/src/ptr.rs
index 50ef56db167..901a1634ddc 100644
--- a/src/tools/clippy/clippy_lints/src/ptr.rs
+++ b/src/tools/clippy/clippy_lints/src/ptr.rs
@@ -3,7 +3,7 @@ use clippy_utils::source::SpanRangeExt;
 use clippy_utils::sugg::Sugg;
 use clippy_utils::visitors::contains_unsafe_block;
 use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, std_or_core};
-use hir::LifetimeName;
+use hir::LifetimeKind;
 use rustc_abi::ExternAbi;
 use rustc_errors::{Applicability, MultiSpan};
 use rustc_hir::hir_id::{HirId, HirIdMap};
@@ -432,7 +432,7 @@ fn check_fn_args<'cx, 'tcx: 'cx>(
                             }
                             None
                         }) {
-                            if let LifetimeName::Param(param_def_id) = lifetime.res
+                            if let LifetimeKind::Param(param_def_id) = lifetime.kind
                                 && !lifetime.is_anonymous()
                                 && fn_sig
                                     .output()
diff --git a/src/tools/clippy/clippy_lints/src/same_name_method.rs b/src/tools/clippy/clippy_lints/src/same_name_method.rs
index 552135b15fd..33815cc3bac 100644
--- a/src/tools/clippy/clippy_lints/src/same_name_method.rs
+++ b/src/tools/clippy/clippy_lints/src/same_name_method.rs
@@ -3,7 +3,6 @@ use rustc_data_structures::fx::FxHashMap;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::{HirId, Impl, ItemKind, Node, Path, QPath, TraitRef, TyKind};
 use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::ty::AssocKind;
 use rustc_session::declare_lint_pass;
 use rustc_span::Span;
 use rustc_span::symbol::Symbol;
@@ -85,8 +84,8 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod {
                             cx.tcx
                                 .associated_items(did)
                                 .in_definition_order()
-                                .filter(|assoc_item| matches!(assoc_item.kind, AssocKind::Fn))
-                                .map(|assoc_item| assoc_item.name)
+                                .filter(|assoc_item| assoc_item.is_fn())
+                                .map(|assoc_item| assoc_item.name())
                                 .collect()
                         } else {
                             BTreeSet::new()
diff --git a/src/tools/clippy/clippy_lints/src/unconditional_recursion.rs b/src/tools/clippy/clippy_lints/src/unconditional_recursion.rs
index 51c7d6fce31..0c17cc5d8f6 100644
--- a/src/tools/clippy/clippy_lints/src/unconditional_recursion.rs
+++ b/src/tools/clippy/clippy_lints/src/unconditional_recursion.rs
@@ -10,7 +10,7 @@ use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId, Item, ItemKind, Node, QPath
 use rustc_hir_analysis::lower_ty;
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_middle::hir::nested_filter;
-use rustc_middle::ty::{self, AssocKind, Ty, TyCtxt};
+use rustc_middle::ty::{self, Ty, TyCtxt};
 use rustc_session::impl_lint_pass;
 use rustc_span::symbol::{Ident, kw};
 use rustc_span::{Span, sym};
@@ -322,7 +322,7 @@ impl UnconditionalRecursion {
                             .in_definition_order()
                             // We're not interested in foreign implementations of the `Default` trait.
                             .find(|item| {
-                                item.kind == AssocKind::Fn && item.def_id.is_local() && item.name == kw::Default
+                                item.is_fn() && item.def_id.is_local() && item.name() == kw::Default
                             })
                         && let Some(body_node) = cx.tcx.hir_get_if_local(assoc_item.def_id)
                         && let Some(body_id) = body_node.body_id()
diff --git a/src/tools/clippy/clippy_lints/src/unused_self.rs b/src/tools/clippy/clippy_lints/src/unused_self.rs
index 582aa6e6001..d0067b1a65e 100644
--- a/src/tools/clippy/clippy_lints/src/unused_self.rs
+++ b/src/tools/clippy/clippy_lints/src/unused_self.rs
@@ -74,7 +74,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedSelf {
             .is_some()
         };
         if let ItemKind::Impl(Impl { of_trait: None, .. }) = parent_item.kind
-            && assoc_item.fn_has_self_parameter
+            && assoc_item.is_method()
             && let ImplItemKind::Fn(.., body_id) = &impl_item.kind
             && (!cx.effective_visibilities.is_exported(impl_item.owner_id.def_id) || !self.avoid_breaking_exported_api)
             && let body = cx.tcx.hir_body(*body_id)
diff --git a/src/tools/clippy/clippy_utils/src/hir_utils.rs b/src/tools/clippy/clippy_utils/src/hir_utils.rs
index b813cd361ed..be295b59f60 100644
--- a/src/tools/clippy/clippy_utils/src/hir_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/hir_utils.rs
@@ -8,7 +8,7 @@ use rustc_hir::MatchSource::TryDesugar;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::{
     AssocItemConstraint, BinOpKind, BindingMode, Block, BodyId, Closure, ConstArg, ConstArgKind, Expr, ExprField,
-    ExprKind, FnRetTy, GenericArg, GenericArgs, HirId, HirIdMap, InlineAsmOperand, LetExpr, Lifetime, LifetimeName,
+    ExprKind, FnRetTy, GenericArg, GenericArgs, HirId, HirIdMap, InlineAsmOperand, LetExpr, Lifetime, LifetimeKind,
     Pat, PatExpr, PatExprKind, PatField, PatKind, Path, PathSegment, PrimTy, QPath, Stmt, StmtKind, StructTailExpr,
     TraitBoundModifiers, Ty, TyKind, TyPat, TyPatKind,
 };
@@ -483,7 +483,7 @@ impl HirEqInterExpr<'_, '_, '_> {
     }
 
     fn eq_lifetime(left: &Lifetime, right: &Lifetime) -> bool {
-        left.res == right.res
+        left.kind == right.kind
     }
 
     fn eq_pat_field(&mut self, left: &PatField<'_>, right: &PatField<'_>) -> bool {
@@ -1245,8 +1245,8 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> {
 
     pub fn hash_lifetime(&mut self, lifetime: &Lifetime) {
         lifetime.ident.name.hash(&mut self.s);
-        std::mem::discriminant(&lifetime.res).hash(&mut self.s);
-        if let LifetimeName::Param(param_id) = lifetime.res {
+        std::mem::discriminant(&lifetime.kind).hash(&mut self.s);
+        if let LifetimeKind::Param(param_id) = lifetime.kind {
             param_id.hash(&mut self.s);
         }
     }
diff --git a/src/tools/clippy/clippy_utils/src/ty/mod.rs b/src/tools/clippy/clippy_utils/src/ty/mod.rs
index 29cbf62c3d4..d33e59342a5 100644
--- a/src/tools/clippy/clippy_utils/src/ty/mod.rs
+++ b/src/tools/clippy/clippy_utils/src/ty/mod.rs
@@ -19,7 +19,7 @@ use rustc_middle::mir::interpret::Scalar;
 use rustc_middle::traits::EvaluationResult;
 use rustc_middle::ty::layout::ValidityRequirement;
 use rustc_middle::ty::{
-    self, AdtDef, AliasTy, AssocItem, AssocKind, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind,
+    self, AdtDef, AliasTy, AssocItem, AssocTag, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind,
     GenericArgsRef, GenericParamDefKind, IntTy, ParamEnv, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable,
     TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr,
 };
@@ -156,7 +156,7 @@ pub fn contains_ty_adt_constructor_opaque<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'
 pub fn get_iterator_item_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
     cx.tcx
         .get_diagnostic_item(sym::Iterator)
-        .and_then(|iter_did| cx.get_associated_type(ty, iter_did, "Item"))
+        .and_then(|iter_did| cx.get_associated_type(ty, iter_did, sym::Item))
 }
 
 /// Get the diagnostic name of a type, e.g. `sym::HashMap`. To check if a type
@@ -1112,7 +1112,7 @@ pub fn make_projection<'tcx>(
         let Some(assoc_item) = tcx.associated_items(container_id).find_by_ident_and_kind(
             tcx,
             Ident::with_dummy_span(assoc_ty),
-            AssocKind::Type,
+            AssocTag::Type,
             container_id,
         ) else {
             debug_assert!(false, "type `{assoc_ty}` not found in `{container_id:?}`");
@@ -1345,7 +1345,7 @@ pub fn get_adt_inherent_method<'a>(cx: &'a LateContext<'_>, ty: Ty<'_>, method_n
                 .associated_items(did)
                 .filter_by_name_unhygienic(method_name)
                 .next()
-                .filter(|item| item.kind == AssocKind::Fn)
+                .filter(|item| item.as_tag() == AssocTag::Fn)
         })
     } else {
         None
diff --git a/src/tools/compiletest/Cargo.toml b/src/tools/compiletest/Cargo.toml
index 3db34ed24cc..ba1b8f25658 100644
--- a/src/tools/compiletest/Cargo.toml
+++ b/src/tools/compiletest/Cargo.toml
@@ -10,6 +10,7 @@ doctest = false
 # tidy-alphabetical-start
 anstyle-svg = "0.1.3"
 build_helper = { path = "../../build_helper" }
+camino = "1"
 colored = "2"
 diff = "0.1.10"
 getopts = "0.2"
diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs
index 6750b5288f4..31c696ed41f 100644
--- a/src/tools/compiletest/src/common.rs
+++ b/src/tools/compiletest/src/common.rs
@@ -1,18 +1,17 @@
 use std::collections::{BTreeSet, HashMap, HashSet};
-use std::ffi::OsString;
-use std::path::{Path, PathBuf};
 use std::process::Command;
 use std::str::FromStr;
 use std::sync::OnceLock;
 use std::{fmt, iter};
 
 use build_helper::git::GitConfig;
+use camino::{Utf8Path, Utf8PathBuf};
 use semver::Version;
 use serde::de::{Deserialize, Deserializer, Error as _};
 
 pub use self::Mode::*;
 use crate::executor::{ColorConfig, OutputFormat};
-use crate::util::{PathBufExt, add_dylib_path};
+use crate::util::{Utf8PathBufExt, add_dylib_path};
 
 macro_rules! string_enum {
     ($(#[$meta:meta])* $vis:vis enum $name:ident { $($variant:ident => $repr:expr,)* }) => {
@@ -183,25 +182,25 @@ pub struct Config {
     pub fail_fast: bool,
 
     /// The library paths required for running the compiler.
-    pub compile_lib_path: PathBuf,
+    pub compile_lib_path: Utf8PathBuf,
 
     /// The library paths required for running compiled programs.
-    pub run_lib_path: PathBuf,
+    pub run_lib_path: Utf8PathBuf,
 
     /// The rustc executable.
-    pub rustc_path: PathBuf,
+    pub rustc_path: Utf8PathBuf,
 
     /// The cargo executable.
-    pub cargo_path: Option<PathBuf>,
+    pub cargo_path: Option<Utf8PathBuf>,
 
     /// Rustc executable used to compile run-make recipes.
-    pub stage0_rustc_path: Option<PathBuf>,
+    pub stage0_rustc_path: Option<Utf8PathBuf>,
 
     /// The rustdoc executable.
-    pub rustdoc_path: Option<PathBuf>,
+    pub rustdoc_path: Option<Utf8PathBuf>,
 
     /// The coverage-dump executable.
-    pub coverage_dump_path: Option<PathBuf>,
+    pub coverage_dump_path: Option<Utf8PathBuf>,
 
     /// The Python executable to use for LLDB and htmldocck.
     pub python: String,
@@ -213,27 +212,27 @@ pub struct Config {
     pub jsondoclint_path: Option<String>,
 
     /// The LLVM `FileCheck` binary path.
-    pub llvm_filecheck: Option<PathBuf>,
+    pub llvm_filecheck: Option<Utf8PathBuf>,
 
     /// Path to LLVM's bin directory.
-    pub llvm_bin_dir: Option<PathBuf>,
+    pub llvm_bin_dir: Option<Utf8PathBuf>,
 
     /// The path to the Clang executable to run Clang-based tests with. If
     /// `None` then these tests will be ignored.
     pub run_clang_based_tests_with: Option<String>,
 
     /// The directory containing the sources.
-    pub src_root: PathBuf,
+    pub src_root: Utf8PathBuf,
     /// The directory containing the test suite sources. Must be a subdirectory of `src_root`.
-    pub src_test_suite_root: PathBuf,
+    pub src_test_suite_root: Utf8PathBuf,
 
     /// Root build directory (e.g. `build/`).
-    pub build_root: PathBuf,
+    pub build_root: Utf8PathBuf,
     /// Test suite specific build directory (e.g. `build/host/test/ui/`).
-    pub build_test_suite_root: PathBuf,
+    pub build_test_suite_root: Utf8PathBuf,
 
     /// The directory containing the compiler sysroot
-    pub sysroot_base: PathBuf,
+    pub sysroot_base: Utf8PathBuf,
 
     /// The number of the stage under test.
     pub stage: u32,
@@ -301,7 +300,7 @@ pub struct Config {
     pub host: String,
 
     /// Path to / name of the Microsoft Console Debugger (CDB) executable
-    pub cdb: Option<OsString>,
+    pub cdb: Option<Utf8PathBuf>,
 
     /// Version of CDB
     pub cdb_version: Option<[u16; 4]>,
@@ -322,7 +321,7 @@ pub struct Config {
     pub system_llvm: bool,
 
     /// Path to the android tools
-    pub android_cross_path: PathBuf,
+    pub android_cross_path: Utf8PathBuf,
 
     /// Extra parameter to run adb on arm-linux-androideabi
     pub adb_path: String,
@@ -346,7 +345,7 @@ pub struct Config {
     pub color: ColorConfig,
 
     /// where to find the remote test client process, if we're using it
-    pub remote_test_client: Option<PathBuf>,
+    pub remote_test_client: Option<Utf8PathBuf>,
 
     /// mode describing what file the actual ui output will be compared to
     pub compare_mode: Option<CompareMode>,
@@ -414,7 +413,12 @@ pub struct Config {
     /// Path to minicore aux library, used for `no_core` tests that need `core` stubs in
     /// cross-compilation scenarios that do not otherwise want/need to `-Zbuild-std`. Used in e.g.
     /// ABI tests.
-    pub minicore_path: PathBuf,
+    pub minicore_path: Utf8PathBuf,
+
+    /// If true, run tests with the "new" executor that was written to replace
+    /// compiletest's dependency on libtest. Eventually this will become the
+    /// default, and the libtest dependency will be removed.
+    pub new_executor: bool,
 }
 
 impl Config {
@@ -804,8 +808,8 @@ fn serde_parse_u32<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u32, D:
 
 #[derive(Debug, Clone)]
 pub struct TestPaths {
-    pub file: PathBuf,         // e.g., compile-test/foo/bar/baz.rs
-    pub relative_dir: PathBuf, // e.g., foo/bar
+    pub file: Utf8PathBuf,         // e.g., compile-test/foo/bar/baz.rs
+    pub relative_dir: Utf8PathBuf, // e.g., foo/bar
 }
 
 /// Used by `ui` tests to generate things like `foo.stderr` from `foo.rs`.
@@ -814,7 +818,7 @@ pub fn expected_output_path(
     revision: Option<&str>,
     compare_mode: &Option<CompareMode>,
     kind: &str,
-) -> PathBuf {
+) -> Utf8PathBuf {
     assert!(UI_EXTENSIONS.contains(&kind));
     let mut parts = Vec::new();
 
@@ -865,7 +869,7 @@ pub const UI_COVERAGE_MAP: &str = "cov-map";
 /// ```
 ///
 /// This is created early when tests are collected to avoid race conditions.
-pub fn output_relative_path(config: &Config, relative_dir: &Path) -> PathBuf {
+pub fn output_relative_path(config: &Config, relative_dir: &Utf8Path) -> Utf8PathBuf {
     config.build_test_suite_root.join(relative_dir)
 }
 
@@ -874,10 +878,10 @@ pub fn output_testname_unique(
     config: &Config,
     testpaths: &TestPaths,
     revision: Option<&str>,
-) -> PathBuf {
+) -> Utf8PathBuf {
     let mode = config.compare_mode.as_ref().map_or("", |m| m.to_str());
     let debugger = config.debugger.as_ref().map_or("", |m| m.to_str());
-    PathBuf::from(&testpaths.file.file_stem().unwrap())
+    Utf8PathBuf::from(&testpaths.file.file_stem().unwrap())
         .with_extra_extension(config.mode.output_dir_disambiguator())
         .with_extra_extension(revision.unwrap_or(""))
         .with_extra_extension(mode)
@@ -887,7 +891,11 @@ pub fn output_testname_unique(
 /// Absolute path to the directory where all output for the given
 /// test/revision should reside. Example:
 ///   /path/to/build/host-tuple/test/ui/relative/testname.revision.mode/
-pub fn output_base_dir(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
+pub fn output_base_dir(
+    config: &Config,
+    testpaths: &TestPaths,
+    revision: Option<&str>,
+) -> Utf8PathBuf {
     output_relative_path(config, &testpaths.relative_dir)
         .join(output_testname_unique(config, testpaths, revision))
 }
@@ -895,12 +903,20 @@ pub fn output_base_dir(config: &Config, testpaths: &TestPaths, revision: Option<
 /// Absolute path to the base filename used as output for the given
 /// test/revision. Example:
 ///   /path/to/build/host-tuple/test/ui/relative/testname.revision.mode/testname
-pub fn output_base_name(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
+pub fn output_base_name(
+    config: &Config,
+    testpaths: &TestPaths,
+    revision: Option<&str>,
+) -> Utf8PathBuf {
     output_base_dir(config, testpaths, revision).join(testpaths.file.file_stem().unwrap())
 }
 
 /// Absolute path to the directory to use for incremental compilation. Example:
 ///   /path/to/build/host-tuple/test/ui/relative/testname.mode/testname.inc
-pub fn incremental_dir(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
+pub fn incremental_dir(
+    config: &Config,
+    testpaths: &TestPaths,
+    revision: Option<&str>,
+) -> Utf8PathBuf {
     output_base_name(config, testpaths, revision).with_extension("inc")
 }
diff --git a/src/tools/compiletest/src/compute_diff.rs b/src/tools/compiletest/src/compute_diff.rs
index 4c942c51bae..509e7e11703 100644
--- a/src/tools/compiletest/src/compute_diff.rs
+++ b/src/tools/compiletest/src/compute_diff.rs
@@ -1,6 +1,7 @@
 use std::collections::VecDeque;
 use std::fs::{File, FileType};
-use std::path::Path;
+
+use camino::Utf8Path;
 
 #[derive(Debug, PartialEq)]
 pub enum DiffLine {
@@ -112,8 +113,8 @@ pub(crate) fn write_diff(expected: &str, actual: &str, context_size: usize) -> S
 /// Returns whether any data was actually written.
 pub(crate) fn write_filtered_diff<Filter>(
     diff_filename: &str,
-    out_dir: &Path,
-    compare_dir: &Path,
+    out_dir: &Utf8Path,
+    compare_dir: &Utf8Path,
     verbose: bool,
     filter: Filter,
 ) -> bool
@@ -123,19 +124,21 @@ where
     use std::io::{Read, Write};
     let mut diff_output = File::create(diff_filename).unwrap();
     let mut wrote_data = false;
-    for entry in walkdir::WalkDir::new(out_dir) {
+    for entry in walkdir::WalkDir::new(out_dir.as_std_path()) {
         let entry = entry.expect("failed to read file");
         let extension = entry.path().extension().and_then(|p| p.to_str());
         if filter(entry.file_type(), extension) {
-            let expected_path = compare_dir.join(entry.path().strip_prefix(&out_dir).unwrap());
+            let expected_path = compare_dir
+                .as_std_path()
+                .join(entry.path().strip_prefix(&out_dir.as_std_path()).unwrap());
             let expected = if let Ok(s) = std::fs::read(&expected_path) { s } else { continue };
             let actual_path = entry.path();
             let actual = std::fs::read(&actual_path).unwrap();
             let diff = unified_diff::diff(
                 &expected,
-                &expected_path.to_string_lossy(),
+                &expected_path.to_str().unwrap(),
                 &actual,
-                &actual_path.to_string_lossy(),
+                &actual_path.to_str().unwrap(),
                 3,
             );
             wrote_data |= !diff.is_empty();
diff --git a/src/tools/compiletest/src/debuggers.rs b/src/tools/compiletest/src/debuggers.rs
index 5126e55aea1..c133d7fd4fb 100644
--- a/src/tools/compiletest/src/debuggers.rs
+++ b/src/tools/compiletest/src/debuggers.rs
@@ -1,9 +1,9 @@
 use std::env;
-use std::ffi::OsString;
-use std::path::{Path, PathBuf};
 use std::process::Command;
 use std::sync::Arc;
 
+use camino::{Utf8Path, Utf8PathBuf};
+
 use crate::common::{Config, Debugger};
 
 pub(crate) fn configure_cdb(config: &Config) -> Option<Arc<Config>> {
@@ -78,12 +78,15 @@ fn is_pc_windows_msvc_target(target: &str) -> bool {
     target.ends_with("-pc-windows-msvc")
 }
 
-fn find_cdb(target: &str) -> Option<OsString> {
+fn find_cdb(target: &str) -> Option<Utf8PathBuf> {
     if !(cfg!(windows) && is_pc_windows_msvc_target(target)) {
         return None;
     }
 
-    let pf86 = env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?;
+    let pf86 = Utf8PathBuf::from_path_buf(
+        env::var_os("ProgramFiles(x86)").or_else(|| env::var_os("ProgramFiles"))?.into(),
+    )
+    .unwrap();
     let cdb_arch = if cfg!(target_arch = "x86") {
         "x86"
     } else if cfg!(target_arch = "x86_64") {
@@ -96,8 +99,7 @@ fn find_cdb(target: &str) -> Option<OsString> {
         return None; // No compatible CDB.exe in the Windows 10 SDK
     };
 
-    let mut path = PathBuf::new();
-    path.push(pf86);
+    let mut path = pf86;
     path.push(r"Windows Kits\10\Debuggers"); // We could check 8.1 etc. too?
     path.push(cdb_arch);
     path.push(r"cdb.exe");
@@ -106,15 +108,15 @@ fn find_cdb(target: &str) -> Option<OsString> {
         return None;
     }
 
-    Some(path.into_os_string())
+    Some(path)
 }
 
 /// Returns Path to CDB
 pub(crate) fn analyze_cdb(
     cdb: Option<String>,
     target: &str,
-) -> (Option<OsString>, Option<[u16; 4]>) {
-    let cdb = cdb.map(OsString::from).or_else(|| find_cdb(target));
+) -> (Option<Utf8PathBuf>, Option<[u16; 4]>) {
+    let cdb = cdb.map(Utf8PathBuf::from).or_else(|| find_cdb(target));
 
     let mut version = None;
     if let Some(cdb) = cdb.as_ref() {
@@ -143,7 +145,7 @@ pub(crate) fn extract_cdb_version(full_version_line: &str) -> Option<[u16; 4]> {
 pub(crate) fn analyze_gdb(
     gdb: Option<String>,
     target: &str,
-    android_cross_path: &Path,
+    android_cross_path: &Utf8Path,
 ) -> (Option<String>, Option<u32>) {
     #[cfg(not(windows))]
     const GDB_FALLBACK: &str = "gdb";
@@ -152,10 +154,7 @@ pub(crate) fn analyze_gdb(
 
     let fallback_gdb = || {
         if is_android_gdb_target(target) {
-            let mut gdb_path = match android_cross_path.to_str() {
-                Some(x) => x.to_owned(),
-                None => panic!("cannot find android cross path"),
-            };
+            let mut gdb_path = android_cross_path.to_string();
             gdb_path.push_str("/bin/gdb");
             gdb_path
         } else {
diff --git a/src/tools/compiletest/src/errors.rs b/src/tools/compiletest/src/errors.rs
index 64d68eb7f23..3bb98276bf5 100644
--- a/src/tools/compiletest/src/errors.rs
+++ b/src/tools/compiletest/src/errors.rs
@@ -2,9 +2,9 @@ use std::fmt;
 use std::fs::File;
 use std::io::BufReader;
 use std::io::prelude::*;
-use std::path::Path;
 use std::sync::OnceLock;
 
+use camino::Utf8Path;
 use regex::Regex;
 use tracing::*;
 
@@ -102,8 +102,8 @@ impl Error {
 ///
 /// If revision is not None, then we look
 /// for `//[X]~` instead, where `X` is the current revision.
-pub fn load_errors(testfile: &Path, revision: Option<&str>) -> Vec<Error> {
-    let rdr = BufReader::new(File::open(testfile).unwrap());
+pub fn load_errors(testfile: &Utf8Path, revision: Option<&str>) -> Vec<Error> {
+    let rdr = BufReader::new(File::open(testfile.as_std_path()).unwrap());
 
     // `last_nonfollow_error` tracks the most recently seen
     // line with an error template that did not use the
diff --git a/src/tools/compiletest/src/executor.rs b/src/tools/compiletest/src/executor.rs
index 527d6b8a36e..0c173d476af 100644
--- a/src/tools/compiletest/src/executor.rs
+++ b/src/tools/compiletest/src/executor.rs
@@ -1,22 +1,251 @@
-//! This module encapsulates all of the code that interacts directly with
-//! libtest, to execute the collected tests.
-//!
-//! This will hopefully make it easier to migrate away from libtest someday.
+//! This module contains a reimplementation of the subset of libtest
+//! functionality needed by compiletest.
 
 use std::borrow::Cow;
-use std::io;
-use std::sync::Arc;
+use std::collections::HashMap;
+use std::hash::{BuildHasherDefault, DefaultHasher};
+use std::num::NonZero;
+use std::sync::{Arc, Mutex, mpsc};
+use std::{env, hint, io, mem, panic, thread};
 
 use crate::common::{Config, TestPaths};
 
-/// Delegates to libtest to run the list of collected tests.
+mod deadline;
+mod json;
+pub(crate) mod libtest;
+
+pub(crate) fn run_tests(config: &Config, tests: Vec<CollectedTest>) -> bool {
+    let tests_len = tests.len();
+    let filtered = filter_tests(config, tests);
+    // Iterator yielding tests that haven't been started yet.
+    let mut fresh_tests = (0..).map(TestId).zip(&filtered);
+
+    let concurrency = get_concurrency();
+    assert!(concurrency > 0);
+    let concurrent_capacity = concurrency.min(filtered.len());
+
+    let mut listener = json::Listener::new();
+    let mut running_tests = HashMap::with_capacity_and_hasher(
+        concurrent_capacity,
+        BuildHasherDefault::<DefaultHasher>::new(),
+    );
+    let mut deadline_queue = deadline::DeadlineQueue::with_capacity(concurrent_capacity);
+
+    let num_filtered_out = tests_len - filtered.len();
+    listener.suite_started(filtered.len(), num_filtered_out);
+
+    // Channel used by test threads to report the test outcome when done.
+    let (completion_tx, completion_rx) = mpsc::channel::<TestCompletion>();
+
+    // Unlike libtest, we don't have a separate code path for concurrency=1.
+    // In that case, the tests will effectively be run serially anyway.
+    loop {
+        // Spawn new test threads, up to the concurrency limit.
+        // FIXME(let_chains): Use a let-chain here when stable in bootstrap.
+        'spawn: while running_tests.len() < concurrency {
+            let Some((id, test)) = fresh_tests.next() else { break 'spawn };
+            listener.test_started(test);
+            deadline_queue.push(id, test);
+            let join_handle = spawn_test_thread(id, test, completion_tx.clone());
+            running_tests.insert(id, RunningTest { test, join_handle });
+        }
+
+        // If all running tests have finished, and there weren't any unstarted
+        // tests to spawn, then we're done.
+        if running_tests.is_empty() {
+            break;
+        }
+
+        let completion = deadline_queue
+            .read_channel_while_checking_deadlines(&completion_rx, |_id, test| {
+                listener.test_timed_out(test);
+            })
+            .expect("receive channel should never be closed early");
+
+        let RunningTest { test, join_handle } = running_tests.remove(&completion.id).unwrap();
+        if let Some(join_handle) = join_handle {
+            join_handle.join().unwrap_or_else(|_| {
+                panic!("thread for `{}` panicked after reporting completion", test.desc.name)
+            });
+        }
+
+        listener.test_finished(test, &completion);
+
+        if completion.outcome.is_failed() && config.fail_fast {
+            // Prevent any other in-flight threads from panicking when they
+            // write to the completion channel.
+            mem::forget(completion_rx);
+            break;
+        }
+    }
+
+    let suite_passed = listener.suite_finished();
+    suite_passed
+}
+
+/// Spawns a thread to run a single test, and returns the thread's join handle.
 ///
-/// Returns `Ok(true)` if all tests passed, or `Ok(false)` if one or more tests failed.
-pub(crate) fn execute_tests(config: &Config, tests: Vec<CollectedTest>) -> io::Result<bool> {
-    let opts = test_opts(config);
-    let tests = tests.into_iter().map(|t| t.into_libtest()).collect::<Vec<_>>();
+/// Returns `None` if the test was ignored, so no thread was spawned.
+fn spawn_test_thread(
+    id: TestId,
+    test: &CollectedTest,
+    completion_tx: mpsc::Sender<TestCompletion>,
+) -> Option<thread::JoinHandle<()>> {
+    if test.desc.ignore && !test.config.run_ignored {
+        completion_tx
+            .send(TestCompletion { id, outcome: TestOutcome::Ignored, stdout: None })
+            .unwrap();
+        return None;
+    }
+
+    let runnable_test = RunnableTest::new(test);
+    let should_panic = test.desc.should_panic;
+    let run_test = move || run_test_inner(id, should_panic, runnable_test, completion_tx);
+
+    let thread_builder = thread::Builder::new().name(test.desc.name.clone());
+    let join_handle = thread_builder.spawn(run_test).unwrap();
+    Some(join_handle)
+}
+
+/// Runs a single test, within the dedicated thread spawned by the caller.
+fn run_test_inner(
+    id: TestId,
+    should_panic: ShouldPanic,
+    runnable_test: RunnableTest,
+    completion_sender: mpsc::Sender<TestCompletion>,
+) {
+    let is_capture = !runnable_test.config.nocapture;
+    let capture_buf = is_capture.then(|| Arc::new(Mutex::new(vec![])));
+
+    if let Some(capture_buf) = &capture_buf {
+        io::set_output_capture(Some(Arc::clone(capture_buf)));
+    }
+
+    let panic_payload = panic::catch_unwind(move || runnable_test.run()).err();
+
+    if is_capture {
+        io::set_output_capture(None);
+    }
+
+    let outcome = match (should_panic, panic_payload) {
+        (ShouldPanic::No, None) | (ShouldPanic::Yes, Some(_)) => TestOutcome::Succeeded,
+        (ShouldPanic::No, Some(_)) => TestOutcome::Failed { message: None },
+        (ShouldPanic::Yes, None) => {
+            TestOutcome::Failed { message: Some("test did not panic as expected") }
+        }
+    };
+    let stdout = capture_buf.map(|mutex| mutex.lock().unwrap_or_else(|e| e.into_inner()).to_vec());
+
+    completion_sender.send(TestCompletion { id, outcome, stdout }).unwrap();
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+struct TestId(usize);
+
+struct RunnableTest {
+    config: Arc<Config>,
+    testpaths: TestPaths,
+    revision: Option<String>,
+}
+
+impl RunnableTest {
+    fn new(test: &CollectedTest) -> Self {
+        let config = Arc::clone(&test.config);
+        let testpaths = test.testpaths.clone();
+        let revision = test.revision.clone();
+        Self { config, testpaths, revision }
+    }
+
+    fn run(&self) {
+        __rust_begin_short_backtrace(|| {
+            crate::runtest::run(
+                Arc::clone(&self.config),
+                &self.testpaths,
+                self.revision.as_deref(),
+            );
+        });
+    }
+}
+
+/// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`.
+#[inline(never)]
+fn __rust_begin_short_backtrace<T, F: FnOnce() -> T>(f: F) -> T {
+    let result = f();
+
+    // prevent this frame from being tail-call optimised away
+    hint::black_box(result)
+}
 
-    test::run_tests_console(&opts, tests)
+struct RunningTest<'a> {
+    test: &'a CollectedTest,
+    join_handle: Option<thread::JoinHandle<()>>,
+}
+
+/// Test completion message sent by individual test threads when their test
+/// finishes (successfully or unsuccessfully).
+struct TestCompletion {
+    id: TestId,
+    outcome: TestOutcome,
+    stdout: Option<Vec<u8>>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+enum TestOutcome {
+    Succeeded,
+    Failed { message: Option<&'static str> },
+    Ignored,
+}
+
+impl TestOutcome {
+    fn is_failed(&self) -> bool {
+        matches!(self, Self::Failed { .. })
+    }
+}
+
+/// Applies command-line arguments for filtering/skipping tests by name.
+///
+/// Adapted from `filter_tests` in libtest.
+///
+/// FIXME(#139660): After the libtest dependency is removed, redesign the whole
+/// filtering system to do a better job of understanding and filtering _paths_,
+/// instead of being tied to libtest's substring/exact matching behaviour.
+fn filter_tests(opts: &Config, tests: Vec<CollectedTest>) -> Vec<CollectedTest> {
+    let mut filtered = tests;
+
+    let matches_filter = |test: &CollectedTest, filter_str: &str| {
+        let test_name = &test.desc.name;
+        if opts.filter_exact { test_name == filter_str } else { test_name.contains(filter_str) }
+    };
+
+    // Remove tests that don't match the test filter
+    if !opts.filters.is_empty() {
+        filtered.retain(|test| opts.filters.iter().any(|filter| matches_filter(test, filter)));
+    }
+
+    // Skip tests that match any of the skip filters
+    if !opts.skip.is_empty() {
+        filtered.retain(|test| !opts.skip.iter().any(|sf| matches_filter(test, sf)));
+    }
+
+    filtered
+}
+
+/// Determines the number of tests to run concurrently.
+///
+/// Copied from `get_concurrency` in libtest.
+///
+/// FIXME(#139660): After the libtest dependency is removed, consider making
+/// bootstrap specify the number of threads on the command-line, instead of
+/// propagating the `RUST_TEST_THREADS` environment variable.
+fn get_concurrency() -> usize {
+    if let Ok(value) = env::var("RUST_TEST_THREADS") {
+        match value.parse::<NonZero<usize>>().ok() {
+            Some(n) => n.get(),
+            _ => panic!("RUST_TEST_THREADS is `{value}`, should be a positive integer."),
+        }
+    } else {
+        thread::available_parallelism().map(|n| n.get()).unwrap_or(1)
+    }
 }
 
 /// Information needed to create a `test::TestDescAndFn`.
@@ -35,45 +264,6 @@ pub(crate) struct CollectedTestDesc {
     pub(crate) should_panic: ShouldPanic,
 }
 
-impl CollectedTest {
-    fn into_libtest(self) -> test::TestDescAndFn {
-        let Self { desc, config, testpaths, revision } = self;
-        let CollectedTestDesc { name, ignore, ignore_message, should_panic } = desc;
-
-        // Libtest requires the ignore message to be a &'static str, so we might
-        // have to leak memory to create it. This is fine, as we only do so once
-        // per test, so the leak won't grow indefinitely.
-        let ignore_message = ignore_message.map(|msg| match msg {
-            Cow::Borrowed(s) => s,
-            Cow::Owned(s) => &*String::leak(s),
-        });
-
-        let desc = test::TestDesc {
-            name: test::DynTestName(name),
-            ignore,
-            ignore_message,
-            source_file: "",
-            start_line: 0,
-            start_col: 0,
-            end_line: 0,
-            end_col: 0,
-            should_panic: should_panic.to_libtest(),
-            compile_fail: false,
-            no_run: false,
-            test_type: test::TestType::Unknown,
-        };
-
-        // This closure is invoked when libtest returns control to compiletest
-        // to execute the test.
-        let testfn = test::DynTestFn(Box::new(move || {
-            crate::runtest::run(config, &testpaths, revision.as_deref());
-            Ok(())
-        }));
-
-        test::TestDescAndFn { desc, testfn }
-    }
-}
-
 /// Whether console output should be colored or not.
 #[derive(Copy, Clone, Default, Debug)]
 pub enum ColorConfig {
@@ -83,16 +273,6 @@ pub enum ColorConfig {
     NeverColor,
 }
 
-impl ColorConfig {
-    fn to_libtest(self) -> test::ColorConfig {
-        match self {
-            Self::AutoColor => test::ColorConfig::AutoColor,
-            Self::AlwaysColor => test::ColorConfig::AlwaysColor,
-            Self::NeverColor => test::ColorConfig::NeverColor,
-        }
-    }
-}
-
 /// Format of the test results output.
 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
 pub enum OutputFormat {
@@ -105,52 +285,9 @@ pub enum OutputFormat {
     Json,
 }
 
-impl OutputFormat {
-    fn to_libtest(self) -> test::OutputFormat {
-        match self {
-            Self::Pretty => test::OutputFormat::Pretty,
-            Self::Terse => test::OutputFormat::Terse,
-            Self::Json => test::OutputFormat::Json,
-        }
-    }
-}
-
 /// Whether test is expected to panic or not.
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 pub(crate) enum ShouldPanic {
     No,
     Yes,
 }
-
-impl ShouldPanic {
-    fn to_libtest(self) -> test::ShouldPanic {
-        match self {
-            Self::No => test::ShouldPanic::No,
-            Self::Yes => test::ShouldPanic::Yes,
-        }
-    }
-}
-
-fn test_opts(config: &Config) -> test::TestOpts {
-    test::TestOpts {
-        exclude_should_panic: false,
-        filters: config.filters.clone(),
-        filter_exact: config.filter_exact,
-        run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
-        format: config.format.to_libtest(),
-        logfile: None,
-        run_tests: true,
-        bench_benchmarks: true,
-        nocapture: config.nocapture,
-        color: config.color.to_libtest(),
-        shuffle: false,
-        shuffle_seed: None,
-        test_threads: None,
-        skip: config.skip.clone(),
-        list: false,
-        options: test::Options::new(),
-        time_options: None,
-        force_run_in_process: false,
-        fail_fast: config.fail_fast,
-    }
-}
diff --git a/src/tools/compiletest/src/executor/deadline.rs b/src/tools/compiletest/src/executor/deadline.rs
new file mode 100644
index 00000000000..83b8591a416
--- /dev/null
+++ b/src/tools/compiletest/src/executor/deadline.rs
@@ -0,0 +1,78 @@
+use std::collections::VecDeque;
+use std::sync::mpsc::{self, RecvError, RecvTimeoutError};
+use std::time::{Duration, Instant};
+
+use crate::executor::{CollectedTest, TestId};
+
+const TEST_WARN_TIMEOUT_S: u64 = 60;
+
+struct DeadlineEntry<'a> {
+    id: TestId,
+    test: &'a CollectedTest,
+    deadline: Instant,
+}
+
+pub(crate) struct DeadlineQueue<'a> {
+    queue: VecDeque<DeadlineEntry<'a>>,
+}
+
+impl<'a> DeadlineQueue<'a> {
+    pub(crate) fn with_capacity(capacity: usize) -> Self {
+        Self { queue: VecDeque::with_capacity(capacity) }
+    }
+
+    pub(crate) fn push(&mut self, id: TestId, test: &'a CollectedTest) {
+        let deadline = Instant::now() + Duration::from_secs(TEST_WARN_TIMEOUT_S);
+        self.queue.push_back(DeadlineEntry { id, test, deadline });
+    }
+
+    /// Equivalent to `rx.read()`, except that if any test exceeds its deadline
+    /// during the wait, the given callback will also be called for that test.
+    pub(crate) fn read_channel_while_checking_deadlines<T>(
+        &mut self,
+        rx: &mpsc::Receiver<T>,
+        mut on_deadline_passed: impl FnMut(TestId, &CollectedTest),
+    ) -> Result<T, RecvError> {
+        loop {
+            let Some(next_deadline) = self.next_deadline() else {
+                // All currently-running tests have already exceeded their
+                // deadline, so do a normal receive.
+                return rx.recv();
+            };
+            let wait_duration = next_deadline.saturating_duration_since(Instant::now());
+
+            let recv_result = rx.recv_timeout(wait_duration);
+            match recv_result {
+                Ok(value) => return Ok(value),
+                Err(RecvTimeoutError::Timeout) => {
+                    // Notify the callback of tests that have exceeded their
+                    // deadline, then loop and do annother channel read.
+                    for DeadlineEntry { id, test, .. } in self.remove_tests_past_deadline() {
+                        on_deadline_passed(id, test);
+                    }
+                }
+                Err(RecvTimeoutError::Disconnected) => return Err(RecvError),
+            }
+        }
+    }
+
+    fn next_deadline(&self) -> Option<Instant> {
+        Some(self.queue.front()?.deadline)
+    }
+
+    fn remove_tests_past_deadline(&mut self) -> Vec<DeadlineEntry<'a>> {
+        let now = Instant::now();
+        let mut timed_out = vec![];
+        while let Some(deadline_entry) = pop_front_if(&mut self.queue, |entry| now < entry.deadline)
+        {
+            timed_out.push(deadline_entry);
+        }
+        timed_out
+    }
+}
+
+/// FIXME(vec_deque_pop_if): Use `VecDeque::pop_front_if` when it is stable in bootstrap.
+fn pop_front_if<T>(queue: &mut VecDeque<T>, predicate: impl FnOnce(&T) -> bool) -> Option<T> {
+    let first = queue.front()?;
+    if predicate(first) { queue.pop_front() } else { None }
+}
diff --git a/src/tools/compiletest/src/executor/json.rs b/src/tools/compiletest/src/executor/json.rs
new file mode 100644
index 00000000000..c74ed81a36b
--- /dev/null
+++ b/src/tools/compiletest/src/executor/json.rs
@@ -0,0 +1,111 @@
+//! Collects statistics and emits suite/test events as JSON messages, using
+//! the same JSON format as libtest's JSON formatter.
+//!
+//! These messages are then parsed by bootstrap, which replaces them with
+//! user-friendly terminal output.
+
+use std::time::Instant;
+
+use serde_json::json;
+
+use crate::executor::{CollectedTest, TestCompletion, TestOutcome};
+
+pub(crate) struct Listener {
+    suite_start: Option<Instant>,
+    passed: usize,
+    failed: usize,
+    ignored: usize,
+    filtered_out: usize,
+}
+
+impl Listener {
+    pub(crate) fn new() -> Self {
+        Self { suite_start: None, passed: 0, failed: 0, ignored: 0, filtered_out: 0 }
+    }
+
+    fn print_message(&self, message: &serde_json::Value) {
+        println!("{message}");
+    }
+
+    fn now(&self) -> Instant {
+        Instant::now()
+    }
+
+    pub(crate) fn suite_started(&mut self, test_count: usize, filtered_out: usize) {
+        self.suite_start = Some(self.now());
+        self.filtered_out = filtered_out;
+        let message = json!({ "type": "suite", "event": "started", "test_count": test_count });
+        self.print_message(&message);
+    }
+
+    pub(crate) fn test_started(&mut self, test: &CollectedTest) {
+        let name = test.desc.name.as_str();
+        let message = json!({ "type": "test", "event": "started", "name": name });
+        self.print_message(&message);
+    }
+
+    pub(crate) fn test_timed_out(&mut self, test: &CollectedTest) {
+        let name = test.desc.name.as_str();
+        let message = json!({ "type": "test", "event": "timeout", "name": name });
+        self.print_message(&message);
+    }
+
+    pub(crate) fn test_finished(&mut self, test: &CollectedTest, completion: &TestCompletion) {
+        let event;
+        let name = test.desc.name.as_str();
+        let mut maybe_message = None;
+        let maybe_stdout = completion.stdout.as_deref().map(String::from_utf8_lossy);
+
+        match completion.outcome {
+            TestOutcome::Succeeded => {
+                self.passed += 1;
+                event = "ok";
+            }
+            TestOutcome::Failed { message } => {
+                self.failed += 1;
+                maybe_message = message;
+                event = "failed";
+            }
+            TestOutcome::Ignored => {
+                self.ignored += 1;
+                maybe_message = test.desc.ignore_message.as_deref();
+                event = "ignored";
+            }
+        };
+
+        // This emits optional fields as `null`, instead of omitting them
+        // completely as libtest does, but bootstrap can parse the result
+        // either way.
+        let json = json!({
+            "type": "test",
+            "event": event,
+            "name": name,
+            "message": maybe_message,
+            "stdout": maybe_stdout,
+        });
+
+        self.print_message(&json);
+    }
+
+    pub(crate) fn suite_finished(&mut self) -> bool {
+        let exec_time = self.suite_start.map(|start| (self.now() - start).as_secs_f64());
+        let suite_passed = self.failed == 0;
+
+        let event = if suite_passed { "ok" } else { "failed" };
+        let message = json!({
+            "type": "suite",
+            "event": event,
+            "passed": self.passed,
+            "failed": self.failed,
+            "ignored": self.ignored,
+            // Compiletest doesn't run any benchmarks, but we still need to set this
+            // field to 0 so that bootstrap's JSON parser can read our message.
+            "measured": 0,
+            "filtered_out": self.filtered_out,
+            "exec_time": exec_time,
+        });
+
+        self.print_message(&message);
+        suite_passed
+    }
+}
diff --git a/src/tools/compiletest/src/executor/libtest.rs b/src/tools/compiletest/src/executor/libtest.rs
new file mode 100644
index 00000000000..032b3f4fa9a
--- /dev/null
+++ b/src/tools/compiletest/src/executor/libtest.rs
@@ -0,0 +1,111 @@
+//! This submodule encapsulates all of the code that actually interacts with
+//! libtest, so that it can be easily removed after the new executor becomes
+//! the default.
+
+use std::borrow::Cow;
+use std::io;
+
+use crate::common::Config;
+use crate::executor::{CollectedTest, CollectedTestDesc, ColorConfig, OutputFormat, ShouldPanic};
+
+/// Delegates to libtest to run the list of collected tests.
+///
+/// Returns `Ok(true)` if all tests passed, or `Ok(false)` if one or more tests failed.
+pub(crate) fn execute_tests(config: &Config, tests: Vec<CollectedTest>) -> io::Result<bool> {
+    let opts = test_opts(config);
+    let tests = tests.into_iter().map(|t| t.into_libtest()).collect::<Vec<_>>();
+
+    test::run_tests_console(&opts, tests)
+}
+
+impl CollectedTest {
+    fn into_libtest(self) -> test::TestDescAndFn {
+        let Self { desc, config, testpaths, revision } = self;
+        let CollectedTestDesc { name, ignore, ignore_message, should_panic } = desc;
+
+        // Libtest requires the ignore message to be a &'static str, so we might
+        // have to leak memory to create it. This is fine, as we only do so once
+        // per test, so the leak won't grow indefinitely.
+        let ignore_message = ignore_message.map(|msg| match msg {
+            Cow::Borrowed(s) => s,
+            Cow::Owned(s) => &*String::leak(s),
+        });
+
+        let desc = test::TestDesc {
+            name: test::DynTestName(name),
+            ignore,
+            ignore_message,
+            source_file: "",
+            start_line: 0,
+            start_col: 0,
+            end_line: 0,
+            end_col: 0,
+            should_panic: should_panic.to_libtest(),
+            compile_fail: false,
+            no_run: false,
+            test_type: test::TestType::Unknown,
+        };
+
+        // This closure is invoked when libtest returns control to compiletest
+        // to execute the test.
+        let testfn = test::DynTestFn(Box::new(move || {
+            crate::runtest::run(config, &testpaths, revision.as_deref());
+            Ok(())
+        }));
+
+        test::TestDescAndFn { desc, testfn }
+    }
+}
+
+impl ColorConfig {
+    fn to_libtest(self) -> test::ColorConfig {
+        match self {
+            Self::AutoColor => test::ColorConfig::AutoColor,
+            Self::AlwaysColor => test::ColorConfig::AlwaysColor,
+            Self::NeverColor => test::ColorConfig::NeverColor,
+        }
+    }
+}
+
+impl OutputFormat {
+    fn to_libtest(self) -> test::OutputFormat {
+        match self {
+            Self::Pretty => test::OutputFormat::Pretty,
+            Self::Terse => test::OutputFormat::Terse,
+            Self::Json => test::OutputFormat::Json,
+        }
+    }
+}
+
+impl ShouldPanic {
+    fn to_libtest(self) -> test::ShouldPanic {
+        match self {
+            Self::No => test::ShouldPanic::No,
+            Self::Yes => test::ShouldPanic::Yes,
+        }
+    }
+}
+
+fn test_opts(config: &Config) -> test::TestOpts {
+    test::TestOpts {
+        exclude_should_panic: false,
+        filters: config.filters.clone(),
+        filter_exact: config.filter_exact,
+        run_ignored: if config.run_ignored { test::RunIgnored::Yes } else { test::RunIgnored::No },
+        format: config.format.to_libtest(),
+        logfile: None,
+        run_tests: true,
+        bench_benchmarks: true,
+        nocapture: config.nocapture,
+        color: config.color.to_libtest(),
+        shuffle: false,
+        shuffle_seed: None,
+        test_threads: None,
+        skip: config.skip.clone(),
+        list: false,
+        options: test::Options::new(),
+        time_options: None,
+        force_run_in_process: false,
+        fail_fast: config.fail_fast,
+    }
+}
diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs
index 3406e8749a1..2b203bb309c 100644
--- a/src/tools/compiletest/src/header.rs
+++ b/src/tools/compiletest/src/header.rs
@@ -1,11 +1,11 @@
-use std::collections::{HashMap, HashSet};
+use std::collections::HashSet;
 use std::env;
 use std::fs::File;
 use std::io::BufReader;
 use std::io::prelude::*;
-use std::path::{Path, PathBuf};
 use std::process::Command;
 
+use camino::{Utf8Path, Utf8PathBuf};
 use semver::Version;
 use tracing::*;
 
@@ -45,12 +45,12 @@ pub struct EarlyProps {
 }
 
 impl EarlyProps {
-    pub fn from_file(config: &Config, testfile: &Path) -> Self {
-        let file = File::open(testfile).expect("open test file to parse earlyprops");
+    pub fn from_file(config: &Config, testfile: &Utf8Path) -> Self {
+        let file = File::open(testfile.as_std_path()).expect("open test file to parse earlyprops");
         Self::from_reader(config, testfile, file)
     }
 
-    pub fn from_reader<R: Read>(config: &Config, testfile: &Path, rdr: R) -> Self {
+    pub fn from_reader<R: Read>(config: &Config, testfile: &Utf8Path, rdr: R) -> Self {
         let mut props = EarlyProps::default();
         let mut poisoned = false;
         iter_header(
@@ -66,7 +66,7 @@ impl EarlyProps {
         );
 
         if poisoned {
-            eprintln!("errors encountered during EarlyProps parsing: {}", testfile.display());
+            eprintln!("errors encountered during EarlyProps parsing: {}", testfile);
             panic!("errors encountered during EarlyProps parsing");
         }
 
@@ -88,7 +88,7 @@ pub struct TestProps {
     pub doc_flags: Vec<String>,
     // If present, the name of a file that this test should match when
     // pretty-printed
-    pub pp_exact: Option<PathBuf>,
+    pub pp_exact: Option<Utf8PathBuf>,
     /// Auxiliary crates that should be built and made available to this test.
     pub(crate) aux: AuxProps,
     // Environment settings to use for compiling
@@ -134,7 +134,7 @@ pub struct TestProps {
     // not set by end-users; rather it is set by the incremental
     // testing harness and used when generating compilation
     // arguments. (In particular, it propagates to the aux-builds.)
-    pub incremental_dir: Option<PathBuf>,
+    pub incremental_dir: Option<Utf8PathBuf>,
     // If `true`, this test will use incremental compilation.
     //
     // This can be set manually with the `incremental` header, or implicitly
@@ -198,7 +198,7 @@ pub struct TestProps {
     /// that don't otherwise want/need `-Z build-std`.
     pub add_core_stubs: bool,
     /// Whether line annotatins are required for the given error kind.
-    pub require_annotations: HashMap<ErrorKind, bool>,
+    pub dont_require_annotations: HashSet<ErrorKind>,
 }
 
 mod directives {
@@ -301,17 +301,16 @@ impl TestProps {
             no_auto_check_cfg: false,
             has_enzyme: false,
             add_core_stubs: false,
-            require_annotations: HashMap::from([
-                (ErrorKind::Help, true),
-                (ErrorKind::Note, true),
-                (ErrorKind::Error, true),
-                (ErrorKind::Warning, true),
-                (ErrorKind::Suggestion, false),
-            ]),
+            dont_require_annotations: Default::default(),
         }
     }
 
-    pub fn from_aux_file(&self, testfile: &Path, revision: Option<&str>, config: &Config) -> Self {
+    pub fn from_aux_file(
+        &self,
+        testfile: &Utf8Path,
+        revision: Option<&str>,
+        config: &Config,
+    ) -> Self {
         let mut props = TestProps::new();
 
         // copy over select properties to the aux build:
@@ -322,10 +321,10 @@ impl TestProps {
         props
     }
 
-    pub fn from_file(testfile: &Path, revision: Option<&str>, config: &Config) -> Self {
+    pub fn from_file(testfile: &Utf8Path, revision: Option<&str>, config: &Config) -> Self {
         let mut props = TestProps::new();
         props.load_from(testfile, revision, config);
-        props.exec_env.push(("RUSTC".to_string(), config.rustc_path.display().to_string()));
+        props.exec_env.push(("RUSTC".to_string(), config.rustc_path.to_string()));
 
         match (props.pass_mode, props.fail_mode) {
             (None, None) if config.mode == Mode::Ui => props.fail_mode = Some(FailMode::Check),
@@ -340,10 +339,10 @@ impl TestProps {
     /// tied to a particular revision `foo` (indicated by writing
     /// `//@[foo]`), then the property is ignored unless `test_revision` is
     /// `Some("foo")`.
-    fn load_from(&mut self, testfile: &Path, test_revision: Option<&str>, config: &Config) {
+    fn load_from(&mut self, testfile: &Utf8Path, test_revision: Option<&str>, config: &Config) {
         let mut has_edition = false;
         if !testfile.is_dir() {
-            let file = File::open(testfile).unwrap();
+            let file = File::open(testfile.as_std_path()).unwrap();
 
             let mut poisoned = false;
 
@@ -593,14 +592,14 @@ impl TestProps {
                     if let Some(err_kind) =
                         config.parse_name_value_directive(ln, DONT_REQUIRE_ANNOTATIONS)
                     {
-                        self.require_annotations
-                            .insert(ErrorKind::expect_from_user_str(&err_kind), false);
+                        self.dont_require_annotations
+                            .insert(ErrorKind::expect_from_user_str(err_kind.trim()));
                     }
                 },
             );
 
             if poisoned {
-                eprintln!("errors encountered during TestProps parsing: {}", testfile.display());
+                eprintln!("errors encountered during TestProps parsing: {}", testfile);
                 panic!("errors encountered during TestProps parsing");
             }
         }
@@ -871,7 +870,7 @@ fn iter_header(
     mode: Mode,
     _suite: &str,
     poisoned: &mut bool,
-    testfile: &Path,
+    testfile: &Utf8Path,
     rdr: impl Read,
     it: &mut dyn FnMut(DirectiveLine<'_>),
 ) {
@@ -923,9 +922,7 @@ fn iter_header(
 
                 eprintln!(
                     "error: detected unknown compiletest test directive `{}` in {}:{}",
-                    directive_line.raw_directive,
-                    testfile.display(),
-                    line_number,
+                    directive_line.raw_directive, testfile, line_number,
                 );
 
                 return;
@@ -937,10 +934,7 @@ fn iter_header(
                 eprintln!(
                     "error: detected trailing compiletest test directive `{}` in {}:{}\n \
                       help: put the trailing directive in it's own line: `//@ {}`",
-                    trailing_directive,
-                    testfile.display(),
-                    line_number,
-                    trailing_directive,
+                    trailing_directive, testfile, line_number, trailing_directive,
                 );
 
                 return;
@@ -952,7 +946,12 @@ fn iter_header(
 }
 
 impl Config {
-    fn parse_and_update_revisions(&self, testfile: &Path, line: &str, existing: &mut Vec<String>) {
+    fn parse_and_update_revisions(
+        &self,
+        testfile: &Utf8Path,
+        line: &str,
+        existing: &mut Vec<String>,
+    ) {
         const FORBIDDEN_REVISION_NAMES: [&str; 2] = [
             // `//@ revisions: true false` Implying `--cfg=true` and `--cfg=false` makes it very
             // weird for the test, since if the test writer wants a cfg of the same revision name
@@ -965,26 +964,19 @@ impl Config {
 
         if let Some(raw) = self.parse_name_value_directive(line, "revisions") {
             if self.mode == Mode::RunMake {
-                panic!("`run-make` tests do not support revisions: {}", testfile.display());
+                panic!("`run-make` tests do not support revisions: {}", testfile);
             }
 
             let mut duplicates: HashSet<_> = existing.iter().cloned().collect();
             for revision in raw.split_whitespace() {
                 if !duplicates.insert(revision.to_string()) {
-                    panic!(
-                        "duplicate revision: `{}` in line `{}`: {}",
-                        revision,
-                        raw,
-                        testfile.display()
-                    );
+                    panic!("duplicate revision: `{}` in line `{}`: {}", revision, raw, testfile);
                 }
 
                 if FORBIDDEN_REVISION_NAMES.contains(&revision) {
                     panic!(
                         "revision name `{revision}` is not permitted: `{}` in line `{}`: {}",
-                        revision,
-                        raw,
-                        testfile.display()
+                        revision, raw, testfile
                     );
                 }
 
@@ -995,8 +987,7 @@ impl Config {
                         "revision name `{revision}` is not permitted in a test suite that uses \
                         `FileCheck` annotations as it is confusing when used as custom `FileCheck` \
                         prefix: `{revision}` in line `{}`: {}",
-                        raw,
-                        testfile.display()
+                        raw, testfile
                     );
                 }
 
@@ -1016,11 +1007,11 @@ impl Config {
         (name.to_owned(), value.to_owned())
     }
 
-    fn parse_pp_exact(&self, line: &str, testfile: &Path) -> Option<PathBuf> {
+    fn parse_pp_exact(&self, line: &str, testfile: &Utf8Path) -> Option<Utf8PathBuf> {
         if let Some(s) = self.parse_name_value_directive(line, "pp-exact") {
-            Some(PathBuf::from(&s))
+            Some(Utf8PathBuf::from(&s))
         } else if self.parse_name_directive(line, "pp-exact") {
-            testfile.file_name().map(PathBuf::from)
+            testfile.file_name().map(Utf8PathBuf::from)
         } else {
             None
         }
@@ -1126,20 +1117,19 @@ fn expand_variables(mut value: String, config: &Config) -> String {
 
     if value.contains(CWD) {
         let cwd = env::current_dir().unwrap();
-        value = value.replace(CWD, &cwd.to_string_lossy());
+        value = value.replace(CWD, &cwd.to_str().unwrap());
     }
 
     if value.contains(SRC_BASE) {
-        value = value.replace(SRC_BASE, &config.src_test_suite_root.to_str().unwrap());
+        value = value.replace(SRC_BASE, &config.src_test_suite_root.as_str());
     }
 
     if value.contains(TEST_SUITE_BUILD_BASE) {
-        value =
-            value.replace(TEST_SUITE_BUILD_BASE, &config.build_test_suite_root.to_str().unwrap());
+        value = value.replace(TEST_SUITE_BUILD_BASE, &config.build_test_suite_root.as_str());
     }
 
     if value.contains(SYSROOT_BASE) {
-        value = value.replace(SYSROOT_BASE, &config.sysroot_base.to_str().unwrap());
+        value = value.replace(SYSROOT_BASE, &config.sysroot_base.as_str());
     }
 
     if value.contains(TARGET_LINKER) {
@@ -1152,9 +1142,9 @@ fn expand_variables(mut value: String, config: &Config) -> String {
 
     if value.contains(RUST_SRC_BASE) {
         let src_base = config.sysroot_base.join("lib/rustlib/src/rust");
-        src_base.try_exists().expect(&*format!("{} should exists", src_base.display()));
-        let src_base = src_base.read_link().unwrap_or(src_base);
-        value = value.replace(RUST_SRC_BASE, &src_base.to_string_lossy());
+        src_base.try_exists().expect(&*format!("{} should exists", src_base));
+        let src_base = src_base.read_link_utf8().unwrap_or(src_base);
+        value = value.replace(RUST_SRC_BASE, &src_base.as_str());
     }
 
     value
@@ -1257,14 +1247,14 @@ pub fn llvm_has_libzstd(config: &Config) -> bool {
     // contains a path to that static lib, and that it exists.
     //
     // See compiler/rustc_llvm/build.rs for more details and similar expectations.
-    fn is_zstd_in_config(llvm_bin_dir: &Path) -> Option<()> {
+    fn is_zstd_in_config(llvm_bin_dir: &Utf8Path) -> Option<()> {
         let llvm_config_path = llvm_bin_dir.join("llvm-config");
         let output = Command::new(llvm_config_path).arg("--system-libs").output().ok()?;
         assert!(output.status.success(), "running llvm-config --system-libs failed");
 
         let libs = String::from_utf8(output.stdout).ok()?;
         for lib in libs.split_whitespace() {
-            if lib.ends_with("libzstd.a") && Path::new(lib).exists() {
+            if lib.ends_with("libzstd.a") && Utf8Path::new(lib).exists() {
                 return Some(());
             }
         }
@@ -1282,7 +1272,7 @@ pub fn llvm_has_libzstd(config: &Config) -> bool {
     // `lld` supports it. If not, an error will be emitted: "LLVM was not built with
     // LLVM_ENABLE_ZSTD or did not find zstd at build time".
     #[cfg(unix)]
-    fn is_lld_built_with_zstd(llvm_bin_dir: &Path) -> Option<()> {
+    fn is_lld_built_with_zstd(llvm_bin_dir: &Utf8Path) -> Option<()> {
         let lld_path = llvm_bin_dir.join("lld");
         if lld_path.exists() {
             // We can't call `lld` as-is, it expects to be invoked by a compiler driver using a
@@ -1318,7 +1308,7 @@ pub fn llvm_has_libzstd(config: &Config) -> bool {
     }
 
     #[cfg(not(unix))]
-    fn is_lld_built_with_zstd(_llvm_bin_dir: &Path) -> Option<()> {
+    fn is_lld_built_with_zstd(_llvm_bin_dir: &Utf8Path) -> Option<()> {
         None
     }
 
@@ -1385,7 +1375,7 @@ pub(crate) fn make_test_description<R: Read>(
     config: &Config,
     cache: &HeadersCache,
     name: String,
-    path: &Path,
+    path: &Utf8Path,
     src: R,
     test_revision: Option<&str>,
     poisoned: &mut bool,
@@ -1416,7 +1406,7 @@ pub(crate) fn make_test_description<R: Read>(
                             ignore_message = Some(reason.into());
                         }
                         IgnoreDecision::Error { message } => {
-                            eprintln!("error: {}:{line_number}: {message}", path.display());
+                            eprintln!("error: {}:{line_number}: {message}", path);
                             *poisoned = true;
                             return;
                         }
@@ -1446,7 +1436,7 @@ pub(crate) fn make_test_description<R: Read>(
     );
 
     if local_poisoned {
-        eprintln!("errors encountered when trying to make test description: {}", path.display());
+        eprintln!("errors encountered when trying to make test description: {}", path);
         panic!("errors encountered when trying to make test description");
     }
 
@@ -1555,7 +1545,7 @@ fn ignore_lldb(config: &Config, line: &str) -> IgnoreDecision {
     IgnoreDecision::Continue
 }
 
-fn ignore_llvm(config: &Config, path: &Path, line: &str) -> IgnoreDecision {
+fn ignore_llvm(config: &Config, path: &Utf8Path, line: &str) -> IgnoreDecision {
     if let Some(needed_components) =
         config.parse_name_value_directive(line, "needs-llvm-components")
     {
@@ -1567,8 +1557,7 @@ fn ignore_llvm(config: &Config, path: &Path, line: &str) -> IgnoreDecision {
             if env::var_os("COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS").is_some() {
                 panic!(
                     "missing LLVM component {}, and COMPILETEST_REQUIRE_ALL_LLVM_COMPONENTS is set: {}",
-                    missing_component,
-                    path.display()
+                    missing_component, path
                 );
             }
             return IgnoreDecision::Ignore {
diff --git a/src/tools/compiletest/src/header/tests.rs b/src/tools/compiletest/src/header/tests.rs
index f3461f3c244..3a8c3748de9 100644
--- a/src/tools/compiletest/src/header/tests.rs
+++ b/src/tools/compiletest/src/header/tests.rs
@@ -1,6 +1,6 @@
 use std::io::Read;
-use std::path::Path;
 
+use camino::Utf8Path;
 use semver::Version;
 
 use super::{
@@ -13,7 +13,7 @@ use crate::executor::{CollectedTestDesc, ShouldPanic};
 fn make_test_description<R: Read>(
     config: &Config,
     name: String,
-    path: &Path,
+    path: &Utf8Path,
     src: R,
     revision: Option<&str>,
 ) -> CollectedTestDesc {
@@ -230,12 +230,12 @@ fn cfg() -> ConfigBuilder {
 
 fn parse_rs(config: &Config, contents: &str) -> EarlyProps {
     let bytes = contents.as_bytes();
-    EarlyProps::from_reader(config, Path::new("a.rs"), bytes)
+    EarlyProps::from_reader(config, Utf8Path::new("a.rs"), bytes)
 }
 
 fn check_ignore(config: &Config, contents: &str) -> bool {
     let tn = String::new();
-    let p = Path::new("a.rs");
+    let p = Utf8Path::new("a.rs");
     let d = make_test_description(&config, tn, p, std::io::Cursor::new(contents), None);
     d.ignore
 }
@@ -244,7 +244,7 @@ fn check_ignore(config: &Config, contents: &str) -> bool {
 fn should_fail() {
     let config: Config = cfg().build();
     let tn = String::new();
-    let p = Path::new("a.rs");
+    let p = Utf8Path::new("a.rs");
 
     let d = make_test_description(&config, tn.clone(), p, std::io::Cursor::new(""), None);
     assert_eq!(d.should_panic, ShouldPanic::No);
@@ -784,7 +784,7 @@ fn threads_support() {
     }
 }
 
-fn run_path(poisoned: &mut bool, path: &Path, buf: &[u8]) {
+fn run_path(poisoned: &mut bool, path: &Utf8Path, buf: &[u8]) {
     let rdr = std::io::Cursor::new(&buf);
     iter_header(Mode::Ui, "ui", poisoned, path, rdr, &mut |_| {});
 }
@@ -794,7 +794,7 @@ fn test_unknown_directive_check() {
     let mut poisoned = false;
     run_path(
         &mut poisoned,
-        Path::new("a.rs"),
+        Utf8Path::new("a.rs"),
         include_bytes!("./test-auxillary/unknown_directive.rs"),
     );
     assert!(poisoned);
@@ -805,7 +805,7 @@ fn test_known_directive_check_no_error() {
     let mut poisoned = false;
     run_path(
         &mut poisoned,
-        Path::new("a.rs"),
+        Utf8Path::new("a.rs"),
         include_bytes!("./test-auxillary/known_directive.rs"),
     );
     assert!(!poisoned);
@@ -816,7 +816,7 @@ fn test_error_annotation_no_error() {
     let mut poisoned = false;
     run_path(
         &mut poisoned,
-        Path::new("a.rs"),
+        Utf8Path::new("a.rs"),
         include_bytes!("./test-auxillary/error_annotation.rs"),
     );
     assert!(!poisoned);
@@ -827,7 +827,7 @@ fn test_non_rs_unknown_directive_not_checked() {
     let mut poisoned = false;
     run_path(
         &mut poisoned,
-        Path::new("a.Makefile"),
+        Utf8Path::new("a.Makefile"),
         include_bytes!("./test-auxillary/not_rs.Makefile"),
     );
     assert!(!poisoned);
@@ -836,21 +836,21 @@ fn test_non_rs_unknown_directive_not_checked() {
 #[test]
 fn test_trailing_directive() {
     let mut poisoned = false;
-    run_path(&mut poisoned, Path::new("a.rs"), b"//@ only-x86 only-arm");
+    run_path(&mut poisoned, Utf8Path::new("a.rs"), b"//@ only-x86 only-arm");
     assert!(poisoned);
 }
 
 #[test]
 fn test_trailing_directive_with_comment() {
     let mut poisoned = false;
-    run_path(&mut poisoned, Path::new("a.rs"), b"//@ only-x86   only-arm with comment");
+    run_path(&mut poisoned, Utf8Path::new("a.rs"), b"//@ only-x86   only-arm with comment");
     assert!(poisoned);
 }
 
 #[test]
 fn test_not_trailing_directive() {
     let mut poisoned = false;
-    run_path(&mut poisoned, Path::new("a.rs"), b"//@ revisions: incremental");
+    run_path(&mut poisoned, Utf8Path::new("a.rs"), b"//@ revisions: incremental");
     assert!(!poisoned);
 }
 
diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs
index 720663b30ef..4bbd4ab4790 100644
--- a/src/tools/compiletest/src/lib.rs
+++ b/src/tools/compiletest/src/lib.rs
@@ -1,7 +1,8 @@
 #![crate_name = "compiletest"]
-// The `test` crate is the only unstable feature
-// allowed here, just to share similar code.
+// Needed by the libtest-based test executor.
 #![feature(test)]
+// Needed by the "new" test executor that does not depend on libtest.
+#![feature(internal_output_capture)]
 
 extern crate test;
 
@@ -22,16 +23,15 @@ pub mod util;
 
 use core::panic;
 use std::collections::HashSet;
-use std::ffi::OsString;
 use std::fmt::Write;
 use std::io::{self, ErrorKind};
-use std::path::{Path, PathBuf};
 use std::process::{Command, Stdio};
 use std::sync::{Arc, OnceLock};
 use std::time::SystemTime;
 use std::{env, fs, vec};
 
 use build_helper::git::{get_git_modified_files, get_git_untracked_files};
+use camino::{Utf8Path, Utf8PathBuf};
 use getopts::Options;
 use tracing::*;
 use walkdir::WalkDir;
@@ -203,6 +203,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
             "COMMAND",
         )
         .reqopt("", "minicore-path", "path to minicore aux library", "PATH")
+        .optflag("n", "new-executor", "enables the new test executor instead of using libtest")
         .optopt(
             "",
             "debugger",
@@ -230,15 +231,19 @@ pub fn parse_config(args: Vec<String>) -> Config {
         panic!()
     }
 
-    fn opt_path(m: &getopts::Matches, nm: &str) -> PathBuf {
-        match m.opt_str(nm) {
-            Some(s) => PathBuf::from(&s),
-            None => panic!("no option (=path) found for {}", nm),
+    fn make_absolute(path: Utf8PathBuf) -> Utf8PathBuf {
+        if path.is_relative() {
+            Utf8PathBuf::try_from(env::current_dir().unwrap()).unwrap().join(path)
+        } else {
+            path
         }
     }
 
-    fn make_absolute(path: PathBuf) -> PathBuf {
-        if path.is_relative() { env::current_dir().unwrap().join(path) } else { path }
+    fn opt_path(m: &getopts::Matches, nm: &str) -> Utf8PathBuf {
+        match m.opt_str(nm) {
+            Some(s) => Utf8PathBuf::from(&s),
+            None => panic!("no option (=path) found for {}", nm),
+        }
     }
 
     let target = opt_str2(matches.opt_str("target"));
@@ -279,12 +284,12 @@ pub fn parse_config(args: Vec<String>) -> Config {
             .free
             .iter()
             .map(|f| {
-                let path = Path::new(f);
+                let path = Utf8Path::new(f);
                 let mut iter = path.iter().skip(1);
 
                 // We skip the test folder and check if the user passed `rmake.rs`.
                 if iter.next().is_some_and(|s| s == "rmake.rs") && iter.next().is_none() {
-                    path.parent().unwrap().to_str().unwrap().to_string()
+                    path.parent().unwrap().to_string()
                 } else {
                     f.to_string()
                 }
@@ -316,8 +321,8 @@ pub fn parse_config(args: Vec<String>) -> Config {
     assert!(
         src_test_suite_root.starts_with(&src_root),
         "`src-root` must be a parent of `src-test-suite-root`: `src-root`=`{}`, `src-test-suite-root` = `{}`",
-        src_root.display(),
-        src_test_suite_root.display()
+        src_root,
+        src_test_suite_root
     );
 
     let build_root = opt_path(matches, "build-root");
@@ -332,16 +337,16 @@ pub fn parse_config(args: Vec<String>) -> Config {
         compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
         run_lib_path: make_absolute(opt_path(matches, "run-lib-path")),
         rustc_path: opt_path(matches, "rustc-path"),
-        cargo_path: matches.opt_str("cargo-path").map(PathBuf::from),
-        stage0_rustc_path: matches.opt_str("stage0-rustc-path").map(PathBuf::from),
-        rustdoc_path: matches.opt_str("rustdoc-path").map(PathBuf::from),
-        coverage_dump_path: matches.opt_str("coverage-dump-path").map(PathBuf::from),
+        cargo_path: matches.opt_str("cargo-path").map(Utf8PathBuf::from),
+        stage0_rustc_path: matches.opt_str("stage0-rustc-path").map(Utf8PathBuf::from),
+        rustdoc_path: matches.opt_str("rustdoc-path").map(Utf8PathBuf::from),
+        coverage_dump_path: matches.opt_str("coverage-dump-path").map(Utf8PathBuf::from),
         python: matches.opt_str("python").unwrap(),
         jsondocck_path: matches.opt_str("jsondocck-path"),
         jsondoclint_path: matches.opt_str("jsondoclint-path"),
         run_clang_based_tests_with: matches.opt_str("run-clang-based-tests-with"),
-        llvm_filecheck: matches.opt_str("llvm-filecheck").map(PathBuf::from),
-        llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(PathBuf::from),
+        llvm_filecheck: matches.opt_str("llvm-filecheck").map(Utf8PathBuf::from),
+        llvm_bin_dir: matches.opt_str("llvm-bin-dir").map(Utf8PathBuf::from),
 
         src_root,
         src_test_suite_root,
@@ -407,7 +412,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
         },
         only_modified: matches.opt_present("only-modified"),
         color,
-        remote_test_client: matches.opt_str("remote-test-client").map(PathBuf::from),
+        remote_test_client: matches.opt_str("remote-test-client").map(Utf8PathBuf::from),
         compare_mode,
         rustfix_coverage: matches.opt_present("rustfix-coverage"),
         has_html_tidy,
@@ -444,25 +449,27 @@ pub fn parse_config(args: Vec<String>) -> Config {
         diff_command: matches.opt_str("compiletest-diff-tool"),
 
         minicore_path: opt_path(matches, "minicore-path"),
+
+        new_executor: matches.opt_present("new-executor"),
     }
 }
 
 pub fn log_config(config: &Config) {
     let c = config;
     logv(c, "configuration:".to_string());
-    logv(c, format!("compile_lib_path: {:?}", config.compile_lib_path));
-    logv(c, format!("run_lib_path: {:?}", config.run_lib_path));
-    logv(c, format!("rustc_path: {:?}", config.rustc_path.display()));
+    logv(c, format!("compile_lib_path: {}", config.compile_lib_path));
+    logv(c, format!("run_lib_path: {}", config.run_lib_path));
+    logv(c, format!("rustc_path: {}", config.rustc_path));
     logv(c, format!("cargo_path: {:?}", config.cargo_path));
     logv(c, format!("rustdoc_path: {:?}", config.rustdoc_path));
 
-    logv(c, format!("src_root: {}", config.src_root.display()));
-    logv(c, format!("src_test_suite_root: {}", config.src_test_suite_root.display()));
+    logv(c, format!("src_root: {}", config.src_root));
+    logv(c, format!("src_test_suite_root: {}", config.src_test_suite_root));
 
-    logv(c, format!("build_root: {}", config.build_root.display()));
-    logv(c, format!("build_test_suite_root: {}", config.build_test_suite_root.display()));
+    logv(c, format!("build_root: {}", config.build_root));
+    logv(c, format!("build_test_suite_root: {}", config.build_test_suite_root));
 
-    logv(c, format!("sysroot_base: {}", config.sysroot_base.display()));
+    logv(c, format!("sysroot_base: {}", config.sysroot_base));
 
     logv(c, format!("stage: {}", config.stage));
     logv(c, format!("stage_id: {}", config.stage_id));
@@ -480,16 +487,16 @@ pub fn log_config(config: &Config) {
     logv(c, format!("target-rustcflags: {:?}", config.target_rustcflags));
     logv(c, format!("target: {}", config.target));
     logv(c, format!("host: {}", config.host));
-    logv(c, format!("android-cross-path: {:?}", config.android_cross_path.display()));
-    logv(c, format!("adb_path: {:?}", config.adb_path));
-    logv(c, format!("adb_test_dir: {:?}", config.adb_test_dir));
+    logv(c, format!("android-cross-path: {}", config.android_cross_path));
+    logv(c, format!("adb_path: {}", config.adb_path));
+    logv(c, format!("adb_test_dir: {}", config.adb_test_dir));
     logv(c, format!("adb_device_status: {}", config.adb_device_status));
     logv(c, format!("ar: {}", config.ar));
     logv(c, format!("target-linker: {:?}", config.target_linker));
     logv(c, format!("host-linker: {:?}", config.host_linker));
     logv(c, format!("verbose: {}", config.verbose));
     logv(c, format!("format: {:?}", config.format));
-    logv(c, format!("minicore_path: {:?}", config.minicore_path.display()));
+    logv(c, format!("minicore_path: {}", config.minicore_path));
     logv(c, "\n".to_string());
 }
 
@@ -517,7 +524,7 @@ pub fn run_tests(config: Arc<Config>) {
         coverage_file_path.push("rustfix_missing_coverage.txt");
         if coverage_file_path.exists() {
             if let Err(e) = fs::remove_file(&coverage_file_path) {
-                panic!("Could not delete {} due to {}", coverage_file_path.display(), e)
+                panic!("Could not delete {} due to {}", coverage_file_path, e)
             }
         }
     }
@@ -567,10 +574,14 @@ pub fn run_tests(config: Arc<Config>) {
 
     tests.sort_by(|a, b| Ord::cmp(&a.desc.name, &b.desc.name));
 
-    // Delegate to libtest to filter and run the big list of structures created
-    // during test discovery. When libtest decides to run a test, it will
-    // return control to compiletest by invoking a closure.
-    let res = crate::executor::execute_tests(&config, tests);
+    // Delegate to the executor to filter and run the big list of test structures
+    // created during test discovery. When the executor decides to run a test,
+    // it will return control to the rest of compiletest by calling `runtest::run`.
+    let res = if config.new_executor {
+        Ok(executor::run_tests(&config, tests))
+    } else {
+        crate::executor::libtest::execute_tests(&config, tests)
+    };
 
     // Check the outcome reported by libtest.
     match res {
@@ -619,13 +630,13 @@ struct TestCollectorCx {
     config: Arc<Config>,
     cache: HeadersCache,
     common_inputs_stamp: Stamp,
-    modified_tests: Vec<PathBuf>,
+    modified_tests: Vec<Utf8PathBuf>,
 }
 
 /// Mutable state used during test collection.
 struct TestCollector {
     tests: Vec<CollectedTest>,
-    found_path_stems: HashSet<PathBuf>,
+    found_path_stems: HashSet<Utf8PathBuf>,
     poisoned: bool,
 }
 
@@ -635,14 +646,13 @@ struct TestCollector {
 /// regardless of whether any filters/tests were specified on the command-line,
 /// because filtering is handled later by libtest.
 pub(crate) fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest> {
-    debug!("making tests from {}", config.src_test_suite_root.display());
+    debug!("making tests from {}", config.src_test_suite_root);
     let common_inputs_stamp = common_inputs_stamp(&config);
     let modified_tests =
         modified_tests(&config, &config.src_test_suite_root).unwrap_or_else(|err| {
             panic!(
                 "modified_tests got error from dir: {}, error: {}",
-                config.src_test_suite_root.display(),
-                err
+                config.src_test_suite_root, err
             )
         });
     let cache = HeadersCache::load(&config);
@@ -651,12 +661,9 @@ pub(crate) fn collect_and_make_tests(config: Arc<Config>) -> Vec<CollectedTest>
     let mut collector =
         TestCollector { tests: vec![], found_path_stems: HashSet::new(), poisoned: false };
 
-    collect_tests_from_dir(&cx, &mut collector, &cx.config.src_test_suite_root, Path::new(""))
+    collect_tests_from_dir(&cx, &mut collector, &cx.config.src_test_suite_root, Utf8Path::new(""))
         .unwrap_or_else(|reason| {
-            panic!(
-                "Could not read tests from {}: {reason}",
-                cx.config.src_test_suite_root.display()
-            )
+            panic!("Could not read tests from {}: {reason}", cx.config.src_test_suite_root)
         });
 
     let TestCollector { tests, found_path_stems, poisoned } = collector;
@@ -725,24 +732,29 @@ fn common_inputs_stamp(config: &Config) -> Stamp {
 /// the `--only-modified` flag is in use.
 ///
 /// (Might be inaccurate in some cases.)
-fn modified_tests(config: &Config, dir: &Path) -> Result<Vec<PathBuf>, String> {
+fn modified_tests(config: &Config, dir: &Utf8Path) -> Result<Vec<Utf8PathBuf>, String> {
     // If `--only-modified` wasn't passed, the list of modified tests won't be
     // used for anything, so avoid some work and just return an empty list.
     if !config.only_modified {
         return Ok(vec![]);
     }
 
-    let files =
-        get_git_modified_files(&config.git_config(), Some(dir), &vec!["rs", "stderr", "fixed"])?;
+    let files = get_git_modified_files(
+        &config.git_config(),
+        Some(dir.as_std_path()),
+        &vec!["rs", "stderr", "fixed"],
+    )?;
     // Add new test cases to the list, it will be convenient in daily development.
     let untracked_files = get_git_untracked_files(&config.git_config(), None)?.unwrap_or(vec![]);
 
     let all_paths = [&files[..], &untracked_files[..]].concat();
     let full_paths = {
-        let mut full_paths: Vec<PathBuf> = all_paths
+        let mut full_paths: Vec<Utf8PathBuf> = all_paths
             .into_iter()
-            .map(|f| PathBuf::from(f).with_extension("").with_extension("rs"))
-            .filter_map(|f| if Path::new(&f).exists() { f.canonicalize().ok() } else { None })
+            .map(|f| Utf8PathBuf::from(f).with_extension("").with_extension("rs"))
+            .filter_map(
+                |f| if Utf8Path::new(&f).exists() { f.canonicalize_utf8().ok() } else { None },
+            )
             .collect();
         full_paths.dedup();
         full_paths.sort_unstable();
@@ -756,8 +768,8 @@ fn modified_tests(config: &Config, dir: &Path) -> Result<Vec<PathBuf>, String> {
 fn collect_tests_from_dir(
     cx: &TestCollectorCx,
     collector: &mut TestCollector,
-    dir: &Path,
-    relative_dir_path: &Path,
+    dir: &Utf8Path,
+    relative_dir_path: &Utf8Path,
 ) -> io::Result<()> {
     // Ignore directories that contain a file named `compiletest-ignore-dir`.
     if dir.join("compiletest-ignore-dir").exists() {
@@ -790,16 +802,16 @@ fn collect_tests_from_dir(
     // subdirectories we find, except for `auxiliary` directories.
     // FIXME: this walks full tests tree, even if we have something to ignore
     // use walkdir/ignore like in tidy?
-    for file in fs::read_dir(dir)? {
+    for file in fs::read_dir(dir.as_std_path())? {
         let file = file?;
-        let file_path = file.path();
-        let file_name = file.file_name();
+        let file_path = Utf8PathBuf::try_from(file.path()).unwrap();
+        let file_name = file_path.file_name().unwrap();
 
-        if is_test(&file_name)
+        if is_test(file_name)
             && (!cx.config.only_modified || cx.modified_tests.contains(&file_path))
         {
             // We found a test file, so create the corresponding libtest structures.
-            debug!("found test file: {:?}", file_path.display());
+            debug!(%file_path, "found test file");
 
             // Record the stem of the test file, to check for overlaps later.
             let rel_test_path = relative_dir_path.join(file_path.file_stem().unwrap());
@@ -810,22 +822,20 @@ fn collect_tests_from_dir(
             make_test(cx, collector, &paths);
         } else if file_path.is_dir() {
             // Recurse to find more tests in a subdirectory.
-            let relative_file_path = relative_dir_path.join(file.file_name());
-            if &file_name != "auxiliary" {
-                debug!("found directory: {:?}", file_path.display());
+            let relative_file_path = relative_dir_path.join(file_name);
+            if file_name != "auxiliary" {
+                debug!(%file_path, "found directory");
                 collect_tests_from_dir(cx, collector, &file_path, &relative_file_path)?;
             }
         } else {
-            debug!("found other file/directory: {:?}", file_path.display());
+            debug!(%file_path, "found other file/directory");
         }
     }
     Ok(())
 }
 
 /// Returns true if `file_name` looks like a proper test file name.
-pub fn is_test(file_name: &OsString) -> bool {
-    let file_name = file_name.to_str().unwrap();
-
+pub fn is_test(file_name: &str) -> bool {
     if !file_name.ends_with(".rs") {
         return false;
     }
@@ -844,7 +854,7 @@ fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &Te
     let test_path = if cx.config.mode == Mode::RunMake {
         testpaths.file.join("rmake.rs")
     } else {
-        PathBuf::from(&testpaths.file)
+        testpaths.file.clone()
     };
 
     // Scan the test file to discover its revisions, if any.
@@ -899,7 +909,7 @@ fn make_test(cx: &TestCollectorCx, collector: &mut TestCollector, testpaths: &Te
 
 /// The path of the `stamp` file that gets created or updated whenever a
 /// particular test completes successfully.
-fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> PathBuf {
+fn stamp_file_path(config: &Config, testpaths: &TestPaths, revision: Option<&str>) -> Utf8PathBuf {
     output_base_dir(config, testpaths, revision).join("stamp")
 }
 
@@ -912,7 +922,7 @@ fn files_related_to_test(
     testpaths: &TestPaths,
     props: &EarlyProps,
     revision: Option<&str>,
-) -> Vec<PathBuf> {
+) -> Vec<Utf8PathBuf> {
     let mut related = vec![];
 
     if testpaths.file.is_dir() {
@@ -920,7 +930,7 @@ fn files_related_to_test(
         for entry in WalkDir::new(&testpaths.file) {
             let path = entry.unwrap().into_path();
             if path.is_file() {
-                related.push(path);
+                related.push(Utf8PathBuf::try_from(path).unwrap());
             }
         }
     } else {
@@ -991,7 +1001,7 @@ struct Stamp {
 
 impl Stamp {
     /// Creates a timestamp holding the last-modified time of the specified file.
-    fn from_path(path: &Path) -> Self {
+    fn from_path(path: &Utf8Path) -> Self {
         let mut stamp = Stamp { time: SystemTime::UNIX_EPOCH };
         stamp.add_path(path);
         stamp
@@ -999,8 +1009,8 @@ impl Stamp {
 
     /// Updates this timestamp to the last-modified time of the specified file,
     /// if it is later than the currently-stored timestamp.
-    fn add_path(&mut self, path: &Path) {
-        let modified = fs::metadata(path)
+    fn add_path(&mut self, path: &Utf8Path) {
+        let modified = fs::metadata(path.as_std_path())
             .and_then(|metadata| metadata.modified())
             .unwrap_or(SystemTime::UNIX_EPOCH);
         self.time = self.time.max(modified);
@@ -1009,7 +1019,8 @@ impl Stamp {
     /// Updates this timestamp to the most recent last-modified time of all files
     /// recursively contained in the given directory, if it is later than the
     /// currently-stored timestamp.
-    fn add_dir(&mut self, path: &Path) {
+    fn add_dir(&mut self, path: &Utf8Path) {
+        let path = path.as_std_path();
         for entry in WalkDir::new(path) {
             let entry = entry.unwrap();
             if entry.file_type().is_file() {
@@ -1042,7 +1053,7 @@ fn make_test_name(config: &Config, testpaths: &TestPaths, revision: Option<&str>
         config.mode,
         debugger,
         mode_suffix,
-        path.display(),
+        path,
         revision.map_or("".to_string(), |rev| format!("#{}", rev))
     )
 }
@@ -1064,7 +1075,7 @@ fn make_test_name(config: &Config, testpaths: &TestPaths, revision: Option<&str>
 /// To avoid problems, we forbid test names from overlapping in this way.
 ///
 /// See <https://github.com/rust-lang/rust/pull/109509> for more context.
-fn check_for_overlapping_test_paths(found_path_stems: &HashSet<PathBuf>) {
+fn check_for_overlapping_test_paths(found_path_stems: &HashSet<Utf8PathBuf>) {
     let mut collisions = Vec::new();
     for path in found_path_stems {
         for ancestor in path.ancestors().skip(1) {
@@ -1077,7 +1088,7 @@ fn check_for_overlapping_test_paths(found_path_stems: &HashSet<PathBuf>) {
         collisions.sort();
         let collisions: String = collisions
             .into_iter()
-            .map(|(path, check_parent)| format!("test {path:?} clashes with {check_parent:?}\n"))
+            .map(|(path, check_parent)| format!("test {path} clashes with {check_parent}\n"))
             .collect();
         panic!(
             "{collisions}\n\
diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs
index 208d32833c9..eb298060b2e 100644
--- a/src/tools/compiletest/src/runtest.rs
+++ b/src/tools/compiletest/src/runtest.rs
@@ -1,15 +1,15 @@
 use std::borrow::Cow;
 use std::collections::{HashMap, HashSet};
-use std::ffi::{OsStr, OsString};
+use std::ffi::OsString;
 use std::fs::{self, File, create_dir_all};
 use std::hash::{DefaultHasher, Hash, Hasher};
 use std::io::prelude::*;
 use std::io::{self, BufReader};
-use std::path::{Path, PathBuf};
 use std::process::{Child, Command, ExitStatus, Output, Stdio};
 use std::sync::Arc;
 use std::{env, iter, str};
 
+use camino::{Utf8Path, Utf8PathBuf};
 use colored::Colorize;
 use regex::{Captures, Regex};
 use tracing::*;
@@ -22,10 +22,10 @@ use crate::common::{
     output_base_dir, output_base_name, output_testname_unique,
 };
 use crate::compute_diff::{DiffLine, make_diff, write_diff, write_filtered_diff};
-use crate::errors::{self, Error, ErrorKind};
+use crate::errors::{Error, ErrorKind};
 use crate::header::TestProps;
 use crate::read2::{Truncated, read2_abbreviated};
-use crate::util::{PathBufExt, add_dylib_path, logv, static_regex};
+use crate::util::{Utf8PathBufExt, add_dylib_path, logv, static_regex};
 use crate::{ColorConfig, json, stamp_file_path};
 
 mod debugger;
@@ -131,7 +131,7 @@ pub fn run(config: Arc<Config>, testpaths: &TestPaths, revision: Option<&str>) {
         // We're going to be dumping a lot of info. Start on a new line.
         print!("\n\n");
     }
-    debug!("running {:?}", testpaths.file.display());
+    debug!("running {}", testpaths.file);
     let mut props = TestProps::from_file(&testpaths.file, revision, &config);
 
     // For non-incremental (i.e. regular UI) tests, the incremental directory
@@ -144,7 +144,7 @@ pub fn run(config: Arc<Config>, testpaths: &TestPaths, revision: Option<&str>) {
     let cx = TestCx { config: &config, props: &props, testpaths, revision };
 
     if let Err(e) = create_dir_all(&cx.output_base_dir()) {
-        panic!("failed to create output base directory {}: {e}", cx.output_base_dir().display());
+        panic!("failed to create output base directory {}: {e}", cx.output_base_dir());
     }
 
     if props.incremental {
@@ -207,7 +207,8 @@ pub fn compute_stamp_hash(config: &Config) -> String {
     format!("{:x}", hash.finish())
 }
 
-fn remove_and_create_dir_all(path: &Path) {
+fn remove_and_create_dir_all(path: &Utf8Path) {
+    let path = path.as_std_path();
     let _ = fs::remove_dir_all(path);
     fs::create_dir_all(path).unwrap();
 }
@@ -423,7 +424,7 @@ impl<'test> TestCx<'test> {
         let aux_dir = self.aux_output_dir_name();
         let input: &str = match read_from {
             ReadFrom::Stdin(_) => "-",
-            ReadFrom::Path => self.testpaths.file.to_str().unwrap(),
+            ReadFrom::Path => self.testpaths.file.as_str(),
         };
 
         let mut rustc = Command::new(&self.config.rustc_path);
@@ -590,10 +591,7 @@ impl<'test> TestCx<'test> {
                 // FIXME(#65865)
                 return;
             } else {
-                self.fatal(&format!(
-                    "no error pattern specified in {:?}",
-                    self.testpaths.file.display()
-                ));
+                self.fatal(&format!("no error pattern specified in {}", self.testpaths.file));
             }
         }
 
@@ -675,7 +673,7 @@ impl<'test> TestCx<'test> {
         }
     }
 
-    fn check_expected_errors(&self, expected_errors: Vec<errors::Error>, proc_res: &ProcRes) {
+    fn check_expected_errors(&self, expected_errors: Vec<Error>, proc_res: &ProcRes) {
         debug!(
             "check_expected_errors: expected_errors={:?} proc_res.status={:?}",
             expected_errors, proc_res.status
@@ -697,21 +695,25 @@ impl<'test> TestCx<'test> {
         }
 
         // On Windows, translate all '\' path separators to '/'
-        let file_name = format!("{}", self.testpaths.file.display()).replace(r"\", "/");
+        let file_name = self.testpaths.file.to_string().replace(r"\", "/");
 
         // On Windows, keep all '\' path separators to match the paths reported in the JSON output
         // from the compiler
         let diagnostic_file_name = if self.props.remap_src_base {
-            let mut p = PathBuf::from(FAKE_SRC_BASE);
+            let mut p = Utf8PathBuf::from(FAKE_SRC_BASE);
             p.push(&self.testpaths.relative_dir);
             p.push(self.testpaths.file.file_name().unwrap());
-            p.display().to_string()
+            p.to_string()
         } else {
-            self.testpaths.file.display().to_string()
+            self.testpaths.file.to_string()
         };
 
-        let expect_help = expected_errors.iter().any(|ee| ee.kind == Some(ErrorKind::Help));
-        let expect_note = expected_errors.iter().any(|ee| ee.kind == Some(ErrorKind::Note));
+        // Errors and warnings are always expected, other diagnostics are only expected
+        // if one of them actually occurs in the test.
+        let expected_kinds: HashSet<_> = [ErrorKind::Error, ErrorKind::Warning]
+            .into_iter()
+            .chain(expected_errors.iter().filter_map(|e| e.kind))
+            .collect();
 
         // Parse the JSON output from the compiler and extract out the messages.
         let actual_errors = json::parse_output(&diagnostic_file_name, &proc_res.stderr, proc_res);
@@ -737,8 +739,11 @@ impl<'test> TestCx<'test> {
                 }
 
                 None => {
-                    // If the test is a known bug, don't require that the error is annotated
-                    if self.is_unexpected_compiler_message(&actual_error, expect_help, expect_note)
+                    if actual_error.require_annotation
+                        && actual_error.kind.map_or(false, |kind| {
+                            expected_kinds.contains(&kind)
+                                && !self.props.dont_require_annotations.contains(&kind)
+                        })
                     {
                         self.error(&format!(
                             "{}:{}: unexpected {}: '{}'",
@@ -796,27 +801,6 @@ impl<'test> TestCx<'test> {
         }
     }
 
-    /// Returns `true` if we should report an error about `actual_error`,
-    /// which did not match any of the expected error.
-    fn is_unexpected_compiler_message(
-        &self,
-        actual_error: &Error,
-        expect_help: bool,
-        expect_note: bool,
-    ) -> bool {
-        actual_error.require_annotation
-            && actual_error.kind.map_or(false, |err_kind| {
-                // If the test being checked doesn't contain any "help" or "note" annotations, then
-                // we don't require annotating "help" or "note" (respecively) diagnostics at all.
-                let default_require_annotations = self.props.require_annotations[&err_kind];
-                match err_kind {
-                    ErrorKind::Help => expect_help && default_require_annotations,
-                    ErrorKind::Note => expect_note && default_require_annotations,
-                    _ => default_require_annotations,
-                }
-            })
-    }
-
     fn should_emit_metadata(&self, pm: Option<PassMode>) -> Emit {
         match (pm, self.props.fail_mode, self.config.mode) {
             (Some(PassMode::Check), ..) | (_, Some(FailMode::Check), Ui) => Emit::Metadata,
@@ -887,7 +871,7 @@ impl<'test> TestCx<'test> {
 
     /// `root_out_dir` and `root_testpaths` refer to the parameters of the actual test being run.
     /// Auxiliaries, no matter how deep, have the same root_out_dir and root_testpaths.
-    fn document(&self, root_out_dir: &Path, root_testpaths: &TestPaths) -> ProcRes {
+    fn document(&self, root_out_dir: &Utf8Path, root_testpaths: &TestPaths) -> ProcRes {
         if self.props.build_aux_docs {
             for rel_ab in &self.props.aux.builds {
                 let aux_testpaths = self.compute_aux_test_paths(root_testpaths, rel_ab);
@@ -916,13 +900,13 @@ impl<'test> TestCx<'test> {
 
         // actual --out-dir given to the auxiliary or test, as opposed to the root out dir for the entire
         // test
-        let out_dir: Cow<'_, Path> = if self.props.unique_doc_out_dir {
+        let out_dir: Cow<'_, Utf8Path> = if self.props.unique_doc_out_dir {
             let file_name = self.testpaths.file.file_stem().expect("file name should not be empty");
-            let out_dir = PathBuf::from_iter([
+            let out_dir = Utf8PathBuf::from_iter([
                 root_out_dir,
-                Path::new("docs"),
-                Path::new(file_name),
-                Path::new("doc"),
+                Utf8Path::new("docs"),
+                Utf8Path::new(file_name),
+                Utf8Path::new("doc"),
             ]);
             create_dir_all(&out_dir).unwrap();
             Cow::Owned(out_dir)
@@ -935,7 +919,7 @@ impl<'test> TestCx<'test> {
         rustdoc.current_dir(current_dir);
         rustdoc
             .arg("-L")
-            .arg(self.config.run_lib_path.to_str().unwrap())
+            .arg(self.config.run_lib_path.as_path())
             .arg("-L")
             .arg(aux_dir)
             .arg("-o")
@@ -1073,7 +1057,7 @@ impl<'test> TestCx<'test> {
         let test_ab =
             of.file.parent().expect("test file path has no parent").join("auxiliary").join(rel_ab);
         if !test_ab.exists() {
-            self.fatal(&format!("aux-build `{}` source not found", test_ab.display()))
+            self.fatal(&format!("aux-build `{}` source not found", test_ab))
         }
 
         TestPaths {
@@ -1110,7 +1094,7 @@ impl<'test> TestCx<'test> {
             || !self.props.aux.proc_macros.is_empty()
     }
 
-    fn aux_output_dir(&self) -> PathBuf {
+    fn aux_output_dir(&self) -> Utf8PathBuf {
         let aux_dir = self.aux_output_dir_name();
 
         if !self.props.aux.builds.is_empty() {
@@ -1126,7 +1110,7 @@ impl<'test> TestCx<'test> {
         aux_dir
     }
 
-    fn build_all_auxiliary(&self, of: &TestPaths, aux_dir: &Path, rustc: &mut Command) {
+    fn build_all_auxiliary(&self, of: &TestPaths, aux_dir: &Utf8Path, rustc: &mut Command) {
         for rel_ab in &self.props.aux.builds {
             self.build_auxiliary(of, rel_ab, &aux_dir, None);
         }
@@ -1146,12 +1130,7 @@ impl<'test> TestCx<'test> {
             |rustc: &mut Command, aux_name: &str, aux_path: &str, aux_type: AuxType| {
                 let lib_name = get_lib_name(&path_to_crate_name(aux_path), aux_type);
                 if let Some(lib_name) = lib_name {
-                    rustc.arg("--extern").arg(format!(
-                        "{}={}/{}",
-                        aux_name,
-                        aux_dir.display(),
-                        lib_name
-                    ));
+                    rustc.arg("--extern").arg(format!("{}={}/{}", aux_name, aux_dir, lib_name));
                 }
             };
 
@@ -1172,7 +1151,7 @@ impl<'test> TestCx<'test> {
             let aux_type = self.build_auxiliary(of, aux_file, aux_dir, None);
             if let Some(lib_name) = get_lib_name(aux_file.trim_end_matches(".rs"), aux_type) {
                 let lib_path = aux_dir.join(&lib_name);
-                rustc.arg(format!("-Zcodegen-backend={}", lib_path.display()));
+                rustc.arg(format!("-Zcodegen-backend={}", lib_path));
             }
         }
     }
@@ -1188,7 +1167,7 @@ impl<'test> TestCx<'test> {
         if self.props.add_core_stubs {
             let minicore_path = self.build_minicore();
             rustc.arg("--extern");
-            rustc.arg(&format!("minicore={}", minicore_path.to_str().unwrap()));
+            rustc.arg(&format!("minicore={}", minicore_path));
         }
 
         let aux_dir = self.aux_output_dir();
@@ -1206,7 +1185,7 @@ impl<'test> TestCx<'test> {
 
     /// Builds `minicore`. Returns the path to the minicore rlib within the base test output
     /// directory.
-    fn build_minicore(&self) -> PathBuf {
+    fn build_minicore(&self) -> Utf8PathBuf {
         let output_file_path = self.output_base_dir().join("libminicore.rlib");
         let mut rustc = self.make_compile_args(
             &self.config.minicore_path,
@@ -1223,10 +1202,7 @@ impl<'test> TestCx<'test> {
         let res = self.compose_and_run(rustc, self.config.compile_lib_path.as_path(), None, None);
         if !res.status.success() {
             self.fatal_proc_rec(
-                &format!(
-                    "auxiliary build of {:?} failed to compile: ",
-                    self.config.minicore_path.display()
-                ),
+                &format!("auxiliary build of {} failed to compile: ", self.config.minicore_path),
                 &res,
             );
         }
@@ -1241,7 +1217,7 @@ impl<'test> TestCx<'test> {
         &self,
         of: &TestPaths,
         source_path: &str,
-        aux_dir: &Path,
+        aux_dir: &Utf8Path,
         aux_type: Option<AuxType>,
     ) -> AuxType {
         let aux_testpaths = self.compute_aux_test_paths(of, source_path);
@@ -1338,10 +1314,7 @@ impl<'test> TestCx<'test> {
         );
         if !auxres.status.success() {
             self.fatal_proc_rec(
-                &format!(
-                    "auxiliary build of {:?} failed to compile: ",
-                    aux_testpaths.file.display()
-                ),
+                &format!("auxiliary build of {} failed to compile: ", aux_testpaths.file),
                 &auxres,
             );
         }
@@ -1350,8 +1323,8 @@ impl<'test> TestCx<'test> {
 
     fn read2_abbreviated(&self, child: Child) -> (Output, Truncated) {
         let mut filter_paths_from_len = Vec::new();
-        let mut add_path = |path: &Path| {
-            let path = path.display().to_string();
+        let mut add_path = |path: &Utf8Path| {
+            let path = path.to_string();
             let windows = path.replace("\\", "\\\\");
             if windows != path {
                 filter_paths_from_len.push(windows);
@@ -1373,8 +1346,8 @@ impl<'test> TestCx<'test> {
     fn compose_and_run(
         &self,
         mut command: Command,
-        lib_path: &Path,
-        aux_path: Option<&Path>,
+        lib_path: &Utf8Path,
+        aux_path: Option<&Utf8Path>,
         input: Option<String>,
     ) -> ProcRes {
         let cmdline = {
@@ -1419,9 +1392,9 @@ impl<'test> TestCx<'test> {
         matches!(self.config.suite.as_str(), "rustdoc-ui" | "rustdoc-js" | "rustdoc-json")
     }
 
-    fn get_mir_dump_dir(&self) -> PathBuf {
+    fn get_mir_dump_dir(&self) -> Utf8PathBuf {
         let mut mir_dump_dir = self.config.build_test_suite_root.clone();
-        debug!("input_file: {:?}", self.testpaths.file);
+        debug!("input_file: {}", self.testpaths.file);
         mir_dump_dir.push(&self.testpaths.relative_dir);
         mir_dump_dir.push(self.testpaths.file.file_stem().unwrap());
         mir_dump_dir
@@ -1429,7 +1402,7 @@ impl<'test> TestCx<'test> {
 
     fn make_compile_args(
         &self,
-        input_file: &Path,
+        input_file: &Utf8Path,
         output_file: TargetLocation,
         emit: Emit,
         allow_unused: AllowUnused,
@@ -1470,7 +1443,7 @@ impl<'test> TestCx<'test> {
         // Similarly, vendored sources shouldn't be shown when running from a dist tarball.
         rustc.arg("-Z").arg(format!(
             "ignore-directory-in-diagnostics-source-blocks={}",
-            self.config.src_root.join("vendor").to_str().unwrap(),
+            self.config.src_root.join("vendor"),
         ));
 
         // Optionally prevent default --sysroot if specified in test compile-flags.
@@ -1494,7 +1467,7 @@ impl<'test> TestCx<'test> {
 
         if !is_rustdoc {
             if let Some(ref incremental_dir) = self.props.incremental_dir {
-                rustc.args(&["-C", &format!("incremental={}", incremental_dir.display())]);
+                rustc.args(&["-C", &format!("incremental={}", incremental_dir)]);
                 rustc.args(&["-Z", "incremental-verify-ich"]);
             }
 
@@ -1538,7 +1511,7 @@ impl<'test> TestCx<'test> {
             let mir_dump_dir = self.get_mir_dump_dir();
             remove_and_create_dir_all(&mir_dump_dir);
             let mut dir_opt = "-Zdump-mir-dir=".to_string();
-            dir_opt.push_str(mir_dump_dir.to_str().unwrap());
+            dir_opt.push_str(mir_dump_dir.as_str());
             debug!("dir_opt: {:?}", dir_opt);
             rustc.arg(dir_opt);
         };
@@ -1631,8 +1604,7 @@ impl<'test> TestCx<'test> {
         if self.props.remap_src_base {
             rustc.arg(format!(
                 "--remap-path-prefix={}={}",
-                self.config.src_test_suite_root.to_str().unwrap(),
-                FAKE_SRC_BASE,
+                self.config.src_test_suite_root, FAKE_SRC_BASE,
             ));
         }
 
@@ -1755,7 +1727,7 @@ impl<'test> TestCx<'test> {
         rustc
     }
 
-    fn make_exe_name(&self) -> PathBuf {
+    fn make_exe_name(&self) -> Utf8PathBuf {
         // Using a single letter here to keep the path length down for
         // Windows.  Some test names get very long.  rustc creates `rcgu`
         // files with the module name appended to it which can more than
@@ -1806,7 +1778,7 @@ impl<'test> TestCx<'test> {
         }
     }
 
-    fn make_cmdline(&self, command: &Command, libpath: &Path) -> String {
+    fn make_cmdline(&self, command: &Command, libpath: &Utf8Path) -> String {
         use crate::util;
 
         // Linux and mac don't require adjusting the library search path
@@ -1819,7 +1791,7 @@ impl<'test> TestCx<'test> {
                 format!("{}=\"{}\"", util::lib_path_env_var(), util::make_new_path(path))
             }
 
-            format!("{} {:?}", lib_path_cmd_prefix(libpath.to_str().unwrap()), command)
+            format!("{} {:?}", lib_path_cmd_prefix(libpath.as_str()), command)
         }
     }
 
@@ -1833,20 +1805,19 @@ impl<'test> TestCx<'test> {
             return;
         }
 
-        let path = Path::new(proc_name);
+        let path = Utf8Path::new(proc_name);
         let proc_name = if path.file_stem().is_some_and(|p| p == "rmake") {
-            OsString::from_iter(
+            String::from_iter(
                 path.parent()
                     .unwrap()
                     .file_name()
                     .into_iter()
-                    .chain(Some(OsStr::new("/")))
+                    .chain(Some("/"))
                     .chain(path.file_name()),
             )
         } else {
             path.file_name().unwrap().into()
         };
-        let proc_name = proc_name.to_string_lossy();
         println!("------{proc_name} stdout------------------------------");
         println!("{}", out);
         println!("------{proc_name} stderr------------------------------");
@@ -1856,18 +1827,18 @@ impl<'test> TestCx<'test> {
 
     fn dump_output_file(&self, out: &str, extension: &str) {
         let outfile = self.make_out_name(extension);
-        fs::write(&outfile, out).unwrap();
+        fs::write(outfile.as_std_path(), out).unwrap();
     }
 
     /// Creates a filename for output with the given extension.
     /// E.g., `/.../testname.revision.mode/testname.extension`.
-    fn make_out_name(&self, extension: &str) -> PathBuf {
+    fn make_out_name(&self, extension: &str) -> Utf8PathBuf {
         self.output_base_name().with_extension(extension)
     }
 
     /// Gets the directory where auxiliary files are written.
     /// E.g., `/.../testname.revision.mode/auxiliary/`.
-    fn aux_output_dir_name(&self) -> PathBuf {
+    fn aux_output_dir_name(&self) -> Utf8PathBuf {
         self.output_base_dir()
             .join("auxiliary")
             .with_extra_extension(self.config.mode.aux_dir_disambiguator())
@@ -1875,12 +1846,12 @@ impl<'test> TestCx<'test> {
 
     /// Gets the directory where auxiliary binaries are written.
     /// E.g., `/.../testname.revision.mode/auxiliary/bin`.
-    fn aux_bin_output_dir_name(&self) -> PathBuf {
+    fn aux_bin_output_dir_name(&self) -> Utf8PathBuf {
         self.aux_output_dir_name().join("bin")
     }
 
     /// Generates a unique name for the test, such as `testname.revision.mode`.
-    fn output_testname_unique(&self) -> PathBuf {
+    fn output_testname_unique(&self) -> Utf8PathBuf {
         output_testname_unique(self.config, self.testpaths, self.safe_revision())
     }
 
@@ -1893,14 +1864,14 @@ impl<'test> TestCx<'test> {
     /// Gets the absolute path to the directory where all output for the given
     /// test/revision should reside.
     /// E.g., `/path/to/build/host-tuple/test/ui/relative/testname.revision.mode/`.
-    fn output_base_dir(&self) -> PathBuf {
+    fn output_base_dir(&self) -> Utf8PathBuf {
         output_base_dir(self.config, self.testpaths, self.safe_revision())
     }
 
     /// Gets the absolute path to the base filename used as output for the given
     /// test/revision.
     /// E.g., `/.../relative/testname.revision.mode/testname`.
-    fn output_base_name(&self) -> PathBuf {
+    fn output_base_name(&self) -> Utf8PathBuf {
         output_base_name(self.config, self.testpaths, self.safe_revision())
     }
 
@@ -1935,7 +1906,7 @@ impl<'test> TestCx<'test> {
 
     // codegen tests (using FileCheck)
 
-    fn compile_test_and_save_ir(&self) -> (ProcRes, PathBuf) {
+    fn compile_test_and_save_ir(&self) -> (ProcRes, Utf8PathBuf) {
         let output_path = self.output_base_name().with_extension("ll");
         let input_file = &self.testpaths.file;
         let rustc = self.make_compile_args(
@@ -1951,7 +1922,7 @@ impl<'test> TestCx<'test> {
         (proc_res, output_path)
     }
 
-    fn verify_with_filecheck(&self, output: &Path) -> ProcRes {
+    fn verify_with_filecheck(&self, output: &Utf8Path) -> ProcRes {
         let mut filecheck = Command::new(self.config.llvm_filecheck.as_ref().unwrap());
         filecheck.arg("--input-file").arg(output).arg(&self.testpaths.file);
 
@@ -1981,7 +1952,7 @@ impl<'test> TestCx<'test> {
         filecheck.args(&self.props.filecheck_flags);
 
         // FIXME(jieyouxu): don't pass an empty Path
-        self.compose_and_run(filecheck, Path::new(""), None, None)
+        self.compose_and_run(filecheck, Utf8Path::new(""), None, None)
     }
 
     fn charset() -> &'static str {
@@ -1989,7 +1960,7 @@ impl<'test> TestCx<'test> {
         if cfg!(target_os = "freebsd") { "ISO-8859-1" } else { "UTF-8" }
     }
 
-    fn compare_to_default_rustdoc(&mut self, out_dir: &Path) {
+    fn compare_to_default_rustdoc(&mut self, out_dir: &Utf8Path) {
         if !self.config.has_html_tidy {
             return;
         }
@@ -2141,12 +2112,8 @@ impl<'test> TestCx<'test> {
         };
     }
 
-    fn get_lines<P: AsRef<Path>>(
-        &self,
-        path: &P,
-        mut other_files: Option<&mut Vec<String>>,
-    ) -> Vec<usize> {
-        let content = fs::read_to_string(&path).unwrap();
+    fn get_lines(&self, path: &Utf8Path, mut other_files: Option<&mut Vec<String>>) -> Vec<usize> {
+        let content = fs::read_to_string(path.as_std_path()).unwrap();
         let mut ignore = false;
         content
             .lines()
@@ -2192,8 +2159,8 @@ impl<'test> TestCx<'test> {
         for other_file in other_files {
             let mut path = self.testpaths.file.clone();
             path.set_file_name(&format!("{}.rs", other_file));
-            let path = fs::canonicalize(path).expect("failed to canonicalize");
-            let normalized = path.to_str().unwrap().replace('\\', "/");
+            let path = path.canonicalize_utf8().expect("failed to canonicalize");
+            let normalized = path.as_str().replace('\\', "/");
             files.insert(normalized, self.get_lines(&path, None));
         }
 
@@ -2377,26 +2344,24 @@ impl<'test> TestCx<'test> {
 
         let mut normalized = output.to_string();
 
-        let mut normalize_path = |from: &Path, to: &str| {
-            let mut from = from.display().to_string();
-            if json {
-                from = from.replace("\\", "\\\\");
-            }
-            normalized = normalized.replace(&from, to);
+        let mut normalize_path = |from: &Utf8Path, to: &str| {
+            let from = if json { &from.as_str().replace("\\", "\\\\") } else { from.as_str() };
+
+            normalized = normalized.replace(from, to);
         };
 
         let parent_dir = self.testpaths.file.parent().unwrap();
         normalize_path(parent_dir, "$DIR");
 
         if self.props.remap_src_base {
-            let mut remapped_parent_dir = PathBuf::from(FAKE_SRC_BASE);
-            if self.testpaths.relative_dir != Path::new("") {
+            let mut remapped_parent_dir = Utf8PathBuf::from(FAKE_SRC_BASE);
+            if self.testpaths.relative_dir != Utf8Path::new("") {
                 remapped_parent_dir.push(&self.testpaths.relative_dir);
             }
             normalize_path(&remapped_parent_dir, "$DIR");
         }
 
-        let base_dir = Path::new("/rustc/FAKE_PREFIX");
+        let base_dir = Utf8Path::new("/rustc/FAKE_PREFIX");
         // Fake paths into the libstd/libcore
         normalize_path(&base_dir.join("library"), "$SRC_DIR");
         // `ui-fulldeps` tests can show paths to the compiler source when testing macros from
@@ -2406,19 +2371,26 @@ impl<'test> TestCx<'test> {
 
         // Real paths into the libstd/libcore
         let rust_src_dir = &self.config.sysroot_base.join("lib/rustlib/src/rust");
-        rust_src_dir.try_exists().expect(&*format!("{} should exists", rust_src_dir.display()));
-        let rust_src_dir = rust_src_dir.read_link().unwrap_or(rust_src_dir.to_path_buf());
+        rust_src_dir.try_exists().expect(&*format!("{} should exists", rust_src_dir));
+        let rust_src_dir = rust_src_dir.read_link_utf8().unwrap_or(rust_src_dir.to_path_buf());
         normalize_path(&rust_src_dir.join("library"), "$SRC_DIR_REAL");
 
         // eg.
         // /home/user/rust/build/x86_64-unknown-linux-gnu/test/ui/<test_dir>/$name.$revision.$mode/
         normalize_path(&self.output_base_dir(), "$TEST_BUILD_DIR");
+        // Same as above, but with a canonicalized path.
+        // This is required because some tests print canonical paths inside test build directory,
+        // so if the build directory is a symlink, normalization doesn't help.
+        //
+        // NOTE: There are also tests which print the non-canonical name, so we need both this and
+        // the above normalizations.
+        normalize_path(&self.output_base_dir().canonicalize_utf8().unwrap(), "$TEST_BUILD_DIR");
         // eg. /home/user/rust/build
         normalize_path(&self.config.build_root, "$BUILD_DIR");
 
         if json {
             // escaped newlines in json strings should be readable
-            // in the stderr files. There's no point int being correct,
+            // in the stderr files. There's no point in being correct,
             // since only humans process the stderr files.
             // Thus we just turn escaped newlines back into newlines.
             normalized = normalized.replace("\\n", "\n");
@@ -2547,7 +2519,7 @@ impl<'test> TestCx<'test> {
             .replace("\r\n", "\n")
     }
 
-    fn expected_output_path(&self, kind: &str) -> PathBuf {
+    fn expected_output_path(&self, kind: &str) -> Utf8PathBuf {
         let mut path =
             expected_output_path(&self.testpaths, self.revision, &self.config.compare_mode, kind);
 
@@ -2576,19 +2548,18 @@ impl<'test> TestCx<'test> {
         }
     }
 
-    fn load_expected_output_from_path(&self, path: &Path) -> Result<String, String> {
-        fs::read_to_string(path).map_err(|err| {
-            format!("failed to load expected output from `{}`: {}", path.display(), err)
-        })
+    fn load_expected_output_from_path(&self, path: &Utf8Path) -> Result<String, String> {
+        fs::read_to_string(path)
+            .map_err(|err| format!("failed to load expected output from `{}`: {}", path, err))
     }
 
-    fn delete_file(&self, file: &Path) {
+    fn delete_file(&self, file: &Utf8Path) {
         if !file.exists() {
             // Deleting a nonexistent file would error.
             return;
         }
-        if let Err(e) = fs::remove_file(file) {
-            self.fatal(&format!("failed to delete `{}`: {}", file.display(), e,));
+        if let Err(e) = fs::remove_file(file.as_std_path()) {
+            self.fatal(&format!("failed to delete `{}`: {}", file, e,));
         }
     }
 
@@ -2694,8 +2665,8 @@ impl<'test> TestCx<'test> {
     fn show_diff(
         &self,
         stream: &str,
-        expected_path: &Path,
-        actual_path: &Path,
+        expected_path: &Utf8Path,
+        actual_path: &Utf8Path,
         expected: &str,
         actual: &str,
         actual_unnormalized: &str,
@@ -2834,7 +2805,7 @@ impl<'test> TestCx<'test> {
         fs::create_dir_all(&incremental_dir).unwrap();
 
         if self.config.verbose {
-            println!("init_incremental_test: incremental_dir={}", incremental_dir.display());
+            println!("init_incremental_test: incremental_dir={incremental_dir}");
         }
     }
 }
@@ -2892,8 +2863,8 @@ impl ProcRes {
 
 #[derive(Debug)]
 enum TargetLocation {
-    ThisFile(PathBuf),
-    ThisDirectory(PathBuf),
+    ThisFile(Utf8PathBuf),
+    ThisDirectory(Utf8PathBuf),
 }
 
 enum AllowUnused {
diff --git a/src/tools/compiletest/src/runtest/assembly.rs b/src/tools/compiletest/src/runtest/assembly.rs
index 89d7de58c20..91d4f620f71 100644
--- a/src/tools/compiletest/src/runtest/assembly.rs
+++ b/src/tools/compiletest/src/runtest/assembly.rs
@@ -1,4 +1,4 @@
-use std::path::PathBuf;
+use camino::Utf8PathBuf;
 
 use super::{AllowUnused, Emit, LinkToAux, ProcRes, TargetLocation, TestCx};
 
@@ -19,7 +19,7 @@ impl TestCx<'_> {
         }
     }
 
-    fn compile_test_and_save_assembly(&self) -> (ProcRes, PathBuf) {
+    fn compile_test_and_save_assembly(&self) -> (ProcRes, Utf8PathBuf) {
         // This works with both `--emit asm` (as default output name for the assembly)
         // and `ptx-linker` because the latter can write output at requested location.
         let output_path = self.output_base_name().with_extension("s");
diff --git a/src/tools/compiletest/src/runtest/codegen_units.rs b/src/tools/compiletest/src/runtest/codegen_units.rs
index 6c866cbef21..8dfa8d18d1a 100644
--- a/src/tools/compiletest/src/runtest/codegen_units.rs
+++ b/src/tools/compiletest/src/runtest/codegen_units.rs
@@ -26,9 +26,7 @@ impl TestCx<'_> {
             .stdout
             .lines()
             .filter(|line| line.starts_with(PREFIX))
-            .map(|line| {
-                line.replace(&self.testpaths.file.display().to_string(), "TEST_PATH").to_string()
-            })
+            .map(|line| line.replace(&self.testpaths.file.as_str(), "TEST_PATH").to_string())
             .map(|line| str_to_mono_item(&line, true))
             .collect();
 
diff --git a/src/tools/compiletest/src/runtest/coverage.rs b/src/tools/compiletest/src/runtest/coverage.rs
index 56fc5baf5f2..41cfeaee35f 100644
--- a/src/tools/compiletest/src/runtest/coverage.rs
+++ b/src/tools/compiletest/src/runtest/coverage.rs
@@ -1,9 +1,9 @@
 //! Code specific to the coverage test suites.
 
 use std::ffi::OsStr;
-use std::path::{Path, PathBuf};
 use std::process::Command;
 
+use camino::{Utf8Path, Utf8PathBuf};
 use glob::glob;
 
 use crate::common::{UI_COVERAGE, UI_COVERAGE_MAP};
@@ -11,7 +11,7 @@ use crate::runtest::{Emit, ProcRes, TestCx, WillExecute};
 use crate::util::static_regex;
 
 impl<'test> TestCx<'test> {
-    fn coverage_dump_path(&self) -> &Path {
+    fn coverage_dump_path(&self) -> &Utf8Path {
         self.config
             .coverage_dump_path
             .as_deref()
@@ -79,10 +79,8 @@ impl<'test> TestCx<'test> {
             std::fs::remove_file(&profdata_path).unwrap();
         }
 
-        let proc_res = self.exec_compiled_test_general(
-            &[("LLVM_PROFILE_FILE", &profraw_path.to_str().unwrap())],
-            false,
-        );
+        let proc_res =
+            self.exec_compiled_test_general(&[("LLVM_PROFILE_FILE", profraw_path.as_str())], false);
         if self.props.failure_status.is_some() {
             self.check_correct_failure_status(&proc_res);
         } else if !proc_res.status.success() {
@@ -158,8 +156,8 @@ impl<'test> TestCx<'test> {
     /// `.profraw` files and doctest executables to the given vectors.
     fn run_doctests_for_coverage(
         &self,
-        profraw_paths: &mut Vec<PathBuf>,
-        bin_paths: &mut Vec<PathBuf>,
+        profraw_paths: &mut Vec<Utf8PathBuf>,
+        bin_paths: &mut Vec<Utf8PathBuf>,
     ) {
         // Put .profraw files and doctest executables in dedicated directories,
         // to make it easier to glob them all later.
@@ -204,10 +202,9 @@ impl<'test> TestCx<'test> {
             self.fatal_proc_rec("rustdoc --test failed!", &proc_res)
         }
 
-        fn glob_iter(path: impl AsRef<Path>) -> impl Iterator<Item = PathBuf> {
-            let path_str = path.as_ref().to_str().unwrap();
-            let iter = glob(path_str).unwrap();
-            iter.map(Result::unwrap)
+        fn glob_iter(path: impl AsRef<Utf8Path>) -> impl Iterator<Item = Utf8PathBuf> {
+            let iter = glob(path.as_ref().as_str()).unwrap();
+            iter.map(Result::unwrap).map(Utf8PathBuf::try_from).map(Result::unwrap)
         }
 
         // Find all profraw files in the profraw directory.
diff --git a/src/tools/compiletest/src/runtest/debugger.rs b/src/tools/compiletest/src/runtest/debugger.rs
index d9e5c3fa0d8..a4103c5b4a9 100644
--- a/src/tools/compiletest/src/runtest/debugger.rs
+++ b/src/tools/compiletest/src/runtest/debugger.rs
@@ -1,7 +1,8 @@
 use std::fmt::Write;
 use std::fs::File;
 use std::io::{BufRead, BufReader};
-use std::path::{Path, PathBuf};
+
+use camino::{Utf8Path, Utf8PathBuf};
 
 use crate::common::Config;
 use crate::runtest::ProcRes;
@@ -15,11 +16,15 @@ pub(super) struct DebuggerCommands {
     /// Contains the source line number to check and the line itself
     check_lines: Vec<(usize, String)>,
     /// Source file name
-    file: PathBuf,
+    file: Utf8PathBuf,
 }
 
 impl DebuggerCommands {
-    pub fn parse_from(file: &Path, config: &Config, debugger_prefix: &str) -> Result<Self, String> {
+    pub fn parse_from(
+        file: &Utf8Path,
+        config: &Config,
+        debugger_prefix: &str,
+    ) -> Result<Self, String> {
         let command_directive = format!("{debugger_prefix}-command");
         let check_directive = format!("{debugger_prefix}-check");
 
@@ -27,7 +32,7 @@ impl DebuggerCommands {
         let mut commands = vec![];
         let mut check_lines = vec![];
         let mut counter = 0;
-        let reader = BufReader::new(File::open(file).unwrap());
+        let reader = BufReader::new(File::open(file.as_std_path()).unwrap());
         for (line_no, line) in reader.lines().enumerate() {
             counter += 1;
             let line = line.map_err(|e| format!("Error while parsing debugger commands: {}", e))?;
@@ -50,7 +55,7 @@ impl DebuggerCommands {
             }
         }
 
-        Ok(Self { commands, breakpoint_lines, check_lines, file: file.to_owned() })
+        Ok(Self { commands, breakpoint_lines, check_lines, file: file.to_path_buf() })
     }
 
     /// Given debugger output and lines to check, ensure that every line is
@@ -81,10 +86,10 @@ impl DebuggerCommands {
         if missing.is_empty() {
             Ok(())
         } else {
-            let fname = self.file.file_name().unwrap().to_string_lossy();
+            let fname = self.file.file_name().unwrap();
             let mut msg = format!(
                 "check directive(s) from `{}` not found in debugger output. errors:",
-                self.file.display()
+                self.file
             );
 
             for (src_lineno, err_line) in missing {
diff --git a/src/tools/compiletest/src/runtest/debuginfo.rs b/src/tools/compiletest/src/runtest/debuginfo.rs
index 50e733cd31b..31240dff9a1 100644
--- a/src/tools/compiletest/src/runtest/debuginfo.rs
+++ b/src/tools/compiletest/src/runtest/debuginfo.rs
@@ -1,9 +1,9 @@
 use std::ffi::{OsStr, OsString};
 use std::fs::File;
 use std::io::{BufRead, BufReader, Read};
-use std::path::Path;
 use std::process::{Command, Output, Stdio};
 
+use camino::Utf8Path;
 use tracing::debug;
 
 use super::debugger::DebuggerCommands;
@@ -73,11 +73,11 @@ impl TestCx<'_> {
         let mut js_extension = self.testpaths.file.clone();
         js_extension.set_extension("cdb.js");
         if js_extension.exists() {
-            script_str.push_str(&format!(".scriptload \"{}\"\n", js_extension.to_string_lossy()));
+            script_str.push_str(&format!(".scriptload \"{}\"\n", js_extension));
         }
 
         // Set breakpoints on every line that contains the string "#break"
-        let source_file_name = self.testpaths.file.file_name().unwrap().to_string_lossy();
+        let source_file_name = self.testpaths.file.file_name().unwrap();
         for line in &dbg_cmds.breakpoint_lines {
             script_str.push_str(&format!("bp `{}:{}`\n", source_file_name, line));
         }
@@ -151,16 +151,11 @@ impl TestCx<'_> {
         if is_android_gdb_target(&self.config.target) {
             cmds = cmds.replace("run", "continue");
 
-            let tool_path = match self.config.android_cross_path.to_str() {
-                Some(x) => x.to_owned(),
-                None => self.fatal("cannot find android cross path"),
-            };
-
             // write debugger script
             let mut script_str = String::with_capacity(2048);
             script_str.push_str(&format!("set charset {}\n", Self::charset()));
-            script_str.push_str(&format!("set sysroot {}\n", tool_path));
-            script_str.push_str(&format!("file {}\n", exe_file.to_str().unwrap()));
+            script_str.push_str(&format!("set sysroot {}\n", &self.config.android_cross_path));
+            script_str.push_str(&format!("file {}\n", exe_file));
             script_str.push_str("target remote :5039\n");
             script_str.push_str(&format!(
                 "set solib-search-path \
@@ -169,12 +164,8 @@ impl TestCx<'_> {
             ));
             for line in &dbg_cmds.breakpoint_lines {
                 script_str.push_str(
-                    format!(
-                        "break {:?}:{}\n",
-                        self.testpaths.file.file_name().unwrap().to_string_lossy(),
-                        *line
-                    )
-                    .as_str(),
+                    format!("break {}:{}\n", self.testpaths.file.file_name().unwrap(), *line)
+                        .as_str(),
                 );
             }
             script_str.push_str(&cmds);
@@ -203,7 +194,7 @@ impl TestCx<'_> {
                 self.config.adb_test_dir.clone(),
                 if self.config.target.contains("aarch64") { "64" } else { "" },
                 self.config.adb_test_dir.clone(),
-                exe_file.file_name().unwrap().to_str().unwrap()
+                exe_file.file_name().unwrap()
             );
 
             debug!("adb arg: {}", adb_arg);
@@ -242,7 +233,7 @@ impl TestCx<'_> {
                 let mut gdb = Command::new(&format!("{}-gdb", self.config.target));
                 gdb.args(debugger_opts);
                 // FIXME(jieyouxu): don't pass an empty Path
-                let cmdline = self.make_cmdline(&gdb, Path::new(""));
+                let cmdline = self.make_cmdline(&gdb, Utf8Path::new(""));
                 logv(self.config, format!("executing {}", cmdline));
                 cmdline
             };
@@ -259,7 +250,6 @@ impl TestCx<'_> {
             }
         } else {
             let rust_pp_module_abs_path = self.config.src_root.join("src").join("etc");
-            let rust_pp_module_abs_path = rust_pp_module_abs_path.to_str().unwrap();
             // write debugger script
             let mut script_str = String::with_capacity(2048);
             script_str.push_str(&format!("set charset {}\n", Self::charset()));
@@ -274,17 +264,15 @@ impl TestCx<'_> {
                         // GDB's script auto loading safe path
                         script_str.push_str(&format!(
                             "add-auto-load-safe-path {}\n",
-                            rust_pp_module_abs_path.replace(r"\", r"\\")
+                            rust_pp_module_abs_path.as_str().replace(r"\", r"\\")
                         ));
 
-                        let output_base_dir = self.output_base_dir().to_str().unwrap().to_owned();
-
                         // Add the directory containing the output binary to
                         // include embedded pretty printers to GDB's script
                         // auto loading safe path
                         script_str.push_str(&format!(
                             "add-auto-load-safe-path {}\n",
-                            output_base_dir.replace(r"\", r"\\")
+                            self.output_base_dir().as_str().replace(r"\", r"\\")
                         ));
                     }
                 }
@@ -301,12 +289,13 @@ impl TestCx<'_> {
             script_str.push_str("set print pretty off\n");
 
             // Add the pretty printer directory to GDB's source-file search path
-            script_str
-                .push_str(&format!("directory {}\n", rust_pp_module_abs_path.replace(r"\", r"\\")));
+            script_str.push_str(&format!(
+                "directory {}\n",
+                rust_pp_module_abs_path.as_str().replace(r"\", r"\\")
+            ));
 
             // Load the target executable
-            script_str
-                .push_str(&format!("file {}\n", exe_file.to_str().unwrap().replace(r"\", r"\\")));
+            script_str.push_str(&format!("file {}\n", exe_file.as_str().replace(r"\", r"\\")));
 
             // Force GDB to print values in the Rust format.
             script_str.push_str("set language rust\n");
@@ -315,7 +304,7 @@ impl TestCx<'_> {
             for line in &dbg_cmds.breakpoint_lines {
                 script_str.push_str(&format!(
                     "break '{}':{}\n",
-                    self.testpaths.file.file_name().unwrap().to_string_lossy(),
+                    self.testpaths.file.file_name().unwrap(),
                     *line
                 ));
             }
@@ -410,14 +399,14 @@ impl TestCx<'_> {
 
         script_str.push_str(&format!(
             "command script import {}/lldb_lookup.py\n",
-            rust_pp_module_abs_path.to_str().unwrap()
+            rust_pp_module_abs_path
         ));
         File::open(rust_pp_module_abs_path.join("lldb_commands"))
             .and_then(|mut file| file.read_to_string(&mut script_str))
             .expect("Failed to read lldb_commands");
 
         // Set breakpoints on every line that contains the string "#break"
-        let source_file_name = self.testpaths.file.file_name().unwrap().to_string_lossy();
+        let source_file_name = self.testpaths.file.file_name().unwrap();
         for line in &dbg_cmds.breakpoint_lines {
             script_str.push_str(&format!(
                 "breakpoint set --file '{}' --line {}\n",
@@ -451,7 +440,7 @@ impl TestCx<'_> {
         }
     }
 
-    fn run_lldb(&self, test_executable: &Path, debugger_script: &Path) -> ProcRes {
+    fn run_lldb(&self, test_executable: &Utf8Path, debugger_script: &Utf8Path) -> ProcRes {
         // Prepare the lldb_batchmode which executes the debugger script
         let lldb_script_path = self.config.src_root.join("src/etc/lldb_batchmode.py");
         let pythonpath = if let Ok(pp) = std::env::var("PYTHONPATH") {
diff --git a/src/tools/compiletest/src/runtest/js_doc.rs b/src/tools/compiletest/src/runtest/js_doc.rs
index d630affbec1..fd53f01ca17 100644
--- a/src/tools/compiletest/src/runtest/js_doc.rs
+++ b/src/tools/compiletest/src/runtest/js_doc.rs
@@ -9,8 +9,7 @@ impl TestCx<'_> {
 
             self.document(&out_dir, &self.testpaths);
 
-            let file_stem =
-                self.testpaths.file.file_stem().and_then(|f| f.to_str()).expect("no file stem");
+            let file_stem = self.testpaths.file.file_stem().expect("no file stem");
             let res = self.run_command_to_procres(
                 Command::new(&nodejs)
                     .arg(self.config.src_root.join("src/tools/rustdoc-js/tester.js"))
diff --git a/src/tools/compiletest/src/runtest/mir_opt.rs b/src/tools/compiletest/src/runtest/mir_opt.rs
index d1ec0035744..ded6a68fe58 100644
--- a/src/tools/compiletest/src/runtest/mir_opt.rs
+++ b/src/tools/compiletest/src/runtest/mir_opt.rs
@@ -1,6 +1,6 @@
 use std::fs;
-use std::path::{Path, PathBuf};
 
+use camino::{Utf8Path, Utf8PathBuf};
 use glob::glob;
 use miropt_test_tools::{MiroptTest, MiroptTestFile, files_for_miropt_test};
 use tracing::debug;
@@ -14,7 +14,7 @@ impl TestCx<'_> {
         let should_run = self.should_run(pm);
 
         let mut test_info = files_for_miropt_test(
-            &self.testpaths.file,
+            &self.testpaths.file.as_std_path(),
             self.config.get_pointer_width(),
             self.config.target_cfg().panic.for_miropt_test_tools(),
         );
@@ -38,20 +38,15 @@ impl TestCx<'_> {
 
     fn check_mir_dump(&self, test_info: MiroptTest) {
         let test_dir = self.testpaths.file.parent().unwrap();
-        let test_crate =
-            self.testpaths.file.file_stem().unwrap().to_str().unwrap().replace('-', "_");
+        let test_crate = self.testpaths.file.file_stem().unwrap().replace('-', "_");
 
         let MiroptTest { run_filecheck, suffix, files, passes: _ } = test_info;
 
         if self.config.bless {
-            for e in
-                glob(&format!("{}/{}.*{}.mir", test_dir.display(), test_crate, suffix)).unwrap()
-            {
+            for e in glob(&format!("{}/{}.*{}.mir", test_dir, test_crate, suffix)).unwrap() {
                 fs::remove_file(e.unwrap()).unwrap();
             }
-            for e in
-                glob(&format!("{}/{}.*{}.diff", test_dir.display(), test_crate, suffix)).unwrap()
-            {
+            for e in glob(&format!("{}/{}.*{}.diff", test_dir, test_crate, suffix)).unwrap() {
                 fs::remove_file(e.unwrap()).unwrap();
             }
         }
@@ -60,19 +55,15 @@ impl TestCx<'_> {
             let dumped_string = if let Some(after) = to_file {
                 self.diff_mir_files(from_file.into(), after.into())
             } else {
-                let mut output_file = PathBuf::new();
+                let mut output_file = Utf8PathBuf::new();
                 output_file.push(self.get_mir_dump_dir());
                 output_file.push(&from_file);
-                debug!(
-                    "comparing the contents of: {} with {}",
-                    output_file.display(),
-                    expected_file.display()
-                );
+                debug!("comparing the contents of: {} with {:?}", output_file, expected_file);
                 if !output_file.exists() {
                     panic!(
                         "Output file `{}` from test does not exist, available files are in `{}`",
-                        output_file.display(),
-                        output_file.parent().unwrap().display()
+                        output_file,
+                        output_file.parent().unwrap()
                     );
                 }
                 self.check_mir_test_timestamp(&from_file, &output_file);
@@ -107,21 +98,20 @@ impl TestCx<'_> {
         }
     }
 
-    fn diff_mir_files(&self, before: PathBuf, after: PathBuf) -> String {
-        let to_full_path = |path: PathBuf| {
+    fn diff_mir_files(&self, before: Utf8PathBuf, after: Utf8PathBuf) -> String {
+        let to_full_path = |path: Utf8PathBuf| {
             let full = self.get_mir_dump_dir().join(&path);
             if !full.exists() {
                 panic!(
                     "the mir dump file for {} does not exist (requested in {})",
-                    path.display(),
-                    self.testpaths.file.display(),
+                    path, self.testpaths.file,
                 );
             }
             full
         };
         let before = to_full_path(before);
         let after = to_full_path(after);
-        debug!("comparing the contents of: {} with {}", before.display(), after.display());
+        debug!("comparing the contents of: {} with {}", before, after);
         let before = fs::read_to_string(before).unwrap();
         let after = fs::read_to_string(after).unwrap();
         let before = self.normalize_output(&before, &[]);
@@ -138,8 +128,8 @@ impl TestCx<'_> {
         dumped_string
     }
 
-    fn check_mir_test_timestamp(&self, test_name: &str, output_file: &Path) {
-        let t = |file| fs::metadata(file).unwrap().modified().unwrap();
+    fn check_mir_test_timestamp(&self, test_name: &str, output_file: &Utf8Path) {
+        let t = |file: &Utf8Path| fs::metadata(file.as_std_path()).unwrap().modified().unwrap();
         let source_file = &self.testpaths.file;
         let output_time = t(output_file);
         let source_time = t(source_file);
@@ -147,8 +137,7 @@ impl TestCx<'_> {
             debug!("source file time: {:?} output file time: {:?}", source_time, output_time);
             panic!(
                 "test source file `{}` is newer than potentially stale output file `{}`.",
-                source_file.display(),
-                test_name
+                source_file, test_name
             );
         }
     }
diff --git a/src/tools/compiletest/src/runtest/run_make.rs b/src/tools/compiletest/src/runtest/run_make.rs
index 073116933bd..a5ce929f9b8 100644
--- a/src/tools/compiletest/src/runtest/run_make.rs
+++ b/src/tools/compiletest/src/runtest/run_make.rs
@@ -1,8 +1,8 @@
-use std::path::Path;
 use std::process::{Command, Output, Stdio};
 use std::{env, fs};
 
 use build_helper::fs::{ignore_not_found, recursive_remove};
+use camino::{Utf8Path, Utf8PathBuf};
 
 use super::{ProcRes, TestCx, disable_error_reporting};
 use crate::util::{copy_dir_all, dylib_env_var};
@@ -39,14 +39,16 @@ impl TestCx<'_> {
         // Copy all input files (apart from rmake.rs) to the temporary directory,
         // so that the input directory structure from `tests/run-make/<test>` is mirrored
         // to the `rmake_out` directory.
-        for path in walkdir::WalkDir::new(&self.testpaths.file).min_depth(1) {
-            let path = path.unwrap().path().to_path_buf();
+        for entry in walkdir::WalkDir::new(&self.testpaths.file).min_depth(1) {
+            let entry = entry.unwrap();
+            let path = entry.path();
+            let path = <&Utf8Path>::try_from(path).unwrap();
             if path.file_name().is_some_and(|s| s != "rmake.rs") {
                 let target = rmake_out_dir.join(path.strip_prefix(&self.testpaths.file).unwrap());
                 if path.is_dir() {
-                    copy_dir_all(&path, target).unwrap();
+                    copy_dir_all(&path, &target).unwrap();
                 } else {
-                    fs::copy(&path, target).unwrap();
+                    fs::copy(path.as_std_path(), target).unwrap();
                 }
             }
         }
@@ -83,8 +85,10 @@ impl TestCx<'_> {
         //    on some linux distros.
         // 2. Specific library paths in `self.config.compile_lib_path` needed for running rustc.
 
-        let base_dylib_search_paths =
-            Vec::from_iter(env::split_paths(&env::var(dylib_env_var()).unwrap()));
+        let base_dylib_search_paths = Vec::from_iter(
+            env::split_paths(&env::var(dylib_env_var()).unwrap())
+                .map(|p| Utf8PathBuf::try_from(p).expect("dylib env var contains non-UTF8 paths")),
+        );
 
         // Calculate the paths of the recipe binary. As previously discussed, this is placed at
         // `<base_dir>/<bin_name>` with `bin_name` being `rmake` or `rmake.exe` depending on
@@ -113,13 +117,13 @@ impl TestCx<'_> {
             .arg("-o")
             .arg(&recipe_bin)
             // Specify library search paths for `run_make_support`.
-            .arg(format!("-Ldependency={}", &support_lib_path.parent().unwrap().to_string_lossy()))
-            .arg(format!("-Ldependency={}", &support_lib_deps.to_string_lossy()))
-            .arg(format!("-Ldependency={}", &support_lib_deps_deps.to_string_lossy()))
+            .arg(format!("-Ldependency={}", &support_lib_path.parent().unwrap()))
+            .arg(format!("-Ldependency={}", &support_lib_deps))
+            .arg(format!("-Ldependency={}", &support_lib_deps_deps))
             // Provide `run_make_support` as extern prelude, so test writers don't need to write
             // `extern run_make_support;`.
             .arg("--extern")
-            .arg(format!("run_make_support={}", &support_lib_path.to_string_lossy()))
+            .arg(format!("run_make_support={}", &support_lib_path))
             .arg("--edition=2021")
             .arg(&self.testpaths.file.join("rmake.rs"))
             .arg("-Cprefer-dynamic");
@@ -240,7 +244,7 @@ impl TestCx<'_> {
         if self.config.target.contains("msvc") && !self.config.cc.is_empty() {
             // We need to pass a path to `lib.exe`, so assume that `cc` is `cl.exe`
             // and that `lib.exe` lives next to it.
-            let lib = Path::new(&self.config.cc).parent().unwrap().join("lib.exe");
+            let lib = Utf8Path::new(&self.config.cc).parent().unwrap().join("lib.exe");
 
             // MSYS doesn't like passing flags of the form `/foo` as it thinks it's
             // a path and instead passes `C:\msys64\foo`, so convert all
@@ -262,8 +266,8 @@ impl TestCx<'_> {
 
             cmd.env("IS_MSVC", "1")
                 .env("IS_WINDOWS", "1")
-                .env("MSVC_LIB", format!("'{}' -nologo", lib.display()))
-                .env("MSVC_LIB_PATH", format!("{}", lib.display()))
+                .env("MSVC_LIB", format!("'{}' -nologo", lib))
+                .env("MSVC_LIB_PATH", &lib)
                 // Note: we diverge from legacy run_make and don't lump `CC` the compiler and
                 // default flags together.
                 .env("CC_DEFAULT_FLAGS", &cflags)
diff --git a/src/tools/compiletest/src/runtest/ui.rs b/src/tools/compiletest/src/runtest/ui.rs
index 974e5170465..e87b037cd28 100644
--- a/src/tools/compiletest/src/runtest/ui.rs
+++ b/src/tools/compiletest/src/runtest/ui.rs
@@ -68,7 +68,7 @@ impl TestCx<'_> {
             {
                 let mut coverage_file_path = self.config.build_test_suite_root.clone();
                 coverage_file_path.push("rustfix_missing_coverage.txt");
-                debug!("coverage_file_path: {}", coverage_file_path.display());
+                debug!("coverage_file_path: {}", coverage_file_path);
 
                 let mut file = OpenOptions::new()
                     .create(true)
@@ -76,8 +76,8 @@ impl TestCx<'_> {
                     .open(coverage_file_path.as_path())
                     .expect("could not create or open file");
 
-                if let Err(e) = writeln!(file, "{}", self.testpaths.file.display()) {
-                    panic!("couldn't write to {}: {e:?}", coverage_file_path.display());
+                if let Err(e) = writeln!(file, "{}", self.testpaths.file) {
+                    panic!("couldn't write to {}: {e:?}", coverage_file_path);
                 }
             }
         } else if self.props.run_rustfix {
@@ -119,7 +119,7 @@ impl TestCx<'_> {
                 self.testpaths.relative_dir.join(self.testpaths.file.file_name().unwrap());
             println!(
                 "To only update this specific test, also pass `--test-args {}`",
-                relative_path_to_file.display(),
+                relative_path_to_file,
             );
             self.fatal_proc_rec(
                 &format!("{} errors occurred comparing output.", errors),
@@ -211,8 +211,6 @@ impl TestCx<'_> {
                 let crate_name =
                     self.testpaths.file.file_stem().expect("test must have a file stem");
                 // crate name must be alphanumeric or `_`.
-                let crate_name =
-                    crate_name.to_str().expect("crate name implies file name must be valid UTF-8");
                 // replace `a.foo` -> `a__foo` for crate name purposes.
                 // replace `revision-name-with-dashes` -> `revision_name_with_underscore`
                 let crate_name = crate_name.replace('.', "__");
diff --git a/src/tools/compiletest/src/tests.rs b/src/tools/compiletest/src/tests.rs
index 43c6dc0a67e..e3e4a81755d 100644
--- a/src/tools/compiletest/src/tests.rs
+++ b/src/tools/compiletest/src/tests.rs
@@ -1,5 +1,3 @@
-use std::ffi::OsString;
-
 use crate::debuggers::{extract_gdb_version, extract_lldb_version};
 use crate::is_test;
 
@@ -60,11 +58,11 @@ fn test_extract_lldb_version() {
 
 #[test]
 fn is_test_test() {
-    assert!(is_test(&OsString::from("a_test.rs")));
-    assert!(!is_test(&OsString::from(".a_test.rs")));
-    assert!(!is_test(&OsString::from("a_cat.gif")));
-    assert!(!is_test(&OsString::from("#a_dog_gif")));
-    assert!(!is_test(&OsString::from("~a_temp_file")));
+    assert!(is_test("a_test.rs"));
+    assert!(!is_test(".a_test.rs"));
+    assert!(!is_test("a_cat.gif"));
+    assert!(!is_test("#a_dog_gif"));
+    assert!(!is_test("~a_temp_file"));
 }
 
 #[test]
diff --git a/src/tools/compiletest/src/util.rs b/src/tools/compiletest/src/util.rs
index bff02f1db9f..81f5679aead 100644
--- a/src/tools/compiletest/src/util.rs
+++ b/src/tools/compiletest/src/util.rs
@@ -1,8 +1,7 @@
 use std::env;
-use std::ffi::OsStr;
-use std::path::{Path, PathBuf};
 use std::process::Command;
 
+use camino::{Utf8Path, Utf8PathBuf};
 use tracing::*;
 
 use crate::common::Config;
@@ -34,21 +33,21 @@ pub fn logv(config: &Config, s: String) {
     }
 }
 
-pub trait PathBufExt {
+pub trait Utf8PathBufExt {
     /// Append an extension to the path, even if it already has one.
-    fn with_extra_extension<S: AsRef<OsStr>>(&self, extension: S) -> PathBuf;
+    fn with_extra_extension(&self, extension: &str) -> Utf8PathBuf;
 }
 
-impl PathBufExt for PathBuf {
-    fn with_extra_extension<S: AsRef<OsStr>>(&self, extension: S) -> PathBuf {
-        if extension.as_ref().is_empty() {
+impl Utf8PathBufExt for Utf8PathBuf {
+    fn with_extra_extension(&self, extension: &str) -> Utf8PathBuf {
+        if extension.is_empty() {
             self.clone()
         } else {
-            let mut fname = self.file_name().unwrap().to_os_string();
-            if !extension.as_ref().to_str().unwrap().starts_with('.') {
-                fname.push(".");
+            let mut fname = self.file_name().unwrap().to_string();
+            if !extension.starts_with('.') {
+                fname.push_str(".");
             }
-            fname.push(extension);
+            fname.push_str(extension);
             self.with_file_name(fname)
         }
     }
@@ -71,22 +70,27 @@ pub fn dylib_env_var() -> &'static str {
 
 /// Adds a list of lookup paths to `cmd`'s dynamic library lookup path.
 /// If the dylib_path_var is already set for this cmd, the old value will be overwritten!
-pub fn add_dylib_path(cmd: &mut Command, paths: impl Iterator<Item = impl Into<PathBuf>>) {
+pub fn add_dylib_path(
+    cmd: &mut Command,
+    paths: impl Iterator<Item = impl Into<std::path::PathBuf>>,
+) {
     let path_env = env::var_os(dylib_env_var());
     let old_paths = path_env.as_ref().map(env::split_paths);
     let new_paths = paths.map(Into::into).chain(old_paths.into_iter().flatten());
     cmd.env(dylib_env_var(), env::join_paths(new_paths).unwrap());
 }
 
-pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> std::io::Result<()> {
-    std::fs::create_dir_all(&dst)?;
-    for entry in std::fs::read_dir(src)? {
+pub fn copy_dir_all(src: &Utf8Path, dst: &Utf8Path) -> std::io::Result<()> {
+    std::fs::create_dir_all(dst.as_std_path())?;
+    for entry in std::fs::read_dir(src.as_std_path())? {
         let entry = entry?;
+        let path = Utf8PathBuf::try_from(entry.path()).unwrap();
+        let file_name = path.file_name().unwrap();
         let ty = entry.file_type()?;
         if ty.is_dir() {
-            copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
+            copy_dir_all(&path, &dst.join(file_name))?;
         } else {
-            std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
+            std::fs::copy(path.as_std_path(), dst.join(file_name).as_std_path())?;
         }
     }
     Ok(())
diff --git a/src/tools/compiletest/src/util/tests.rs b/src/tools/compiletest/src/util/tests.rs
index b09a183b14e..5bcae0dcee1 100644
--- a/src/tools/compiletest/src/util/tests.rs
+++ b/src/tools/compiletest/src/util/tests.rs
@@ -3,12 +3,12 @@ use super::*;
 #[test]
 fn path_buf_with_extra_extension_test() {
     assert_eq!(
-        PathBuf::from("foo.rs.stderr"),
-        PathBuf::from("foo.rs").with_extra_extension("stderr")
+        Utf8PathBuf::from("foo.rs.stderr"),
+        Utf8PathBuf::from("foo.rs").with_extra_extension("stderr")
     );
     assert_eq!(
-        PathBuf::from("foo.rs.stderr"),
-        PathBuf::from("foo.rs").with_extra_extension(".stderr")
+        Utf8PathBuf::from("foo.rs.stderr"),
+        Utf8PathBuf::from("foo.rs").with_extra_extension(".stderr")
     );
-    assert_eq!(PathBuf::from("foo.rs"), PathBuf::from("foo.rs").with_extra_extension(""));
+    assert_eq!(Utf8PathBuf::from("foo.rs"), Utf8PathBuf::from("foo.rs").with_extra_extension(""));
 }
diff --git a/src/tools/generate-windows-sys/Cargo.toml b/src/tools/generate-windows-sys/Cargo.toml
index f5c0e56bb3c..152cd504abd 100644
--- a/src/tools/generate-windows-sys/Cargo.toml
+++ b/src/tools/generate-windows-sys/Cargo.toml
@@ -4,4 +4,4 @@ version = "0.1.0"
 edition = "2021"
 
 [dependencies.windows-bindgen]
-version = "0.59.0"
+version = "0.61.0"
diff --git a/src/tools/miri/test_dependencies/Cargo.lock b/src/tools/miri/test_dependencies/Cargo.lock
index 24a8efc873d..276c518e74f 100644
--- a/src/tools/miri/test_dependencies/Cargo.lock
+++ b/src/tools/miri/test_dependencies/Cargo.lock
@@ -156,9 +156,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
 
 [[package]]
 name = "miniz_oxide"
-version = "0.8.7"
+version = "0.8.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430"
+checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
 dependencies = [
  "adler2",
 ]
diff --git a/src/tools/opt-dist/src/main.rs b/src/tools/opt-dist/src/main.rs
index ac5d294f07e..ff4ddbaea49 100644
--- a/src/tools/opt-dist/src/main.rs
+++ b/src/tools/opt-dist/src/main.rs
@@ -76,7 +76,7 @@ enum EnvironmentCmd {
         rustc_perf_checkout_dir: Option<Utf8PathBuf>,
 
         /// Is LLVM for `rustc` built in shared library mode?
-        #[arg(long, default_value_t = true)]
+        #[arg(long, default_value_t = true, action(clap::ArgAction::Set))]
         llvm_shared: bool,
 
         /// Should BOLT optimization be used? If yes, host LLVM must have BOLT binaries
diff --git a/src/tools/opt-dist/src/training.rs b/src/tools/opt-dist/src/training.rs
index 30c79f95947..47159a43140 100644
--- a/src/tools/opt-dist/src/training.rs
+++ b/src/tools/opt-dist/src/training.rs
@@ -70,7 +70,9 @@ fn merge_llvm_profiles(
     profdata: LlvmProfdata,
 ) -> anyhow::Result<()> {
     let llvm_profdata = match profdata {
-        LlvmProfdata::Host => env.host_llvm_dir().join("bin/llvm-profdata"),
+        LlvmProfdata::Host => {
+            env.host_llvm_dir().join(format!("bin/llvm-profdata{}", executable_extension()))
+        }
         LlvmProfdata::Target => env
             .build_artifacts()
             .join("llvm")
diff --git a/src/tools/opt-dist/src/utils/mod.rs b/src/tools/opt-dist/src/utils/mod.rs
index 32d88a59af9..fb4f14ea41a 100644
--- a/src/tools/opt-dist/src/utils/mod.rs
+++ b/src/tools/opt-dist/src/utils/mod.rs
@@ -36,7 +36,9 @@ pub fn clear_llvm_files(env: &Environment) -> anyhow::Result<()> {
     // directories ourselves.
     log::info!("Clearing LLVM build files");
     delete_directory(&env.build_artifacts().join("llvm"))?;
-    delete_directory(&env.build_artifacts().join("lld"))?;
+    if env.build_artifacts().join("lld").is_dir() {
+        delete_directory(&env.build_artifacts().join("lld"))?;
+    }
     Ok(())
 }
 
diff --git a/src/tools/run-make-support/src/artifact_names.rs b/src/tools/run-make-support/src/artifact_names.rs
index 8968f831542..b0d588d3550 100644
--- a/src/tools/run-make-support/src/artifact_names.rs
+++ b/src/tools/run-make-support/src/artifact_names.rs
@@ -1,11 +1,11 @@
 //! A collection of helpers to construct artifact names, such as names of dynamic or static
-//! librarys which are target-dependent.
-
-// FIXME(jieyouxu): convert these to return `PathBuf`s instead of strings!
+//! libraries which are target-dependent.
 
+use crate::target;
 use crate::targets::is_msvc;
 
 /// Construct the static library name based on the target.
+#[track_caller]
 #[must_use]
 pub fn static_lib_name(name: &str) -> String {
     assert!(!name.contains(char::is_whitespace), "static library name cannot contain whitespace");
@@ -14,15 +14,34 @@ pub fn static_lib_name(name: &str) -> String {
 }
 
 /// Construct the dynamic library name based on the target.
+#[track_caller]
 #[must_use]
 pub fn dynamic_lib_name(name: &str) -> String {
     assert!(!name.contains(char::is_whitespace), "dynamic library name cannot contain whitespace");
 
-    format!("{}{name}.{}", std::env::consts::DLL_PREFIX, std::env::consts::DLL_EXTENSION)
+    format!("{}{name}.{}", dynamic_lib_prefix(), dynamic_lib_extension())
+}
+
+fn dynamic_lib_prefix() -> &'static str {
+    if target().contains("windows") { "" } else { "lib" }
 }
 
-/// Construct the name of the import library for the dynamic library, exclusive to MSVC and
-/// accepted by link.exe.
+/// Construct the dynamic library extension based on the target.
+#[must_use]
+pub fn dynamic_lib_extension() -> &'static str {
+    let target = target();
+
+    if target.contains("apple") {
+        "dylib"
+    } else if target.contains("windows") {
+        "dll"
+    } else {
+        "so"
+    }
+}
+
+/// Construct the name of the import library for the dynamic library, exclusive to MSVC and accepted
+/// by link.exe.
 #[track_caller]
 #[must_use]
 pub fn msvc_import_dynamic_lib_name(name: &str) -> String {
@@ -32,20 +51,28 @@ pub fn msvc_import_dynamic_lib_name(name: &str) -> String {
     format!("{name}.dll.lib")
 }
 
-/// Construct the dynamic library extension based on the target.
-#[must_use]
-pub fn dynamic_lib_extension() -> &'static str {
-    std::env::consts::DLL_EXTENSION
-}
-
 /// Construct the name of a rust library (rlib).
+#[track_caller]
 #[must_use]
 pub fn rust_lib_name(name: &str) -> String {
     format!("lib{name}.rlib")
 }
 
 /// Construct the binary (executable) name based on the target.
+#[track_caller]
 #[must_use]
 pub fn bin_name(name: &str) -> String {
-    format!("{name}{}", std::env::consts::EXE_SUFFIX)
+    let target = target();
+
+    if target.contains("windows") {
+        format!("{name}.exe")
+    } else if target.contains("uefi") {
+        format!("{name}.efi")
+    } else if target.contains("wasm") {
+        format!("{name}.wasm")
+    } else if target.contains("nvptx") {
+        format!("{name}.ptx")
+    } else {
+        name.to_string()
+    }
 }
diff --git a/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs b/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs
index 0a7a7d1fb24..706d04484f6 100644
--- a/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs
+++ b/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs
@@ -3795,35 +3795,6 @@ The tracking issue for this feature is: [#64797]
         deny_since: None,
     },
     Lint {
-        label: "cfg_boolean_literals",
-        description: r##"# `cfg_boolean_literals`
-
-The tracking issue for this feature is: [#131204]
-
-[#131204]: https://github.com/rust-lang/rust/issues/131204
-
-------------------------
-
-The `cfg_boolean_literals` feature makes it possible to use the `true`/`false`
-literal as cfg predicate. They always evaluate to true/false respectively.
-
-## Examples
-
-```rust
-#![feature(cfg_boolean_literals)]
-
-#[cfg(true)]
-const A: i32 = 5;
-
-#[cfg(all(false))]
-const A: i32 = 58 * 89;
-```
-"##,
-        default_severity: Severity::Allow,
-        warn_since: None,
-        deny_since: None,
-    },
-    Lint {
         label: "cfg_eval",
         description: r##"# `cfg_eval`
 
diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs
index 59293ee3f96..80f6d85a3d0 100644
--- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs
+++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/rust_analyzer_span.rs
@@ -11,7 +11,7 @@ use std::{
 
 use intern::Symbol;
 use proc_macro::bridge::{self, server};
-use span::{FileId, Span, FIXUP_ERASED_FILE_AST_ID_MARKER};
+use span::{Span, FIXUP_ERASED_FILE_AST_ID_MARKER};
 use tt::{TextRange, TextSize};
 
 use crate::server_impl::{literal_kind_to_internal, token_stream::TokenStreamBuilder, TopSubtree};
@@ -27,10 +27,6 @@ mod tt {
 
 type TokenStream = crate::server_impl::TokenStream<Span>;
 
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-pub struct SourceFile {
-    file_id: FileId,
-}
 pub struct FreeFunctions;
 
 pub struct RaSpanServer {
@@ -46,7 +42,6 @@ pub struct RaSpanServer {
 impl server::Types for RaSpanServer {
     type FreeFunctions = FreeFunctions;
     type TokenStream = TokenStream;
-    type SourceFile = SourceFile;
     type Span = Span;
     type Symbol = Symbol;
 }
@@ -245,25 +240,17 @@ impl server::TokenStream for RaSpanServer {
     }
 }
 
-impl server::SourceFile for RaSpanServer {
-    fn eq(&mut self, file1: &Self::SourceFile, file2: &Self::SourceFile) -> bool {
-        file1 == file2
-    }
-    fn path(&mut self, _file: &Self::SourceFile) -> String {
-        // FIXME
-        String::new()
-    }
-    fn is_real(&mut self, _file: &Self::SourceFile) -> bool {
-        true
-    }
-}
-
 impl server::Span for RaSpanServer {
     fn debug(&mut self, span: Self::Span) -> String {
         format!("{:?}", span)
     }
-    fn source_file(&mut self, span: Self::Span) -> Self::SourceFile {
-        SourceFile { file_id: span.anchor.file_id.file_id() }
+    fn file(&mut self, _: Self::Span) -> String {
+        // FIXME
+        String::new()
+    }
+    fn local_file(&mut self, _: Self::Span) -> Option<String> {
+        // FIXME
+        None
     }
     fn save_span(&mut self, _span: Self::Span) -> usize {
         // FIXME, quote is incompatible with third-party tools
diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs
index 409cf3cc781..4d7c7c46766 100644
--- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs
+++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/server_impl/token_id.rs
@@ -24,8 +24,6 @@ type Literal = tt::Literal;
 type Span = tt::TokenId;
 type TokenStream = crate::server_impl::TokenStream<Span>;
 
-#[derive(Clone)]
-pub struct SourceFile;
 pub struct FreeFunctions;
 
 pub struct TokenIdServer {
@@ -37,7 +35,6 @@ pub struct TokenIdServer {
 impl server::Types for TokenIdServer {
     type FreeFunctions = FreeFunctions;
     type TokenStream = TokenStream;
-    type SourceFile = SourceFile;
     type Span = Span;
     type Symbol = Symbol;
 }
@@ -223,24 +220,15 @@ impl server::TokenStream for TokenIdServer {
     }
 }
 
-impl server::SourceFile for TokenIdServer {
-    fn eq(&mut self, _file1: &Self::SourceFile, _file2: &Self::SourceFile) -> bool {
-        true
-    }
-    fn path(&mut self, _file: &Self::SourceFile) -> String {
-        String::new()
-    }
-    fn is_real(&mut self, _file: &Self::SourceFile) -> bool {
-        true
-    }
-}
-
 impl server::Span for TokenIdServer {
     fn debug(&mut self, span: Self::Span) -> String {
         format!("{:?}", span.0)
     }
-    fn source_file(&mut self, _span: Self::Span) -> Self::SourceFile {
-        SourceFile {}
+    fn file(&mut self, _span: Self::Span) -> String {
+        String::new()
+    }
+    fn local_file(&mut self, _span: Self::Span) -> Option<String> {
+        None
     }
     fn save_span(&mut self, _span: Self::Span) -> usize {
         0
diff --git a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs
index 15de88ea656..4bd365be7ca 100644
--- a/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs
+++ b/src/tools/rust-analyzer/crates/proc-macro-srv/src/tests/mod.rs
@@ -97,6 +97,7 @@ fn test_fn_like_macro_clone_raw_ident() {
 }
 
 #[test]
+#[cfg(not(bootstrap))]
 fn test_fn_like_fn_like_span_join() {
     assert_expand(
         "fn_like_span_join",
@@ -111,6 +112,7 @@ fn test_fn_like_fn_like_span_join() {
 }
 
 #[test]
+#[cfg(not(bootstrap))]
 fn test_fn_like_fn_like_span_ops() {
     assert_expand(
         "fn_like_span_ops",
diff --git a/src/tools/rustbook/Cargo.lock b/src/tools/rustbook/Cargo.lock
index 09bbbd61de5..c14372dd6ae 100644
--- a/src/tools/rustbook/Cargo.lock
+++ b/src/tools/rustbook/Cargo.lock
@@ -951,6 +951,7 @@ dependencies = [
  "once_cell",
  "pathdiff",
  "pulldown-cmark 0.10.3",
+ "railroad",
  "regex",
  "semver",
  "serde_json",
@@ -981,9 +982,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
 
 [[package]]
 name = "miniz_oxide"
-version = "0.8.7"
+version = "0.8.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430"
+checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
 dependencies = [
  "adler2",
 ]
@@ -1301,6 +1302,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
 
 [[package]]
+name = "railroad"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ecedffc46c1b2cb04f4b80e094eae6b3f3f470a9635f1f396dd5206428f6b58"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
 name = "rand"
 version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/src/tools/rustdoc-gui-test/Cargo.toml b/src/tools/rustdoc-gui-test/Cargo.toml
index f7384a98f85..8d958ac94f3 100644
--- a/src/tools/rustdoc-gui-test/Cargo.toml
+++ b/src/tools/rustdoc-gui-test/Cargo.toml
@@ -5,6 +5,7 @@ edition = "2021"
 
 [dependencies]
 build_helper = { path = "../../build_helper" }
+camino = "1"
 compiletest = { path = "../compiletest" }
 getopts = "0.2"
 walkdir = "2"
diff --git a/src/tools/rustdoc-gui-test/src/main.rs b/src/tools/rustdoc-gui-test/src/main.rs
index f1c6e13d3ae..addb0af4a54 100644
--- a/src/tools/rustdoc-gui-test/src/main.rs
+++ b/src/tools/rustdoc-gui-test/src/main.rs
@@ -118,7 +118,11 @@ If you want to install the `browser-ui-test` dependency, run `npm install browse
                     ..Default::default()
                 };
 
-                let test_props = TestProps::from_file(&librs, None, &compiletest_c);
+                let test_props = TestProps::from_file(
+                    &camino::Utf8PathBuf::try_from(librs).unwrap(),
+                    None,
+                    &compiletest_c,
+                );
 
                 if !test_props.compile_flags.is_empty() {
                     cargo.env("RUSTDOCFLAGS", test_props.compile_flags.join(" "));
diff --git a/src/tools/rustfmt/src/macros.rs b/src/tools/rustfmt/src/macros.rs
index ddf3d2ce96a..1e16aace304 100644
--- a/src/tools/rustfmt/src/macros.rs
+++ b/src/tools/rustfmt/src/macros.rs
@@ -858,18 +858,18 @@ impl MacroArgParser {
         };
 
         self.result.push(ParsedMacroArg {
-            kind: MacroArgKind::Repeat(delim, inner, another, self.last_tok.clone()),
+            kind: MacroArgKind::Repeat(delim, inner, another, self.last_tok),
         });
         Some(())
     }
 
-    fn update_buffer(&mut self, t: &Token) {
+    fn update_buffer(&mut self, t: Token) {
         if self.buf.is_empty() {
-            self.start_tok = t.clone();
+            self.start_tok = t;
         } else {
             let needs_space = match next_space(&self.last_tok.kind) {
-                SpaceState::Ident => ident_like(t),
-                SpaceState::Punctuation => !ident_like(t),
+                SpaceState::Ident => ident_like(&t),
+                SpaceState::Punctuation => !ident_like(&t),
                 SpaceState::Always => true,
                 SpaceState::Never => false,
             };
@@ -878,7 +878,7 @@ impl MacroArgParser {
             }
         }
 
-        self.buf.push_str(&pprust::token_to_string(t));
+        self.buf.push_str(&pprust::token_to_string(&t));
     }
 
     fn need_space_prefix(&self) -> bool {
@@ -937,7 +937,7 @@ impl MacroArgParser {
                 ) if self.is_meta_var => {
                     self.add_meta_variable(&mut iter)?;
                 }
-                TokenTree::Token(ref t, _) => self.update_buffer(t),
+                &TokenTree::Token(t, _) => self.update_buffer(t),
                 &TokenTree::Delimited(_dspan, _spacing, delimited, ref tts) => {
                     if !self.buf.is_empty() {
                         if next_space(&self.last_tok.kind) == SpaceState::Always {
diff --git a/src/tools/tidy/src/deps.rs b/src/tools/tidy/src/deps.rs
index bf3b4112324..fd8e4eacc0f 100644
--- a/src/tools/tidy/src/deps.rs
+++ b/src/tools/tidy/src/deps.rs
@@ -132,15 +132,16 @@ const EXCEPTIONS_CARGO: ExceptionList = &[
     ("fiat-crypto", "MIT OR Apache-2.0 OR BSD-1-Clause"),
     ("foldhash", "Zlib"),
     ("im-rc", "MPL-2.0+"),
+    ("libz-rs-sys", "Zlib"),
     ("normalize-line-endings", "Apache-2.0"),
     ("openssl", "Apache-2.0"),
     ("ryu", "Apache-2.0 OR BSL-1.0"), // BSL is not acceptble, but we use it under Apache-2.0
-    ("sha1_smol", "BSD-3-Clause"),
     ("similar", "Apache-2.0"),
     ("sized-chunks", "MPL-2.0+"),
     ("subtle", "BSD-3-Clause"),
     ("supports-hyperlinks", "Apache-2.0"),
     ("unicode-bom", "Apache-2.0"),
+    ("zlib-rs", "Zlib"),
     // tidy-alphabetical-end
 ];
 
@@ -269,7 +270,6 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[
     "darling_core",
     "darling_macro",
     "datafrog",
-    "deranged",
     "derive-where",
     "derive_setters",
     "digest",
@@ -310,6 +310,8 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[
     "intl_pluralrules",
     "itertools",
     "itoa",
+    "jiff",
+    "jiff-static",
     "jobserver",
     "lazy_static",
     "leb128",
@@ -327,7 +329,6 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[
     "miniz_oxide",
     "nix",
     "nu-ansi-term",
-    "num-conv",
     "num_cpus",
     "object",
     "odht",
@@ -340,7 +341,7 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[
     "pin-project-lite",
     "polonius-engine",
     "portable-atomic", // dependency for platforms doesn't support `AtomicU64` in std
-    "powerfmt",
+    "portable-atomic-util",
     "ppv-lite86",
     "proc-macro-hack",
     "proc-macro2",
@@ -393,9 +394,6 @@ const PERMITTED_RUSTC_DEPENDENCIES: &[&str] = &[
     "thorin-dwp",
     "thread_local",
     "tikv-jemalloc-sys",
-    "time",
-    "time-core",
-    "time-macros",
     "tinystr",
     "tinyvec",
     "tinyvec_macros",
@@ -685,8 +683,10 @@ pub static CRATES: &[&str] = &[
 pub fn has_missing_submodule(root: &Path, submodules: &[&str]) -> bool {
     !CiEnv::is_ci()
         && submodules.iter().any(|submodule| {
+            let path = root.join(submodule);
+            !path.exists()
             // If the directory is empty, we can consider it as an uninitialized submodule.
-            read_dir(root.join(submodule)).unwrap().next().is_none()
+            || read_dir(path).unwrap().next().is_none()
         })
 }