diff --git a/Cargo.lock b/Cargo.lock
index bf519e0392605..901113bbff561 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2312,6 +2312,7 @@ name = "miri"
 version = "0.1.0"
 dependencies = [
  "aes",
+ "bitflags",
  "chrono",
  "chrono-tz",
  "colored",
diff --git a/compiler/rustc_ast/src/expand/mod.rs b/compiler/rustc_ast/src/expand/mod.rs
index 04c8162932369..323a8fab6d592 100644
--- a/compiler/rustc_ast/src/expand/mod.rs
+++ b/compiler/rustc_ast/src/expand/mod.rs
@@ -13,12 +13,12 @@ pub mod typetree;
 #[derive(Debug, Clone, Encodable, Decodable, HashStable_Generic)]
 pub struct StrippedCfgItem<ModId = DefId> {
     pub parent_module: ModId,
-    pub name: Ident,
+    pub ident: Ident,
     pub cfg: MetaItem,
 }
 
 impl<ModId> StrippedCfgItem<ModId> {
     pub fn map_mod_id<New>(self, f: impl FnOnce(ModId) -> New) -> StrippedCfgItem<New> {
-        StrippedCfgItem { parent_module: f(self.parent_module), name: self.name, cfg: self.cfg }
+        StrippedCfgItem { parent_module: f(self.parent_module), ident: self.ident, cfg: self.cfg }
     }
 }
diff --git a/compiler/rustc_ast_lowering/src/delegation.rs b/compiler/rustc_ast_lowering/src/delegation.rs
index 9899ee03a513a..2296b05f69b4c 100644
--- a/compiler/rustc_ast_lowering/src/delegation.rs
+++ b/compiler/rustc_ast_lowering/src/delegation.rs
@@ -47,7 +47,7 @@ use rustc_errors::ErrorGuaranteed;
 use rustc_hir::def_id::DefId;
 use rustc_middle::span_bug;
 use rustc_middle::ty::{Asyncness, ResolverAstLowering};
-use rustc_span::{Ident, Span};
+use rustc_span::{Ident, Span, Symbol};
 use {rustc_ast as ast, rustc_hir as hir};
 
 use super::{GenericArgsMode, ImplTraitContext, LoweringContext, ParamMode};
@@ -234,12 +234,13 @@ impl<'hir> LoweringContext<'_, 'hir> {
         hir::FnSig { decl, header, span }
     }
 
-    fn generate_param(&mut self, span: Span) -> (hir::Param<'hir>, NodeId) {
+    fn generate_param(&mut self, idx: usize, span: Span) -> (hir::Param<'hir>, NodeId) {
         let pat_node_id = self.next_node_id();
         let pat_id = self.lower_node_id(pat_node_id);
+        let ident = Ident::with_dummy_span(Symbol::intern(&format!("arg{idx}")));
         let pat = self.arena.alloc(hir::Pat {
             hir_id: pat_id,
-            kind: hir::PatKind::Binding(hir::BindingMode::NONE, pat_id, Ident::empty(), None),
+            kind: hir::PatKind::Binding(hir::BindingMode::NONE, pat_id, ident, None),
             span,
             default_binding_modes: false,
         });
@@ -247,9 +248,9 @@ impl<'hir> LoweringContext<'_, 'hir> {
         (hir::Param { hir_id: self.next_id(), pat, ty_span: span, span }, pat_node_id)
     }
 
-    fn generate_arg(&mut self, param_id: HirId, span: Span) -> hir::Expr<'hir> {
+    fn generate_arg(&mut self, idx: usize, param_id: HirId, span: Span) -> hir::Expr<'hir> {
         let segments = self.arena.alloc_from_iter(iter::once(hir::PathSegment {
-            ident: Ident::empty(),
+            ident: Ident::with_dummy_span(Symbol::intern(&format!("arg{idx}"))),
             hir_id: self.next_id(),
             res: Res::Local(param_id),
             args: None,
@@ -273,7 +274,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
             let mut args: Vec<hir::Expr<'_>> = Vec::with_capacity(param_count);
 
             for idx in 0..param_count {
-                let (param, pat_node_id) = this.generate_param(span);
+                let (param, pat_node_id) = this.generate_param(idx, span);
                 parameters.push(param);
 
                 let arg = if let Some(block) = block
@@ -289,7 +290,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
                     this.ident_and_label_to_local_id.insert(pat_node_id, param.pat.hir_id.local_id);
                     this.lower_target_expr(&block)
                 } else {
-                    this.generate_arg(param.pat.hir_id, span)
+                    this.generate_arg(idx, param.pat.hir_id, span)
                 };
                 args.push(arg);
             }
diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs
index 958a6917dff87..59099e5a55451 100644
--- a/compiler/rustc_ast_lowering/src/item.rs
+++ b/compiler/rustc_ast_lowering/src/item.rs
@@ -645,7 +645,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
                         (
                             // Disallow `impl Trait` in foreign items.
                             this.lower_fn_decl(fdec, i.id, sig.span, FnDeclKind::ExternFn, None),
-                            this.lower_fn_params_to_names(fdec),
+                            this.lower_fn_params_to_idents(fdec),
                         )
                     });
 
@@ -833,7 +833,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
             }) => {
                 // FIXME(contracts): Deny contract here since it won't apply to
                 // any impl method or callees.
-                let names = self.lower_fn_params_to_names(&sig.decl);
+                let idents = self.lower_fn_params_to_idents(&sig.decl);
                 let (generics, sig) = self.lower_method_sig(
                     generics,
                     sig,
@@ -851,7 +851,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
                 (
                     *ident,
                     generics,
-                    hir::TraitItemKind::Fn(sig, hir::TraitFn::Required(names)),
+                    hir::TraitItemKind::Fn(sig, hir::TraitFn::Required(idents)),
                     false,
                 )
             }
diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs
index 6aa6a18ee9a72..446e02e402420 100644
--- a/compiler/rustc_ast_lowering/src/lib.rs
+++ b/compiler/rustc_ast_lowering/src/lib.rs
@@ -1247,7 +1247,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
                     safety: self.lower_safety(f.safety, hir::Safety::Safe),
                     abi: self.lower_extern(f.ext),
                     decl: self.lower_fn_decl(&f.decl, t.id, t.span, FnDeclKind::Pointer, None),
-                    param_names: self.lower_fn_params_to_names(&f.decl),
+                    param_idents: self.lower_fn_params_to_idents(&f.decl),
                 }))
             }
             TyKind::UnsafeBinder(f) => {
@@ -1494,7 +1494,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
         }))
     }
 
-    fn lower_fn_params_to_names(&mut self, decl: &FnDecl) -> &'hir [Option<Ident>] {
+    fn lower_fn_params_to_idents(&mut self, decl: &FnDecl) -> &'hir [Option<Ident>] {
         self.arena.alloc_from_iter(decl.inputs.iter().map(|param| match param.pat.kind {
             PatKind::Missing => None,
             PatKind::Ident(_, ident, _) => Some(self.lower_ident(ident)),
diff --git a/compiler/rustc_ast_pretty/src/pprust/tests.rs b/compiler/rustc_ast_pretty/src/pprust/tests.rs
index 4c42dd1f2023f..bc7f22766a5ca 100644
--- a/compiler/rustc_ast_pretty/src/pprust/tests.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/tests.rs
@@ -7,12 +7,12 @@ use super::*;
 fn fun_to_string(
     decl: &ast::FnDecl,
     header: ast::FnHeader,
-    name: Ident,
+    ident: Ident,
     generics: &ast::Generics,
 ) -> String {
     to_string(|s| {
         s.head("");
-        s.print_fn(decl, header, Some(name), generics);
+        s.print_fn(decl, header, Some(ident), generics);
         s.end(); // Close the head box.
         s.end(); // Close the outer box.
     })
diff --git a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
index 1f4eb0c449f8a..8a8ecc3b96e3c 100644
--- a/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
@@ -2500,11 +2500,11 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
         );
         let ty::Tuple(params) = tupled_params.kind() else { return };
 
-        // Find the first argument with a matching type, get its name
-        let Some(this_name) = params.iter().zip(tcx.hir_body_param_names(closure.body)).find_map(
-            |(param_ty, name)| {
+        // Find the first argument with a matching type and get its identifier.
+        let Some(this_name) = params.iter().zip(tcx.hir_body_param_idents(closure.body)).find_map(
+            |(param_ty, ident)| {
                 // FIXME: also support deref for stuff like `Rc` arguments
-                if param_ty.peel_refs() == local_ty { name } else { None }
+                if param_ty.peel_refs() == local_ty { ident } else { None }
             },
         ) else {
             return;
@@ -3774,7 +3774,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
                 method_args,
                 *fn_span,
                 call_source.from_hir_call(),
-                self.infcx.tcx.fn_arg_names(method_did)[0],
+                self.infcx.tcx.fn_arg_idents(method_did)[0],
             )
         {
             err.note(format!("borrow occurs due to deref coercion to `{deref_target_ty}`"));
diff --git a/compiler/rustc_borrowck/src/diagnostics/mod.rs b/compiler/rustc_borrowck/src/diagnostics/mod.rs
index eb664f1d4f214..f9f63ae92a841 100644
--- a/compiler/rustc_borrowck/src/diagnostics/mod.rs
+++ b/compiler/rustc_borrowck/src/diagnostics/mod.rs
@@ -1026,7 +1026,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
                 method_args,
                 *fn_span,
                 call_source.from_hir_call(),
-                self.infcx.tcx.fn_arg_names(method_did)[0],
+                self.infcx.tcx.fn_arg_idents(method_did)[0],
             );
 
             return FnSelfUse {
diff --git a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs
index 8862965c0532c..a91f2d38a93ae 100644
--- a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs
+++ b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs
@@ -20,14 +20,14 @@ use crate::errors;
 struct ProcMacroDerive {
     id: NodeId,
     trait_name: Symbol,
-    function_name: Ident,
+    function_ident: Ident,
     span: Span,
     attrs: Vec<Symbol>,
 }
 
 struct ProcMacroDef {
     id: NodeId,
-    function_name: Ident,
+    function_ident: Ident,
     span: Span,
 }
 
@@ -95,7 +95,7 @@ impl<'a> CollectProcMacros<'a> {
     fn collect_custom_derive(
         &mut self,
         item: &'a ast::Item,
-        function_name: Ident,
+        function_ident: Ident,
         attr: &'a ast::Attribute,
     ) {
         let Some((trait_name, proc_attrs)) =
@@ -109,7 +109,7 @@ impl<'a> CollectProcMacros<'a> {
                 id: item.id,
                 span: item.span,
                 trait_name,
-                function_name,
+                function_ident,
                 attrs: proc_attrs,
             }));
         } else {
@@ -123,12 +123,12 @@ impl<'a> CollectProcMacros<'a> {
         }
     }
 
-    fn collect_attr_proc_macro(&mut self, item: &'a ast::Item, function_name: Ident) {
+    fn collect_attr_proc_macro(&mut self, item: &'a ast::Item, function_ident: Ident) {
         if self.in_root && item.vis.kind.is_pub() {
             self.macros.push(ProcMacro::Attr(ProcMacroDef {
                 id: item.id,
                 span: item.span,
-                function_name,
+                function_ident,
             }));
         } else {
             let msg = if !self.in_root {
@@ -141,12 +141,12 @@ impl<'a> CollectProcMacros<'a> {
         }
     }
 
-    fn collect_bang_proc_macro(&mut self, item: &'a ast::Item, function_name: Ident) {
+    fn collect_bang_proc_macro(&mut self, item: &'a ast::Item, function_ident: Ident) {
         if self.in_root && item.vis.kind.is_pub() {
             self.macros.push(ProcMacro::Bang(ProcMacroDef {
                 id: item.id,
                 span: item.span,
-                function_name,
+                function_ident,
             }));
         } else {
             let msg = if !self.in_root {
@@ -303,7 +303,7 @@ fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> P<ast::Item> {
                 ProcMacro::Derive(m) => m.span,
                 ProcMacro::Attr(m) | ProcMacro::Bang(m) => m.span,
             };
-            let local_path = |cx: &ExtCtxt<'_>, name| cx.expr_path(cx.path(span, vec![name]));
+            let local_path = |cx: &ExtCtxt<'_>, ident| cx.expr_path(cx.path(span, vec![ident]));
             let proc_macro_ty_method_path = |cx: &ExtCtxt<'_>, method| {
                 cx.expr_path(cx.path(
                     span.with_ctxt(harness_span.ctxt()),
@@ -327,7 +327,7 @@ fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> P<ast::Item> {
                                     .map(|&s| cx.expr_str(span, s))
                                     .collect::<ThinVec<_>>(),
                             ),
-                            local_path(cx, cd.function_name),
+                            local_path(cx, cd.function_ident),
                         ],
                     )
                 }
@@ -345,8 +345,8 @@ fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> P<ast::Item> {
                         harness_span,
                         proc_macro_ty_method_path(cx, ident),
                         thin_vec![
-                            cx.expr_str(span, ca.function_name.name),
-                            local_path(cx, ca.function_name),
+                            cx.expr_str(span, ca.function_ident.name),
+                            local_path(cx, ca.function_ident),
                         ],
                     )
                 }
diff --git a/compiler/rustc_codegen_cranelift/src/main_shim.rs b/compiler/rustc_codegen_cranelift/src/main_shim.rs
index 6d5df2b00437b..3b48adb7e918a 100644
--- a/compiler/rustc_codegen_cranelift/src/main_shim.rs
+++ b/compiler/rustc_codegen_cranelift/src/main_shim.rs
@@ -104,7 +104,7 @@ pub(crate) fn maybe_create_entry_wrapper(
                 let termination_trait = tcx.require_lang_item(LangItem::Termination, None);
                 let report = tcx
                     .associated_items(termination_trait)
-                    .find_by_name_and_kind(
+                    .find_by_ident_and_kind(
                         tcx,
                         Ident::from_str("report"),
                         AssocKind::Fn,
diff --git a/compiler/rustc_data_structures/src/sync/freeze.rs b/compiler/rustc_data_structures/src/sync/freeze.rs
index 9720b22ea7d1b..6338afb92c34b 100644
--- a/compiler/rustc_data_structures/src/sync/freeze.rs
+++ b/compiler/rustc_data_structures/src/sync/freeze.rs
@@ -88,7 +88,7 @@ impl<T> FreezeLock<T> {
     #[inline]
     #[track_caller]
     pub fn write(&self) -> FreezeWriteGuard<'_, T> {
-        self.try_write().expect("still mutable")
+        self.try_write().expect("data should not be frozen if we're still attempting to mutate it")
     }
 
     #[inline]
diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs
index 595ac1edd289a..bbc18c371ea0e 100644
--- a/compiler/rustc_driver_impl/src/lib.rs
+++ b/compiler/rustc_driver_impl/src/lib.rs
@@ -348,10 +348,6 @@ pub fn run_compiler(at_args: &[String], callbacks: &mut (dyn Callbacks + Send))
             // Make sure name resolution and macro expansion is run.
             let _ = tcx.resolver_for_lowering();
 
-            if let Some(metrics_dir) = &sess.opts.unstable_opts.metrics_dir {
-                dump_feature_usage_metrics(tcx, metrics_dir);
-            }
-
             if callbacks.after_expansion(compiler, tcx) == Compilation::Stop {
                 return early_exit();
             }
@@ -370,6 +366,10 @@ pub fn run_compiler(at_args: &[String], callbacks: &mut (dyn Callbacks + Send))
 
             tcx.ensure_ok().analysis(());
 
+            if let Some(metrics_dir) = &sess.opts.unstable_opts.metrics_dir {
+                dump_feature_usage_metrics(tcx, metrics_dir);
+            }
+
             if callbacks.after_analysis(compiler, tcx) == Compilation::Stop {
                 return early_exit();
             }
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index d14e476ba3223..49f6d58172ff6 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -1102,7 +1102,7 @@ pub trait ResolverExpand {
     /// HIR proc macros items back to their harness items.
     fn declare_proc_macro(&mut self, id: NodeId);
 
-    fn append_stripped_cfg_item(&mut self, parent_node: NodeId, name: Ident, cfg: ast::MetaItem);
+    fn append_stripped_cfg_item(&mut self, parent_node: NodeId, ident: Ident, cfg: ast::MetaItem);
 
     /// Tools registered with `#![register_tool]` and used by tool attributes and lints.
     fn registered_tools(&self) -> &RegisteredTools;
diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs
index bca846d2ec423..1b539477d51ec 100644
--- a/compiler/rustc_expand/src/expand.rs
+++ b/compiler/rustc_expand/src/expand.rs
@@ -1169,9 +1169,9 @@ trait InvocationCollectorNode: HasAttrs + HasNodeId + Sized {
         collector.cx.dcx().emit_err(RemoveNodeNotSupported { span, descr: Self::descr() });
     }
 
-    /// All of the names (items) declared by this node.
+    /// All of the identifiers (items) declared by this node.
     /// This is an approximation and should only be used for diagnostics.
-    fn declared_names(&self) -> Vec<Ident> {
+    fn declared_idents(&self) -> Vec<Ident> {
         vec![]
     }
 }
@@ -1306,7 +1306,7 @@ impl InvocationCollectorNode for P<ast::Item> {
         res
     }
 
-    fn declared_names(&self) -> Vec<Ident> {
+    fn declared_idents(&self) -> Vec<Ident> {
         if let ItemKind::Use(ut) = &self.kind {
             fn collect_use_tree_leaves(ut: &ast::UseTree, idents: &mut Vec<Ident>) {
                 match &ut.kind {
@@ -2061,10 +2061,10 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
                         }
 
                         if let Some(meta_item) = meta_item {
-                            for name in node.declared_names() {
+                            for ident in node.declared_idents() {
                                 self.cx.resolver.append_stripped_cfg_item(
                                     self.cx.current_expansion.lint_node_id,
-                                    name,
+                                    ident,
                                     meta_item.clone(),
                                 )
                             }
diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs
index c61477951c920..c9c4936c15640 100644
--- a/compiler/rustc_hir/src/hir.rs
+++ b/compiler/rustc_hir/src/hir.rs
@@ -3399,9 +3399,9 @@ pub struct BareFnTy<'hir> {
     pub abi: ExternAbi,
     pub generic_params: &'hir [GenericParam<'hir>],
     pub decl: &'hir FnDecl<'hir>,
-    // `Option` because bare fn parameter names are optional. We also end up
+    // `Option` because bare fn parameter identifiers are optional. We also end up
     // with `None` in some error cases, e.g. invalid parameter patterns.
-    pub param_names: &'hir [Option<Ident>],
+    pub param_idents: &'hir [Option<Ident>],
 }
 
 #[derive(Debug, Clone, Copy, HashStable_Generic)]
diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs
index ea3f396761b16..93d20dfa79966 100644
--- a/compiler/rustc_hir/src/intravisit.rs
+++ b/compiler/rustc_hir/src/intravisit.rs
@@ -652,10 +652,10 @@ pub fn walk_foreign_item<'v, V: Visitor<'v>>(
     try_visit!(visitor.visit_ident(foreign_item.ident));
 
     match foreign_item.kind {
-        ForeignItemKind::Fn(ref sig, param_names, ref generics) => {
+        ForeignItemKind::Fn(ref sig, param_idents, ref generics) => {
             try_visit!(visitor.visit_generics(generics));
             try_visit!(visitor.visit_fn_decl(sig.decl));
-            for ident in param_names.iter().copied() {
+            for ident in param_idents.iter().copied() {
                 visit_opt!(visitor, visit_ident, ident);
             }
         }
@@ -1169,9 +1169,9 @@ pub fn walk_trait_item<'v, V: Visitor<'v>>(
             try_visit!(visitor.visit_ty_unambig(ty));
             visit_opt!(visitor, visit_nested_body, default);
         }
-        TraitItemKind::Fn(ref sig, TraitFn::Required(param_names)) => {
+        TraitItemKind::Fn(ref sig, TraitFn::Required(param_idents)) => {
             try_visit!(visitor.visit_fn_decl(sig.decl));
-            for ident in param_names.iter().copied() {
+            for ident in param_idents.iter().copied() {
                 visit_opt!(visitor, visit_ident, ident);
             }
         }
diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl
index 2f7c3cb3c7d80..92701e3328e92 100644
--- a/compiler/rustc_hir_analysis/messages.ftl
+++ b/compiler/rustc_hir_analysis/messages.ftl
@@ -1,5 +1,5 @@
-hir_analysis_ambiguous_assoc_item = ambiguous associated {$assoc_kind} `{$assoc_name}` in bounds of `{$qself}`
-    .label = ambiguous associated {$assoc_kind} `{$assoc_name}`
+hir_analysis_ambiguous_assoc_item = ambiguous associated {$assoc_kind} `{$assoc_ident}` in bounds of `{$qself}`
+    .label = ambiguous associated {$assoc_kind} `{$assoc_ident}`
 
 hir_analysis_ambiguous_lifetime_bound =
     ambiguous lifetime bound, explicit lifetime bound required
@@ -12,13 +12,13 @@ hir_analysis_assoc_item_is_private = {$kind} `{$name}` is private
     .label = private {$kind}
     .defined_here_label = the {$kind} is defined here
 
-hir_analysis_assoc_item_not_found = associated {$assoc_kind} `{$assoc_name}` not found for `{$qself}`
+hir_analysis_assoc_item_not_found = associated {$assoc_kind} `{$assoc_ident}` not found for `{$qself}`
 
 hir_analysis_assoc_item_not_found_found_in_other_trait_label = there is {$identically_named ->
         [true] an
         *[false] a similarly named
     } associated {$assoc_kind} `{$suggested_name}` in the trait `{$trait_name}`
-hir_analysis_assoc_item_not_found_label = associated {$assoc_kind} `{$assoc_name}` not found
+hir_analysis_assoc_item_not_found_label = associated {$assoc_kind} `{$assoc_ident}` not found
 hir_analysis_assoc_item_not_found_other_sugg = `{$qself}` has the following associated {$assoc_kind}
 hir_analysis_assoc_item_not_found_similar_in_other_trait_qpath_sugg =
     consider fully qualifying{$identically_named ->
diff --git a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs
index 32a8f101849c8..29a9931696ffa 100644
--- a/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs
+++ b/compiler/rustc_hir_analysis/src/check/compare_impl_item.rs
@@ -1046,11 +1046,11 @@ fn report_trait_method_mismatch<'tcx>(
             // argument pattern and type.
             let (sig, body) = tcx.hir_expect_impl_item(impl_m.def_id.expect_local()).expect_fn();
             let span = tcx
-                .hir_body_param_names(body)
+                .hir_body_param_idents(body)
                 .zip(sig.decl.inputs.iter())
-                .map(|(param_name, ty)| {
-                    if let Some(param_name) = param_name {
-                        param_name.span.to(ty.span)
+                .map(|(param_ident, ty)| {
+                    if let Some(param_ident) = param_ident {
+                        param_ident.span.to(ty.span)
                     } else {
                         ty.span
                     }
diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs
index 69b921fccbf03..deded6904d484 100644
--- a/compiler/rustc_hir_analysis/src/collect.rs
+++ b/compiler/rustc_hir_analysis/src/collect.rs
@@ -439,9 +439,9 @@ impl<'tcx> HirTyLowerer<'tcx> for ItemCtxt<'tcx> {
         &self,
         span: Span,
         def_id: LocalDefId,
-        assoc_name: Ident,
+        assoc_ident: Ident,
     ) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]> {
-        self.tcx.at(span).type_param_predicates((self.item_def_id, def_id, assoc_name))
+        self.tcx.at(span).type_param_predicates((self.item_def_id, def_id, assoc_ident))
     }
 
     fn lower_assoc_shared(
diff --git a/compiler/rustc_hir_analysis/src/collect/predicates_of.rs b/compiler/rustc_hir_analysis/src/collect/predicates_of.rs
index e90a1cc24c165..ce0f83d0ec288 100644
--- a/compiler/rustc_hir_analysis/src/collect/predicates_of.rs
+++ b/compiler/rustc_hir_analysis/src/collect/predicates_of.rs
@@ -584,12 +584,12 @@ pub(super) fn explicit_super_predicates_of<'tcx>(
 
 pub(super) fn explicit_supertraits_containing_assoc_item<'tcx>(
     tcx: TyCtxt<'tcx>,
-    (trait_def_id, assoc_name): (DefId, Ident),
+    (trait_def_id, assoc_ident): (DefId, Ident),
 ) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]> {
     implied_predicates_with_filter(
         tcx,
         trait_def_id,
-        PredicateFilter::SelfTraitThatDefines(assoc_name),
+        PredicateFilter::SelfTraitThatDefines(assoc_ident),
     )
 }
 
@@ -617,7 +617,7 @@ pub(super) fn implied_predicates_with_filter<'tcx>(
     filter: PredicateFilter,
 ) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]> {
     let Some(trait_def_id) = trait_def_id.as_local() else {
-        // if `assoc_name` is None, then the query should've been redirected to an
+        // if `assoc_ident` is None, then the query should've been redirected to an
         // external provider
         assert_matches!(filter, PredicateFilter::SelfTraitThatDefines(_));
         return tcx.explicit_super_predicates_of(trait_def_id);
@@ -834,11 +834,11 @@ pub(super) fn assert_only_contains_predicates_from<'tcx>(
 #[instrument(level = "trace", skip(tcx))]
 pub(super) fn type_param_predicates<'tcx>(
     tcx: TyCtxt<'tcx>,
-    (item_def_id, def_id, assoc_name): (LocalDefId, LocalDefId, Ident),
+    (item_def_id, def_id, assoc_ident): (LocalDefId, LocalDefId, Ident),
 ) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]> {
     match tcx.opt_rpitit_info(item_def_id.to_def_id()) {
         Some(ty::ImplTraitInTraitData::Trait { opaque_def_id, .. }) => {
-            return tcx.type_param_predicates((opaque_def_id.expect_local(), def_id, assoc_name));
+            return tcx.type_param_predicates((opaque_def_id.expect_local(), def_id, assoc_ident));
         }
         Some(ty::ImplTraitInTraitData::Impl { .. }) => {
             unreachable!("should not be lowering bounds on RPITIT in impl")
@@ -863,7 +863,7 @@ pub(super) fn type_param_predicates<'tcx>(
 
     let result = if let Some(parent) = parent {
         let icx = ItemCtxt::new(tcx, parent);
-        icx.probe_ty_param_bounds(DUMMY_SP, def_id, assoc_name)
+        icx.probe_ty_param_bounds(DUMMY_SP, def_id, assoc_ident)
     } else {
         ty::EarlyBinder::bind(&[] as &[_])
     };
@@ -889,7 +889,7 @@ pub(super) fn type_param_predicates<'tcx>(
     let extra_predicates = extend.into_iter().chain(icx.probe_ty_param_bounds_in_generics(
         hir_generics,
         def_id,
-        PredicateFilter::SelfTraitThatDefines(assoc_name),
+        PredicateFilter::SelfTraitThatDefines(assoc_ident),
     ));
 
     let bounds =
@@ -908,7 +908,7 @@ pub(super) fn type_param_predicates<'tcx>(
         _ => unreachable!(),
     };
     assert_only_contains_predicates_from(
-        PredicateFilter::SelfTraitThatDefines(assoc_name),
+        PredicateFilter::SelfTraitThatDefines(assoc_ident),
         bounds,
         self_ty,
     );
diff --git a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs
index 404753875ee5e..9bcda35ee87ad 100644
--- a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs
+++ b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs
@@ -1874,13 +1874,13 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> {
     fn supertrait_hrtb_vars(
         tcx: TyCtxt<'tcx>,
         def_id: DefId,
-        assoc_name: Ident,
+        assoc_ident: Ident,
         assoc_kind: ty::AssocKind,
     ) -> Option<(Vec<ty::BoundVariableKind>, &'tcx ty::AssocItem)> {
         let trait_defines_associated_item_named = |trait_def_id: DefId| {
-            tcx.associated_items(trait_def_id).find_by_name_and_kind(
+            tcx.associated_items(trait_def_id).find_by_ident_and_kind(
                 tcx,
-                assoc_name,
+                assoc_ident,
                 assoc_kind,
                 trait_def_id,
             )
@@ -1904,7 +1904,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> {
             if let Some(assoc_item) = trait_defines_associated_item_named(def_id) {
                 break Some((bound_vars.into_iter().collect(), assoc_item));
             }
-            let predicates = tcx.explicit_supertraits_containing_assoc_item((def_id, assoc_name));
+            let predicates = tcx.explicit_supertraits_containing_assoc_item((def_id, assoc_ident));
             let obligations = predicates.iter_identity_copied().filter_map(|(pred, _)| {
                 let bound_predicate = pred.kind();
                 match bound_predicate.skip_binder() {
diff --git a/compiler/rustc_hir_analysis/src/errors.rs b/compiler/rustc_hir_analysis/src/errors.rs
index e6090a128b1c8..508970cf2554d 100644
--- a/compiler/rustc_hir_analysis/src/errors.rs
+++ b/compiler/rustc_hir_analysis/src/errors.rs
@@ -23,7 +23,7 @@ pub(crate) struct AmbiguousAssocItem<'a> {
     #[label]
     pub span: Span,
     pub assoc_kind: &'static str,
-    pub assoc_name: Ident,
+    pub assoc_ident: Ident,
     pub qself: &'a str,
 }
 
@@ -75,7 +75,7 @@ pub(crate) struct AssocItemIsPrivate {
 pub(crate) struct AssocItemNotFound<'a> {
     #[primary_span]
     pub span: Span,
-    pub assoc_name: Ident,
+    pub assoc_ident: Ident,
     pub assoc_kind: &'static str,
     pub qself: &'a str,
     #[subdiagnostic]
diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs
index 55087d1f400f5..24d05b49861c5 100644
--- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs
+++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs
@@ -363,10 +363,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
         for hir_bound in hir_bounds {
             // In order to avoid cycles, when we're lowering `SelfTraitThatDefines`,
             // we skip over any traits that don't define the given associated type.
-            if let PredicateFilter::SelfTraitThatDefines(assoc_name) = predicate_filter {
+            if let PredicateFilter::SelfTraitThatDefines(assoc_ident) = predicate_filter {
                 if let Some(trait_ref) = hir_bound.trait_ref()
                     && let Some(trait_did) = trait_ref.trait_def_id()
-                    && self.tcx().trait_may_define_assoc_item(trait_did, assoc_name)
+                    && self.tcx().trait_may_define_assoc_item(trait_did, assoc_ident)
                 {
                     // Okay
                 } else {
diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/cmse.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/cmse.rs
index ecb453bced0a7..d1ee5a5494c00 100644
--- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/cmse.rs
+++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/cmse.rs
@@ -49,13 +49,13 @@ pub(crate) fn validate_cmse_abi<'tcx>(
                 Ok(Err(index)) => {
                     // fn(x: u32, u32, u32, u16, y: u16) -> u32,
                     //                           ^^^^^^
-                    let span = if let Some(ident) = bare_fn_ty.param_names[index] {
+                    let span = if let Some(ident) = bare_fn_ty.param_idents[index] {
                         ident.span.to(bare_fn_ty.decl.inputs[index].span)
                     } else {
                         bare_fn_ty.decl.inputs[index].span
                     }
                     .to(bare_fn_ty.decl.inputs.last().unwrap().span);
-                    let plural = bare_fn_ty.param_names.len() - index != 1;
+                    let plural = bare_fn_ty.param_idents.len() - index != 1;
                     dcx.emit_err(errors::CmseInputsStackSpill { span, plural, abi });
                 }
                 Err(layout_err) => {
diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs
index 6e9c178d33a64..5a0524d33fdea 100644
--- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs
+++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs
@@ -117,7 +117,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
         all_candidates: impl Fn() -> I,
         qself: AssocItemQSelf,
         assoc_kind: ty::AssocKind,
-        assoc_name: Ident,
+        assoc_ident: Ident,
         span: Span,
         constraint: Option<&hir::AssocItemConstraint<'tcx>>,
     ) -> ErrorGuaranteed
@@ -129,11 +129,15 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
         // First and foremost, provide a more user-friendly & “intuitive” error on kind mismatches.
         if let Some(assoc_item) = all_candidates().find_map(|r| {
             tcx.associated_items(r.def_id())
-                .filter_by_name_unhygienic(assoc_name.name)
-                .find(|item| tcx.hygienic_eq(assoc_name, item.ident(tcx), r.def_id()))
+                .filter_by_name_unhygienic(assoc_ident.name)
+                .find(|item| tcx.hygienic_eq(assoc_ident, item.ident(tcx), r.def_id()))
         }) {
             return self.complain_about_assoc_kind_mismatch(
-                assoc_item, assoc_kind, assoc_name, span, constraint,
+                assoc_item,
+                assoc_kind,
+                assoc_ident,
+                span,
+                constraint,
             );
         }
 
@@ -142,18 +146,18 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
 
         // The fallback span is needed because `assoc_name` might be an `Fn()`'s `Output` without a
         // valid span, so we point at the whole path segment instead.
-        let is_dummy = assoc_name.span == DUMMY_SP;
+        let is_dummy = assoc_ident.span == DUMMY_SP;
 
         let mut err = errors::AssocItemNotFound {
-            span: if is_dummy { span } else { assoc_name.span },
-            assoc_name,
+            span: if is_dummy { span } else { assoc_ident.span },
+            assoc_ident,
             assoc_kind: assoc_kind_str,
             qself: &qself_str,
             label: None,
             sugg: None,
             // Try to get the span of the identifier within the path's syntax context
             // (if that's different).
-            within_macro_span: assoc_name.span.within_macro(span, tcx.sess.source_map()),
+            within_macro_span: assoc_ident.span.within_macro(span, tcx.sess.source_map()),
         };
 
         if is_dummy {
@@ -169,10 +173,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
             .collect();
 
         if let Some(suggested_name) =
-            find_best_match_for_name(&all_candidate_names, assoc_name.name, None)
+            find_best_match_for_name(&all_candidate_names, assoc_ident.name, None)
         {
             err.sugg = Some(errors::AssocItemNotFoundSugg::Similar {
-                span: assoc_name.span,
+                span: assoc_ident.span,
                 assoc_kind: assoc_kind_str,
                 suggested_name,
             });
@@ -201,7 +205,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
             .collect();
 
         if let Some(suggested_name) =
-            find_best_match_for_name(&wider_candidate_names, assoc_name.name, None)
+            find_best_match_for_name(&wider_candidate_names, assoc_ident.name, None)
         {
             if let [best_trait] = visible_traits
                 .iter()
@@ -215,11 +219,11 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
             {
                 let trait_name = tcx.def_path_str(best_trait);
                 err.label = Some(errors::AssocItemNotFoundLabel::FoundInOtherTrait {
-                    span: assoc_name.span,
+                    span: assoc_ident.span,
                     assoc_kind: assoc_kind_str,
                     trait_name: &trait_name,
                     suggested_name,
-                    identically_named: suggested_name == assoc_name.name,
+                    identically_named: suggested_name == assoc_ident.name,
                 });
                 if let AssocItemQSelf::TyParam(ty_param_def_id, ty_param_span) = qself
                     // Not using `self.item_def_id()` here as that would yield the opaque type itself if we're
@@ -246,7 +250,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
                         // The type param already has a bound for `trait_name`, we just need to
                         // change the associated item.
                         err.sugg = Some(errors::AssocItemNotFoundSugg::SimilarInOtherTrait {
-                            span: assoc_name.span,
+                            span: assoc_ident.span,
                             assoc_kind: assoc_kind_str,
                             suggested_name,
                         });
@@ -265,7 +269,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
                         Applicability::MaybeIncorrect
                     };
 
-                    let identically_named = suggested_name == assoc_name.name;
+                    let identically_named = suggested_name == assoc_ident.name;
 
                     if let DefKind::TyAlias = tcx.def_kind(item_def_id)
                         && !tcx.type_alias_is_lazy(item_def_id)
@@ -273,7 +277,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
                         err.sugg = Some(errors::AssocItemNotFoundSugg::SimilarInOtherTraitQPath {
                             lo: ty_param_span.shrink_to_lo(),
                             mi: ty_param_span.shrink_to_hi(),
-                            hi: (!identically_named).then_some(assoc_name.span),
+                            hi: (!identically_named).then_some(assoc_ident.span),
                             trait_ref,
                             identically_named,
                             suggested_name,
@@ -294,7 +298,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
                             // We suggested constraining a type parameter, but the associated item on it
                             // was also not an exact match, so we also suggest changing it.
                             err.span_suggestion_verbose(
-                                assoc_name.span,
+                                assoc_ident.span,
                                 fluent::hir_analysis_assoc_item_not_found_similar_in_other_trait_with_bound_sugg,
                                 suggested_name,
                                 Applicability::MaybeIncorrect,
@@ -311,13 +315,13 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
         // suggest using it.
         if let [candidate_name] = all_candidate_names.as_slice() {
             err.sugg = Some(errors::AssocItemNotFoundSugg::Other {
-                span: assoc_name.span,
+                span: assoc_ident.span,
                 qself: &qself_str,
                 assoc_kind: assoc_kind_str,
                 suggested_name: *candidate_name,
             });
         } else {
-            err.label = Some(errors::AssocItemNotFoundLabel::NotFound { span: assoc_name.span });
+            err.label = Some(errors::AssocItemNotFoundLabel::NotFound { span: assoc_ident.span });
         }
 
         self.dcx().emit_err(err)
@@ -805,7 +809,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
                         return None;
                     };
 
-                    let assoc_item = tcx.associated_items(trait_def).find_by_name_and_kind(
+                    let assoc_item = tcx.associated_items(trait_def).find_by_ident_and_kind(
                         tcx,
                         ident,
                         ty::AssocKind::Type,
diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
index b4a71edc118c4..83aa0d9562015 100644
--- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
+++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
@@ -147,7 +147,7 @@ pub trait HirTyLowerer<'tcx> {
         &self,
         span: Span,
         def_id: LocalDefId,
-        assoc_name: Ident,
+        assoc_ident: Ident,
     ) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]>;
 
     /// Lower an associated type/const (from a trait) to a projection.
@@ -933,11 +933,11 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
         &self,
         trait_def_id: DefId,
         assoc_kind: ty::AssocKind,
-        assoc_name: Ident,
+        assoc_ident: Ident,
     ) -> bool {
         self.tcx()
             .associated_items(trait_def_id)
-            .find_by_name_and_kind(self.tcx(), assoc_name, assoc_kind, trait_def_id)
+            .find_by_ident_and_kind(self.tcx(), assoc_ident, assoc_kind, trait_def_id)
             .is_some()
     }
 
@@ -964,7 +964,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
     }
 
     /// Search for a trait bound on a type parameter whose trait defines the associated item
-    /// given by `assoc_name` and `kind`.
+    /// given by `assoc_ident` and `kind`.
     ///
     /// This fails if there is no such bound in the list of candidates or if there are multiple
     /// candidates in which case it reports ambiguity.
@@ -976,13 +976,13 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
         ty_param_def_id: LocalDefId,
         ty_param_span: Span,
         kind: ty::AssocKind,
-        assoc_name: Ident,
+        assoc_ident: Ident,
         span: Span,
     ) -> Result<ty::PolyTraitRef<'tcx>, ErrorGuaranteed> {
-        debug!(?ty_param_def_id, ?assoc_name, ?span);
+        debug!(?ty_param_def_id, ?assoc_ident, ?span);
         let tcx = self.tcx();
 
-        let predicates = &self.probe_ty_param_bounds(span, ty_param_def_id, assoc_name);
+        let predicates = &self.probe_ty_param_bounds(span, ty_param_def_id, assoc_ident);
         debug!("predicates={:#?}", predicates);
 
         self.probe_single_bound_for_assoc_item(
@@ -990,17 +990,18 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
                 let trait_refs = predicates
                     .iter_identity_copied()
                     .filter_map(|(p, _)| Some(p.as_trait_clause()?.map_bound(|t| t.trait_ref)));
-                traits::transitive_bounds_that_define_assoc_item(tcx, trait_refs, assoc_name)
+                traits::transitive_bounds_that_define_assoc_item(tcx, trait_refs, assoc_ident)
             },
             AssocItemQSelf::TyParam(ty_param_def_id, ty_param_span),
             kind,
-            assoc_name,
+            assoc_ident,
             span,
             None,
         )
     }
 
-    /// Search for a single trait bound whose trait defines the associated item given by `assoc_name`.
+    /// Search for a single trait bound whose trait defines the associated item given by
+    /// `assoc_ident`.
     ///
     /// This fails if there is no such bound in the list of candidates or if there are multiple
     /// candidates in which case it reports ambiguity.
@@ -1010,7 +1011,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
         all_candidates: impl Fn() -> I,
         qself: AssocItemQSelf,
         assoc_kind: ty::AssocKind,
-        assoc_name: Ident,
+        assoc_ident: Ident,
         span: Span,
         constraint: Option<&hir::AssocItemConstraint<'tcx>>,
     ) -> Result<ty::PolyTraitRef<'tcx>, ErrorGuaranteed>
@@ -1020,7 +1021,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
         let tcx = self.tcx();
 
         let mut matching_candidates = all_candidates().filter(|r| {
-            self.probe_trait_that_defines_assoc_item(r.def_id(), assoc_kind, assoc_name)
+            self.probe_trait_that_defines_assoc_item(r.def_id(), assoc_kind, assoc_ident)
         });
 
         let Some(bound) = matching_candidates.next() else {
@@ -1028,7 +1029,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
                 all_candidates,
                 qself,
                 assoc_kind,
-                assoc_name,
+                assoc_ident,
                 span,
                 constraint,
             );
@@ -1044,7 +1045,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
             let mut err = self.dcx().create_err(crate::errors::AmbiguousAssocItem {
                 span,
                 assoc_kind: assoc_kind_str,
-                assoc_name,
+                assoc_ident,
                 qself: &qself_str,
             });
             // Provide a more specific error code index entry for equality bindings.
@@ -1065,13 +1066,13 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
                 let bound_id = bound.def_id();
                 let bound_span = tcx
                     .associated_items(bound_id)
-                    .find_by_name_and_kind(tcx, assoc_name, assoc_kind, bound_id)
+                    .find_by_ident_and_kind(tcx, assoc_ident, assoc_kind, bound_id)
                     .and_then(|item| tcx.hir_span_if_local(item.def_id));
 
                 if let Some(bound_span) = bound_span {
                     err.span_label(
                         bound_span,
-                        format!("ambiguous `{assoc_name}` from `{}`", bound.print_trait_sugared(),),
+                        format!("ambiguous `{assoc_ident}` from `{}`", bound.print_trait_sugared(),),
                     );
                     if let Some(constraint) = constraint {
                         match constraint.kind {
@@ -1087,7 +1088,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
                                 }
                                 // FIXME(#97583): This isn't syntactically well-formed!
                                 where_bounds.push(format!(
-                                    "        T: {trait}::{assoc_name} = {term}",
+                                    "        T: {trait}::{assoc_ident} = {term}",
                                     trait = bound.print_only_trait_path(),
                                 ));
                             }
@@ -1096,7 +1097,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
                         }
                     } else {
                         err.span_suggestion_verbose(
-                            span.with_hi(assoc_name.span.lo()),
+                            span.with_hi(assoc_ident.span.lo()),
                             "use fully-qualified syntax to disambiguate",
                             format!("<{qself_str} as {}>::", bound.print_only_trait_path()),
                             Applicability::MaybeIncorrect,
@@ -1104,7 +1105,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
                     }
                 } else {
                     err.note(format!(
-                        "associated {assoc_kind_str} `{assoc_name}` could derive from `{}`",
+                        "associated {assoc_kind_str} `{assoc_ident}` could derive from `{}`",
                         bound.print_only_trait_path(),
                     ));
                 }
@@ -2858,7 +2859,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
 
         let trait_ref = self.lower_impl_trait_ref(i.of_trait.as_ref()?, self.lower_ty(i.self_ty));
 
-        let assoc = tcx.associated_items(trait_ref.def_id).find_by_name_and_kind(
+        let assoc = tcx.associated_items(trait_ref.def_id).find_by_ident_and_kind(
             tcx,
             *ident,
             ty::AssocKind::Fn,
diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs
index 865209b6a9606..e5ab317685f9f 100644
--- a/compiler/rustc_hir_pretty/src/lib.rs
+++ b/compiler/rustc_hir_pretty/src/lib.rs
@@ -397,7 +397,7 @@ impl<'a> State<'a> {
                 self.pclose();
             }
             hir::TyKind::BareFn(f) => {
-                self.print_ty_fn(f.abi, f.safety, f.decl, None, f.generic_params, f.param_names);
+                self.print_ty_fn(f.abi, f.safety, f.decl, None, f.generic_params, f.param_idents);
             }
             hir::TyKind::UnsafeBinder(unsafe_binder) => {
                 self.print_unsafe_binder(unsafe_binder);
@@ -473,14 +473,14 @@ impl<'a> State<'a> {
         self.maybe_print_comment(item.span.lo());
         self.print_attrs_as_outer(self.attrs(item.hir_id()));
         match item.kind {
-            hir::ForeignItemKind::Fn(sig, arg_names, generics) => {
+            hir::ForeignItemKind::Fn(sig, arg_idents, generics) => {
                 self.head("");
                 self.print_fn(
                     sig.decl,
                     sig.header,
                     Some(item.ident.name),
                     generics,
-                    arg_names,
+                    arg_idents,
                     None,
                 );
                 self.end(); // end head-ibox
@@ -899,10 +899,10 @@ impl<'a> State<'a> {
         ident: Ident,
         m: &hir::FnSig<'_>,
         generics: &hir::Generics<'_>,
-        arg_names: &[Option<Ident>],
+        arg_idents: &[Option<Ident>],
         body_id: Option<hir::BodyId>,
     ) {
-        self.print_fn(m.decl, m.header, Some(ident.name), generics, arg_names, body_id);
+        self.print_fn(m.decl, m.header, Some(ident.name), generics, arg_idents, body_id);
     }
 
     fn print_trait_item(&mut self, ti: &hir::TraitItem<'_>) {
@@ -914,8 +914,8 @@ impl<'a> State<'a> {
             hir::TraitItemKind::Const(ty, default) => {
                 self.print_associated_const(ti.ident, ti.generics, ty, default);
             }
-            hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Required(arg_names)) => {
-                self.print_method_sig(ti.ident, sig, ti.generics, arg_names, None);
+            hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Required(arg_idents)) => {
+                self.print_method_sig(ti.ident, sig, ti.generics, arg_idents, None);
                 self.word(";");
             }
             hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Provided(body)) => {
@@ -2122,7 +2122,7 @@ impl<'a> State<'a> {
         header: hir::FnHeader,
         name: Option<Symbol>,
         generics: &hir::Generics<'_>,
-        arg_names: &[Option<Ident>],
+        arg_idents: &[Option<Ident>],
         body_id: Option<hir::BodyId>,
     ) {
         self.print_fn_header_info(header);
@@ -2134,16 +2134,16 @@ impl<'a> State<'a> {
         self.print_generic_params(generics.params);
 
         self.popen();
-        // Make sure we aren't supplied *both* `arg_names` and `body_id`.
-        assert!(arg_names.is_empty() || body_id.is_none());
+        // Make sure we aren't supplied *both* `arg_idents` and `body_id`.
+        assert!(arg_idents.is_empty() || body_id.is_none());
         let mut i = 0;
         let mut print_arg = |s: &mut Self, ty: Option<&hir::Ty<'_>>| {
             if i == 0 && decl.implicit_self.has_implicit_self() {
                 s.print_implicit_self(&decl.implicit_self);
             } else {
-                if let Some(arg_name) = arg_names.get(i) {
-                    if let Some(arg_name) = arg_name {
-                        s.word(arg_name.to_string());
+                if let Some(arg_ident) = arg_idents.get(i) {
+                    if let Some(arg_ident) = arg_ident {
+                        s.word(arg_ident.to_string());
                         s.word(":");
                         s.space();
                     }
@@ -2452,7 +2452,7 @@ impl<'a> State<'a> {
         decl: &hir::FnDecl<'_>,
         name: Option<Symbol>,
         generic_params: &[hir::GenericParam<'_>],
-        arg_names: &[Option<Ident>],
+        arg_idents: &[Option<Ident>],
     ) {
         self.ibox(INDENT_UNIT);
         self.print_formal_generic_params(generic_params);
@@ -2467,7 +2467,7 @@ impl<'a> State<'a> {
             },
             name,
             generics,
-            arg_names,
+            arg_idents,
             None,
         );
         self.end();
diff --git a/compiler/rustc_hir_typeck/messages.ftl b/compiler/rustc_hir_typeck/messages.ftl
index 872861d6289d9..9e1b70f5767b4 100644
--- a/compiler/rustc_hir_typeck/messages.ftl
+++ b/compiler/rustc_hir_typeck/messages.ftl
@@ -148,7 +148,7 @@ hir_typeck_never_type_fallback_flowing_into_unsafe_path = never type fallback af
 hir_typeck_never_type_fallback_flowing_into_unsafe_union_field = never type fallback affects this union access
     .help = specify the type explicitly
 
-hir_typeck_no_associated_item = no {$item_kind} named `{$item_name}` found for {$ty_prefix} `{$ty_str}`{$trait_missing_method ->
+hir_typeck_no_associated_item = no {$item_kind} named `{$item_ident}` found for {$ty_prefix} `{$ty_str}`{$trait_missing_method ->
     [true] {""}
     *[other] {" "}in the current scope
 }
diff --git a/compiler/rustc_hir_typeck/src/errors.rs b/compiler/rustc_hir_typeck/src/errors.rs
index b73cd26927a5c..dfaa374592bc2 100644
--- a/compiler/rustc_hir_typeck/src/errors.rs
+++ b/compiler/rustc_hir_typeck/src/errors.rs
@@ -727,7 +727,7 @@ pub(crate) struct NoAssociatedItem {
     #[primary_span]
     pub span: Span,
     pub item_kind: &'static str,
-    pub item_name: Ident,
+    pub item_ident: Ident,
     pub ty_prefix: Cow<'static, str>,
     pub ty_str: String,
     pub trait_missing_method: bool,
diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
index f4bd7ec701f8a..81eb8510785b5 100644
--- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
+++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
@@ -1136,7 +1136,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 && let self_implicit =
                     matches!(call_expr.kind, hir::ExprKind::MethodCall(..)) as usize
                 && let Some(Some(arg)) =
-                    self.tcx.fn_arg_names(fn_def_id).get(expected_idx.as_usize() + self_implicit)
+                    self.tcx.fn_arg_idents(fn_def_id).get(expected_idx.as_usize() + self_implicit)
                 && arg.name != kw::SelfLower
             {
                 format!("/* {} */", arg.name)
@@ -2619,7 +2619,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         is_method: bool,
     ) -> Option<(IndexVec<ExpectedIdx, (Option<GenericIdx>, FnParam<'_>)>, &hir::Generics<'_>)>
     {
-        let (sig, generics, body_id, param_names) = match self.tcx.hir_get_if_local(def_id)? {
+        let (sig, generics, body_id, params) = match self.tcx.hir_get_if_local(def_id)? {
             hir::Node::TraitItem(&hir::TraitItem {
                 generics,
                 kind: hir::TraitItemKind::Fn(sig, trait_fn),
@@ -2661,7 +2661,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 None
             }
         });
-        match (body_id, param_names) {
+        match (body_id, params) {
             (Some(_), Some(_)) | (None, None) => unreachable!(),
             (Some(body), None) => {
                 let params = self.tcx.hir_body(body).params;
@@ -2678,7 +2678,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     params.get(is_method as usize..params.len() - sig.decl.c_variadic as usize)?;
                 debug_assert_eq!(params.len(), fn_inputs.len());
                 Some((
-                    fn_inputs.zip(params.iter().map(|&ident| FnParam::Name(ident))).collect(),
+                    fn_inputs.zip(params.iter().map(|&ident| FnParam::Ident(ident))).collect(),
                     generics,
                 ))
             }
@@ -2709,14 +2709,14 @@ impl<'tcx> Visitor<'tcx> for FindClosureArg<'tcx> {
 #[derive(Clone, Copy)]
 enum FnParam<'hir> {
     Param(&'hir hir::Param<'hir>),
-    Name(Option<Ident>),
+    Ident(Option<Ident>),
 }
 
 impl FnParam<'_> {
     fn span(&self) -> Span {
         match self {
             Self::Param(param) => param.span,
-            Self::Name(ident) => {
+            Self::Ident(ident) => {
                 if let Some(ident) = ident {
                     ident.span
                 } else {
@@ -2738,7 +2738,7 @@ impl FnParam<'_> {
                     {
                         Some(ident.name)
                     }
-                    FnParam::Name(ident)
+                    FnParam::Ident(ident)
                         if let Some(ident) = ident
                             && ident.name != kw::Underscore =>
                     {
diff --git a/compiler/rustc_hir_typeck/src/method/mod.rs b/compiler/rustc_hir_typeck/src/method/mod.rs
index 4008021c3a857..ddfd27ccf6b7f 100644
--- a/compiler/rustc_hir_typeck/src/method/mod.rs
+++ b/compiler/rustc_hir_typeck/src/method/mod.rs
@@ -534,12 +534,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         Ok((def_kind, pick.item.def_id))
     }
 
-    /// Finds item with name `item_name` defined in impl/trait `def_id`
+    /// Finds item with name `item_ident` defined in impl/trait `def_id`
     /// and return it, or `None`, if no such item was defined there.
-    fn associated_value(&self, def_id: DefId, item_name: Ident) -> Option<ty::AssocItem> {
+    fn associated_value(&self, def_id: DefId, item_ident: Ident) -> Option<ty::AssocItem> {
         self.tcx
             .associated_items(def_id)
-            .find_by_name_and_namespace(self.tcx, item_name, Namespace::ValueNS, def_id)
+            .find_by_ident_and_namespace(self.tcx, item_ident, Namespace::ValueNS, def_id)
             .copied()
     }
 }
diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs
index 8be4d55542d08..68f13d654d6e8 100644
--- a/compiler/rustc_hir_typeck/src/method/suggest.rs
+++ b/compiler/rustc_hir_typeck/src/method/suggest.rs
@@ -585,7 +585,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         &self,
         mut span: Span,
         rcvr_ty: Ty<'tcx>,
-        item_name: Ident,
+        item_ident: Ident,
         expr_id: hir::HirId,
         source: SelfSource<'tcx>,
         args: Option<&'tcx [hir::Expr<'tcx>]>,
@@ -616,7 +616,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         } else if rcvr_ty.is_enum() {
             "variant or associated item"
         } else {
-            match (item_name.as_str().chars().next(), rcvr_ty.is_fresh_ty()) {
+            match (item_ident.as_str().chars().next(), rcvr_ty.is_fresh_ty()) {
                 (Some(name), false) if name.is_lowercase() => "function or associated item",
                 (Some(_), false) => "associated item",
                 (Some(_), true) | (None, false) => "variant or associated item",
@@ -631,7 +631,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             rcvr_ty,
             source,
             span,
-            item_name,
+            item_ident,
             &short_ty_str,
             &mut ty_file,
         ) {
@@ -643,13 +643,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             source,
             span,
             item_kind,
-            item_name,
+            item_ident,
             &short_ty_str,
             &mut ty_file,
         ) {
             return guar;
         }
-        span = item_name.span;
+        span = item_ident.span;
 
         // Don't show generic arguments when the method can't be found in any implementation (#81576).
         let mut ty_str_reported = ty_str.clone();
@@ -661,7 +661,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         self.tcx
                             .inherent_impls(adt_def.did())
                             .into_iter()
-                            .any(|def_id| self.associated_value(*def_id, item_name).is_some())
+                            .any(|def_id| self.associated_value(*def_id, item_ident).is_some())
                     } else {
                         false
                     }
@@ -678,14 +678,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         let is_write = sugg_span.ctxt().outer_expn_data().macro_def_id.is_some_and(|def_id| {
             tcx.is_diagnostic_item(sym::write_macro, def_id)
                 || tcx.is_diagnostic_item(sym::writeln_macro, def_id)
-        }) && item_name.name == sym::write_fmt;
+        }) && item_ident.name == sym::write_fmt;
         let mut err = if is_write && let SelfSource::MethodCall(rcvr_expr) = source {
             self.suggest_missing_writer(rcvr_ty, rcvr_expr)
         } else {
             let mut err = self.dcx().create_err(NoAssociatedItem {
                 span,
                 item_kind,
-                item_name,
+                item_ident,
                 ty_prefix: if trait_missing_method {
                     // FIXME(mu001999) E0599 maybe not suitable here because it is for types
                     Cow::from("trait")
@@ -699,7 +699,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             if is_method {
                 self.suggest_use_shadowed_binding_with_method(
                     source,
-                    item_name,
+                    item_ident,
                     &ty_str_reported,
                     &mut err,
                 );
@@ -710,9 +710,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 && let hir::TyKind::Path(hir::QPath::Resolved(_, path)) = ty.kind
                 && let Res::SelfTyAlias { alias_to: impl_def_id, .. } = path.res
                 && let DefKind::Impl { .. } = self.tcx.def_kind(impl_def_id)
-                && let Some(candidate) = tcx.associated_items(impl_def_id).find_by_name_and_kind(
+                && let Some(candidate) = tcx.associated_items(impl_def_id).find_by_ident_and_kind(
                     self.tcx,
-                    item_name,
+                    item_ident,
                     ty::AssocKind::Type,
                     impl_def_id,
                 )
@@ -722,7 +722,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             {
                 let def_path = tcx.def_path_str(adt_def.did());
                 err.span_suggestion(
-                    ty.span.to(item_name.span),
+                    ty.span.to(item_ident.span),
                     format!("to construct a value of type `{}`, use the explicit path", def_path),
                     def_path,
                     Applicability::MachineApplicable,
@@ -750,7 +750,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             self.find_builder_fn(&mut err, rcvr_ty, expr_id);
         }
 
-        if tcx.ty_is_opaque_future(rcvr_ty) && item_name.name == sym::poll {
+        if tcx.ty_is_opaque_future(rcvr_ty) && item_ident.name == sym::poll {
             err.help(format!(
                 "method `poll` found on `Pin<&mut {ty_str}>`, \
                 see documentation for `std::pin::Pin`"
@@ -765,7 +765,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         {
             self.suggest_await_before_method(
                 &mut err,
-                item_name,
+                item_ident,
                 rcvr_ty,
                 cal,
                 span,
@@ -787,7 +787,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         if let SelfSource::MethodCall(rcvr_expr) = source
             && let ty::RawPtr(ty, ptr_mutbl) = *rcvr_ty.kind()
             && let Ok(pick) = self.lookup_probe_for_diagnostic(
-                item_name,
+                item_ident,
                 Ty::new_ref(tcx, ty::Region::new_error_misc(tcx), ty, ptr_mutbl),
                 self.tcx.hir_expect_expr(self.tcx.parent_hir_id(rcvr_expr.hir_id)),
                 ProbeScope::TraitsInScope,
@@ -808,7 +808,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             };
             err.span_note(
                 tcx.def_span(pick.item.def_id),
-                format!("the method `{item_name}` exists on the type `{ty}`", ty = pick.self_ty),
+                format!("the method `{item_ident}` exists on the type `{ty}`", ty = pick.self_ty),
             );
             let mut_str = ptr_mutbl.ptr_str();
             err.note(format!(
@@ -834,7 +834,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             self.suggest_fn_call(&mut err, rcvr_expr, rcvr_ty, |output_ty| {
                 let call_expr = self.tcx.hir_expect_expr(self.tcx.parent_hir_id(rcvr_expr.hir_id));
                 let probe = self.lookup_probe_for_diagnostic(
-                    item_name,
+                    item_ident,
                     output_ty,
                     call_expr,
                     ProbeScope::AllTraits,
@@ -873,13 +873,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 static_candidates,
                 rcvr_ty,
                 source,
-                item_name,
+                item_ident,
                 args,
                 sugg_span,
             );
             self.note_candidates_on_method_error(
                 rcvr_ty,
-                item_name,
+                item_ident,
                 source,
                 args,
                 span,
@@ -890,7 +890,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         } else if static_candidates.len() > 1 {
             self.note_candidates_on_method_error(
                 rcvr_ty,
-                item_name,
+                item_ident,
                 source,
                 args,
                 span,
@@ -904,7 +904,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         let mut restrict_type_params = false;
         let mut suggested_derive = false;
         let mut unsatisfied_bounds = false;
-        if item_name.name == sym::count && self.is_slice_ty(rcvr_ty, span) {
+        if item_ident.name == sym::count && self.is_slice_ty(rcvr_ty, span) {
             let msg = "consider using `len` instead";
             if let SelfSource::MethodCall(_expr) = source {
                 err.span_suggestion_short(span, msg, "len", Applicability::MachineApplicable);
@@ -1349,7 +1349,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 };
                 let primary_message = primary_message.unwrap_or_else(|| {
                     format!(
-                        "the {item_kind} `{item_name}` exists for {actual_prefix} `{ty_str}`, \
+                        "the {item_kind} `{item_ident}` exists for {actual_prefix} `{ty_str}`, \
                          but its trait bounds were not satisfied"
                     )
                 });
@@ -1379,7 +1379,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             // `Pin<&Self>`.
             if targs.len() == 1 {
                 let mut item_segment = hir::PathSegment::invalid();
-                item_segment.ident = item_name;
+                item_segment.ident = item_ident;
                 for t in [Ty::new_mut_ref, Ty::new_imm_ref, |_, _, t| t] {
                     let new_args =
                         tcx.mk_args_from_iter(targs.iter().map(|arg| match arg.as_type() {
@@ -1423,9 +1423,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     ty::Adt(adt, _) => self.tcx.is_lang_item(adt.did(), LangItem::String),
                     _ => false,
                 };
-                if is_string_or_ref_str && item_name.name == sym::iter {
+                if is_string_or_ref_str && item_ident.name == sym::iter {
                     err.span_suggestion_verbose(
-                        item_name.span,
+                        item_ident.span,
                         "because of the in-memory representation of `&str`, to obtain \
                          an `Iterator` over each of its codepoint use method `chars`",
                         "chars",
@@ -1439,7 +1439,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                         .into_iter()
                         .copied()
                         .filter(|def_id| {
-                            if let Some(assoc) = self.associated_value(*def_id, item_name) {
+                            if let Some(assoc) = self.associated_value(*def_id, item_ident) {
                                 // Check for both mode is the same so we avoid suggesting
                                 // incorrect associated item.
                                 match (mode, assoc.fn_has_self_parameter, source) {
@@ -1500,7 +1500,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         // If the method name is the name of a field with a function or closure type,
         // give a helping note that it has to be called as `(x.f)(...)`.
         if let SelfSource::MethodCall(expr) = source {
-            if !self.suggest_calling_field_as_fn(span, rcvr_ty, expr, item_name, &mut err)
+            if !self.suggest_calling_field_as_fn(span, rcvr_ty, expr, item_ident, &mut err)
                 && similar_candidate.is_none()
                 && !custom_span_label
             {
@@ -1513,7 +1513,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         let confusable_suggested = self.confusable_method_name(
             &mut err,
             rcvr_ty,
-            item_name,
+            item_ident,
             args.map(|args| {
                 args.iter()
                     .map(|expr| {
@@ -1531,12 +1531,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 source,
                 span,
                 rcvr_ty,
-                item_name,
+                item_ident,
                 expected.only_has_type(self),
             );
         }
 
-        self.suggest_unwrapping_inner_self(&mut err, source, rcvr_ty, item_name);
+        self.suggest_unwrapping_inner_self(&mut err, source, rcvr_ty, item_ident);
 
         for (span, mut bounds) in bound_spans {
             if !tcx.sess.source_map().is_span_accessible(span) {
@@ -1547,7 +1547,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             let pre = if Some(span) == ty_span {
                 ty_span.take();
                 format!(
-                    "{item_kind} `{item_name}` not found for this {} because it ",
+                    "{item_kind} `{item_ident}` not found for this {} because it ",
                     rcvr_ty.prefix_string(self.tcx)
                 )
             } else {
@@ -1567,7 +1567,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             err.span_label(
                 span,
                 format!(
-                    "{item_kind} `{item_name}` not found for this {}",
+                    "{item_kind} `{item_ident}` not found for this {}",
                     rcvr_ty.prefix_string(self.tcx)
                 ),
             );
@@ -1579,7 +1579,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 &mut err,
                 span,
                 rcvr_ty,
-                item_name,
+                item_ident,
                 args.map(|args| args.len() + 1),
                 source,
                 no_match_data.out_of_scope_traits.clone(),
@@ -1596,7 +1596,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             let adt_def = rcvr_ty.ty_adt_def().expect("enum is not an ADT");
             if let Some(var_name) = edit_distance::find_best_match_for_name(
                 &adt_def.variants().iter().map(|s| s.name).collect::<Vec<_>>(),
-                item_name.name,
+                item_ident.name,
                 None,
             ) && let Some(variant) = adt_def.variants().iter().find(|s| s.name == var_name)
             {
@@ -1737,14 +1737,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         if !find_candidate_for_method {
             self.lookup_segments_chain_for_no_match_method(
                 &mut err,
-                item_name,
+                item_ident,
                 item_kind,
                 source,
                 no_match_data,
             );
         }
 
-        self.note_derefed_ty_has_method(&mut err, source, rcvr_ty, item_name, expected);
+        self.note_derefed_ty_has_method(&mut err, source, rcvr_ty, item_ident, expected);
         err.emit()
     }
 
diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs
index 885a7308bdc6d..16c9e08c78d38 100644
--- a/compiler/rustc_lint/src/context.rs
+++ b/compiler/rustc_lint/src/context.rs
@@ -859,7 +859,7 @@ impl<'tcx> LateContext<'tcx> {
     ) -> Option<Ty<'tcx>> {
         let tcx = self.tcx;
         tcx.associated_items(trait_id)
-            .find_by_name_and_kind(tcx, Ident::from_str(name), ty::AssocKind::Type, trait_id)
+            .find_by_ident_and_kind(tcx, Ident::from_str(name), ty::AssocKind::Type, trait_id)
             .and_then(|assoc| {
                 let proj = Ty::new_projection(tcx, assoc.def_id, [self_ty]);
                 tcx.try_normalize_erasing_regions(self.typing_env(), proj).ok()
diff --git a/compiler/rustc_lint/src/nonstandard_style.rs b/compiler/rustc_lint/src/nonstandard_style.rs
index df567e80e5568..a3e7c84584d30 100644
--- a/compiler/rustc_lint/src/nonstandard_style.rs
+++ b/compiler/rustc_lint/src/nonstandard_style.rs
@@ -423,11 +423,11 @@ impl<'tcx> LateLintPass<'tcx> for NonSnakeCase {
     }
 
     fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &hir::TraitItem<'_>) {
-        if let hir::TraitItemKind::Fn(_, hir::TraitFn::Required(pnames)) = item.kind {
+        if let hir::TraitItemKind::Fn(_, hir::TraitFn::Required(param_idents)) = item.kind {
             self.check_snake_case(cx, "trait method", &item.ident);
-            for param_name in pnames {
-                if let Some(param_name) = param_name {
-                    self.check_snake_case(cx, "variable", param_name);
+            for param_ident in param_idents {
+                if let Some(param_ident) = param_ident {
+                    self.check_snake_case(cx, "variable", param_ident);
                 }
             }
         }
diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs
index 4610a571da088..5798c4b382729 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder.rs
@@ -1313,7 +1313,7 @@ impl<'a> CrateMetadataRef<'a> {
     fn get_fn_has_self_parameter(self, id: DefIndex, sess: &'a Session) -> bool {
         self.root
             .tables
-            .fn_arg_names
+            .fn_arg_idents
             .get(self, id)
             .expect("argument names not encoded for a function")
             .decode((self, sess))
diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
index 3dc82ce9d183d..ecc2dcc5318d0 100644
--- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
+++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
@@ -286,7 +286,7 @@ provide! { tcx, def_id, other, cdata,
     rendered_const => { table }
     rendered_precise_capturing_args => { table }
     asyncness => { table_direct }
-    fn_arg_names => { table }
+    fn_arg_idents => { table }
     coroutine_kind => { table_direct }
     coroutine_for_closure => { table }
     coroutine_by_move_body_def_id => { table }
diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs
index 55bb984c5b69f..30e2e7fee47be 100644
--- a/compiler/rustc_metadata/src/rmeta/encoder.rs
+++ b/compiler/rustc_metadata/src/rmeta/encoder.rs
@@ -1469,7 +1469,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
             }
             if let DefKind::Fn | DefKind::AssocFn = def_kind {
                 self.tables.asyncness.set_some(def_id.index, tcx.asyncness(def_id));
-                record_array!(self.tables.fn_arg_names[def_id] <- tcx.fn_arg_names(def_id));
+                record_array!(self.tables.fn_arg_idents[def_id] <- tcx.fn_arg_idents(def_id));
             }
             if let Some(name) = tcx.intrinsic(def_id) {
                 record!(self.tables.intrinsic[def_id] <- name);
diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs
index 96a1f65eeb0fb..93125149fe631 100644
--- a/compiler/rustc_metadata/src/rmeta/mod.rs
+++ b/compiler/rustc_metadata/src/rmeta/mod.rs
@@ -451,7 +451,7 @@ define_tables! {
     rendered_const: Table<DefIndex, LazyValue<String>>,
     rendered_precise_capturing_args: Table<DefIndex, LazyArray<PreciseCapturingArgKind<Symbol, Symbol>>>,
     asyncness: Table<DefIndex, ty::Asyncness>,
-    fn_arg_names: Table<DefIndex, LazyArray<Option<Ident>>>,
+    fn_arg_idents: Table<DefIndex, LazyArray<Option<Ident>>>,
     coroutine_kind: Table<DefIndex, hir::CoroutineKind>,
     coroutine_for_closure: Table<DefIndex, RawDefId>,
     adt_destructor: Table<DefIndex, LazyValue<ty::Destructor>>,
diff --git a/compiler/rustc_middle/src/hir/map.rs b/compiler/rustc_middle/src/hir/map.rs
index 21ab06c98a777..7f2b9e9d4ffd5 100644
--- a/compiler/rustc_middle/src/hir/map.rs
+++ b/compiler/rustc_middle/src/hir/map.rs
@@ -281,7 +281,7 @@ impl<'tcx> TyCtxt<'tcx> {
         })
     }
 
-    pub fn hir_body_param_names(self, id: BodyId) -> impl Iterator<Item = Option<Ident>> {
+    pub fn hir_body_param_idents(self, id: BodyId) -> impl Iterator<Item = Option<Ident>> {
         self.hir_body(id).params.iter().map(|param| match param.pat.kind {
             PatKind::Binding(_, _, ident, _) => Some(ident),
             PatKind::Wild => Some(Ident::new(kw::Underscore, param.pat.span)),
diff --git a/compiler/rustc_middle/src/hir/mod.rs b/compiler/rustc_middle/src/hir/mod.rs
index 74369b6636c01..a3177a6416d0b 100644
--- a/compiler/rustc_middle/src/hir/mod.rs
+++ b/compiler/rustc_middle/src/hir/mod.rs
@@ -215,9 +215,9 @@ pub fn provide(providers: &mut Providers) {
         let hir_id = tcx.local_def_id_to_hir_id(def_id);
         tcx.hir_opt_ident_span(hir_id)
     };
-    providers.fn_arg_names = |tcx, def_id| {
+    providers.fn_arg_idents = |tcx, def_id| {
         if let Some(body_id) = tcx.hir_node_by_def_id(def_id).body_id() {
-            tcx.arena.alloc_from_iter(tcx.hir_body_param_names(body_id))
+            tcx.arena.alloc_from_iter(tcx.hir_body_param_idents(body_id))
         } else if let Node::TraitItem(&TraitItem {
             kind: TraitItemKind::Fn(_, TraitFn::Required(idents)),
             ..
@@ -231,7 +231,7 @@ pub fn provide(providers: &mut Providers) {
         } else {
             span_bug!(
                 tcx.hir_span(tcx.local_def_id_to_hir_id(def_id)),
-                "fn_arg_names: unexpected item {:?}",
+                "fn_arg_idents: unexpected item {:?}",
                 def_id
             );
         }
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index 40d0028db86db..28a59d3e73e80 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -1442,8 +1442,8 @@ rustc_queries! {
         desc { |tcx| "computing target features for inline asm of `{}`", tcx.def_path_str(def_id) }
     }
 
-    query fn_arg_names(def_id: DefId) -> &'tcx [Option<rustc_span::Ident>] {
-        desc { |tcx| "looking up function parameter names for `{}`", tcx.def_path_str(def_id) }
+    query fn_arg_idents(def_id: DefId) -> &'tcx [Option<rustc_span::Ident>] {
+        desc { |tcx| "looking up function parameter identifiers for `{}`", tcx.def_path_str(def_id) }
         separate_provide_extern
     }
 
@@ -1900,6 +1900,11 @@ rustc_queries! {
 
     // The macro which defines `rustc_metadata::provide_extern` depends on this query's name.
     // Changing the name should cause a compiler error, but in case that changes, be aware.
+    //
+    // The hash should not be calculated before the `analysis` pass is complete, specifically
+    // until `tcx.untracked().definitions.freeze()` has been called, otherwise if incremental
+    // compilation is enabled calculating this hash can freeze this structure too early in
+    // compilation and cause subsequent crashes when attempting to write to `definitions`
     query crate_hash(_: CrateNum) -> Svh {
         eval_always
         desc { "looking up the hash a crate" }
diff --git a/compiler/rustc_middle/src/ty/assoc.rs b/compiler/rustc_middle/src/ty/assoc.rs
index ce4c08aa485e5..e3d332036f1f7 100644
--- a/compiler/rustc_middle/src/ty/assoc.rs
+++ b/compiler/rustc_middle/src/ty/assoc.rs
@@ -199,8 +199,9 @@ impl AssocItems {
         self.items.get_by_key(name)
     }
 
-    /// Returns the associated item with the given name and `AssocKind`, if one exists.
-    pub fn find_by_name_and_kind(
+    /// Returns the associated item with the given identifier and `AssocKind`, if one exists.
+    /// The identifier is matched hygienically.
+    pub fn find_by_ident_and_kind(
         &self,
         tcx: TyCtxt<'_>,
         ident: Ident,
@@ -212,8 +213,9 @@ impl AssocItems {
             .find(|item| tcx.hygienic_eq(ident, item.ident(tcx), parent_def_id))
     }
 
-    /// Returns the associated item with the given name and any of `AssocKind`, if one exists.
-    pub fn find_by_name_and_kinds(
+    /// Returns the associated item with the given identifier and any of `AssocKind`, if one
+    /// exists. The identifier is matched hygienically.
+    pub fn find_by_ident_and_kinds(
         &self,
         tcx: TyCtxt<'_>,
         ident: Ident,
@@ -221,11 +223,12 @@ impl AssocItems {
         kinds: &[AssocKind],
         parent_def_id: DefId,
     ) -> Option<&ty::AssocItem> {
-        kinds.iter().find_map(|kind| self.find_by_name_and_kind(tcx, ident, *kind, parent_def_id))
+        kinds.iter().find_map(|kind| self.find_by_ident_and_kind(tcx, ident, *kind, parent_def_id))
     }
 
-    /// Returns the associated item with the given name in the given `Namespace`, if one exists.
-    pub fn find_by_name_and_namespace(
+    /// Returns the associated item with the given identifier in the given `Namespace`, if one
+    /// exists. The identifier is matched hygienically.
+    pub fn find_by_ident_and_namespace(
         &self,
         tcx: TyCtxt<'_>,
         ident: Ident,
diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs
index 80f1bd7c6f464..a2b3acac3f26b 100644
--- a/compiler/rustc_middle/src/ty/mod.rs
+++ b/compiler/rustc_middle/src/ty/mod.rs
@@ -1939,15 +1939,15 @@ impl<'tcx> TyCtxt<'tcx> {
     /// Hygienically compares a use-site name (`use_name`) for a field or an associated item with
     /// its supposed definition name (`def_name`). The method also needs `DefId` of the supposed
     /// definition's parent/scope to perform comparison.
-    pub fn hygienic_eq(self, use_name: Ident, def_name: Ident, def_parent_def_id: DefId) -> bool {
-        // We could use `Ident::eq` here, but we deliberately don't. The name
+    pub fn hygienic_eq(self, use_ident: Ident, def_ident: Ident, def_parent_def_id: DefId) -> bool {
+        // We could use `Ident::eq` here, but we deliberately don't. The identifier
         // comparison fails frequently, and we want to avoid the expensive
         // `normalize_to_macros_2_0()` calls required for the span comparison whenever possible.
-        use_name.name == def_name.name
-            && use_name
+        use_ident.name == def_ident.name
+            && use_ident
                 .span
                 .ctxt()
-                .hygienic_eq(def_name.span.ctxt(), self.expn_that_defined(def_parent_def_id))
+                .hygienic_eq(def_ident.span.ctxt(), self.expn_that_defined(def_parent_def_id))
     }
 
     pub fn adjust_ident(self, mut ident: Ident, scope: DefId) -> Ident {
diff --git a/compiler/rustc_monomorphize/src/mono_checks/move_check.rs b/compiler/rustc_monomorphize/src/mono_checks/move_check.rs
index a484573f0d860..55d52d5075dcc 100644
--- a/compiler/rustc_monomorphize/src/mono_checks/move_check.rs
+++ b/compiler/rustc_monomorphize/src/mono_checks/move_check.rs
@@ -191,7 +191,7 @@ impl<'tcx> MoveCheckVisitor<'tcx> {
 
 fn assoc_fn_of_type<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, fn_ident: Ident) -> Option<DefId> {
     for impl_def_id in tcx.inherent_impls(def_id) {
-        if let Some(new) = tcx.associated_items(impl_def_id).find_by_name_and_kind(
+        if let Some(new) = tcx.associated_items(impl_def_id).find_by_ident_and_kind(
             tcx,
             fn_ident,
             AssocKind::Fn,
diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs
index 6dc854758da56..56bc826c94f73 100644
--- a/compiler/rustc_resolve/src/diagnostics.rs
+++ b/compiler/rustc_resolve/src/diagnostics.rs
@@ -2550,7 +2550,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
                 .iter()
                 .filter_map(|item| {
                     let parent_module = self.opt_local_def_id(item.parent_module)?.to_def_id();
-                    Some(StrippedCfgItem { parent_module, name: item.name, cfg: item.cfg.clone() })
+                    Some(StrippedCfgItem {
+                        parent_module,
+                        ident: item.ident,
+                        cfg: item.cfg.clone(),
+                    })
                 })
                 .collect::<Vec<_>>();
             local_items.as_slice()
@@ -2558,12 +2562,12 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
             self.tcx.stripped_cfg_items(module.krate)
         };
 
-        for &StrippedCfgItem { parent_module, name, ref cfg } in symbols {
-            if parent_module != module || name.name != *segment {
+        for &StrippedCfgItem { parent_module, ident, ref cfg } in symbols {
+            if parent_module != module || ident.name != *segment {
                 continue;
             }
 
-            let note = errors::FoundItemConfigureOut { span: name.span };
+            let note = errors::FoundItemConfigureOut { span: ident.span };
             err.subdiagnostic(note);
 
             if let MetaItemKind::List(nested) = &cfg.kind
diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs
index b62bc6c45e0c5..c2761bd2717f4 100644
--- a/compiler/rustc_resolve/src/late/diagnostics.rs
+++ b/compiler/rustc_resolve/src/late/diagnostics.rs
@@ -2238,7 +2238,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
                                     .get(&def_id)
                                     .is_some_and(|sig| sig.has_self),
                                 None => {
-                                    self.r.tcx.fn_arg_names(def_id).first().is_some_and(|&ident| {
+                                    self.r.tcx.fn_arg_idents(def_id).first().is_some_and(|&ident| {
                                         matches!(ident, Some(Ident { name: kw::SelfLower, .. }))
                                     })
                                 }
diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs
index 3ac66840d87de..e1476814d5c0f 100644
--- a/compiler/rustc_resolve/src/lib.rs
+++ b/compiler/rustc_resolve/src/lib.rs
@@ -1648,7 +1648,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
                 .filter_map(|item| {
                     let parent_module =
                         self.node_id_to_def_id.get(&item.parent_module)?.key().to_def_id();
-                    Some(StrippedCfgItem { parent_module, name: item.name, cfg: item.cfg })
+                    Some(StrippedCfgItem { parent_module, ident: item.ident, cfg: item.cfg })
                 })
                 .collect(),
         );
diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs
index 9d6ae0aa9d130..e2f783d887e4c 100644
--- a/compiler/rustc_resolve/src/macros.rs
+++ b/compiler/rustc_resolve/src/macros.rs
@@ -469,8 +469,8 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> {
         self.proc_macros.push(id)
     }
 
-    fn append_stripped_cfg_item(&mut self, parent_node: NodeId, name: Ident, cfg: ast::MetaItem) {
-        self.stripped_cfg_items.push(StrippedCfgItem { parent_module: parent_node, name, cfg });
+    fn append_stripped_cfg_item(&mut self, parent_node: NodeId, ident: Ident, cfg: ast::MetaItem) {
+        self.stripped_cfg_items.push(StrippedCfgItem { parent_module: parent_node, ident, cfg });
     }
 
     fn registered_tools(&self) -> &RegisteredTools {
diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs
index 38fcba4ea6256..3583bc8ad8ffc 100644
--- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs
+++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs
@@ -1988,7 +1988,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
         {
             let closure: Vec<_> = self
                 .tcx
-                .fn_arg_names(fn_def_id)
+                .fn_arg_idents(fn_def_id)
                 .iter()
                 .enumerate()
                 .map(|(i, ident)| {
@@ -5397,7 +5397,7 @@ fn point_at_assoc_type_restriction<G: EmissionGuarantee>(
                 );
             }
             if let Some(new) =
-                tcx.associated_items(data.impl_or_alias_def_id).find_by_name_and_kind(
+                tcx.associated_items(data.impl_or_alias_def_id).find_by_ident_and_kind(
                     tcx,
                     Ident::with_dummy_span(name),
                     ty::AssocKind::Type,
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index e0e09b53fc28b..3a2b6974681bc 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -497,7 +497,7 @@ pub(crate) fn build_impl(
                         };
                         let trait_item = tcx
                             .associated_items(associated_trait.def_id)
-                            .find_by_name_and_kind(
+                            .find_by_ident_and_kind(
                                 tcx,
                                 item.ident,
                                 assoc_kind,
@@ -524,7 +524,7 @@ pub(crate) fn build_impl(
                     if let Some(associated_trait) = associated_trait {
                         let trait_item = tcx
                             .associated_items(associated_trait.def_id)
-                            .find_by_name_and_kind(
+                            .find_by_ident_and_kind(
                                 tcx,
                                 item.ident(tcx),
                                 item.kind,
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index ee6008b7851d3..45a915719e9f2 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -1088,7 +1088,7 @@ fn clean_fn_decl_legacy_const_generics(func: &mut Function, attrs: &[hir::Attrib
 
 enum FunctionArgs<'tcx> {
     Body(hir::BodyId),
-    Names(&'tcx [Option<Ident>]),
+    Idents(&'tcx [Option<Ident>]),
 }
 
 fn clean_function<'tcx>(
@@ -1104,8 +1104,8 @@ fn clean_function<'tcx>(
             FunctionArgs::Body(body_id) => {
                 clean_args_from_types_and_body_id(cx, sig.decl.inputs, body_id)
             }
-            FunctionArgs::Names(names) => {
-                clean_args_from_types_and_names(cx, sig.decl.inputs, names)
+            FunctionArgs::Idents(idents) => {
+                clean_args_from_types_and_names(cx, sig.decl.inputs, idents)
             }
         };
         let decl = clean_fn_decl_with_args(cx, sig.decl, Some(&sig.header), args);
@@ -1117,7 +1117,7 @@ fn clean_function<'tcx>(
 fn clean_args_from_types_and_names<'tcx>(
     cx: &mut DocContext<'tcx>,
     types: &[hir::Ty<'tcx>],
-    names: &[Option<Ident>],
+    idents: &[Option<Ident>],
 ) -> Arguments {
     fn nonempty_name(ident: &Option<Ident>) -> Option<Symbol> {
         if let Some(ident) = ident
@@ -1131,7 +1131,7 @@ fn clean_args_from_types_and_names<'tcx>(
 
     // If at least one argument has a name, use `_` as the name of unnamed
     // arguments. Otherwise omit argument names.
-    let default_name = if names.iter().any(|ident| nonempty_name(ident).is_some()) {
+    let default_name = if idents.iter().any(|ident| nonempty_name(ident).is_some()) {
         kw::Underscore
     } else {
         kw::Empty
@@ -1143,7 +1143,7 @@ fn clean_args_from_types_and_names<'tcx>(
             .enumerate()
             .map(|(i, ty)| Argument {
                 type_: clean_ty(ty, cx),
-                name: names.get(i).and_then(nonempty_name).unwrap_or(default_name),
+                name: idents.get(i).and_then(nonempty_name).unwrap_or(default_name),
                 is_const: false,
             })
             .collect(),
@@ -1193,7 +1193,7 @@ fn clean_poly_fn_sig<'tcx>(
     did: Option<DefId>,
     sig: ty::PolyFnSig<'tcx>,
 ) -> FnDecl {
-    let mut names = did.map_or(&[] as &[_], |did| cx.tcx.fn_arg_names(did)).iter();
+    let mut names = did.map_or(&[] as &[_], |did| cx.tcx.fn_arg_idents(did)).iter();
 
     // We assume all empty tuples are default return type. This theoretically can discard `-> ()`,
     // but shouldn't change any code meaning.
@@ -1270,8 +1270,8 @@ fn clean_trait_item<'tcx>(trait_item: &hir::TraitItem<'tcx>, cx: &mut DocContext
                 let m = clean_function(cx, sig, trait_item.generics, FunctionArgs::Body(body));
                 MethodItem(m, None)
             }
-            hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Required(names)) => {
-                let m = clean_function(cx, sig, trait_item.generics, FunctionArgs::Names(names));
+            hir::TraitItemKind::Fn(ref sig, hir::TraitFn::Required(idents)) => {
+                let m = clean_function(cx, sig, trait_item.generics, FunctionArgs::Idents(idents));
                 RequiredMethodItem(m)
             }
             hir::TraitItemKind::Type(bounds, Some(default)) => {
@@ -2612,7 +2612,7 @@ fn clean_bare_fn_ty<'tcx>(
             .filter(|p| !is_elided_lifetime(p))
             .map(|x| clean_generic_param(cx, None, x))
             .collect();
-        let args = clean_args_from_types_and_names(cx, bare_fn.decl.inputs, bare_fn.param_names);
+        let args = clean_args_from_types_and_names(cx, bare_fn.decl.inputs, bare_fn.param_idents);
         let decl = clean_fn_decl_with_args(cx, bare_fn.decl, None, args);
         (generic_params, decl)
     });
@@ -3148,8 +3148,8 @@ fn clean_maybe_renamed_foreign_item<'tcx>(
     let def_id = item.owner_id.to_def_id();
     cx.with_param_env(def_id, |cx| {
         let kind = match item.kind {
-            hir::ForeignItemKind::Fn(sig, names, generics) => ForeignFunctionItem(
-                clean_function(cx, &sig, generics, FunctionArgs::Names(names)),
+            hir::ForeignItemKind::Fn(sig, idents, generics) => ForeignFunctionItem(
+                clean_function(cx, &sig, generics, FunctionArgs::Idents(idents)),
                 sig.header.safety(),
             ),
             hir::ForeignItemKind::Static(ty, mutability, safety) => ForeignStaticItem(
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 adac2f27ea8cf..ad18c7039eedf 100644
--- a/src/tools/clippy/clippy_lints/src/bool_assert_comparison.rs
+++ b/src/tools/clippy/clippy_lints/src/bool_assert_comparison.rs
@@ -53,7 +53,7 @@ fn is_impl_not_trait_with_bool_out<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -
         .not_trait()
         .filter(|trait_id| implements_trait(cx, ty, *trait_id, &[]))
         .and_then(|trait_id| {
-            cx.tcx.associated_items(trait_id).find_by_name_and_kind(
+            cx.tcx.associated_items(trait_id).find_by_ident_and_kind(
                 cx.tcx,
                 Ident::from_str("Output"),
                 ty::AssocKind::Type,
diff --git a/src/tools/clippy/clippy_lints/src/functions/renamed_function_params.rs b/src/tools/clippy/clippy_lints/src/functions/renamed_function_params.rs
index 041f6228fba2d..4495aeb5953e0 100644
--- a/src/tools/clippy/clippy_lints/src/functions/renamed_function_params.rs
+++ b/src/tools/clippy/clippy_lints/src/functions/renamed_function_params.rs
@@ -22,8 +22,8 @@ pub(super) fn check_impl_item(cx: &LateContext<'_>, item: &ImplItem<'_>, ignored
         && let Some(did) = trait_item_def_id_of_impl(items, item.owner_id)
         && !is_from_ignored_trait(trait_ref, ignored_traits)
     {
-        let mut param_idents_iter = cx.tcx.hir_body_param_names(body_id);
-        let mut default_param_idents_iter = cx.tcx.fn_arg_names(did).iter().copied();
+        let mut param_idents_iter = cx.tcx.hir_body_param_idents(body_id);
+        let mut default_param_idents_iter = cx.tcx.fn_arg_idents(did).iter().copied();
 
         let renames = RenamedFnArgs::new(&mut default_param_idents_iter, &mut param_idents_iter);
         if !renames.0.is_empty() {
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 56ff7e2c61b22..239ee6c729fb0 100644
--- a/src/tools/clippy/clippy_lints/src/methods/needless_collect.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/needless_collect.rs
@@ -238,7 +238,7 @@ fn is_contains_sig(cx: &LateContext<'_>, call_id: HirId, iter_expr: &Expr<'_>) -
             .instantiate_bound_regions_with_erased(sig.rebind(search_ty))
             .kind()
         && let Some(iter_trait) = cx.tcx.get_diagnostic_item(sym::Iterator)
-        && let Some(iter_item) = cx.tcx.associated_items(iter_trait).find_by_name_and_kind(
+        && 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,
diff --git a/src/tools/clippy/clippy_utils/src/ty/mod.rs b/src/tools/clippy/clippy_utils/src/ty/mod.rs
index 6fdf4c244f8d8..29cbf62c3d4c1 100644
--- a/src/tools/clippy/clippy_utils/src/ty/mod.rs
+++ b/src/tools/clippy/clippy_utils/src/ty/mod.rs
@@ -1109,7 +1109,7 @@ pub fn make_projection<'tcx>(
         assoc_ty: Symbol,
         args: GenericArgsRef<'tcx>,
     ) -> Option<AliasTy<'tcx>> {
-        let Some(assoc_item) = tcx.associated_items(container_id).find_by_name_and_kind(
+        let Some(assoc_item) = tcx.associated_items(container_id).find_by_ident_and_kind(
             tcx,
             Ident::with_dummy_span(assoc_ty),
             AssocKind::Type,
diff --git a/src/tools/compiletest/Cargo.toml b/src/tools/compiletest/Cargo.toml
index 06e618c2d254b..3db34ed24cc20 100644
--- a/src/tools/compiletest/Cargo.toml
+++ b/src/tools/compiletest/Cargo.toml
@@ -1,7 +1,7 @@
 [package]
 name = "compiletest"
 version = "0.0.0"
-edition = "2021"
+edition = "2024"
 
 [lib]
 doctest = false
diff --git a/src/tools/compiletest/src/debuggers.rs b/src/tools/compiletest/src/debuggers.rs
index 20e3c8dfb9ee7..5126e55aea123 100644
--- a/src/tools/compiletest/src/debuggers.rs
+++ b/src/tools/compiletest/src/debuggers.rs
@@ -40,7 +40,9 @@ pub(crate) fn configure_gdb(config: &Config) -> Option<Arc<Config>> {
         //
         // we should figure out how to lift this restriction! (run them all
         // on different ports allocated dynamically).
-        env::set_var("RUST_TEST_THREADS", "1");
+        //
+        // SAFETY: at this point we are still single-threaded.
+        unsafe { env::set_var("RUST_TEST_THREADS", "1") };
     }
 
     Some(Arc::new(Config { debugger: Some(Debugger::Gdb), ..config.clone() }))
diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs
index 782f6e0f2d8d2..dfd678a7e2d65 100644
--- a/src/tools/compiletest/src/lib.rs
+++ b/src/tools/compiletest/src/lib.rs
@@ -529,10 +529,14 @@ pub fn run_tests(config: Arc<Config>) {
     }
     // Prevent issue #21352 UAC blocking .exe containing 'patch' etc. on Windows
     // If #11207 is resolved (adding manifest to .exe) this becomes unnecessary
-    env::set_var("__COMPAT_LAYER", "RunAsInvoker");
-
-    // Let tests know which target they're running as
-    env::set_var("TARGET", &config.target);
+    //
+    // SAFETY: at this point we're still single-threaded.
+    unsafe { env::set_var("__COMPAT_LAYER", "RunAsInvoker") };
+
+    // Let tests know which target they're running as.
+    //
+    // SAFETY: at this point we're still single-threaded.
+    unsafe { env::set_var("TARGET", &config.target) };
 
     let mut configs = Vec::new();
     if let Mode::DebugInfo = config.mode {
diff --git a/src/tools/compiletest/src/raise_fd_limit.rs b/src/tools/compiletest/src/raise_fd_limit.rs
index 7b12ba946b9eb..653b125a6b413 100644
--- a/src/tools/compiletest/src/raise_fd_limit.rs
+++ b/src/tools/compiletest/src/raise_fd_limit.rs
@@ -6,6 +6,7 @@
 /// This fixes issue #7772.
 #[cfg(target_vendor = "apple")]
 #[allow(non_camel_case_types)]
+// FIXME(#139616): document caller contract.
 pub unsafe fn raise_fd_limit() {
     use std::ptr::null_mut;
     use std::{cmp, io};
@@ -21,8 +22,10 @@ pub unsafe fn raise_fd_limit() {
     let mut mib: [libc::c_int; 2] = [CTL_KERN, KERN_MAXFILESPERPROC];
     let mut maxfiles: libc::c_int = 0;
     let mut size: libc::size_t = size_of_val(&maxfiles) as libc::size_t;
-    if libc::sysctl(&mut mib[0], 2, &mut maxfiles as *mut _ as *mut _, &mut size, null_mut(), 0)
-        != 0
+    // FIXME(#139616): justify why this is sound.
+    if unsafe {
+        libc::sysctl(&mut mib[0], 2, &mut maxfiles as *mut _ as *mut _, &mut size, null_mut(), 0)
+    } != 0
     {
         let err = io::Error::last_os_error();
         panic!("raise_fd_limit: error calling sysctl: {}", err);
@@ -30,7 +33,8 @@ pub unsafe fn raise_fd_limit() {
 
     // Fetch the current resource limits
     let mut rlim = libc::rlimit { rlim_cur: 0, rlim_max: 0 };
-    if libc::getrlimit(libc::RLIMIT_NOFILE, &mut rlim) != 0 {
+    // FIXME(#139616): justify why this is sound.
+    if unsafe { libc::getrlimit(libc::RLIMIT_NOFILE, &mut rlim) } != 0 {
         let err = io::Error::last_os_error();
         panic!("raise_fd_limit: error calling getrlimit: {}", err);
     }
@@ -41,7 +45,8 @@ pub unsafe fn raise_fd_limit() {
         rlim.rlim_cur = cmp::min(maxfiles as libc::rlim_t, rlim.rlim_max);
 
         // Set our newly-increased resource limit.
-        if libc::setrlimit(libc::RLIMIT_NOFILE, &rlim) != 0 {
+        // FIXME(#139616): justify why this is sound.
+        if unsafe { libc::setrlimit(libc::RLIMIT_NOFILE, &rlim) } != 0 {
             let err = io::Error::last_os_error();
             panic!("raise_fd_limit: error calling setrlimit: {}", err);
         }
diff --git a/src/tools/compiletest/src/read2.rs b/src/tools/compiletest/src/read2.rs
index 28ca5589992a2..2213dd07160a7 100644
--- a/src/tools/compiletest/src/read2.rs
+++ b/src/tools/compiletest/src/read2.rs
@@ -165,6 +165,7 @@ mod imp {
         mut err_pipe: ChildStderr,
         data: &mut dyn FnMut(bool, &mut Vec<u8>, bool),
     ) -> io::Result<()> {
+        // FIXME(#139616): justify why this is sound.
         unsafe {
             libc::fcntl(out_pipe.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK);
             libc::fcntl(err_pipe.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK);
@@ -175,6 +176,7 @@ mod imp {
         let mut out = Vec::new();
         let mut err = Vec::new();
 
+        // FIXME(#139616): justify why this is sound.
         let mut fds: [libc::pollfd; 2] = unsafe { mem::zeroed() };
         fds[0].fd = out_pipe.as_raw_fd();
         fds[0].events = libc::POLLIN;
@@ -185,6 +187,7 @@ mod imp {
 
         while nfds > 0 {
             // wait for either pipe to become readable using `select`
+            // FIXME(#139616): justify why this is sound.
             let r = unsafe { libc::poll(fds.as_mut_ptr(), nfds, -1) };
             if r == -1 {
                 let err = io::Error::last_os_error();
@@ -256,6 +259,7 @@ mod imp {
         port.add_handle(0, &out_pipe)?;
         port.add_handle(1, &err_pipe)?;
 
+        // FIXME(#139616): justify why this is sound.
         unsafe {
             let mut out_pipe = Pipe::new(out_pipe, &mut out);
             let mut err_pipe = Pipe::new(err_pipe, &mut err);
@@ -284,18 +288,23 @@ mod imp {
     }
 
     impl<'a> Pipe<'a> {
+        // FIXME(#139616): document caller contract.
         unsafe fn new<P: IntoRawHandle>(p: P, dst: &'a mut Vec<u8>) -> Pipe<'a> {
             Pipe {
                 dst,
-                pipe: NamedPipe::from_raw_handle(p.into_raw_handle()),
+                // FIXME(#139616): justify why this is sound.
+                pipe: unsafe { NamedPipe::from_raw_handle(p.into_raw_handle()) },
                 overlapped: Overlapped::zero(),
                 done: false,
             }
         }
 
+        // FIXME(#139616): document caller contract.
         unsafe fn read(&mut self) -> io::Result<()> {
-            let dst = slice_to_end(self.dst);
-            match self.pipe.read_overlapped(dst, self.overlapped.raw()) {
+            // FIXME(#139616): justify why this is sound.
+            let dst = unsafe { slice_to_end(self.dst) };
+            // FIXME(#139616): justify why this is sound.
+            match unsafe { self.pipe.read_overlapped(dst, self.overlapped.raw()) } {
                 Ok(_) => Ok(()),
                 Err(e) => {
                     if e.raw_os_error() == Some(ERROR_BROKEN_PIPE.0 as i32) {
@@ -308,15 +317,18 @@ mod imp {
             }
         }
 
+        // FIXME(#139616): document caller contract.
         unsafe fn complete(&mut self, status: &CompletionStatus) {
             let prev = self.dst.len();
-            self.dst.set_len(prev + status.bytes_transferred() as usize);
+            // FIXME(#139616): justify why this is sound.
+            unsafe { self.dst.set_len(prev + status.bytes_transferred() as usize) };
             if status.bytes_transferred() == 0 {
                 self.done = true;
             }
         }
     }
 
+    // FIXME(#139616): document caller contract.
     unsafe fn slice_to_end(v: &mut Vec<u8>) -> &mut [u8] {
         if v.capacity() == 0 {
             v.reserve(16);
@@ -324,6 +336,12 @@ mod imp {
         if v.capacity() == v.len() {
             v.reserve(1);
         }
-        slice::from_raw_parts_mut(v.as_mut_ptr().offset(v.len() as isize), v.capacity() - v.len())
+        // FIXME(#139616): justify why this is sound.
+        unsafe {
+            slice::from_raw_parts_mut(
+                v.as_mut_ptr().offset(v.len() as isize),
+                v.capacity() - v.len(),
+            )
+        }
     }
 }
diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs
index a3aae39be81d0..9c03fa141bd36 100644
--- a/src/tools/compiletest/src/runtest.rs
+++ b/src/tools/compiletest/src/runtest.rs
@@ -445,8 +445,8 @@ impl<'test> TestCx<'test> {
 
         self.compose_and_run(
             rustc,
-            self.config.compile_lib_path.to_str().unwrap(),
-            Some(aux_dir.to_str().unwrap()),
+            self.config.compile_lib_path.as_path(),
+            Some(aux_dir.as_path()),
             src,
         )
     }
@@ -1020,8 +1020,8 @@ impl<'test> TestCx<'test> {
 
                 self.compose_and_run(
                     test_client,
-                    self.config.run_lib_path.to_str().unwrap(),
-                    Some(aux_dir.to_str().unwrap()),
+                    self.config.run_lib_path.as_path(),
+                    Some(aux_dir.as_path()),
                     None,
                 )
             }
@@ -1035,8 +1035,8 @@ impl<'test> TestCx<'test> {
 
                 self.compose_and_run(
                     wr_run,
-                    self.config.run_lib_path.to_str().unwrap(),
-                    Some(aux_dir.to_str().unwrap()),
+                    self.config.run_lib_path.as_path(),
+                    Some(aux_dir.as_path()),
                     None,
                 )
             }
@@ -1050,8 +1050,8 @@ impl<'test> TestCx<'test> {
 
                 self.compose_and_run(
                     program,
-                    self.config.run_lib_path.to_str().unwrap(),
-                    Some(aux_dir.to_str().unwrap()),
+                    self.config.run_lib_path.as_path(),
+                    Some(aux_dir.as_path()),
                     None,
                 )
             }
@@ -1197,8 +1197,8 @@ impl<'test> TestCx<'test> {
         self.props.unset_rustc_env.iter().fold(&mut rustc, Command::env_remove);
         self.compose_and_run(
             rustc,
-            self.config.compile_lib_path.to_str().unwrap(),
-            Some(aux_dir.to_str().unwrap()),
+            self.config.compile_lib_path.as_path(),
+            Some(aux_dir.as_path()),
             input,
         )
     }
@@ -1219,8 +1219,7 @@ impl<'test> TestCx<'test> {
         rustc.args(&["--crate-type", "rlib"]);
         rustc.arg("-Cpanic=abort");
 
-        let res =
-            self.compose_and_run(rustc, self.config.compile_lib_path.to_str().unwrap(), None, None);
+        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!(
@@ -1332,8 +1331,8 @@ impl<'test> TestCx<'test> {
 
         let auxres = aux_cx.compose_and_run(
             aux_rustc,
-            aux_cx.config.compile_lib_path.to_str().unwrap(),
-            Some(aux_dir.to_str().unwrap()),
+            aux_cx.config.compile_lib_path.as_path(),
+            Some(aux_dir.as_path()),
             None,
         );
         if !auxres.status.success() {
@@ -1373,8 +1372,8 @@ impl<'test> TestCx<'test> {
     fn compose_and_run(
         &self,
         mut command: Command,
-        lib_path: &str,
-        aux_path: Option<&str>,
+        lib_path: &Path,
+        aux_path: Option<&Path>,
         input: Option<String>,
     ) -> ProcRes {
         let cmdline = {
@@ -1806,7 +1805,7 @@ impl<'test> TestCx<'test> {
         }
     }
 
-    fn make_cmdline(&self, command: &Command, libpath: &str) -> String {
+    fn make_cmdline(&self, command: &Command, libpath: &Path) -> String {
         use crate::util;
 
         // Linux and mac don't require adjusting the library search path
@@ -1819,7 +1818,7 @@ impl<'test> TestCx<'test> {
                 format!("{}=\"{}\"", util::lib_path_env_var(), util::make_new_path(path))
             }
 
-            format!("{} {:?}", lib_path_cmd_prefix(libpath), command)
+            format!("{} {:?}", lib_path_cmd_prefix(libpath.to_str().unwrap()), command)
         }
     }
 
@@ -1980,7 +1979,8 @@ impl<'test> TestCx<'test> {
         // Add custom flags supplied by the `filecheck-flags:` test header.
         filecheck.args(&self.props.filecheck_flags);
 
-        self.compose_and_run(filecheck, "", None, None)
+        // FIXME(jieyouxu): don't pass an empty Path
+        self.compose_and_run(filecheck, Path::new(""), None, None)
     }
 
     fn charset() -> &'static str {
diff --git a/src/tools/compiletest/src/runtest/debuginfo.rs b/src/tools/compiletest/src/runtest/debuginfo.rs
index 170b8a8099687..50e733cd31b6b 100644
--- a/src/tools/compiletest/src/runtest/debuginfo.rs
+++ b/src/tools/compiletest/src/runtest/debuginfo.rs
@@ -104,7 +104,7 @@ impl TestCx<'_> {
 
         let debugger_run_result = self.compose_and_run(
             cdb,
-            self.config.run_lib_path.to_str().unwrap(),
+            self.config.run_lib_path.as_path(),
             None, // aux_path
             None, // input
         );
@@ -241,7 +241,8 @@ impl TestCx<'_> {
             let cmdline = {
                 let mut gdb = Command::new(&format!("{}-gdb", self.config.target));
                 gdb.args(debugger_opts);
-                let cmdline = self.make_cmdline(&gdb, "");
+                // FIXME(jieyouxu): don't pass an empty Path
+                let cmdline = self.make_cmdline(&gdb, Path::new(""));
                 logv(self.config, format!("executing {}", cmdline));
                 cmdline
             };
@@ -340,7 +341,7 @@ impl TestCx<'_> {
             gdb.args(debugger_opts).env("PYTHONPATH", pythonpath);
 
             debugger_run_result =
-                self.compose_and_run(gdb, self.config.run_lib_path.to_str().unwrap(), None, None);
+                self.compose_and_run(gdb, self.config.run_lib_path.as_path(), None, None);
         }
 
         if !debugger_run_result.status.success() {
diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock
index bb228962ecc9c..0c6f4a3dd06e7 100644
--- a/src/tools/miri/Cargo.lock
+++ b/src/tools/miri/Cargo.lock
@@ -538,6 +538,7 @@ name = "miri"
 version = "0.1.0"
 dependencies = [
  "aes",
+ "bitflags",
  "chrono",
  "chrono-tz",
  "colored",
diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml
index 5bb07648f75c6..e9ee19b793266 100644
--- a/src/tools/miri/Cargo.toml
+++ b/src/tools/miri/Cargo.toml
@@ -26,6 +26,7 @@ measureme = "12"
 chrono = { version = "0.4.38", default-features = false }
 chrono-tz = "0.10"
 directories = "6"
+bitflags = "2.6"
 
 # Copied from `compiler/rustc/Cargo.toml`.
 # But only for some targets, it fails for others. Rustc configures this in its CI, but we can't
diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh
index 7155d692ee5c9..b690bd9cd2b97 100755
--- a/src/tools/miri/ci/ci.sh
+++ b/src/tools/miri/ci/ci.sh
@@ -164,7 +164,7 @@ case $HOST_TARGET in
     # Partially supported targets (tier 2)
     BASIC="empty_main integer heap_alloc libc-mem vec string btreemap" # ensures we have the basics: pre-main code, system allocator
     UNIX="hello panic/panic panic/unwind concurrency/simple atomic libc-mem libc-misc libc-random env num_cpus" # the things that are very similar across all Unixes, and hence easily supported there
-    TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal $BASIC $UNIX time hashmap random threadname pthread fs libc-pipe
+    TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal $BASIC $UNIX time hashmap random threadname pthread fs libc-pipe concurrency sync
     TEST_TARGET=i686-unknown-freebsd   run_tests_minimal $BASIC $UNIX time hashmap random threadname pthread fs libc-pipe
     TEST_TARGET=aarch64-linux-android  run_tests_minimal $BASIC $UNIX time hashmap random sync concurrency thread epoll eventfd
     TEST_TARGET=wasm32-wasip2          run_tests_minimal $BASIC wasm
diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version
index a6f2951087975..d2b8b51473061 100644
--- a/src/tools/miri/rust-version
+++ b/src/tools/miri/rust-version
@@ -1 +1 @@
-25a615bf829b9f6d6f22da537e3851043f92e5f2
+7d7de5bf3c3cbf9c2c5bbc5cbfb9197a8a427d35
diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs
index 6b4f4cdc922a0..42603e784bbd7 100644
--- a/src/tools/miri/src/shims/files.rs
+++ b/src/tools/miri/src/shims/files.rs
@@ -1,6 +1,7 @@
 use std::any::Any;
 use std::collections::BTreeMap;
-use std::io::{IsTerminal, SeekFrom, Write};
+use std::fs::{File, Metadata};
+use std::io::{IsTerminal, Seek, SeekFrom, Write};
 use std::marker::CoercePointee;
 use std::ops::Deref;
 use std::rc::{Rc, Weak};
@@ -192,7 +193,7 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
         false
     }
 
-    fn as_unix(&self) -> &dyn UnixFileDescription {
+    fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
         panic!("Not a unix file descriptor: {}", self.name());
     }
 }
@@ -278,6 +279,97 @@ impl FileDescription for io::Stderr {
     }
 }
 
+#[derive(Debug)]
+pub struct FileHandle {
+    pub(crate) file: File,
+    pub(crate) writable: bool,
+}
+
+impl FileDescription for FileHandle {
+    fn name(&self) -> &'static str {
+        "file"
+    }
+
+    fn read<'tcx>(
+        self: FileDescriptionRef<Self>,
+        communicate_allowed: bool,
+        ptr: Pointer,
+        len: usize,
+        ecx: &mut MiriInterpCx<'tcx>,
+        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
+    ) -> InterpResult<'tcx> {
+        assert!(communicate_allowed, "isolation should have prevented even opening a file");
+
+        let result = ecx.read_from_host(&self.file, len, ptr)?;
+        finish.call(ecx, result)
+    }
+
+    fn write<'tcx>(
+        self: FileDescriptionRef<Self>,
+        communicate_allowed: bool,
+        ptr: Pointer,
+        len: usize,
+        ecx: &mut MiriInterpCx<'tcx>,
+        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
+    ) -> InterpResult<'tcx> {
+        assert!(communicate_allowed, "isolation should have prevented even opening a file");
+
+        let result = ecx.write_to_host(&self.file, len, ptr)?;
+        finish.call(ecx, result)
+    }
+
+    fn seek<'tcx>(
+        &self,
+        communicate_allowed: bool,
+        offset: SeekFrom,
+    ) -> InterpResult<'tcx, io::Result<u64>> {
+        assert!(communicate_allowed, "isolation should have prevented even opening a file");
+        interp_ok((&mut &self.file).seek(offset))
+    }
+
+    fn close<'tcx>(
+        self,
+        communicate_allowed: bool,
+        _ecx: &mut MiriInterpCx<'tcx>,
+    ) -> InterpResult<'tcx, io::Result<()>> {
+        assert!(communicate_allowed, "isolation should have prevented even opening a file");
+        // We sync the file if it was opened in a mode different than read-only.
+        if self.writable {
+            // `File::sync_all` does the checks that are done when closing a file. We do this to
+            // to handle possible errors correctly.
+            let result = self.file.sync_all();
+            // Now we actually close the file and return the result.
+            drop(self.file);
+            interp_ok(result)
+        } else {
+            // We drop the file, this closes it but ignores any errors
+            // produced when closing it. This is done because
+            // `File::sync_all` cannot be done over files like
+            // `/dev/urandom` which are read-only. Check
+            // https://github.com/rust-lang/miri/issues/999#issuecomment-568920439
+            // for a deeper discussion.
+            drop(self.file);
+            interp_ok(Ok(()))
+        }
+    }
+
+    fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
+        interp_ok(self.file.metadata())
+    }
+
+    fn is_tty(&self, communicate_allowed: bool) -> bool {
+        communicate_allowed && self.file.is_terminal()
+    }
+
+    fn as_unix<'tcx>(&self, ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
+        assert!(
+            ecx.target_os_is_unix(),
+            "unix file operations are only available for unix targets"
+        );
+        self
+    }
+}
+
 /// Like /dev/null
 #[derive(Debug)]
 pub struct NullOutput;
@@ -300,10 +392,13 @@ impl FileDescription for NullOutput {
     }
 }
 
+/// Internal type of a file-descriptor - this is what [`FdTable`] expects
+pub type FdNum = i32;
+
 /// The file descriptor table
 #[derive(Debug)]
 pub struct FdTable {
-    pub fds: BTreeMap<i32, DynFileDescriptionRef>,
+    pub fds: BTreeMap<FdNum, DynFileDescriptionRef>,
     /// Unique identifier for file description, used to differentiate between various file description.
     next_file_description_id: FdId,
 }
@@ -339,12 +434,12 @@ impl FdTable {
     }
 
     /// Insert a new file description to the FdTable.
-    pub fn insert_new(&mut self, fd: impl FileDescription) -> i32 {
+    pub fn insert_new(&mut self, fd: impl FileDescription) -> FdNum {
         let fd_ref = self.new_ref(fd);
         self.insert(fd_ref)
     }
 
-    pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> i32 {
+    pub fn insert(&mut self, fd_ref: DynFileDescriptionRef) -> FdNum {
         self.insert_with_min_num(fd_ref, 0)
     }
 
@@ -352,8 +447,8 @@ impl FdTable {
     pub fn insert_with_min_num(
         &mut self,
         file_handle: DynFileDescriptionRef,
-        min_fd_num: i32,
-    ) -> i32 {
+        min_fd_num: FdNum,
+    ) -> FdNum {
         // Find the lowest unused FD, starting from min_fd. If the first such unused FD is in
         // between used FDs, the find_map combinator will return it. If the first such unused FD
         // is after all other used FDs, the find_map combinator will return None, and we will use
@@ -379,16 +474,16 @@ impl FdTable {
         new_fd_num
     }
 
-    pub fn get(&self, fd_num: i32) -> Option<DynFileDescriptionRef> {
+    pub fn get(&self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
         let fd = self.fds.get(&fd_num)?;
         Some(fd.clone())
     }
 
-    pub fn remove(&mut self, fd_num: i32) -> Option<DynFileDescriptionRef> {
+    pub fn remove(&mut self, fd_num: FdNum) -> Option<DynFileDescriptionRef> {
         self.fds.remove(&fd_num)
     }
 
-    pub fn is_fd_num(&self, fd_num: i32) -> bool {
+    pub fn is_fd_num(&self, fd_num: FdNum) -> bool {
         self.fds.contains_key(&fd_num)
     }
 }
diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs
index d7c445b47cb0e..fb80a36af9fc2 100644
--- a/src/tools/miri/src/shims/time.rs
+++ b/src/tools/miri/src/shims/time.rs
@@ -219,16 +219,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
         let filetime = this.deref_pointer_as(LPFILETIME_op, this.windows_ty_layout("FILETIME"))?;
 
-        let NANOS_PER_SEC = this.eval_windows_u64("time", "NANOS_PER_SEC");
-        let INTERVALS_PER_SEC = this.eval_windows_u64("time", "INTERVALS_PER_SEC");
-        let INTERVALS_TO_UNIX_EPOCH = this.eval_windows_u64("time", "INTERVALS_TO_UNIX_EPOCH");
-        let NANOS_PER_INTERVAL = NANOS_PER_SEC / INTERVALS_PER_SEC;
-        let SECONDS_TO_UNIX_EPOCH = INTERVALS_TO_UNIX_EPOCH / INTERVALS_PER_SEC;
-
-        let duration = system_time_to_duration(&SystemTime::now())?
-            + Duration::from_secs(SECONDS_TO_UNIX_EPOCH);
-        let duration_ticks = u64::try_from(duration.as_nanos() / u128::from(NANOS_PER_INTERVAL))
-            .map_err(|_| err_unsup_format!("programs running more than 2^64 Windows ticks after the Windows epoch are not supported"))?;
+        let duration = this.system_time_since_windows_epoch(&SystemTime::now())?;
+        let duration_ticks = this.windows_ticks_for(duration)?;
 
         let dwLowDateTime = u32::try_from(duration_ticks & 0x00000000FFFFFFFF).unwrap();
         let dwHighDateTime = u32::try_from((duration_ticks & 0xFFFFFFFF00000000) >> 32).unwrap();
@@ -281,6 +273,30 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         interp_ok(Scalar::from_i32(-1)) // Return non-zero on success
     }
 
+    #[allow(non_snake_case, clippy::arithmetic_side_effects)]
+    fn system_time_since_windows_epoch(&self, time: &SystemTime) -> InterpResult<'tcx, Duration> {
+        let this = self.eval_context_ref();
+
+        let INTERVALS_PER_SEC = this.eval_windows_u64("time", "INTERVALS_PER_SEC");
+        let INTERVALS_TO_UNIX_EPOCH = this.eval_windows_u64("time", "INTERVALS_TO_UNIX_EPOCH");
+        let SECONDS_TO_UNIX_EPOCH = INTERVALS_TO_UNIX_EPOCH / INTERVALS_PER_SEC;
+
+        interp_ok(system_time_to_duration(time)? + Duration::from_secs(SECONDS_TO_UNIX_EPOCH))
+    }
+
+    #[allow(non_snake_case, clippy::arithmetic_side_effects)]
+    fn windows_ticks_for(&self, duration: Duration) -> InterpResult<'tcx, u64> {
+        let this = self.eval_context_ref();
+
+        let NANOS_PER_SEC = this.eval_windows_u64("time", "NANOS_PER_SEC");
+        let INTERVALS_PER_SEC = this.eval_windows_u64("time", "INTERVALS_PER_SEC");
+        let NANOS_PER_INTERVAL = NANOS_PER_SEC / INTERVALS_PER_SEC;
+
+        let ticks = u64::try_from(duration.as_nanos() / u128::from(NANOS_PER_INTERVAL))
+            .map_err(|_| err_unsup_format!("programs running more than 2^64 Windows ticks after the Windows epoch are not supported"))?;
+        interp_ok(ticks)
+    }
+
     fn mach_absolute_time(&self) -> InterpResult<'tcx, Scalar> {
         let this = self.eval_context_ref();
 
diff --git a/src/tools/miri/src/shims/unix/fd.rs b/src/tools/miri/src/shims/unix/fd.rs
index 3f85b9ae9bd94..41be9df7e2da7 100644
--- a/src/tools/miri/src/shims/unix/fd.rs
+++ b/src/tools/miri/src/shims/unix/fd.rs
@@ -121,7 +121,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             throw_unsup_format!("unsupported flags {:#x}", op);
         };
 
-        let result = fd.as_unix().flock(this.machine.communicate(), parsed_op)?;
+        let result = fd.as_unix(this).flock(this.machine.communicate(), parsed_op)?;
         // return `0` if flock is successful
         let result = result.map(|()| 0i32);
         interp_ok(Scalar::from_i32(this.try_unwrap_io_result(result)?))
@@ -273,7 +273,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let Ok(offset) = u64::try_from(offset) else {
                     return this.set_last_error_and_return(LibcError("EINVAL"), dest);
                 };
-                fd.as_unix().pread(communicate, offset, buf, count, this, finish)?
+                fd.as_unix(this).pread(communicate, offset, buf, count, this, finish)?
             }
         };
         interp_ok(())
@@ -333,7 +333,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let Ok(offset) = u64::try_from(offset) else {
                     return this.set_last_error_and_return(LibcError("EINVAL"), dest);
                 };
-                fd.as_unix().pwrite(communicate, buf, count, offset, this, finish)?
+                fd.as_unix(this).pwrite(communicate, buf, count, offset, this, finish)?
             }
         };
         interp_ok(())
diff --git a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
index 08d06fe5d4c61..21a386b29272a 100644
--- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
@@ -2,6 +2,7 @@ use rustc_middle::ty::Ty;
 use rustc_span::Symbol;
 use rustc_target::callconv::{Conv, FnAbi};
 
+use super::sync::EvalContextExt as _;
 use crate::shims::unix::*;
 use crate::*;
 
@@ -55,6 +56,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(res, dest)?;
             }
 
+            // Synchronization primitives
+            "_umtx_op" => {
+                let [obj, op, val, uaddr, uaddr2] =
+                    this.check_shim(abi, Conv::C, link_name, args)?;
+                this._umtx_op(obj, op, val, uaddr, uaddr2, dest)?;
+            }
+
             // File related shims
             // For those, we both intercept `func` and `call@FBSD_1.0` symbols cases
             // since freebsd 12 the former form can be expected.
diff --git a/src/tools/miri/src/shims/unix/freebsd/mod.rs b/src/tools/miri/src/shims/unix/freebsd/mod.rs
index 09c6507b24f84..50fb2b9d32870 100644
--- a/src/tools/miri/src/shims/unix/freebsd/mod.rs
+++ b/src/tools/miri/src/shims/unix/freebsd/mod.rs
@@ -1 +1,2 @@
 pub mod foreign_items;
+pub mod sync;
diff --git a/src/tools/miri/src/shims/unix/freebsd/sync.rs b/src/tools/miri/src/shims/unix/freebsd/sync.rs
new file mode 100644
index 0000000000000..54650f35b2cb7
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/freebsd/sync.rs
@@ -0,0 +1,251 @@
+//! Contains FreeBSD-specific synchronization functions
+
+use core::time::Duration;
+
+use crate::concurrency::sync::FutexRef;
+use crate::*;
+
+pub struct FreeBsdFutex {
+    futex: FutexRef,
+}
+
+/// Extended variant of the `timespec` struct.
+pub struct UmtxTime {
+    timeout: Duration,
+    abs_time: bool,
+    timeout_clock: TimeoutClock,
+}
+
+impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
+pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
+    /// Implementation of the FreeBSD [`_umtx_op`](https://man.freebsd.org/cgi/man.cgi?query=_umtx_op&sektion=2&manpath=FreeBSD+14.2-RELEASE+and+Ports) syscall.
+    /// This is used for futex operations on FreeBSD.
+    ///
+    /// `obj`: a pointer to the futex object (can be a lot of things, mostly *AtomicU32)
+    /// `op`: the futex operation to run
+    /// `val`: the current value of the object as a `c_long` (for wait/wake)
+    /// `uaddr`: `op`-specific optional parameter, pointer-sized integer or pointer to an `op`-specific struct
+    /// `uaddr2`: `op`-specific optional parameter, pointer-sized integer or pointer to an `op`-specific struct
+    /// `dest`: the place this syscall returns to, 0 for success, -1 for failure
+    ///
+    /// # Note
+    /// Curently only the WAIT and WAKE operations are implemented.
+    fn _umtx_op(
+        &mut self,
+        obj: &OpTy<'tcx>,
+        op: &OpTy<'tcx>,
+        val: &OpTy<'tcx>,
+        uaddr: &OpTy<'tcx>,
+        uaddr2: &OpTy<'tcx>,
+        dest: &MPlaceTy<'tcx>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        let obj = this.read_pointer(obj)?;
+        let op = this.read_scalar(op)?.to_i32()?;
+        let val = this.read_target_usize(val)?;
+        let uaddr = this.read_target_usize(uaddr)?;
+        let uaddr2 = this.read_pointer(uaddr2)?;
+
+        let wait = this.eval_libc_i32("UMTX_OP_WAIT");
+        let wait_uint = this.eval_libc_i32("UMTX_OP_WAIT_UINT");
+        let wait_uint_private = this.eval_libc_i32("UMTX_OP_WAIT_UINT_PRIVATE");
+
+        let wake = this.eval_libc_i32("UMTX_OP_WAKE");
+        let wake_private = this.eval_libc_i32("UMTX_OP_WAKE_PRIVATE");
+
+        let timespec_layout = this.libc_ty_layout("timespec");
+        let umtx_time_layout = this.libc_ty_layout("_umtx_time");
+        assert!(
+            timespec_layout.size != umtx_time_layout.size,
+            "`struct timespec` and `struct _umtx_time` should have different sizes."
+        );
+
+        match op {
+            // UMTX_OP_WAIT_UINT and UMTX_OP_WAIT_UINT_PRIVATE only differ in whether they work across
+            // processes or not. For Miri, we can treat them the same.
+            op if op == wait || op == wait_uint || op == wait_uint_private => {
+                let obj_layout =
+                    if op == wait { this.machine.layouts.isize } else { this.machine.layouts.u32 };
+                let obj = this.ptr_to_mplace(obj, obj_layout);
+
+                // Read the Linux futex wait implementation in Miri to understand why this fence is needed.
+                this.atomic_fence(AtomicFenceOrd::SeqCst)?;
+                let obj_val = this
+                    .read_scalar_atomic(&obj, AtomicReadOrd::Acquire)?
+                    .to_bits(obj_layout.size)?; // isize and u32 can have different sizes
+
+                if obj_val == u128::from(val) {
+                    // This cannot fail since we already did an atomic acquire read on that pointer.
+                    // Acquire reads are only allowed on mutable memory.
+                    let futex_ref = this
+                        .get_sync_or_init(obj.ptr(), |_| FreeBsdFutex { futex: Default::default() })
+                        .unwrap()
+                        .futex
+                        .clone();
+
+                    // From the manual:
+                    // The timeout is specified by passing either the address of `struct timespec`, or its
+                    // extended variant, `struct _umtx_time`, as the `uaddr2` argument of _umtx_op().
+                    // They are distinguished by the `uaddr` value, which must be equal
+                    // to the size of the structure pointed to by `uaddr2`, casted to uintptr_t.
+                    let timeout = if this.ptr_is_null(uaddr2)? {
+                        // no timeout parameter
+                        None
+                    } else {
+                        if uaddr == umtx_time_layout.size.bytes() {
+                            // `uaddr2` points to a `struct _umtx_time`.
+                            let umtx_time_place = this.ptr_to_mplace(uaddr2, umtx_time_layout);
+
+                            let umtx_time = match this.read_umtx_time(&umtx_time_place)? {
+                                Some(ut) => ut,
+                                None => {
+                                    return this
+                                        .set_last_error_and_return(LibcError("EINVAL"), dest);
+                                }
+                            };
+
+                            let anchor = if umtx_time.abs_time {
+                                TimeoutAnchor::Absolute
+                            } else {
+                                TimeoutAnchor::Relative
+                            };
+
+                            Some((umtx_time.timeout_clock, anchor, umtx_time.timeout))
+                        } else if uaddr == timespec_layout.size.bytes() {
+                            // RealTime clock can't be used in isolation mode.
+                            this.check_no_isolation("`_umtx_op` with `timespec` timeout")?;
+
+                            // `uaddr2` points to a `struct timespec`.
+                            let timespec = this.ptr_to_mplace(uaddr2, timespec_layout);
+                            let duration = match this.read_timespec(&timespec)? {
+                                Some(duration) => duration,
+                                None => {
+                                    return this
+                                        .set_last_error_and_return(LibcError("EINVAL"), dest);
+                                }
+                            };
+
+                            // FreeBSD does not seem to document which clock is used when the timeout
+                            // is passed as a `struct timespec*`. Based on discussions online and the source
+                            // code (umtx_copyin_umtx_time() in kern_umtx.c), it seems to default to CLOCK_REALTIME,
+                            // so that's what we also do.
+                            // Discussion in golang: https://github.com/golang/go/issues/17168#issuecomment-250235271
+                            Some((TimeoutClock::RealTime, TimeoutAnchor::Relative, duration))
+                        } else {
+                            return this.set_last_error_and_return(LibcError("EINVAL"), dest);
+                        }
+                    };
+
+                    let dest = dest.clone();
+                    this.futex_wait(
+                        futex_ref,
+                        u32::MAX, // we set the bitset to include all bits
+                        timeout,
+                        callback!(
+                            @capture<'tcx> {
+                                dest: MPlaceTy<'tcx>,
+                            }
+                            |ecx, unblock: UnblockKind| match unblock {
+                                UnblockKind::Ready => {
+                                    // From the manual:
+                                    // If successful, all requests, except UMTX_SHM_CREAT and UMTX_SHM_LOOKUP
+                                    // sub-requests of the UMTX_OP_SHM request, will return zero.
+                                    ecx.write_int(0, &dest)
+                                }
+                                UnblockKind::TimedOut => {
+                                    ecx.set_last_error_and_return(LibcError("ETIMEDOUT"), &dest)
+                                }
+                            }
+                        ),
+                    );
+                    interp_ok(())
+                } else {
+                    // The manual doesn’t specify what should happen if the futex value doesn’t match the expected one.
+                    // On FreeBSD 14.2, testing shows that WAIT operations return 0 even when the value is incorrect.
+                    this.write_int(0, dest)?;
+                    interp_ok(())
+                }
+            }
+            // UMTX_OP_WAKE and UMTX_OP_WAKE_PRIVATE only differ in whether they work across
+            // processes or not. For Miri, we can treat them the same.
+            op if op == wake || op == wake_private => {
+                let Some(futex_ref) =
+                    this.get_sync_or_init(obj, |_| FreeBsdFutex { futex: Default::default() })
+                else {
+                    // From Linux implemenation:
+                    // No AllocId, or no live allocation at that AllocId.
+                    // Return an error code. (That seems nicer than silently doing something non-intuitive.)
+                    // This means that if an address gets reused by a new allocation,
+                    // we'll use an independent futex queue for this... that seems acceptable.
+                    return this.set_last_error_and_return(LibcError("EFAULT"), dest);
+                };
+                let futex_ref = futex_ref.futex.clone();
+
+                // Saturating cast for when usize is smaller than u64.
+                let count = usize::try_from(val).unwrap_or(usize::MAX);
+
+                // Read the Linux futex wake implementation in Miri to understand why this fence is needed.
+                this.atomic_fence(AtomicFenceOrd::SeqCst)?;
+
+                // `_umtx_op` doesn't return the amount of woken threads.
+                let _woken = this.futex_wake(
+                    &futex_ref,
+                    u32::MAX, // we set the bitset to include all bits
+                    count,
+                )?;
+
+                // From the manual:
+                // If successful, all requests, except UMTX_SHM_CREAT and UMTX_SHM_LOOKUP
+                // sub-requests of the UMTX_OP_SHM request, will return zero.
+                this.write_int(0, dest)?;
+                interp_ok(())
+            }
+            op => {
+                throw_unsup_format!("Miri does not support `_umtx_op` syscall with op={}", op)
+            }
+        }
+    }
+
+    /// Parses a `_umtx_time` struct.
+    /// Returns `None` if the underlying `timespec` struct is invalid.
+    fn read_umtx_time(&mut self, ut: &MPlaceTy<'tcx>) -> InterpResult<'tcx, Option<UmtxTime>> {
+        let this = self.eval_context_mut();
+        // Only flag allowed is UMTX_ABSTIME.
+        let abs_time = this.eval_libc_u32("UMTX_ABSTIME");
+
+        let timespec_place = this.project_field(ut, 0)?;
+        // Inner `timespec` must still be valid.
+        let duration = match this.read_timespec(&timespec_place)? {
+            Some(dur) => dur,
+            None => return interp_ok(None),
+        };
+
+        let flags_place = this.project_field(ut, 1)?;
+        let flags = this.read_scalar(&flags_place)?.to_u32()?;
+        let abs_time_flag = flags == abs_time;
+
+        let clock_id_place = this.project_field(ut, 2)?;
+        let clock_id = this.read_scalar(&clock_id_place)?.to_i32()?;
+        let timeout_clock = this.translate_umtx_time_clock_id(clock_id)?;
+
+        interp_ok(Some(UmtxTime { timeout: duration, abs_time: abs_time_flag, timeout_clock }))
+    }
+
+    /// Translate raw FreeBSD clockid to a Miri TimeoutClock.
+    /// FIXME: share this code with the pthread and clock_gettime shims.
+    fn translate_umtx_time_clock_id(&mut self, raw_id: i32) -> InterpResult<'tcx, TimeoutClock> {
+        let this = self.eval_context_mut();
+
+        let timeout = if raw_id == this.eval_libc_i32("CLOCK_REALTIME") {
+            // RealTime clock can't be used in isolation mode.
+            this.check_no_isolation("`_umtx_op` with `CLOCK_REALTIME` timeout")?;
+            TimeoutClock::RealTime
+        } else if raw_id == this.eval_libc_i32("CLOCK_MONOTONIC") {
+            TimeoutClock::Monotonic
+        } else {
+            throw_unsup_format!("unsupported clock id {raw_id}");
+        };
+        interp_ok(timeout)
+    }
+}
diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs
index f8e0c638c90d5..fc0f57694a715 100644
--- a/src/tools/miri/src/shims/unix/fs.rs
+++ b/src/tools/miri/src/shims/unix/fs.rs
@@ -2,10 +2,9 @@
 
 use std::borrow::Cow;
 use std::fs::{
-    DirBuilder, File, FileType, Metadata, OpenOptions, ReadDir, read_dir, remove_dir, remove_file,
-    rename,
+    DirBuilder, File, FileType, OpenOptions, ReadDir, read_dir, remove_dir, remove_file, rename,
 };
-use std::io::{self, ErrorKind, IsTerminal, Read, Seek, SeekFrom, Write};
+use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
 use std::path::{Path, PathBuf};
 use std::time::SystemTime;
 
@@ -14,98 +13,11 @@ use rustc_data_structures::fx::FxHashMap;
 
 use self::shims::time::system_time_to_duration;
 use crate::helpers::check_min_vararg_count;
-use crate::shims::files::{EvalContextExt as _, FileDescription, FileDescriptionRef};
+use crate::shims::files::FileHandle;
 use crate::shims::os_str::bytes_to_os_str;
 use crate::shims::unix::fd::{FlockOp, UnixFileDescription};
 use crate::*;
 
-#[derive(Debug)]
-struct FileHandle {
-    file: File,
-    writable: bool,
-}
-
-impl FileDescription for FileHandle {
-    fn name(&self) -> &'static str {
-        "file"
-    }
-
-    fn read<'tcx>(
-        self: FileDescriptionRef<Self>,
-        communicate_allowed: bool,
-        ptr: Pointer,
-        len: usize,
-        ecx: &mut MiriInterpCx<'tcx>,
-        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
-    ) -> InterpResult<'tcx> {
-        assert!(communicate_allowed, "isolation should have prevented even opening a file");
-
-        let result = ecx.read_from_host(&self.file, len, ptr)?;
-        finish.call(ecx, result)
-    }
-
-    fn write<'tcx>(
-        self: FileDescriptionRef<Self>,
-        communicate_allowed: bool,
-        ptr: Pointer,
-        len: usize,
-        ecx: &mut MiriInterpCx<'tcx>,
-        finish: DynMachineCallback<'tcx, Result<usize, IoError>>,
-    ) -> InterpResult<'tcx> {
-        assert!(communicate_allowed, "isolation should have prevented even opening a file");
-
-        let result = ecx.write_to_host(&self.file, len, ptr)?;
-        finish.call(ecx, result)
-    }
-
-    fn seek<'tcx>(
-        &self,
-        communicate_allowed: bool,
-        offset: SeekFrom,
-    ) -> InterpResult<'tcx, io::Result<u64>> {
-        assert!(communicate_allowed, "isolation should have prevented even opening a file");
-        interp_ok((&mut &self.file).seek(offset))
-    }
-
-    fn close<'tcx>(
-        self,
-        communicate_allowed: bool,
-        _ecx: &mut MiriInterpCx<'tcx>,
-    ) -> InterpResult<'tcx, io::Result<()>> {
-        assert!(communicate_allowed, "isolation should have prevented even opening a file");
-        // We sync the file if it was opened in a mode different than read-only.
-        if self.writable {
-            // `File::sync_all` does the checks that are done when closing a file. We do this to
-            // to handle possible errors correctly.
-            let result = self.file.sync_all();
-            // Now we actually close the file and return the result.
-            drop(self.file);
-            interp_ok(result)
-        } else {
-            // We drop the file, this closes it but ignores any errors
-            // produced when closing it. This is done because
-            // `File::sync_all` cannot be done over files like
-            // `/dev/urandom` which are read-only. Check
-            // https://github.com/rust-lang/miri/issues/999#issuecomment-568920439
-            // for a deeper discussion.
-            drop(self.file);
-            interp_ok(Ok(()))
-        }
-    }
-
-    fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
-        interp_ok(self.file.metadata())
-    }
-
-    fn is_tty(&self, communicate_allowed: bool) -> bool {
-        communicate_allowed && self.file.is_terminal()
-    }
-
-    fn as_unix(&self) -> &dyn UnixFileDescription {
-        self
-    }
-}
-
 impl UnixFileDescription for FileHandle {
     fn pread<'tcx>(
         &self,
diff --git a/src/tools/miri/src/shims/unix/linux_like/epoll.rs b/src/tools/miri/src/shims/unix/linux_like/epoll.rs
index de8bcb54aef5b..b489595b4cd04 100644
--- a/src/tools/miri/src/shims/unix/linux_like/epoll.rs
+++ b/src/tools/miri/src/shims/unix/linux_like/epoll.rs
@@ -153,7 +153,7 @@ impl FileDescription for Epoll {
         interp_ok(Ok(()))
     }
 
-    fn as_unix(&self) -> &dyn UnixFileDescription {
+    fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
         self
     }
 }
@@ -590,7 +590,7 @@ fn check_and_update_one_event_interest<'tcx>(
     ecx: &MiriInterpCx<'tcx>,
 ) -> InterpResult<'tcx, bool> {
     // Get the bitmask of ready events for a file description.
-    let ready_events_bitmask = fd_ref.as_unix().get_epoll_ready_events()?.get_event_bitmask(ecx);
+    let ready_events_bitmask = fd_ref.as_unix(ecx).get_epoll_ready_events()?.get_event_bitmask(ecx);
     let epoll_event_interest = interest.borrow();
     let epfd = epoll_event_interest.weak_epfd.upgrade().unwrap();
     // This checks if any of the events specified in epoll_event_interest.events
diff --git a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs
index 936d436bd82d6..ee7deb8d38308 100644
--- a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs
+++ b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs
@@ -100,7 +100,7 @@ impl FileDescription for EventFd {
         eventfd_write(buf_place, self, ecx, finish)
     }
 
-    fn as_unix(&self) -> &dyn UnixFileDescription {
+    fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
         self
     }
 }
diff --git a/src/tools/miri/src/shims/unix/unnamed_socket.rs b/src/tools/miri/src/shims/unix/unnamed_socket.rs
index e183bfdf0e137..135d8f6bee7e1 100644
--- a/src/tools/miri/src/shims/unix/unnamed_socket.rs
+++ b/src/tools/miri/src/shims/unix/unnamed_socket.rs
@@ -107,7 +107,7 @@ impl FileDescription for AnonSocket {
         anonsocket_write(self, ptr, len, ecx, finish)
     }
 
-    fn as_unix(&self) -> &dyn UnixFileDescription {
+    fn as_unix<'tcx>(&self, _ecx: &MiriInterpCx<'tcx>) -> &dyn UnixFileDescription {
         self
     }
 }
diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs
index fae6170a9e72c..7d97e333cd9b0 100644
--- a/src/tools/miri/src/shims/windows/foreign_items.rs
+++ b/src/tools/miri/src/shims/windows/foreign_items.rs
@@ -9,14 +9,9 @@ use rustc_target::callconv::{Conv, FnAbi};
 
 use self::shims::windows::handle::{Handle, PseudoHandle};
 use crate::shims::os_str::bytes_to_os_str;
-use crate::shims::windows::handle::HandleError;
 use crate::shims::windows::*;
 use crate::*;
 
-// The NTSTATUS STATUS_INVALID_HANDLE (0xC0000008) encoded as a HRESULT by setting the N bit.
-// (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/0642cb2f-2075-4469-918c-4441e69c548a)
-const STATUS_INVALID_HANDLE: u32 = 0xD0000008;
-
 pub fn is_dyn_sym(name: &str) -> bool {
     // std does dynamic detection for these symbols
     matches!(
@@ -26,57 +21,107 @@ pub fn is_dyn_sym(name: &str) -> bool {
 }
 
 #[cfg(windows)]
-fn win_absolute<'tcx>(path: &Path) -> InterpResult<'tcx, io::Result<PathBuf>> {
+fn win_get_full_path_name<'tcx>(path: &Path) -> InterpResult<'tcx, io::Result<PathBuf>> {
     // We are on Windows so we can simply let the host do this.
     interp_ok(path::absolute(path))
 }
 
 #[cfg(unix)]
 #[expect(clippy::get_first, clippy::arithmetic_side_effects)]
-fn win_absolute<'tcx>(path: &Path) -> InterpResult<'tcx, io::Result<PathBuf>> {
-    // We are on Unix, so we need to implement parts of the logic ourselves.
+fn win_get_full_path_name<'tcx>(path: &Path) -> InterpResult<'tcx, io::Result<PathBuf>> {
+    use std::sync::LazyLock;
+
+    use rustc_data_structures::fx::FxHashSet;
+
+    // We are on Unix, so we need to implement parts of the logic ourselves. `path` will use `/`
+    // separators, and the result should also use `/`.
+    // See <https://chrisdenton.github.io/omnipath/Overview.html#absolute-win32-paths> for more
+    // information about Windows paths.
+    // This does not handle all corner cases correctly, see
+    // <https://github.com/rust-lang/miri/pull/4262#issuecomment-2792168853> for more cursed
+    // examples.
     let bytes = path.as_os_str().as_encoded_bytes();
-    // If it starts with `//` (these were backslashes but are already converted)
-    // then this is a magic special path, we just leave it unchanged.
-    if bytes.get(0).copied() == Some(b'/') && bytes.get(1).copied() == Some(b'/') {
+    // If it starts with `//./` or `//?/` then this is a magic special path, we just leave it
+    // unchanged.
+    if bytes.get(0).copied() == Some(b'/')
+        && bytes.get(1).copied() == Some(b'/')
+        && matches!(bytes.get(2), Some(b'.' | b'?'))
+        && bytes.get(3).copied() == Some(b'/')
+    {
         return interp_ok(Ok(path.into()));
     };
-    // Special treatment for Windows' magic filenames: they are treated as being relative to `\\.\`.
-    let magic_filenames = &[
-        "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8",
-        "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
-    ];
-    if magic_filenames.iter().any(|m| m.as_bytes() == bytes) {
-        let mut result: Vec<u8> = br"//./".into();
+    let is_unc = bytes.starts_with(b"//");
+    // Special treatment for Windows' magic filenames: they are treated as being relative to `//./`.
+    static MAGIC_FILENAMES: LazyLock<FxHashSet<&'static str>> = LazyLock::new(|| {
+        FxHashSet::from_iter([
+            "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7",
+            "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
+        ])
+    });
+    if str::from_utf8(bytes).is_ok_and(|s| MAGIC_FILENAMES.contains(&*s.to_ascii_uppercase())) {
+        let mut result: Vec<u8> = b"//./".into();
         result.extend(bytes);
         return interp_ok(Ok(bytes_to_os_str(&result)?.into()));
     }
     // Otherwise we try to do something kind of close to what Windows does, but this is probably not
-    // right in all cases. We iterate over the components between `/`, and remove trailing `.`,
-    // except that trailing `..` remain unchanged.
-    let mut result = vec![];
+    // right in all cases.
+    let mut result: Vec<&[u8]> = vec![]; // will be a vecot of components, joined by `/`.
     let mut bytes = bytes; // the remaining bytes to process
-    loop {
-        let len = bytes.iter().position(|&b| b == b'/').unwrap_or(bytes.len());
-        let mut component = &bytes[..len];
-        if len >= 2 && component[len - 1] == b'.' && component[len - 2] != b'.' {
-            // Strip trailing `.`
-            component = &component[..len - 1];
+    let mut stop = false;
+    while !stop {
+        // Find next component, and advance `bytes`.
+        let mut component = match bytes.iter().position(|&b| b == b'/') {
+            Some(pos) => {
+                let (component, tail) = bytes.split_at(pos);
+                bytes = &tail[1..]; // remove the `/`.
+                component
+            }
+            None => {
+                // There's no more `/`.
+                stop = true;
+                let component = bytes;
+                bytes = &[];
+                component
+            }
+        };
+        // `NUL` and only `NUL` also gets changed to be relative to `//./` later in the path.
+        // (This changed with Windows 11; previously, all magic filenames behaved like this.)
+        // Also, this does not apply to UNC paths.
+        if !is_unc && component.eq_ignore_ascii_case(b"NUL") {
+            let mut result: Vec<u8> = b"//./".into();
+            result.extend(component);
+            return interp_ok(Ok(bytes_to_os_str(&result)?.into()));
         }
-        // Add this component to output.
-        result.extend(component);
-        // Prepare next iteration.
-        if len < bytes.len() {
-            // There's a component after this; add `/` and process remaining bytes.
-            result.push(b'/');
-            bytes = &bytes[len + 1..];
+        // Deal with `..` -- Windows handles this entirely syntactically.
+        if component == b".." {
+            // Remove previous component, unless we are at the "root" already, then just ignore the `..`.
+            let is_root = {
+                // Paths like `/C:`.
+                result.len() == 2 && matches!(result[0], []) && matches!(result[1], [_, b':'])
+            } || {
+                // Paths like `//server/share`
+                result.len() == 4 && matches!(result[0], []) && matches!(result[1], [])
+            };
+            if !is_root {
+                result.pop();
+            }
             continue;
-        } else {
-            // This was the last component and it did not have a trailing `/`.
-            break;
         }
+        // Preserve this component.
+        // Strip trailing `.`, but preserve trailing `..`. But not for UNC paths!
+        let len = component.len();
+        if !is_unc && len >= 2 && component[len - 1] == b'.' && component[len - 2] != b'.' {
+            component = &component[..len - 1];
+        }
+        // Add this component to output.
+        result.push(component);
+    }
+    // Drive letters must be followed by a `/`.
+    if result.len() == 2 && matches!(result[0], []) && matches!(result[1], [_, b':']) {
+        result.push(&[]);
     }
-    // Let the host `absolute` function do working-dir handling
+    // Let the host `absolute` function do working-dir handling.
+    let result = result.join(&b'/');
     interp_ok(path::absolute(bytes_to_os_str(&result)?))
 }
 
@@ -231,7 +276,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 }
 
                 let filename = this.read_path_from_wide_str(filename)?;
-                let result = match win_absolute(&filename)? {
+                let result = match win_get_full_path_name(&filename)? {
                     Err(err) => {
                         this.set_last_error(err)?;
                         Scalar::from_u32(0) // return zero upon failure
@@ -246,6 +291,32 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 };
                 this.write_scalar(result, dest)?;
             }
+            "CreateFileW" => {
+                let [
+                    file_name,
+                    desired_access,
+                    share_mode,
+                    security_attributes,
+                    creation_disposition,
+                    flags_and_attributes,
+                    template_file,
+                ] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let handle = this.CreateFileW(
+                    file_name,
+                    desired_access,
+                    share_mode,
+                    security_attributes,
+                    creation_disposition,
+                    flags_and_attributes,
+                    template_file,
+                )?;
+                this.write_scalar(handle.to_scalar(this), dest)?;
+            }
+            "GetFileInformationByHandle" => {
+                let [handle, info] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let res = this.GetFileInformationByHandle(handle, info)?;
+                this.write_scalar(res, dest)?;
+            }
 
             // Allocation
             "HeapAlloc" => {
@@ -498,52 +569,37 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "SetThreadDescription" => {
                 let [handle, name] = this.check_shim(abi, sys_conv, link_name, args)?;
 
-                let handle = this.read_scalar(handle)?;
+                let handle = this.read_handle(handle, "SetThreadDescription")?;
                 let name = this.read_wide_str(this.read_pointer(name)?)?;
 
-                let thread = match Handle::try_from_scalar(handle, this)? {
-                    Ok(Handle::Thread(thread)) => Ok(thread),
-                    Ok(Handle::Pseudo(PseudoHandle::CurrentThread)) => Ok(this.active_thread()),
-                    Ok(_) | Err(HandleError::InvalidHandle) =>
-                        this.invalid_handle("SetThreadDescription")?,
-                    Err(HandleError::ThreadNotFound(e)) => Err(e),
-                };
-                let res = match thread {
-                    Ok(thread) => {
-                        // FIXME: use non-lossy conversion
-                        this.set_thread_name(thread, String::from_utf16_lossy(&name).into_bytes());
-                        Scalar::from_u32(0)
-                    }
-                    Err(_) => Scalar::from_u32(STATUS_INVALID_HANDLE),
+                let thread = match handle {
+                    Handle::Thread(thread) => thread,
+                    Handle::Pseudo(PseudoHandle::CurrentThread) => this.active_thread(),
+                    _ => this.invalid_handle("SetThreadDescription")?,
                 };
-
-                this.write_scalar(res, dest)?;
+                // FIXME: use non-lossy conversion
+                this.set_thread_name(thread, String::from_utf16_lossy(&name).into_bytes());
+                this.write_scalar(Scalar::from_u32(0), dest)?;
             }
             "GetThreadDescription" => {
                 let [handle, name_ptr] = this.check_shim(abi, sys_conv, link_name, args)?;
 
-                let handle = this.read_scalar(handle)?;
+                let handle = this.read_handle(handle, "GetThreadDescription")?;
                 let name_ptr = this.deref_pointer_as(name_ptr, this.machine.layouts.mut_raw_ptr)?; // the pointer where we should store the ptr to the name
 
-                let thread = match Handle::try_from_scalar(handle, this)? {
-                    Ok(Handle::Thread(thread)) => Ok(thread),
-                    Ok(Handle::Pseudo(PseudoHandle::CurrentThread)) => Ok(this.active_thread()),
-                    Ok(_) | Err(HandleError::InvalidHandle) =>
-                        this.invalid_handle("GetThreadDescription")?,
-                    Err(HandleError::ThreadNotFound(e)) => Err(e),
-                };
-                let (name, res) = match thread {
-                    Ok(thread) => {
-                        // Looks like the default thread name is empty.
-                        let name = this.get_thread_name(thread).unwrap_or(b"").to_owned();
-                        let name = this.alloc_os_str_as_wide_str(
-                            bytes_to_os_str(&name)?,
-                            MiriMemoryKind::WinLocal.into(),
-                        )?;
-                        (Scalar::from_maybe_pointer(name, this), Scalar::from_u32(0))
-                    }
-                    Err(_) => (Scalar::null_ptr(this), Scalar::from_u32(STATUS_INVALID_HANDLE)),
+                let thread = match handle {
+                    Handle::Thread(thread) => thread,
+                    Handle::Pseudo(PseudoHandle::CurrentThread) => this.active_thread(),
+                    _ => this.invalid_handle("GetThreadDescription")?,
                 };
+                // Looks like the default thread name is empty.
+                let name = this.get_thread_name(thread).unwrap_or(b"").to_owned();
+                let name = this.alloc_os_str_as_wide_str(
+                    bytes_to_os_str(&name)?,
+                    MiriMemoryKind::WinLocal.into(),
+                )?;
+                let name = Scalar::from_maybe_pointer(name, this);
+                let res = Scalar::from_u32(0);
 
                 this.write_scalar(name, &name_ptr)?;
                 this.write_scalar(res, dest)?;
@@ -638,11 +694,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let [handle, filename, size] = this.check_shim(abi, sys_conv, link_name, args)?;
                 this.check_no_isolation("`GetModuleFileNameW`")?;
 
-                let handle = this.read_target_usize(handle)?;
+                let handle = this.read_handle(handle, "GetModuleFileNameW")?;
                 let filename = this.read_pointer(filename)?;
                 let size = this.read_scalar(size)?.to_u32()?;
 
-                if handle != 0 {
+                if handle != Handle::Null {
                     throw_unsup_format!("`GetModuleFileNameW` only supports the NULL handle");
                 }
 
diff --git a/src/tools/miri/src/shims/windows/fs.rs b/src/tools/miri/src/shims/windows/fs.rs
new file mode 100644
index 0000000000000..32bab54896938
--- /dev/null
+++ b/src/tools/miri/src/shims/windows/fs.rs
@@ -0,0 +1,402 @@
+use std::fs::{Metadata, OpenOptions};
+use std::io;
+use std::path::PathBuf;
+use std::time::SystemTime;
+
+use bitflags::bitflags;
+
+use crate::shims::files::{FileDescription, FileHandle};
+use crate::shims::windows::handle::{EvalContextExt as _, Handle};
+use crate::*;
+
+#[derive(Debug)]
+pub struct DirHandle {
+    pub(crate) path: PathBuf,
+}
+
+impl FileDescription for DirHandle {
+    fn name(&self) -> &'static str {
+        "directory"
+    }
+
+    fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
+        interp_ok(self.path.metadata())
+    }
+
+    fn close<'tcx>(
+        self,
+        _communicate_allowed: bool,
+        _ecx: &mut MiriInterpCx<'tcx>,
+    ) -> InterpResult<'tcx, io::Result<()>> {
+        interp_ok(Ok(()))
+    }
+}
+
+/// Windows supports handles without any read/write/delete permissions - these handles can get
+/// metadata, but little else. We represent that by storing the metadata from the time the handle
+/// was opened.
+#[derive(Debug)]
+pub struct MetadataHandle {
+    pub(crate) meta: Metadata,
+}
+
+impl FileDescription for MetadataHandle {
+    fn name(&self) -> &'static str {
+        "metadata-only"
+    }
+
+    fn metadata<'tcx>(&self) -> InterpResult<'tcx, io::Result<Metadata>> {
+        interp_ok(Ok(self.meta.clone()))
+    }
+
+    fn close<'tcx>(
+        self,
+        _communicate_allowed: bool,
+        _ecx: &mut MiriInterpCx<'tcx>,
+    ) -> InterpResult<'tcx, io::Result<()>> {
+        interp_ok(Ok(()))
+    }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+enum CreationDisposition {
+    CreateAlways,
+    CreateNew,
+    OpenAlways,
+    OpenExisting,
+    TruncateExisting,
+}
+
+impl CreationDisposition {
+    fn new<'tcx>(
+        value: u32,
+        ecx: &mut MiriInterpCx<'tcx>,
+    ) -> InterpResult<'tcx, CreationDisposition> {
+        let create_always = ecx.eval_windows_u32("c", "CREATE_ALWAYS");
+        let create_new = ecx.eval_windows_u32("c", "CREATE_NEW");
+        let open_always = ecx.eval_windows_u32("c", "OPEN_ALWAYS");
+        let open_existing = ecx.eval_windows_u32("c", "OPEN_EXISTING");
+        let truncate_existing = ecx.eval_windows_u32("c", "TRUNCATE_EXISTING");
+
+        let out = if value == create_always {
+            CreationDisposition::CreateAlways
+        } else if value == create_new {
+            CreationDisposition::CreateNew
+        } else if value == open_always {
+            CreationDisposition::OpenAlways
+        } else if value == open_existing {
+            CreationDisposition::OpenExisting
+        } else if value == truncate_existing {
+            CreationDisposition::TruncateExisting
+        } else {
+            throw_unsup_format!("CreateFileW: Unsupported creation disposition: {value}");
+        };
+        interp_ok(out)
+    }
+}
+
+bitflags! {
+    #[derive(PartialEq)]
+    struct FileAttributes: u32 {
+        const ZERO = 0;
+        const NORMAL = 1 << 0;
+        /// This must be passed to allow getting directory handles. If not passed, we error on trying
+        /// to open directories
+        const BACKUP_SEMANTICS = 1 << 1;
+        /// Open a reparse point as a regular file - this is basically similar to 'readlink' in Unix
+        /// terminology. A reparse point is a file with custom logic when navigated to, of which
+        /// a symlink is one specific example.
+        const OPEN_REPARSE = 1 << 2;
+    }
+}
+
+impl FileAttributes {
+    fn new<'tcx>(
+        mut value: u32,
+        ecx: &mut MiriInterpCx<'tcx>,
+    ) -> InterpResult<'tcx, FileAttributes> {
+        let file_attribute_normal = ecx.eval_windows_u32("c", "FILE_ATTRIBUTE_NORMAL");
+        let file_flag_backup_semantics = ecx.eval_windows_u32("c", "FILE_FLAG_BACKUP_SEMANTICS");
+        let file_flag_open_reparse_point =
+            ecx.eval_windows_u32("c", "FILE_FLAG_OPEN_REPARSE_POINT");
+
+        let mut out = FileAttributes::ZERO;
+        if value & file_flag_backup_semantics != 0 {
+            value &= !file_flag_backup_semantics;
+            out |= FileAttributes::BACKUP_SEMANTICS;
+        }
+        if value & file_flag_open_reparse_point != 0 {
+            value &= !file_flag_open_reparse_point;
+            out |= FileAttributes::OPEN_REPARSE;
+        }
+        if value & file_attribute_normal != 0 {
+            value &= !file_attribute_normal;
+            out |= FileAttributes::NORMAL;
+        }
+
+        if value != 0 {
+            throw_unsup_format!("CreateFileW: Unsupported flags_and_attributes: {value}");
+        }
+
+        if out == FileAttributes::ZERO {
+            // NORMAL is equivalent to 0. Avoid needing to check both cases by unifying the two.
+            out = FileAttributes::NORMAL;
+        }
+        interp_ok(out)
+    }
+}
+
+impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
+#[allow(non_snake_case)]
+pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
+    fn CreateFileW(
+        &mut self,
+        file_name: &OpTy<'tcx>,            // LPCWSTR
+        desired_access: &OpTy<'tcx>,       // DWORD
+        share_mode: &OpTy<'tcx>,           // DWORD
+        security_attributes: &OpTy<'tcx>,  // LPSECURITY_ATTRIBUTES
+        creation_disposition: &OpTy<'tcx>, // DWORD
+        flags_and_attributes: &OpTy<'tcx>, // DWORD
+        template_file: &OpTy<'tcx>,        // HANDLE
+    ) -> InterpResult<'tcx, Handle> {
+        // ^ Returns HANDLE
+        use CreationDisposition::*;
+
+        let this = self.eval_context_mut();
+        this.assert_target_os("windows", "CreateFileW");
+        this.check_no_isolation("`CreateFileW`")?;
+
+        // This function appears to always set the error to 0. This is important for some flag
+        // combinations, which may set error code on success.
+        this.set_last_error(IoError::Raw(Scalar::from_i32(0)))?;
+
+        let file_name = this.read_path_from_wide_str(this.read_pointer(file_name)?)?;
+        let mut desired_access = this.read_scalar(desired_access)?.to_u32()?;
+        let share_mode = this.read_scalar(share_mode)?.to_u32()?;
+        let security_attributes = this.read_pointer(security_attributes)?;
+        let creation_disposition = this.read_scalar(creation_disposition)?.to_u32()?;
+        let flags_and_attributes = this.read_scalar(flags_and_attributes)?.to_u32()?;
+        let template_file = this.read_target_usize(template_file)?;
+
+        let generic_read = this.eval_windows_u32("c", "GENERIC_READ");
+        let generic_write = this.eval_windows_u32("c", "GENERIC_WRITE");
+
+        let file_share_delete = this.eval_windows_u32("c", "FILE_SHARE_DELETE");
+        let file_share_read = this.eval_windows_u32("c", "FILE_SHARE_READ");
+        let file_share_write = this.eval_windows_u32("c", "FILE_SHARE_WRITE");
+
+        let creation_disposition = CreationDisposition::new(creation_disposition, this)?;
+        let attributes = FileAttributes::new(flags_and_attributes, this)?;
+
+        if share_mode != (file_share_delete | file_share_read | file_share_write) {
+            throw_unsup_format!("CreateFileW: Unsupported share mode: {share_mode}");
+        }
+        if !this.ptr_is_null(security_attributes)? {
+            throw_unsup_format!("CreateFileW: Security attributes are not supported");
+        }
+
+        if attributes.contains(FileAttributes::OPEN_REPARSE) && creation_disposition == CreateAlways
+        {
+            throw_machine_stop!(TerminationInfo::Abort("Invalid CreateFileW argument combination: FILE_FLAG_OPEN_REPARSE_POINT with CREATE_ALWAYS".to_string()));
+        }
+
+        if template_file != 0 {
+            throw_unsup_format!("CreateFileW: Template files are not supported");
+        }
+
+        // We need to know if the file is a directory to correctly open directory handles.
+        // This is racy, but currently the stdlib doesn't appear to offer a better solution.
+        let is_dir = file_name.is_dir();
+
+        // BACKUP_SEMANTICS is how Windows calls the act of opening a directory handle.
+        if !attributes.contains(FileAttributes::BACKUP_SEMANTICS) && is_dir {
+            this.set_last_error(IoError::WindowsError("ERROR_ACCESS_DENIED"))?;
+            return interp_ok(Handle::Invalid);
+        }
+
+        let desired_read = desired_access & generic_read != 0;
+        let desired_write = desired_access & generic_write != 0;
+
+        let mut options = OpenOptions::new();
+        if desired_read {
+            desired_access &= !generic_read;
+            options.read(true);
+        }
+        if desired_write {
+            desired_access &= !generic_write;
+            options.write(true);
+        }
+
+        if desired_access != 0 {
+            throw_unsup_format!(
+                "CreateFileW: Unsupported bits set for access mode: {desired_access:#x}"
+            );
+        }
+
+        // Per the documentation:
+        // If the specified file exists and is writable, the function truncates the file,
+        // the function succeeds, and last-error code is set to ERROR_ALREADY_EXISTS.
+        // If the specified file does not exist and is a valid path, a new file is created,
+        // the function succeeds, and the last-error code is set to zero.
+        // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
+        //
+        // This is racy, but there doesn't appear to be an std API that both succeeds if a
+        // file exists but tells us it isn't new. Either we accept racing one way or another,
+        // or we use an iffy heuristic like file creation time. This implementation prefers
+        // to fail in the direction of erroring more often.
+        if let CreateAlways | OpenAlways = creation_disposition
+            && file_name.exists()
+        {
+            this.set_last_error(IoError::WindowsError("ERROR_ALREADY_EXISTS"))?;
+        }
+
+        let handle = if is_dir {
+            // Open this as a directory.
+            let fd_num = this.machine.fds.insert_new(DirHandle { path: file_name });
+            Ok(Handle::File(fd_num))
+        } else if creation_disposition == OpenExisting && !(desired_read || desired_write) {
+            // Windows supports handles with no permissions. These allow things such as reading
+            // metadata, but not file content.
+            file_name.metadata().map(|meta| {
+                let fd_num = this.machine.fds.insert_new(MetadataHandle { meta });
+                Handle::File(fd_num)
+            })
+        } else {
+            // Open this as a standard file.
+            match creation_disposition {
+                CreateAlways | OpenAlways => {
+                    options.create(true);
+                    if creation_disposition == CreateAlways {
+                        options.truncate(true);
+                    }
+                }
+                CreateNew => {
+                    options.create_new(true);
+                    // Per `create_new` documentation:
+                    // The file must be opened with write or append access in order to create a new file.
+                    // https://doc.rust-lang.org/std/fs/struct.OpenOptions.html#method.create_new
+                    if !desired_write {
+                        options.append(true);
+                    }
+                }
+                OpenExisting => {} // Default options
+                TruncateExisting => {
+                    options.truncate(true);
+                }
+            }
+
+            options.open(file_name).map(|file| {
+                let fd_num =
+                    this.machine.fds.insert_new(FileHandle { file, writable: desired_write });
+                Handle::File(fd_num)
+            })
+        };
+
+        match handle {
+            Ok(handle) => interp_ok(handle),
+            Err(e) => {
+                this.set_last_error(e)?;
+                interp_ok(Handle::Invalid)
+            }
+        }
+    }
+
+    fn GetFileInformationByHandle(
+        &mut self,
+        file: &OpTy<'tcx>,             // HANDLE
+        file_information: &OpTy<'tcx>, // LPBY_HANDLE_FILE_INFORMATION
+    ) -> InterpResult<'tcx, Scalar> {
+        // ^ Returns BOOL (i32 on Windows)
+        let this = self.eval_context_mut();
+        this.assert_target_os("windows", "GetFileInformationByHandle");
+        this.check_no_isolation("`GetFileInformationByHandle`")?;
+
+        let file = this.read_handle(file, "GetFileInformationByHandle")?;
+        let file_information = this.deref_pointer_as(
+            file_information,
+            this.windows_ty_layout("BY_HANDLE_FILE_INFORMATION"),
+        )?;
+
+        let fd_num = if let Handle::File(fd_num) = file {
+            fd_num
+        } else {
+            this.invalid_handle("GetFileInformationByHandle")?
+        };
+
+        let Some(desc) = this.machine.fds.get(fd_num) else {
+            this.invalid_handle("GetFileInformationByHandle")?
+        };
+
+        let metadata = match desc.metadata()? {
+            Ok(meta) => meta,
+            Err(e) => {
+                this.set_last_error(e)?;
+                return interp_ok(this.eval_windows("c", "FALSE"));
+            }
+        };
+
+        let size = metadata.len();
+
+        let file_type = metadata.file_type();
+        let attributes = if file_type.is_dir() {
+            this.eval_windows_u32("c", "FILE_ATTRIBUTE_DIRECTORY")
+        } else if file_type.is_file() {
+            this.eval_windows_u32("c", "FILE_ATTRIBUTE_NORMAL")
+        } else {
+            this.eval_windows_u32("c", "FILE_ATTRIBUTE_DEVICE")
+        };
+
+        // Per the Windows documentation:
+        // "If the underlying file system does not support the [...] time, this member is zero (0)."
+        // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/ns-fileapi-by_handle_file_information
+        let created = extract_windows_epoch(this, metadata.created())?.unwrap_or((0, 0));
+        let accessed = extract_windows_epoch(this, metadata.accessed())?.unwrap_or((0, 0));
+        let written = extract_windows_epoch(this, metadata.modified())?.unwrap_or((0, 0));
+
+        this.write_int_fields_named(&[("dwFileAttributes", attributes.into())], &file_information)?;
+        write_filetime_field(this, &file_information, "ftCreationTime", created)?;
+        write_filetime_field(this, &file_information, "ftLastAccessTime", accessed)?;
+        write_filetime_field(this, &file_information, "ftLastWriteTime", written)?;
+        this.write_int_fields_named(
+            &[
+                ("dwVolumeSerialNumber", 0),
+                ("nFileSizeHigh", (size >> 32).into()),
+                ("nFileSizeLow", (size & 0xFFFFFFFF).into()),
+                ("nNumberOfLinks", 1),
+                ("nFileIndexHigh", 0),
+                ("nFileIndexLow", 0),
+            ],
+            &file_information,
+        )?;
+
+        interp_ok(this.eval_windows("c", "TRUE"))
+    }
+}
+
+/// Windows FILETIME is measured in 100-nanosecs since 1601
+fn extract_windows_epoch<'tcx>(
+    ecx: &MiriInterpCx<'tcx>,
+    time: io::Result<SystemTime>,
+) -> InterpResult<'tcx, Option<(u32, u32)>> {
+    match time.ok() {
+        Some(time) => {
+            let duration = ecx.system_time_since_windows_epoch(&time)?;
+            let duration_ticks = ecx.windows_ticks_for(duration)?;
+            #[allow(clippy::cast_possible_truncation)]
+            interp_ok(Some((duration_ticks as u32, (duration_ticks >> 32) as u32)))
+        }
+        None => interp_ok(None),
+    }
+}
+
+fn write_filetime_field<'tcx>(
+    cx: &mut MiriInterpCx<'tcx>,
+    val: &MPlaceTy<'tcx>,
+    name: &str,
+    (low, high): (u32, u32),
+) -> InterpResult<'tcx> {
+    cx.write_int_fields_named(
+        &[("dwLowDateTime", low.into()), ("dwHighDateTime", high.into())],
+        &cx.project_field_named(val, name)?,
+    )
+}
diff --git a/src/tools/miri/src/shims/windows/handle.rs b/src/tools/miri/src/shims/windows/handle.rs
index c4eb11fbd3f97..eec6c62bebc73 100644
--- a/src/tools/miri/src/shims/windows/handle.rs
+++ b/src/tools/miri/src/shims/windows/handle.rs
@@ -3,6 +3,7 @@ use std::mem::variant_count;
 use rustc_abi::HasDataLayout;
 
 use crate::concurrency::thread::ThreadNotFound;
+use crate::shims::files::FdNum;
 use crate::*;
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
@@ -16,6 +17,8 @@ pub enum Handle {
     Null,
     Pseudo(PseudoHandle),
     Thread(ThreadId),
+    File(FdNum),
+    Invalid,
 }
 
 impl PseudoHandle {
@@ -47,12 +50,18 @@ impl Handle {
     const NULL_DISCRIMINANT: u32 = 0;
     const PSEUDO_DISCRIMINANT: u32 = 1;
     const THREAD_DISCRIMINANT: u32 = 2;
+    const FILE_DISCRIMINANT: u32 = 3;
+    // Chosen to ensure Handle::Invalid encodes to -1. Update this value if there are ever more than
+    // 8 discriminants.
+    const INVALID_DISCRIMINANT: u32 = 7;
 
     fn discriminant(self) -> u32 {
         match self {
             Self::Null => Self::NULL_DISCRIMINANT,
             Self::Pseudo(_) => Self::PSEUDO_DISCRIMINANT,
             Self::Thread(_) => Self::THREAD_DISCRIMINANT,
+            Self::File(_) => Self::FILE_DISCRIMINANT,
+            Self::Invalid => Self::INVALID_DISCRIMINANT,
         }
     }
 
@@ -61,17 +70,27 @@ impl Handle {
             Self::Null => 0,
             Self::Pseudo(pseudo_handle) => pseudo_handle.value(),
             Self::Thread(thread) => thread.to_u32(),
+            #[expect(clippy::cast_sign_loss)]
+            Self::File(fd) => fd as u32,
+            // INVALID_HANDLE_VALUE is -1. This fact is explicitly declared or implied in several
+            // pages of Windows documentation.
+            // 1: https://learn.microsoft.com/en-us/dotnet/api/microsoft.win32.safehandles.safefilehandle?view=net-9.0
+            // 2: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle?view=msvc-170
+            Self::Invalid => 0x1FFFFFFF,
         }
     }
 
     fn packed_disc_size() -> u32 {
-        // ceil(log2(x)) is how many bits it takes to store x numbers
+        // ceil(log2(x)) is how many bits it takes to store x numbers.
+        // We ensure that INVALID_HANDLE_VALUE (0xFFFFFFFF) decodes to Handle::Invalid.
+        // see https://devblogs.microsoft.com/oldnewthing/20230914-00/?p=108766 for more detail on
+        // INVALID_HANDLE_VALUE.
         let variant_count = variant_count::<Self>();
 
-        // however, std's ilog2 is floor(log2(x))
+        // However, std's ilog2 is floor(log2(x)).
         let floor_log2 = variant_count.ilog2();
 
-        // we need to add one for non powers of two to compensate for the difference
+        // We need to add one for non powers of two to compensate for the difference.
         #[expect(clippy::arithmetic_side_effects)] // cannot overflow
         if variant_count.is_power_of_two() { floor_log2 } else { floor_log2 + 1 }
     }
@@ -105,6 +124,13 @@ impl Handle {
             Self::NULL_DISCRIMINANT if data == 0 => Some(Self::Null),
             Self::PSEUDO_DISCRIMINANT => Some(Self::Pseudo(PseudoHandle::from_value(data)?)),
             Self::THREAD_DISCRIMINANT => Some(Self::Thread(ThreadId::new_unchecked(data))),
+            #[expect(clippy::cast_possible_wrap)]
+            Self::FILE_DISCRIMINANT => {
+                // This cast preserves all bits.
+                assert_eq!(size_of_val(&data), size_of::<FdNum>());
+                Some(Self::File(data as FdNum))
+            }
+            Self::INVALID_DISCRIMINANT => Some(Self::Invalid),
             _ => None,
         }
     }
@@ -139,7 +165,7 @@ impl Handle {
     /// Structurally invalid handles return [`HandleError::InvalidHandle`].
     /// If the handle is structurally valid but semantically invalid, e.g. a for non-existent thread
     /// ID, returns [`HandleError::ThreadNotFound`].
-    pub fn try_from_scalar<'tcx>(
+    fn try_from_scalar<'tcx>(
         handle: Scalar,
         cx: &MiriInterpCx<'tcx>,
     ) -> InterpResult<'tcx, Result<Self, HandleError>> {
@@ -171,6 +197,27 @@ impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
 
 #[allow(non_snake_case)]
 pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
+    /// Convert a scalar into a structured `Handle`.
+    /// If the handle is invalid, or references a non-existent item, execution is aborted.
+    #[track_caller]
+    fn read_handle(&self, handle: &OpTy<'tcx>, function_name: &str) -> InterpResult<'tcx, Handle> {
+        let this = self.eval_context_ref();
+        let handle = this.read_scalar(handle)?;
+        match Handle::try_from_scalar(handle, this)? {
+            Ok(handle) => interp_ok(handle),
+            Err(HandleError::InvalidHandle) =>
+                throw_machine_stop!(TerminationInfo::Abort(format!(
+                    "invalid handle {} passed to {function_name}",
+                    handle.to_target_isize(this)?,
+                ))),
+            Err(HandleError::ThreadNotFound(_)) =>
+                throw_machine_stop!(TerminationInfo::Abort(format!(
+                    "invalid thread ID {} passed to {function_name}",
+                    handle.to_target_isize(this)?,
+                ))),
+        }
+    }
+
     fn invalid_handle(&mut self, function_name: &str) -> InterpResult<'tcx, !> {
         throw_machine_stop!(TerminationInfo::Abort(format!(
             "invalid handle passed to `{function_name}`"
@@ -180,15 +227,38 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     fn CloseHandle(&mut self, handle_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
         let this = self.eval_context_mut();
 
-        let handle = this.read_scalar(handle_op)?;
-        let ret = match Handle::try_from_scalar(handle, this)? {
-            Ok(Handle::Thread(thread)) => {
+        let handle = this.read_handle(handle_op, "CloseHandle")?;
+        let ret = match handle {
+            Handle::Thread(thread) => {
                 this.detach_thread(thread, /*allow_terminated_joined*/ true)?;
                 this.eval_windows("c", "TRUE")
             }
+            Handle::File(fd_num) =>
+                if let Some(fd) = this.machine.fds.remove(fd_num) {
+                    let err = fd.close_ref(this.machine.communicate(), this)?;
+                    if let Err(e) = err {
+                        this.set_last_error(e)?;
+                        this.eval_windows("c", "FALSE")
+                    } else {
+                        this.eval_windows("c", "TRUE")
+                    }
+                } else {
+                    this.invalid_handle("CloseHandle")?
+                },
             _ => this.invalid_handle("CloseHandle")?,
         };
 
         interp_ok(ret)
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_invalid_encoding() {
+        // Ensure the invalid handle encodes to `u32::MAX`/`INVALID_HANDLE_VALUE`.
+        assert_eq!(Handle::Invalid.to_packed(), u32::MAX)
+    }
+}
diff --git a/src/tools/miri/src/shims/windows/mod.rs b/src/tools/miri/src/shims/windows/mod.rs
index 892bd6924fc93..442c5a0dd11fd 100644
--- a/src/tools/miri/src/shims/windows/mod.rs
+++ b/src/tools/miri/src/shims/windows/mod.rs
@@ -1,12 +1,14 @@
 pub mod foreign_items;
 
 mod env;
+mod fs;
 mod handle;
 mod sync;
 mod thread;
 
 // All the Windows-specific extension traits
 pub use self::env::{EvalContextExt as _, WindowsEnvVars};
+pub use self::fs::EvalContextExt as _;
 pub use self::handle::EvalContextExt as _;
 pub use self::sync::EvalContextExt as _;
 pub use self::thread::EvalContextExt as _;
diff --git a/src/tools/miri/src/shims/windows/thread.rs b/src/tools/miri/src/shims/windows/thread.rs
index 5db554044227c..d5f9ed4e968ea 100644
--- a/src/tools/miri/src/shims/windows/thread.rs
+++ b/src/tools/miri/src/shims/windows/thread.rs
@@ -62,14 +62,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     ) -> InterpResult<'tcx, Scalar> {
         let this = self.eval_context_mut();
 
-        let handle = this.read_scalar(handle_op)?;
+        let handle = this.read_handle(handle_op, "WaitForSingleObject")?;
         let timeout = this.read_scalar(timeout_op)?.to_u32()?;
 
-        let thread = match Handle::try_from_scalar(handle, this)? {
-            Ok(Handle::Thread(thread)) => thread,
+        let thread = match handle {
+            Handle::Thread(thread) => thread,
             // Unlike on posix, the outcome of joining the current thread is not documented.
             // On current Windows, it just deadlocks.
-            Ok(Handle::Pseudo(PseudoHandle::CurrentThread)) => this.active_thread(),
+            Handle::Pseudo(PseudoHandle::CurrentThread) => this.active_thread(),
             _ => this.invalid_handle("WaitForSingleObject")?,
         };
 
diff --git a/src/tools/miri/test_dependencies/Cargo.toml b/src/tools/miri/test_dependencies/Cargo.toml
index 837cb1c508166..653228a5e3db6 100644
--- a/src/tools/miri/test_dependencies/Cargo.toml
+++ b/src/tools/miri/test_dependencies/Cargo.toml
@@ -25,6 +25,6 @@ page_size = "0.6"
 tokio = { version = "1", features = ["macros", "rt-multi-thread", "time", "net", "fs", "sync", "signal", "io-util"] }
 
 [target.'cfg(windows)'.dependencies]
-windows-sys = { version = "0.59", features = [ "Win32_Foundation", "Win32_System_Threading" ] }
+windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_System_Threading", "Win32_Storage_FileSystem", "Win32_Security"] }
 
 [workspace]
diff --git a/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.rs b/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.rs
index 279201df867cf..3ee2bf14f9fe4 100644
--- a/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.rs
+++ b/src/tools/miri/tests/fail-dep/concurrency/windows_join_main.rs
@@ -13,7 +13,7 @@ use windows_sys::Win32::System::Threading::{INFINITE, WaitForSingleObject};
 // XXX HACK: This is how miri represents the handle for thread 0.
 // This value can be "legitimately" obtained by using `GetCurrentThread` with `DuplicateHandle`
 // but miri does not implement `DuplicateHandle` yet.
-const MAIN_THREAD: HANDLE = (2i32 << 30) as HANDLE;
+const MAIN_THREAD: HANDLE = (2i32 << 29) as HANDLE;
 
 fn main() {
     thread::spawn(|| {
diff --git a/src/tools/miri/tests/pass-dep/concurrency/freebsd-futex.rs b/src/tools/miri/tests/pass-dep/concurrency/freebsd-futex.rs
new file mode 100644
index 0000000000000..38a0bf58148ed
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/concurrency/freebsd-futex.rs
@@ -0,0 +1,260 @@
+//@only-target: freebsd
+//@compile-flags: -Zmiri-preemption-rate=0 -Zmiri-disable-isolation
+
+use std::mem::{self, MaybeUninit};
+use std::ptr::{self, addr_of};
+use std::sync::atomic::AtomicU32;
+use std::time::Instant;
+use std::{io, thread};
+
+fn wait_wake() {
+    fn wake_nobody() {
+        // Current thread waits on futex.
+        // New thread wakes up 0 threads waiting on that futex.
+        // Current thread should time out.
+        static mut FUTEX: u32 = 0;
+
+        let waker = thread::spawn(|| {
+            unsafe {
+                assert_eq!(
+                    libc::_umtx_op(
+                        addr_of!(FUTEX) as *mut _,
+                        libc::UMTX_OP_WAKE_PRIVATE,
+                        0, // wake up 0 waiters
+                        ptr::null_mut::<libc::c_void>(),
+                        ptr::null_mut::<libc::c_void>(),
+                    ),
+                    0
+                );
+            }
+        });
+
+        // 10ms should be enough.
+        let mut timeout = libc::timespec { tv_sec: 0, tv_nsec: 10_000_000 };
+        let timeout_size_arg =
+            ptr::without_provenance_mut::<libc::c_void>(mem::size_of::<libc::timespec>());
+        unsafe {
+            assert_eq!(
+                libc::_umtx_op(
+                    addr_of!(FUTEX) as *mut _,
+                    libc::UMTX_OP_WAIT_UINT_PRIVATE,
+                    0,
+                    timeout_size_arg,
+                    &mut timeout as *mut _ as _,
+                ),
+                -1
+            );
+            // Main thread did not get woken up, so it timed out.
+            assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT);
+        }
+
+        waker.join().unwrap();
+    }
+
+    fn wake_two_of_three() {
+        // We create 2 threads that wait on a futex with a 100ms timeout.
+        // The main thread wakes up 2 threads waiting on this futex and after this
+        // checks that only those threads woke up and the other one timed out.
+        static mut FUTEX: u32 = 0;
+
+        fn waiter() -> bool {
+            let mut timeout = libc::timespec { tv_sec: 0, tv_nsec: 100_000_000 };
+            let timeout_size_arg =
+                ptr::without_provenance_mut::<libc::c_void>(mem::size_of::<libc::timespec>());
+            unsafe {
+                libc::_umtx_op(
+                    addr_of!(FUTEX) as *mut _,
+                    libc::UMTX_OP_WAIT_UINT_PRIVATE,
+                    0, // FUTEX is 0
+                    timeout_size_arg,
+                    &mut timeout as *mut _ as _,
+                );
+                // Return true if this thread woke up.
+                io::Error::last_os_error().raw_os_error().unwrap() != libc::ETIMEDOUT
+            }
+        }
+
+        let t1 = thread::spawn(waiter);
+        let t2 = thread::spawn(waiter);
+        let t3 = thread::spawn(waiter);
+
+        // Run all the waiters, so they can go to sleep.
+        thread::yield_now();
+
+        // Wake up 2 thread and make sure 1 is still waiting.
+        unsafe {
+            assert_eq!(
+                libc::_umtx_op(
+                    addr_of!(FUTEX) as *mut _,
+                    libc::UMTX_OP_WAKE_PRIVATE,
+                    2,
+                    ptr::null_mut::<libc::c_void>(),
+                    ptr::null_mut::<libc::c_void>(),
+                ),
+                0
+            );
+        }
+
+        // Treat the booleans as numbers to simplify checking how many threads were woken up.
+        let t1 = t1.join().unwrap() as usize;
+        let t2 = t2.join().unwrap() as usize;
+        let t3 = t3.join().unwrap() as usize;
+        let woken_up_count = t1 + t2 + t3;
+        assert!(woken_up_count == 2, "Expected 2 threads to wake up got: {woken_up_count}");
+    }
+
+    wake_nobody();
+    wake_two_of_three();
+}
+
+fn wake_dangling() {
+    let futex = Box::new(0);
+    let ptr: *const u32 = &*futex;
+    drop(futex);
+
+    // Expect error since this is now "unmapped" memory.
+    unsafe {
+        assert_eq!(
+            libc::_umtx_op(
+                ptr as *const AtomicU32 as *mut _,
+                libc::UMTX_OP_WAKE_PRIVATE,
+                0,
+                ptr::null_mut::<libc::c_void>(),
+                ptr::null_mut::<libc::c_void>(),
+            ),
+            -1
+        );
+        assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::EFAULT);
+    }
+}
+
+fn wait_wrong_val() {
+    let futex: u32 = 123;
+
+    // Wait with a wrong value just returns 0
+    unsafe {
+        assert_eq!(
+            libc::_umtx_op(
+                ptr::from_ref(&futex).cast_mut().cast(),
+                libc::UMTX_OP_WAIT_UINT_PRIVATE,
+                456,
+                ptr::null_mut::<libc::c_void>(),
+                ptr::null_mut::<libc::c_void>(),
+            ),
+            0
+        );
+    }
+}
+
+fn wait_relative_timeout() {
+    fn without_timespec() {
+        let start = Instant::now();
+
+        let futex: u32 = 123;
+
+        let mut timeout = libc::timespec { tv_sec: 0, tv_nsec: 200_000_000 };
+        let timeout_size_arg =
+            ptr::without_provenance_mut::<libc::c_void>(mem::size_of::<libc::timespec>());
+        // Wait for 200ms, with nobody waking us up early
+        unsafe {
+            assert_eq!(
+                libc::_umtx_op(
+                    ptr::from_ref(&futex).cast_mut().cast(),
+                    libc::UMTX_OP_WAIT_UINT_PRIVATE,
+                    123,
+                    timeout_size_arg,
+                    &mut timeout as *mut _ as _,
+                ),
+                -1
+            );
+            assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT);
+        }
+
+        assert!((200..1000).contains(&start.elapsed().as_millis()));
+    }
+
+    fn with_timespec() {
+        let futex: u32 = 123;
+        let mut timeout = libc::_umtx_time {
+            _timeout: libc::timespec { tv_sec: 0, tv_nsec: 200_000_000 },
+            _flags: 0,
+            _clockid: libc::CLOCK_MONOTONIC as u32,
+        };
+        let timeout_size_arg =
+            ptr::without_provenance_mut::<libc::c_void>(mem::size_of::<libc::_umtx_time>());
+
+        let start = Instant::now();
+
+        // Wait for 200ms, with nobody waking us up early
+        unsafe {
+            assert_eq!(
+                libc::_umtx_op(
+                    ptr::from_ref(&futex).cast_mut().cast(),
+                    libc::UMTX_OP_WAIT_UINT_PRIVATE,
+                    123,
+                    timeout_size_arg,
+                    &mut timeout as *mut _ as _,
+                ),
+                -1
+            );
+            assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT);
+        }
+        assert!((200..1000).contains(&start.elapsed().as_millis()));
+    }
+
+    without_timespec();
+    with_timespec();
+}
+
+fn wait_absolute_timeout() {
+    let start = Instant::now();
+
+    // Get the current monotonic timestamp as timespec.
+    let mut timeout = unsafe {
+        let mut now: MaybeUninit<libc::timespec> = MaybeUninit::uninit();
+        assert_eq!(libc::clock_gettime(libc::CLOCK_MONOTONIC, now.as_mut_ptr()), 0);
+        now.assume_init()
+    };
+
+    // Add 200ms.
+    timeout.tv_nsec += 200_000_000;
+    if timeout.tv_nsec > 1_000_000_000 {
+        timeout.tv_nsec -= 1_000_000_000;
+        timeout.tv_sec += 1;
+    }
+
+    // Create umtx_timeout struct with that absolute timeout.
+    let umtx_timeout = libc::_umtx_time {
+        _timeout: timeout,
+        _flags: libc::UMTX_ABSTIME,
+        _clockid: libc::CLOCK_MONOTONIC as u32,
+    };
+    let umtx_timeout_ptr = &umtx_timeout as *const _;
+    let umtx_timeout_size = ptr::without_provenance_mut(mem::size_of_val(&umtx_timeout));
+
+    let futex: u32 = 123;
+
+    // Wait for 200ms from now, with nobody waking us up early.
+    unsafe {
+        assert_eq!(
+            libc::_umtx_op(
+                ptr::from_ref(&futex).cast_mut().cast(),
+                libc::UMTX_OP_WAIT_UINT_PRIVATE,
+                123,
+                umtx_timeout_size,
+                umtx_timeout_ptr as *mut _,
+            ),
+            -1
+        );
+        assert_eq!(io::Error::last_os_error().raw_os_error().unwrap(), libc::ETIMEDOUT);
+    }
+    assert!((200..1000).contains(&start.elapsed().as_millis()));
+}
+
+fn main() {
+    wait_wake();
+    wake_dangling();
+    wait_wrong_val();
+    wait_relative_timeout();
+    wait_absolute_timeout();
+}
diff --git a/src/tools/miri/tests/pass-dep/shims/windows-fs.rs b/src/tools/miri/tests/pass-dep/shims/windows-fs.rs
new file mode 100644
index 0000000000000..312df9eb115db
--- /dev/null
+++ b/src/tools/miri/tests/pass-dep/shims/windows-fs.rs
@@ -0,0 +1,198 @@
+//@only-target: windows # this directly tests windows-only functions
+//@compile-flags: -Zmiri-disable-isolation
+#![allow(nonstandard_style)]
+
+use std::os::windows::ffi::OsStrExt;
+use std::path::Path;
+use std::ptr;
+
+#[path = "../../utils/mod.rs"]
+mod utils;
+
+use windows_sys::Win32::Foundation::{
+    CloseHandle, ERROR_ALREADY_EXISTS, GENERIC_READ, GENERIC_WRITE, GetLastError,
+};
+use windows_sys::Win32::Storage::FileSystem::{
+    BY_HANDLE_FILE_INFORMATION, CREATE_ALWAYS, CREATE_NEW, CreateFileW, FILE_ATTRIBUTE_DIRECTORY,
+    FILE_ATTRIBUTE_NORMAL, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_OPEN_REPARSE_POINT,
+    FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, GetFileInformationByHandle, OPEN_ALWAYS,
+    OPEN_EXISTING,
+};
+
+fn main() {
+    unsafe {
+        test_create_dir_file();
+        test_create_normal_file();
+        test_create_always_twice();
+        test_open_always_twice();
+        test_open_dir_reparse();
+    }
+}
+
+unsafe fn test_create_dir_file() {
+    let temp = utils::tmp();
+    let raw_path = to_wide_cstr(&temp);
+    // Open the `temp` directory.
+    let handle = CreateFileW(
+        raw_path.as_ptr(),
+        GENERIC_READ,
+        FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+        ptr::null_mut(),
+        OPEN_EXISTING,
+        FILE_FLAG_BACKUP_SEMANTICS,
+        ptr::null_mut(),
+    );
+    assert_ne!(handle.addr(), usize::MAX, "CreateFileW Failed: {}", GetLastError());
+    let mut info = std::mem::zeroed::<BY_HANDLE_FILE_INFORMATION>();
+    if GetFileInformationByHandle(handle, &mut info) == 0 {
+        panic!("Failed to get file information")
+    };
+    assert!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0);
+    if CloseHandle(handle) == 0 {
+        panic!("Failed to close file")
+    };
+}
+
+unsafe fn test_create_normal_file() {
+    let temp = utils::tmp().join("test.txt");
+    let raw_path = to_wide_cstr(&temp);
+    let handle = CreateFileW(
+        raw_path.as_ptr(),
+        GENERIC_READ | GENERIC_WRITE,
+        FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+        ptr::null_mut(),
+        CREATE_NEW,
+        0,
+        ptr::null_mut(),
+    );
+    assert_ne!(handle.addr(), usize::MAX, "CreateFileW Failed: {}", GetLastError());
+    let mut info = std::mem::zeroed::<BY_HANDLE_FILE_INFORMATION>();
+    if GetFileInformationByHandle(handle, &mut info) == 0 {
+        panic!("Failed to get file information: {}", GetLastError())
+    };
+    assert!(info.dwFileAttributes & FILE_ATTRIBUTE_NORMAL != 0);
+    if CloseHandle(handle) == 0 {
+        panic!("Failed to close file")
+    };
+
+    // Test metadata-only handle
+    let handle = CreateFileW(
+        raw_path.as_ptr(),
+        0,
+        FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+        ptr::null_mut(),
+        OPEN_EXISTING,
+        0,
+        ptr::null_mut(),
+    );
+    assert_ne!(handle.addr(), usize::MAX, "CreateFileW Failed: {}", GetLastError());
+    let mut info = std::mem::zeroed::<BY_HANDLE_FILE_INFORMATION>();
+    if GetFileInformationByHandle(handle, &mut info) == 0 {
+        panic!("Failed to get file information: {}", GetLastError())
+    };
+    assert!(info.dwFileAttributes & FILE_ATTRIBUTE_NORMAL != 0);
+    if CloseHandle(handle) == 0 {
+        panic!("Failed to close file")
+    };
+}
+
+/// Tests that CREATE_ALWAYS sets the error value correctly based on whether the file already exists
+unsafe fn test_create_always_twice() {
+    let temp = utils::tmp().join("test_create_always.txt");
+    let raw_path = to_wide_cstr(&temp);
+    let handle = CreateFileW(
+        raw_path.as_ptr(),
+        GENERIC_READ | GENERIC_WRITE,
+        FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+        ptr::null_mut(),
+        CREATE_ALWAYS,
+        0,
+        ptr::null_mut(),
+    );
+    assert_ne!(handle.addr(), usize::MAX, "CreateFileW Failed: {}", GetLastError());
+    assert_eq!(GetLastError(), 0);
+    if CloseHandle(handle) == 0 {
+        panic!("Failed to close file")
+    };
+
+    let handle = CreateFileW(
+        raw_path.as_ptr(),
+        GENERIC_READ | GENERIC_WRITE,
+        FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+        ptr::null_mut(),
+        CREATE_ALWAYS,
+        0,
+        ptr::null_mut(),
+    );
+    assert_ne!(handle.addr(), usize::MAX, "CreateFileW Failed: {}", GetLastError());
+    assert_eq!(GetLastError(), ERROR_ALREADY_EXISTS);
+    if CloseHandle(handle) == 0 {
+        panic!("Failed to close file")
+    };
+}
+
+/// Tests that OPEN_ALWAYS sets the error value correctly based on whether the file already exists
+unsafe fn test_open_always_twice() {
+    let temp = utils::tmp().join("test_open_always.txt");
+    let raw_path = to_wide_cstr(&temp);
+    let handle = CreateFileW(
+        raw_path.as_ptr(),
+        GENERIC_READ | GENERIC_WRITE,
+        FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+        ptr::null_mut(),
+        OPEN_ALWAYS,
+        0,
+        ptr::null_mut(),
+    );
+    assert_ne!(handle.addr(), usize::MAX, "CreateFileW Failed: {}", GetLastError());
+    assert_eq!(GetLastError(), 0);
+    if CloseHandle(handle) == 0 {
+        panic!("Failed to close file")
+    };
+
+    let handle = CreateFileW(
+        raw_path.as_ptr(),
+        GENERIC_READ | GENERIC_WRITE,
+        FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+        ptr::null_mut(),
+        OPEN_ALWAYS,
+        0,
+        ptr::null_mut(),
+    );
+    assert_ne!(handle.addr(), usize::MAX, "CreateFileW Failed: {}", GetLastError());
+    assert_eq!(GetLastError(), ERROR_ALREADY_EXISTS);
+    if CloseHandle(handle) == 0 {
+        panic!("Failed to close file")
+    };
+}
+
+// TODO: Once we support more of the std API, it would be nice to test against an actual symlink
+unsafe fn test_open_dir_reparse() {
+    let temp = utils::tmp();
+    let raw_path = to_wide_cstr(&temp);
+    // Open the `temp` directory.
+    let handle = CreateFileW(
+        raw_path.as_ptr(),
+        GENERIC_READ,
+        FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+        ptr::null_mut(),
+        OPEN_EXISTING,
+        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
+        ptr::null_mut(),
+    );
+    assert_ne!(handle.addr(), usize::MAX, "CreateFileW Failed: {}", GetLastError());
+    let mut info = std::mem::zeroed::<BY_HANDLE_FILE_INFORMATION>();
+    if GetFileInformationByHandle(handle, &mut info) == 0 {
+        panic!("Failed to get file information")
+    };
+    assert!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY != 0);
+    if CloseHandle(handle) == 0 {
+        panic!("Failed to close file")
+    };
+}
+
+fn to_wide_cstr(path: &Path) -> Vec<u16> {
+    let mut raw_path = path.as_os_str().encode_wide().collect::<Vec<_>>();
+    raw_path.extend([0, 0]);
+    raw_path
+}
diff --git a/src/tools/miri/tests/pass/path.rs b/src/tools/miri/tests/pass/path.rs
index 299ee6cfe9dde..7428d0afcc674 100644
--- a/src/tools/miri/tests/pass/path.rs
+++ b/src/tools/miri/tests/pass/path.rs
@@ -6,7 +6,11 @@ mod utils;
 
 #[track_caller]
 fn assert_absolute_eq(in_: &str, out: &str) {
-    assert_eq!(absolute(in_).unwrap().as_os_str(), Path::new(out).as_os_str());
+    assert_eq!(
+        absolute(in_).unwrap().as_os_str(),
+        Path::new(out).as_os_str(),
+        "incorrect absolute path for {in_:?}"
+    );
 }
 
 fn test_absolute() {
@@ -29,11 +33,28 @@ fn test_absolute() {
         assert_absolute_eq(r"\\?\C:\path\to\file", r"\\?\C:\path\to\file");
         assert_absolute_eq(r"\\?\UNC\server\share\to\file", r"\\?\UNC\server\share\to\file");
         assert_absolute_eq(r"\\?\PIPE\name", r"\\?\PIPE\name");
+        assert_absolute_eq(r"\\server\share\NUL", r"\\server\share\NUL");
+        // This fails on Windows 10 hosts. FIXME: enable this once GHA runners are on Windows 11.
+        //assert_absolute_eq(r"C:\path\to\COM1", r"C:\path\to\COM1");
         // Verbatim paths are always unchanged, no matter what.
         assert_absolute_eq(r"\\?\path.\to/file..", r"\\?\path.\to/file..");
-
+        // Trailing dot is removed here.
         assert_absolute_eq(r"C:\path..\to.\file.", r"C:\path..\to\file");
+        // `..` is resolved here.
+        assert_absolute_eq(r"C:\path\to\..\file", r"C:\path\file");
+        assert_absolute_eq(r"C:\path\to\..\..\file", r"C:\file");
+        assert_absolute_eq(r"C:\path\to\..\..\..\..\..\..\file", r"C:\file");
+        assert_absolute_eq(r"C:\..", r"C:\");
+        assert_absolute_eq(r"\\server\share\to\path\with\..\file", r"\\server\share\to\path\file");
+        assert_absolute_eq(r"\\server\share\to\..\..\..\..\file", r"\\server\share\file");
+        assert_absolute_eq(r"\\server\share\..", r"\\server\share");
+        // Magic filenames.
+        assert_absolute_eq(r"NUL", r"\\.\NUL");
+        assert_absolute_eq(r"nul", r"\\.\nul");
         assert_absolute_eq(r"COM1", r"\\.\COM1");
+        assert_absolute_eq(r"com1", r"\\.\com1");
+        assert_absolute_eq(r"C:\path\to\NUL", r"\\.\NUL");
+        assert_absolute_eq(r"C:\path\to\nul", r"\\.\nul");
     } else {
         panic!("unsupported OS");
     }
diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs
index 289c6aa2fcec9..6ad23055f30eb 100644
--- a/src/tools/miri/tests/pass/shims/fs.rs
+++ b/src/tools/miri/tests/pass/shims/fs.rs
@@ -1,4 +1,3 @@
-//@ignore-target: windows # File handling is not implemented yet
 //@compile-flags: -Zmiri-disable-isolation
 
 #![feature(io_error_more)]
@@ -18,20 +17,23 @@ mod utils;
 
 fn main() {
     test_path_conversion();
-    test_file();
-    test_file_clone();
-    test_file_create_new();
-    test_seek();
-    test_metadata();
-    test_file_set_len();
-    test_file_sync();
-    test_errors();
-    test_rename();
-    test_directory();
-    test_canonicalize();
-    test_from_raw_os_error();
-    #[cfg(unix)]
-    test_pread_pwrite();
+    // Windows file handling is very incomplete.
+    if cfg!(not(windows)) {
+        test_file();
+        test_file_create_new();
+        test_seek();
+        test_file_clone();
+        test_metadata();
+        test_file_set_len();
+        test_file_sync();
+        test_errors();
+        test_rename();
+        test_directory();
+        test_canonicalize();
+        test_from_raw_os_error();
+        #[cfg(unix)]
+        test_pread_pwrite();
+    }
 }
 
 fn test_path_conversion() {
@@ -144,10 +146,10 @@ fn test_metadata() {
     let path = utils::prepare_with_content("miri_test_fs_metadata.txt", bytes);
 
     // Test that metadata of an absolute path is correct.
-    check_metadata(bytes, &path).unwrap();
+    check_metadata(bytes, &path).expect("absolute path metadata");
     // Test that metadata of a relative path is correct.
     std::env::set_current_dir(path.parent().unwrap()).unwrap();
-    check_metadata(bytes, Path::new(path.file_name().unwrap())).unwrap();
+    check_metadata(bytes, Path::new(path.file_name().unwrap())).expect("relative path metadata");
 
     // Removing file should succeed.
     remove_file(&path).unwrap();
diff --git a/src/tools/miri/triagebot.toml b/src/tools/miri/triagebot.toml
index 4e013764d8713..60e80c3f67330 100644
--- a/src/tools/miri/triagebot.toml
+++ b/src/tools/miri/triagebot.toml
@@ -40,3 +40,9 @@ unless = ["S-blocked", "S-waiting-on-team", "S-waiting-on-review"]
 
 # Automatically close and reopen PRs made by bots to run CI on them
 [bot-pull-requests]
+
+# Canonicalize issue numbers to avoid closing the wrong issue when upstreaming this subtree
+[canonicalize-issue-links]
+
+# Prevents mentions in commits to avoid users being spammed
+[no-mentions]
diff --git a/tests/pretty/hir-delegation.pp b/tests/pretty/hir-delegation.pp
new file mode 100644
index 0000000000000..872a6a45aede1
--- /dev/null
+++ b/tests/pretty/hir-delegation.pp
@@ -0,0 +1,23 @@
+//@ pretty-compare-only
+//@ pretty-mode:hir
+//@ pp-exact:hir-delegation.pp
+
+#![allow(incomplete_features)]#![feature(fn_delegation)]
+#[prelude_import]
+use ::std::prelude::rust_2015::*;
+#[macro_use]
+extern crate std;
+
+fn b<C>(e: C) { }
+
+trait G {
+    fn b(arg0: _) -> _ { b({ }) }
+}
+
+mod m {
+    fn add(a: u32, b: u32) -> u32 { a + b }
+}
+
+fn add(arg0: _, arg1: _) -> _ { m::add(arg0, arg1) }
+
+fn main() { { let _ = add(1, 2); }; }
diff --git a/tests/pretty/hir-delegation.rs b/tests/pretty/hir-delegation.rs
new file mode 100644
index 0000000000000..9e351a1e32f3d
--- /dev/null
+++ b/tests/pretty/hir-delegation.rs
@@ -0,0 +1,22 @@
+//@ pretty-compare-only
+//@ pretty-mode:hir
+//@ pp-exact:hir-delegation.pp
+
+#![allow(incomplete_features)]
+#![feature(fn_delegation)]
+
+fn b<C>(e: C) {}
+
+trait G {
+    reuse b {}
+}
+
+mod m {
+    pub fn add(a: u32, b: u32) -> u32 { a + b }
+}
+
+reuse m::add;
+
+fn main() {
+    _ = add(1, 2);
+}
diff --git a/tests/run-make/unstable-feature-usage-metrics-incremental/main.rs b/tests/run-make/unstable-feature-usage-metrics-incremental/main.rs
new file mode 100644
index 0000000000000..f970d395b2cf2
--- /dev/null
+++ b/tests/run-make/unstable-feature-usage-metrics-incremental/main.rs
@@ -0,0 +1,16 @@
+#![feature(ascii_char)] // random lib feature
+#![feature(box_patterns)] // random lang feature
+
+// picked arbitrary unstable features, just need a random lib and lang feature, ideally ones that
+// won't be stabilized any time soon so we don't have to update this test
+fn main() {
+    for s in quix("foo/bar") {
+        print!("{s}");
+    }
+    println!();
+}
+
+// need a latebound var to trigger the incremental compilation ICE
+fn quix(foo: &str) -> impl Iterator<Item = &'_ str> + '_ {
+    foo.split('/')
+}
diff --git a/tests/run-make/unstable-feature-usage-metrics-incremental/rmake.rs b/tests/run-make/unstable-feature-usage-metrics-incremental/rmake.rs
new file mode 100644
index 0000000000000..7e070d80c7933
--- /dev/null
+++ b/tests/run-make/unstable-feature-usage-metrics-incremental/rmake.rs
@@ -0,0 +1,94 @@
+//! This test checks if unstable feature usage metric dump files `unstable-feature-usage*.json` work
+//! as expected.
+//!
+//! - Basic sanity checks on a default ICE dump.
+//!
+//! See <https://github.com/rust-lang/rust/issues/129485>.
+//!
+//! # Test history
+//!
+//! - forked from dump-ice-to-disk test, which has flakeyness issues on i686-mingw, I'm assuming
+//! those will be present in this test as well on the same platform
+
+//@ ignore-windows
+//FIXME(#128911): still flakey on i686-mingw.
+
+use std::path::{Path, PathBuf};
+
+use run_make_support::rfs::create_dir_all;
+use run_make_support::{
+    cwd, filename_contains, has_extension, rfs, run_in_tmpdir, rustc, serde_json,
+    shallow_find_files,
+};
+
+fn find_feature_usage_metrics<P: AsRef<Path>>(dir: P) -> Vec<PathBuf> {
+    shallow_find_files(dir, |path| {
+        if filename_contains(path, "unstable_feature_usage") && has_extension(path, "json") {
+            true
+        } else {
+            dbg!(path);
+            false
+        }
+    })
+}
+
+fn main() {
+    test_metrics_dump();
+    test_metrics_errors();
+}
+
+#[track_caller]
+fn test_metrics_dump() {
+    run_in_tmpdir(|| {
+        let metrics_dir = cwd().join("metrics");
+        create_dir_all(&metrics_dir);
+        rustc()
+            .input("main.rs")
+            .incremental("incremental")
+            .env("RUST_BACKTRACE", "short")
+            .arg(format!("-Zmetrics-dir={}", metrics_dir.display()))
+            .run();
+        let mut metrics = find_feature_usage_metrics(&metrics_dir);
+        let json_path =
+            metrics.pop().expect("there should be one metrics file in the output directory");
+
+        // After the `pop` above, there should be no files left.
+        assert!(
+            metrics.is_empty(),
+            "there should be no more than one metrics file in the output directory"
+        );
+
+        let message = rfs::read_to_string(json_path);
+        let mut parsed: serde_json::Value =
+            serde_json::from_str(&message).expect("metrics should be dumped as json");
+        // remove timestamps
+        assert!(parsed["lib_features"][0]["timestamp"].is_number());
+        assert!(parsed["lang_features"][0]["timestamp"].is_number());
+        parsed["lib_features"][0]["timestamp"] = serde_json::json!(null);
+        parsed["lang_features"][0]["timestamp"] = serde_json::json!(null);
+        let expected = serde_json::json!(
+            {
+                "lib_features":[{"symbol":"ascii_char", "timestamp":null}],
+                "lang_features":[{"symbol":"box_patterns","since":null, "timestamp":null}]
+            }
+        );
+
+        assert_eq!(expected, parsed);
+    });
+}
+
+#[track_caller]
+fn test_metrics_errors() {
+    run_in_tmpdir(|| {
+        rustc()
+            .input("main.rs")
+            .incremental("incremental")
+            .env("RUST_BACKTRACE", "short")
+            .arg("-Zmetrics-dir=invaliddirectorythatdefinitelydoesntexist")
+            .run_fail()
+            .assert_stderr_contains(
+                "error: cannot dump feature usage metrics: No such file or directory",
+            )
+            .assert_stdout_not_contains("internal compiler error");
+    });
+}
diff --git a/tests/ui/resolve/dont-compute-arg-names-for-non-fn.rs b/tests/ui/resolve/dont-compute-arg-names-for-non-fn.rs
index 20bbbff8fd20b..4053ccdfbf141 100644
--- a/tests/ui/resolve/dont-compute-arg-names-for-non-fn.rs
+++ b/tests/ui/resolve/dont-compute-arg-names-for-non-fn.rs
@@ -3,7 +3,7 @@
 extern crate foreign_trait_with_assoc;
 use foreign_trait_with_assoc::Foo;
 
-// Make sure we don't try to call `fn_arg_names` on a non-fn item.
+// Make sure we don't try to call `fn_arg_idents` on a non-fn item.
 
 impl Foo for Bar {}
 //~^ ERROR cannot find type `Bar` in this scope
diff --git a/tests/ui/sanitizer/cfi/coroutine.rs b/tests/ui/sanitizer/cfi/coroutine.rs
index 3ad896afd00b0..39a754f103637 100644
--- a/tests/ui/sanitizer/cfi/coroutine.rs
+++ b/tests/ui/sanitizer/cfi/coroutine.rs
@@ -26,7 +26,7 @@ use std::async_iter::AsyncIterator;
 
 #[test]
 fn general_coroutine() {
-    let mut coro = #[coroutine] |x: i32| {
+    let coro = #[coroutine] |x: i32| {
         yield x;
         "done"
     };