about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-03-29 06:16:56 +0000
committerbors <bors@rust-lang.org>2024-03-29 06:16:56 +0000
commiteae940fcef7f95d31b566f92f511f91f24f2f703 (patch)
treea5635c4d1821e9114fe7cc27d00353f98cac5402 /src
parent58a771ebfa2e59f48cb50229049487c6550b2f7b (diff)
parented29546a27db9fa024c6f99a7dd001e86d75e14f (diff)
downloadrust-eae940fcef7f95d31b566f92f511f91f24f2f703.tar.gz
rust-eae940fcef7f95d31b566f92f511f91f24f2f703.zip
Auto merge of #3427 - rust-lang:rustup-2024-03-29, r=saethlin
Automatic Rustup
Diffstat (limited to 'src')
-rw-r--r--src/bootstrap/src/core/build_steps/test.rs14
-rwxr-xr-xsrc/ci/scripts/install-ninja.sh2
-rw-r--r--src/doc/unstable-book/src/language-features/adt-const-params.md35
-rw-r--r--src/librustdoc/clean/mod.rs13
-rw-r--r--src/librustdoc/config.rs17
-rw-r--r--src/librustdoc/core.rs2
-rw-r--r--src/librustdoc/html/render/print_item.rs4
-rw-r--r--src/librustdoc/html/render/search_index.rs5
-rw-r--r--src/librustdoc/passes/lint/redundant_explicit_links.rs13
m---------src/tools/cargo0
-rw-r--r--src/tools/clippy/clippy_lints/src/float_literal.rs15
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/transmute/utils.rs6
-rw-r--r--src/tools/compiletest/src/header.rs10
-rw-r--r--src/tools/compiletest/src/header/needs.rs5
-rw-r--r--src/tools/miri/README.md2
-rw-r--r--src/tools/miri/cargo-miri/src/phases.rs10
-rw-r--r--src/tools/miri/rust-version2
-rw-r--r--src/tools/miri/src/shims/panic.rs12
-rw-r--r--src/tools/miri/tests/pass/async-closure-drop.rs40
-rw-r--r--src/tools/miri/tests/pass/async-closure-drop.stdout3
-rw-r--r--src/tools/miri/tests/pass/async-closure.rs34
-rw-r--r--src/tools/miri/tests/pass/async-closure.stdout7
-rw-r--r--src/tools/run-make-support/src/lib.rs189
-rw-r--r--src/tools/run-make-support/src/run.rs67
-rw-r--r--src/tools/run-make-support/src/rustc.rs147
-rw-r--r--src/tools/run-make-support/src/rustdoc.rs80
-rw-r--r--src/tools/tidy/src/allowed_run_make_makefiles.txt337
-rw-r--r--src/tools/tidy/src/lib.rs1
-rw-r--r--src/tools/tidy/src/main.rs2
-rw-r--r--src/tools/tidy/src/run_make_tests.rs89
31 files changed, 939 insertions, 230 deletions
diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs
index f22a9ca604e..48596af7ca0 100644
--- a/src/bootstrap/src/core/build_steps/test.rs
+++ b/src/bootstrap/src/core/build_steps/test.rs
@@ -680,9 +680,13 @@ impl Step for Miri {
             .arg("--manifest-path")
             .arg(builder.src.join("src/tools/miri/test-cargo-miri/Cargo.toml"));
         cargo.arg("--target").arg(target.rustc_target_arg());
-        cargo.arg("--tests"); // don't run doctests, they are too confused by the staging
         cargo.arg("--").args(builder.config.test_args());
 
+        // `prepare_tool_cargo` sets RUSTDOC to the bootstrap wrapper and RUSTDOC_REAL to a dummy path as this is a "run", not a "test".
+        // Also, we want the rustdoc from the "next" stage for the same reason that we build a std from the next stage.
+        // So let's just set that here, and bypass bootstrap's RUSTDOC (just like cargo-miri already ignores bootstrap's RUSTC_WRAPPER).
+        cargo.env("RUSTDOC", builder.rustdoc(compiler_std));
+
         // Tell `cargo miri` where to find things.
         cargo.env("MIRI_SYSROOT", &miri_sysroot);
         cargo.env("MIRI_HOST_SYSROOT", sysroot);
@@ -2601,8 +2605,12 @@ impl Step for Crate {
         let mode = self.mode;
 
         // See [field@compile::Std::force_recompile].
-        builder.ensure(compile::Std::force_recompile(compiler, target));
-        builder.ensure(RemoteCopyLibs { compiler, target });
+        builder.ensure(compile::Std::force_recompile(compiler, compiler.host));
+
+        if builder.config.build != target {
+            builder.ensure(compile::Std::force_recompile(compiler, target));
+            builder.ensure(RemoteCopyLibs { compiler, target });
+        }
 
         // If we're not doing a full bootstrap but we're testing a stage2
         // version of libstd, then what we're actually testing is the libstd
diff --git a/src/ci/scripts/install-ninja.sh b/src/ci/scripts/install-ninja.sh
index 5145a03e353..23cbc2eb6d1 100755
--- a/src/ci/scripts/install-ninja.sh
+++ b/src/ci/scripts/install-ninja.sh
@@ -8,7 +8,7 @@ source "$(cd "$(dirname "$0")" && pwd)/../shared.sh"
 
 if isWindows; then
     mkdir ninja
-    curl -o ninja.zip "${MIRRORS_BASE}/2017-03-15-ninja-win.zip"
+    curl -o ninja.zip "${MIRRORS_BASE}/2024-03-28-v1.11.1-ninja-win.zip"
     7z x -oninja ninja.zip
     rm ninja.zip
     ciCommandSetEnv "RUST_CONFIGURE_ARGS" "${RUST_CONFIGURE_ARGS} --enable-ninja"
diff --git a/src/doc/unstable-book/src/language-features/adt-const-params.md b/src/doc/unstable-book/src/language-features/adt-const-params.md
new file mode 100644
index 00000000000..208bf5e4c15
--- /dev/null
+++ b/src/doc/unstable-book/src/language-features/adt-const-params.md
@@ -0,0 +1,35 @@
+# `adt_const_params`
+
+The tracking issue for this feature is: [#95174]
+
+[#95174]: https://github.com/rust-lang/rust/issues/95174
+
+------------------------
+
+Allows for using more complex types for const parameters, such as structs or enums.
+
+```rust
+#![feature(adt_const_params)]
+#![allow(incomplete_features)]
+
+use std::marker::ConstParamTy;
+
+#[derive(ConstParamTy, PartialEq, Eq)]
+enum Foo {
+    A,
+    B,
+    C,
+}
+
+#[derive(ConstParamTy, PartialEq, Eq)]
+struct Bar {
+    flag: bool,
+}
+
+fn is_foo_a_and_bar_true<const F: Foo, const B: Bar>() -> bool {
+    match (F, B.flag) {
+        (Foo::A, true) => true,
+        _ => false,
+    }
+}
+```
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index ef707d179ea..0cdf52bfb00 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -41,7 +41,7 @@ use std::hash::Hash;
 use std::mem;
 use thin_vec::ThinVec;
 
-use crate::core::{self, DocContext, ImplTraitParam};
+use crate::core::{self, DocContext};
 use crate::formats::item_type::ItemType;
 use crate::visit_ast::Module as DocModule;
 
@@ -761,7 +761,7 @@ fn clean_ty_generics<'tcx>(
 ) -> Generics {
     // Don't populate `cx.impl_trait_bounds` before `clean`ning `where` clauses,
     // since `Clean for ty::Predicate` would consume them.
-    let mut impl_trait = BTreeMap::<ImplTraitParam, Vec<GenericBound>>::default();
+    let mut impl_trait = BTreeMap::<u32, Vec<GenericBound>>::default();
 
     // Bounds in the type_params and lifetimes fields are repeated in the
     // predicates field (see rustc_hir_analysis::collect::ty_generics), so remove
@@ -778,7 +778,7 @@ fn clean_ty_generics<'tcx>(
                     return None;
                 }
                 if synthetic {
-                    impl_trait.insert(param.index.into(), vec![]);
+                    impl_trait.insert(param.index, vec![]);
                     return None;
                 }
                 Some(clean_generic_param_def(param, cx))
@@ -823,7 +823,7 @@ fn clean_ty_generics<'tcx>(
             })();
 
             if let Some(param_idx) = param_idx
-                && let Some(bounds) = impl_trait.get_mut(&param_idx.into())
+                && let Some(bounds) = impl_trait.get_mut(&param_idx)
             {
                 let pred = clean_predicate(*pred, cx)?;
 
@@ -847,7 +847,7 @@ fn clean_ty_generics<'tcx>(
         })
         .collect::<Vec<_>>();
 
-    for (param, mut bounds) in impl_trait {
+    for (idx, mut bounds) in impl_trait {
         let mut has_sized = false;
         bounds.retain(|b| {
             if b.is_sized_bound(cx) {
@@ -870,7 +870,6 @@ fn clean_ty_generics<'tcx>(
             bounds.insert(0, GenericBound::sized(cx));
         }
 
-        let crate::core::ImplTraitParam::ParamIndex(idx) = param else { unreachable!() };
         if let Some(proj) = impl_trait_proj.remove(&idx) {
             for (trait_did, name, rhs) in proj {
                 let rhs = clean_middle_term(rhs, cx);
@@ -878,7 +877,7 @@ fn clean_ty_generics<'tcx>(
             }
         }
 
-        cx.impl_trait_bounds.insert(param, bounds);
+        cx.impl_trait_bounds.insert(idx.into(), bounds);
     }
 
     // Now that `cx.impl_trait_bounds` is populated, we can process
diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs
index be7e319bc79..f078ad2fb53 100644
--- a/src/librustdoc/config.rs
+++ b/src/librustdoc/config.rs
@@ -460,8 +460,6 @@ impl Options {
             &matches.free[0]
         });
 
-        let libs =
-            matches.opt_strs("L").iter().map(|s| SearchPath::from_cli_opt(early_dcx, s)).collect();
         let externs = parse_externs(early_dcx, matches, &unstable_opts);
         let extern_html_root_urls = match parse_extern_html_roots(matches) {
             Ok(ex) => ex,
@@ -625,6 +623,20 @@ impl Options {
         }
 
         let target = parse_target_triple(early_dcx, matches);
+        let maybe_sysroot = matches.opt_str("sysroot").map(PathBuf::from);
+
+        let sysroot = match &maybe_sysroot {
+            Some(s) => s.clone(),
+            None => {
+                rustc_session::filesearch::get_or_default_sysroot().expect("Failed finding sysroot")
+            }
+        };
+
+        let libs = matches
+            .opt_strs("L")
+            .iter()
+            .map(|s| SearchPath::from_cli_opt(&sysroot, &target, early_dcx, s))
+            .collect();
 
         let show_coverage = matches.opt_present("show-coverage");
 
@@ -653,7 +665,6 @@ impl Options {
         let bin_crate = crate_types.contains(&CrateType::Executable);
         let proc_macro_crate = crate_types.contains(&CrateType::ProcMacro);
         let playground_url = matches.opt_str("playground-url");
-        let maybe_sysroot = matches.opt_str("sysroot").map(PathBuf::from);
         let module_sorting = if matches.opt_present("sort-modules-by-appearance") {
             ModuleSorting::DeclarationOrder
         } else {
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 80a30ac2727..25b78d9598d 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -509,7 +509,7 @@ impl<'tcx> Visitor<'tcx> for EmitIgnoredResolutionErrors<'tcx> {
 
 /// `DefId` or parameter index (`ty::ParamTy.index`) of a synthetic type parameter
 /// for `impl Trait` in argument position.
-#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+#[derive(Clone, Copy, PartialEq, Eq, Hash)]
 pub(crate) enum ImplTraitParam {
     DefId(DefId),
     ParamIndex(u32),
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index 5d4f1acc4b1..fbb521a6188 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -1728,6 +1728,8 @@ fn item_variants(
         }
         w.write_str("</h3></section>");
 
+        write!(w, "{}", document(cx, variant, Some(it), HeadingOffset::H4));
+
         let heading_and_fields = match &variant_data.kind {
             clean::VariantKind::Struct(s) => {
                 // If there is no field to display, no need to add the heading.
@@ -1789,8 +1791,6 @@ fn item_variants(
             }
             w.write_str("</div>");
         }
-
-        write!(w, "{}", document(cx, variant, Some(it), HeadingOffset::H4));
     }
     write!(w, "</div>");
 }
diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs
index f153a908329..be2786c99ec 100644
--- a/src/librustdoc/html/render/search_index.rs
+++ b/src/librustdoc/html/render/search_index.rs
@@ -65,8 +65,9 @@ pub(crate) fn build_index<'tcx>(
     // Sort search index items. This improves the compressibility of the search index.
     cache.search_index.sort_unstable_by(|k1, k2| {
         // `sort_unstable_by_key` produces lifetime errors
-        let k1 = (&k1.path, k1.name.as_str(), &k1.ty, &k1.parent);
-        let k2 = (&k2.path, k2.name.as_str(), &k2.ty, &k2.parent);
+        // HACK(rustdoc): should not be sorting `CrateNum` or `DefIndex`, this will soon go away, too
+        let k1 = (&k1.path, k1.name.as_str(), &k1.ty, k1.parent.map(|id| (id.index, id.krate)));
+        let k2 = (&k2.path, k2.name.as_str(), &k2.ty, k2.parent.map(|id| (id.index, id.krate)));
         Ord::cmp(&k1, &k2)
     });
 
diff --git a/src/librustdoc/passes/lint/redundant_explicit_links.rs b/src/librustdoc/passes/lint/redundant_explicit_links.rs
index f7bc5464707..569c17ee36e 100644
--- a/src/librustdoc/passes/lint/redundant_explicit_links.rs
+++ b/src/librustdoc/passes/lint/redundant_explicit_links.rs
@@ -90,7 +90,7 @@ fn check_redundant_explicit_link<'md>(
 ) -> Option<()> {
     let mut broken_line_callback = |link: BrokenLink<'md>| Some((link.reference, "".into()));
     let mut offset_iter = Parser::new_with_broken_link_callback(
-        &doc,
+        doc,
         main_body_opts(),
         Some(&mut broken_line_callback),
     )
@@ -264,6 +264,7 @@ fn collect_link_data(offset_iter: &mut OffsetIter<'_, '_>) -> LinkData {
     let mut resolvable_link = None;
     let mut resolvable_link_range = None;
     let mut display_link = String::new();
+    let mut is_resolvable = true;
 
     while let Some((event, range)) = offset_iter.next() {
         match event {
@@ -281,6 +282,11 @@ fn collect_link_data(offset_iter: &mut OffsetIter<'_, '_>) -> LinkData {
                 resolvable_link = Some(code);
                 resolvable_link_range = Some(range);
             }
+            Event::Start(_) => {
+                // If there is anything besides backticks, it's not considered as an intra-doc link
+                // so we ignore it.
+                is_resolvable = false;
+            }
             Event::End(_) => {
                 break;
             }
@@ -288,6 +294,11 @@ fn collect_link_data(offset_iter: &mut OffsetIter<'_, '_>) -> LinkData {
         }
     }
 
+    if !is_resolvable {
+        resolvable_link_range = None;
+        resolvable_link = None;
+    }
+
     LinkData { resolvable_link, resolvable_link_range, display_link }
 }
 
diff --git a/src/tools/cargo b/src/tools/cargo
-Subproject a510712d05c6c98f987af24dd73cdfafee8922e
+Subproject 499a61ce7a0fc6a72040084862a68b2603e770e
diff --git a/src/tools/clippy/clippy_lints/src/float_literal.rs b/src/tools/clippy/clippy_lints/src/float_literal.rs
index 07fbb1cb5c9..981a76d683d 100644
--- a/src/tools/clippy/clippy_lints/src/float_literal.rs
+++ b/src/tools/clippy/clippy_lints/src/float_literal.rs
@@ -83,7 +83,10 @@ impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
                 LitFloatType::Unsuffixed => None,
             };
             let (is_whole, is_inf, mut float_str) = match fty {
-                FloatTy::F16 => unimplemented!("f16_f128"),
+                FloatTy::F16 => {
+                    // FIXME(f16_f128): do a check like the others when parsing is available
+                    return;
+                },
                 FloatTy::F32 => {
                     let value = sym_str.parse::<f32>().unwrap();
 
@@ -94,7 +97,10 @@ impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
 
                     (value.fract() == 0.0, value.is_infinite(), formatter.format(value))
                 },
-                FloatTy::F128 => unimplemented!("f16_f128"),
+                FloatTy::F128 => {
+                    // FIXME(f16_f128): do a check like the others when parsing is available
+                    return;
+                },
             };
 
             if is_inf {
@@ -139,10 +145,11 @@ impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
 #[must_use]
 fn max_digits(fty: FloatTy) -> u32 {
     match fty {
-        FloatTy::F16 => unimplemented!("f16_f128"),
+        // FIXME(f16_f128): replace the magic numbers once `{f16,f128}::DIGITS` are available
+        FloatTy::F16 => 3,
         FloatTy::F32 => f32::DIGITS,
         FloatTy::F64 => f64::DIGITS,
-        FloatTy::F128 => unimplemented!("f16_f128"),
+        FloatTy::F128 => 33,
     }
 }
 
diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
index bc5dd10cad0..e58d4776427 100644
--- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
@@ -12,7 +12,7 @@ use rustc_errors::Applicability;
 use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::DefId;
 use rustc_hir::{BorrowKind, Expr, ExprKind, ItemKind, LangItem, Node};
-use rustc_hir_typeck::{FnCtxt, Inherited};
+use rustc_hir_typeck::{FnCtxt, TypeckRootCtxt};
 use rustc_infer::infer::TyCtxtInferExt;
 use rustc_lint::LateContext;
 use rustc_middle::mir::Mutability;
@@ -438,8 +438,8 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty<
             Node::Item(item) => {
                 if let ItemKind::Fn(_, _, body_id) = &item.kind
                     && let output_ty = return_ty(cx, item.owner_id)
-                    && let inherited = Inherited::new(cx.tcx, item.owner_id.def_id)
-                    && let fn_ctxt = FnCtxt::new(&inherited, cx.param_env, item.owner_id.def_id)
+                    && let root_ctxt = TypeckRootCtxt::new(cx.tcx, item.owner_id.def_id)
+                    && let fn_ctxt = FnCtxt::new(&root_ctxt, cx.param_env, item.owner_id.def_id)
                     && fn_ctxt.can_coerce(ty, output_ty)
                 {
                     if has_lifetime(output_ty) && has_lifetime(ty) {
diff --git a/src/tools/clippy/clippy_lints/src/transmute/utils.rs b/src/tools/clippy/clippy_lints/src/transmute/utils.rs
index 7a7bb9f9c94..15f1890aa39 100644
--- a/src/tools/clippy/clippy_lints/src/transmute/utils.rs
+++ b/src/tools/clippy/clippy_lints/src/transmute/utils.rs
@@ -1,6 +1,6 @@
 use rustc_hir as hir;
 use rustc_hir::Expr;
-use rustc_hir_typeck::{cast, FnCtxt, Inherited};
+use rustc_hir_typeck::{cast, FnCtxt, TypeckRootCtxt};
 use rustc_lint::LateContext;
 use rustc_middle::ty::cast::CastKind;
 use rustc_middle::ty::Ty;
@@ -34,8 +34,8 @@ pub(super) fn check_cast<'tcx>(
     let hir_id = e.hir_id;
     let local_def_id = hir_id.owner.def_id;
 
-    let inherited = Inherited::new(cx.tcx, local_def_id);
-    let fn_ctxt = FnCtxt::new(&inherited, cx.param_env, local_def_id);
+    let root_ctxt = TypeckRootCtxt::new(cx.tcx, local_def_id);
+    let fn_ctxt = FnCtxt::new(&root_ctxt, cx.param_env, local_def_id);
 
     if let Ok(check) = cast::CastCheck::new(
         &fn_ctxt,
diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs
index 6ff47dbffbc..e414bc384f1 100644
--- a/src/tools/compiletest/src/header.rs
+++ b/src/tools/compiletest/src/header.rs
@@ -691,9 +691,9 @@ pub fn line_directive<'line>(
     }
 }
 
-/// This is generated by collecting directives from ui tests and then extracting their directive
-/// names. This is **not** an exhaustive list of all possible directives. Instead, this is a
-/// best-effort approximation for diagnostics.
+/// This was originally generated by collecting directives from ui tests and then extracting their
+/// directive names. This is **not** an exhaustive list of all possible directives. Instead, this is
+/// a best-effort approximation for diagnostics. Add new headers to this list when needed.
 const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
     // tidy-alphabetical-start
     "assembly-output",
@@ -708,6 +708,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
     "check-run-results",
     "check-stdout",
     "check-test-line-numbers-match",
+    "compare-output-lines-by-subset",
     "compile-flags",
     "dont-check-compiler-stderr",
     "dont-check-compiler-stdout",
@@ -826,6 +827,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
     "needs-sanitizer-cfi",
     "needs-sanitizer-dataflow",
     "needs-sanitizer-hwaddress",
+    "needs-sanitizer-kcfi",
     "needs-sanitizer-leak",
     "needs-sanitizer-memory",
     "needs-sanitizer-memtag",
@@ -835,6 +837,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
     "needs-sanitizer-thread",
     "needs-threads",
     "needs-unwind",
+    "needs-wasmtime",
     "needs-xray",
     "no-prefer-dynamic",
     "normalize-stderr-32bit",
@@ -870,6 +873,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
     "only-unix",
     "only-wasm32",
     "only-wasm32-bare",
+    "only-wasm32-wasip1",
     "only-windows",
     "only-x86",
     "only-x86_64",
diff --git a/src/tools/compiletest/src/header/needs.rs b/src/tools/compiletest/src/header/needs.rs
index d7c74038aea..db154932d5b 100644
--- a/src/tools/compiletest/src/header/needs.rs
+++ b/src/tools/compiletest/src/header/needs.rs
@@ -149,6 +149,11 @@ pub(super) fn handle_needs(
             condition: config.target_cfg().relocation_model == "pic",
             ignore_reason: "ignored on targets without PIC relocation model",
         },
+        Need {
+            name: "needs-wasmtime",
+            condition: config.runner.as_ref().is_some_and(|r| r.contains("wasmtime")),
+            ignore_reason: "ignored when wasmtime runner is not available",
+        },
     ];
 
     let (name, comment) = match ln.split_once([':', ' ']) {
diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md
index b043805291e..26e55b89708 100644
--- a/src/tools/miri/README.md
+++ b/src/tools/miri/README.md
@@ -505,6 +505,8 @@ binaries, and as such worth documenting:
 * `MIRI_LOCAL_CRATES` is set by `cargo-miri` to tell the Miri driver which
   crates should be given special treatment in diagnostics, in addition to the
   crate currently being compiled.
+* `MIRI_ORIG_RUSTDOC` is set and read by different phases of `cargo-miri` to remember the
+  value of `RUSTDOC` from before it was overwritten.
 * `MIRI_VERBOSE` when set to any value tells the various `cargo-miri` phases to
   perform verbose logging.
 * `MIRI_HOST_SYSROOT` is set by bootstrap to tell `cargo-miri` which sysroot to use for *host*
diff --git a/src/tools/miri/cargo-miri/src/phases.rs b/src/tools/miri/cargo-miri/src/phases.rs
index 9428f76debd..b61bff2716c 100644
--- a/src/tools/miri/cargo-miri/src/phases.rs
+++ b/src/tools/miri/cargo-miri/src/phases.rs
@@ -211,6 +211,9 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
     cmd.env("MIRI_BE_RUSTC", "target"); // we better remember to *unset* this in the other phases!
 
     // Set rustdoc to us as well, so we can run doctests.
+    if let Some(orig_rustdoc) = env::var_os("RUSTDOC") {
+        cmd.env("MIRI_ORIG_RUSTDOC", orig_rustdoc);
+    }
     cmd.env("RUSTDOC", &cargo_miri_path);
 
     cmd.env("MIRI_LOCAL_CRATES", local_crates(&metadata));
@@ -581,9 +584,10 @@ pub fn phase_rustdoc(mut args: impl Iterator<Item = String>) {
     let verbose = std::env::var("MIRI_VERBOSE")
         .map_or(0, |verbose| verbose.parse().expect("verbosity flag must be an integer"));
 
-    // phase_cargo_miri sets the RUSTDOC env var to ourselves, so we can't use that here;
-    // just default to a straight-forward invocation for now:
-    let mut cmd = Command::new("rustdoc");
+    // phase_cargo_miri sets the RUSTDOC env var to ourselves, and puts a backup
+    // of the old value into MIRI_ORIG_RUSTDOC. So that's what we have to invoke now.
+    let rustdoc = env::var("MIRI_ORIG_RUSTDOC").unwrap_or("rustdoc".to_string());
+    let mut cmd = Command::new(rustdoc);
 
     while let Some(arg) = args.next() {
         if arg == "--extern" {
diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version
index acf96cfab7a..6b432dde3b9 100644
--- a/src/tools/miri/rust-version
+++ b/src/tools/miri/rust-version
@@ -1 +1 @@
-b13a71a2e77f4625d1a2b8a5b9488414686ebca9
+760e567af5398a0d8c512f904e551e1f38e00d79
diff --git a/src/tools/miri/src/shims/panic.rs b/src/tools/miri/src/shims/panic.rs
index 34b6481dd38..bb31ef733cf 100644
--- a/src/tools/miri/src/shims/panic.rs
+++ b/src/tools/miri/src/shims/panic.rs
@@ -256,8 +256,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             }
 
             _ => {
-                // Forward everything else to `panic` lang item.
-                this.start_panic(msg.description(), unwind)?;
+                // Call the lang item associated with this message.
+                let fn_item = this.tcx.require_lang_item(msg.panic_function(), None);
+                let instance = ty::Instance::mono(this.tcx.tcx, fn_item);
+                this.call_function(
+                    instance,
+                    Abi::Rust,
+                    &[],
+                    None,
+                    StackPopCleanup::Goto { ret: None, unwind },
+                )?;
             }
         }
         Ok(())
diff --git a/src/tools/miri/tests/pass/async-closure-drop.rs b/src/tools/miri/tests/pass/async-closure-drop.rs
new file mode 100644
index 00000000000..9b2fc2948bf
--- /dev/null
+++ b/src/tools/miri/tests/pass/async-closure-drop.rs
@@ -0,0 +1,40 @@
+#![feature(async_closure, noop_waker, async_fn_traits)]
+
+use std::future::Future;
+use std::pin::pin;
+use std::task::*;
+
+pub fn block_on<T>(fut: impl Future<Output = T>) -> T {
+    let mut fut = pin!(fut);
+    let ctx = &mut Context::from_waker(Waker::noop());
+
+    loop {
+        match fut.as_mut().poll(ctx) {
+            Poll::Pending => {}
+            Poll::Ready(t) => break t,
+        }
+    }
+}
+
+async fn call_once(f: impl async FnOnce(DropMe)) {
+    f(DropMe("world")).await;
+}
+
+#[derive(Debug)]
+struct DropMe(&'static str);
+
+impl Drop for DropMe {
+    fn drop(&mut self) {
+        println!("{}", self.0);
+    }
+}
+
+pub fn main() {
+    block_on(async {
+        let b = DropMe("hello");
+        let async_closure = async move |a: DropMe| {
+            println!("{a:?} {b:?}");
+        };
+        call_once(async_closure).await;
+    });
+}
diff --git a/src/tools/miri/tests/pass/async-closure-drop.stdout b/src/tools/miri/tests/pass/async-closure-drop.stdout
new file mode 100644
index 00000000000..34cfdedc44a
--- /dev/null
+++ b/src/tools/miri/tests/pass/async-closure-drop.stdout
@@ -0,0 +1,3 @@
+DropMe("world") DropMe("hello")
+world
+hello
diff --git a/src/tools/miri/tests/pass/async-closure.rs b/src/tools/miri/tests/pass/async-closure.rs
index 9b2fc2948bf..2f7ec2b9e6f 100644
--- a/src/tools/miri/tests/pass/async-closure.rs
+++ b/src/tools/miri/tests/pass/async-closure.rs
@@ -1,6 +1,7 @@
 #![feature(async_closure, noop_waker, async_fn_traits)]
 
 use std::future::Future;
+use std::ops::{AsyncFnMut, AsyncFnOnce};
 use std::pin::pin;
 use std::task::*;
 
@@ -16,25 +17,36 @@ pub fn block_on<T>(fut: impl Future<Output = T>) -> T {
     }
 }
 
-async fn call_once(f: impl async FnOnce(DropMe)) {
-    f(DropMe("world")).await;
+async fn call_mut(f: &mut impl AsyncFnMut(i32)) {
+    f(0).await;
 }
 
-#[derive(Debug)]
-struct DropMe(&'static str);
+async fn call_once(f: impl AsyncFnOnce(i32)) {
+    f(1).await;
+}
 
-impl Drop for DropMe {
-    fn drop(&mut self) {
-        println!("{}", self.0);
-    }
+async fn call_normal<F: Future<Output = ()>>(f: &impl Fn(i32) -> F) {
+    f(0).await;
+}
+
+async fn call_normal_once<F: Future<Output = ()>>(f: impl FnOnce(i32) -> F) {
+    f(1).await;
 }
 
 pub fn main() {
     block_on(async {
-        let b = DropMe("hello");
-        let async_closure = async move |a: DropMe| {
-            println!("{a:?} {b:?}");
+        let b = 2i32;
+        let mut async_closure = async move |a: i32| {
+            println!("{a} {b}");
         };
+        call_mut(&mut async_closure).await;
         call_once(async_closure).await;
+
+        // No-capture closures implement `Fn`.
+        let async_closure = async move |a: i32| {
+            println!("{a}");
+        };
+        call_normal(&async_closure).await;
+        call_normal_once(async_closure).await;
     });
 }
diff --git a/src/tools/miri/tests/pass/async-closure.stdout b/src/tools/miri/tests/pass/async-closure.stdout
index 34cfdedc44a..7baae1aa94f 100644
--- a/src/tools/miri/tests/pass/async-closure.stdout
+++ b/src/tools/miri/tests/pass/async-closure.stdout
@@ -1,3 +1,4 @@
-DropMe("world") DropMe("hello")
-world
-hello
+0 2
+1 2
+0
+1
diff --git a/src/tools/run-make-support/src/lib.rs b/src/tools/run-make-support/src/lib.rs
index 419b04231b5..7975677286d 100644
--- a/src/tools/run-make-support/src/lib.rs
+++ b/src/tools/run-make-support/src/lib.rs
@@ -1,19 +1,21 @@
+pub mod run;
+pub mod rustc;
+pub mod rustdoc;
+
 use std::env;
-use std::path::{Path, PathBuf};
-use std::process::{Command, Output};
+use std::path::PathBuf;
+use std::process::Output;
 
 pub use object;
 pub use wasmparser;
 
-pub fn out_dir() -> PathBuf {
-    env::var_os("TMPDIR").unwrap().into()
-}
+pub use run::{run, run_fail};
+pub use rustc::{aux_build, rustc, Rustc};
+pub use rustdoc::{bare_rustdoc, rustdoc, Rustdoc};
 
-fn setup_common_build_cmd(command: &str) -> Command {
-    let rustc = env::var(command).unwrap();
-    let mut cmd = Command::new(rustc);
-    cmd.arg("--out-dir").arg(out_dir()).arg("-L").arg(out_dir());
-    cmd
+/// Path of `TMPDIR` (a temporary build directory, not under `/tmp`).
+pub fn tmp_dir() -> PathBuf {
+    env::var_os("TMPDIR").unwrap().into()
 }
 
 fn handle_failed_output(cmd: &str, output: Output, caller_line_number: u32) -> ! {
@@ -24,170 +26,3 @@ fn handle_failed_output(cmd: &str, output: Output, caller_line_number: u32) -> !
     eprintln!("=== STDERR ===\n{}\n\n", String::from_utf8(output.stderr).unwrap());
     std::process::exit(1)
 }
-
-pub fn rustc() -> RustcInvocationBuilder {
-    RustcInvocationBuilder::new()
-}
-
-pub fn aux_build() -> AuxBuildInvocationBuilder {
-    AuxBuildInvocationBuilder::new()
-}
-
-pub fn rustdoc() -> Rustdoc {
-    Rustdoc::new()
-}
-
-#[derive(Debug)]
-pub struct RustcInvocationBuilder {
-    cmd: Command,
-}
-
-impl RustcInvocationBuilder {
-    fn new() -> Self {
-        let cmd = setup_common_build_cmd("RUSTC");
-        Self { cmd }
-    }
-
-    pub fn arg(&mut self, arg: &str) -> &mut RustcInvocationBuilder {
-        self.cmd.arg(arg);
-        self
-    }
-
-    pub fn args(&mut self, args: &[&str]) -> &mut RustcInvocationBuilder {
-        self.cmd.args(args);
-        self
-    }
-
-    #[track_caller]
-    pub fn run(&mut self) -> Output {
-        let caller_location = std::panic::Location::caller();
-        let caller_line_number = caller_location.line();
-
-        let output = self.cmd.output().unwrap();
-        if !output.status.success() {
-            handle_failed_output(&format!("{:#?}", self.cmd), output, caller_line_number);
-        }
-        output
-    }
-}
-
-#[derive(Debug)]
-pub struct AuxBuildInvocationBuilder {
-    cmd: Command,
-}
-
-impl AuxBuildInvocationBuilder {
-    fn new() -> Self {
-        let mut cmd = setup_common_build_cmd("RUSTC");
-        cmd.arg("--crate-type=lib");
-        Self { cmd }
-    }
-
-    pub fn arg(&mut self, arg: &str) -> &mut AuxBuildInvocationBuilder {
-        self.cmd.arg(arg);
-        self
-    }
-
-    #[track_caller]
-    pub fn run(&mut self) -> Output {
-        let caller_location = std::panic::Location::caller();
-        let caller_line_number = caller_location.line();
-
-        let output = self.cmd.output().unwrap();
-        if !output.status.success() {
-            handle_failed_output(&format!("{:#?}", self.cmd), output, caller_line_number);
-        }
-        output
-    }
-}
-
-#[derive(Debug)]
-pub struct Rustdoc {
-    cmd: Command,
-}
-
-impl Rustdoc {
-    fn new() -> Self {
-        let cmd = setup_common_build_cmd("RUSTDOC");
-        Self { cmd }
-    }
-
-    pub fn arg(&mut self, arg: &str) -> &mut Self {
-        self.cmd.arg(arg);
-        self
-    }
-
-    #[track_caller]
-    pub fn run(&mut self) -> Output {
-        let caller_location = std::panic::Location::caller();
-        let caller_line_number = caller_location.line();
-
-        let output = self.cmd.output().unwrap();
-        if !output.status.success() {
-            handle_failed_output(&format!("{:#?}", self.cmd), output, caller_line_number);
-        }
-        output
-    }
-}
-
-fn run_common(bin_name: &str) -> (Command, Output) {
-    let target = env::var("TARGET").unwrap();
-
-    let bin_name =
-        if target.contains("windows") { format!("{}.exe", bin_name) } else { bin_name.to_owned() };
-
-    let mut bin_path = PathBuf::new();
-    bin_path.push(env::var("TMPDIR").unwrap());
-    bin_path.push(&bin_name);
-    let ld_lib_path_envvar = env::var("LD_LIB_PATH_ENVVAR").unwrap();
-    let mut cmd = Command::new(bin_path);
-    cmd.env(&ld_lib_path_envvar, {
-        let mut paths = vec![];
-        paths.push(PathBuf::from(env::var("TMPDIR").unwrap()));
-        for p in env::split_paths(&env::var("TARGET_RPATH_ENV").unwrap()) {
-            paths.push(p.to_path_buf());
-        }
-        for p in env::split_paths(&env::var(&ld_lib_path_envvar).unwrap()) {
-            paths.push(p.to_path_buf());
-        }
-        env::join_paths(paths.iter()).unwrap()
-    });
-
-    if target.contains("windows") {
-        let mut paths = vec![];
-        for p in env::split_paths(&std::env::var("PATH").unwrap_or(String::new())) {
-            paths.push(p.to_path_buf());
-        }
-        paths.push(Path::new(&std::env::var("TARGET_RPATH_DIR").unwrap()).to_path_buf());
-        cmd.env("PATH", env::join_paths(paths.iter()).unwrap());
-    }
-
-    let output = cmd.output().unwrap();
-    (cmd, output)
-}
-
-/// Run a built binary and make sure it succeeds.
-#[track_caller]
-pub fn run(bin_name: &str) -> Output {
-    let caller_location = std::panic::Location::caller();
-    let caller_line_number = caller_location.line();
-
-    let (cmd, output) = run_common(bin_name);
-    if !output.status.success() {
-        handle_failed_output(&format!("{:#?}", cmd), output, caller_line_number);
-    }
-    output
-}
-
-/// Run a built binary and make sure it fails.
-#[track_caller]
-pub fn run_fail(bin_name: &str) -> Output {
-    let caller_location = std::panic::Location::caller();
-    let caller_line_number = caller_location.line();
-
-    let (cmd, output) = run_common(bin_name);
-    if output.status.success() {
-        handle_failed_output(&format!("{:#?}", cmd), output, caller_line_number);
-    }
-    output
-}
diff --git a/src/tools/run-make-support/src/run.rs b/src/tools/run-make-support/src/run.rs
new file mode 100644
index 00000000000..42dcf54da22
--- /dev/null
+++ b/src/tools/run-make-support/src/run.rs
@@ -0,0 +1,67 @@
+use std::env;
+use std::path::{Path, PathBuf};
+use std::process::{Command, Output};
+
+use super::handle_failed_output;
+
+fn run_common(bin_name: &str) -> (Command, Output) {
+    let target = env::var("TARGET").unwrap();
+
+    let bin_name =
+        if target.contains("windows") { format!("{}.exe", bin_name) } else { bin_name.to_owned() };
+
+    let mut bin_path = PathBuf::new();
+    bin_path.push(env::var("TMPDIR").unwrap());
+    bin_path.push(&bin_name);
+    let ld_lib_path_envvar = env::var("LD_LIB_PATH_ENVVAR").unwrap();
+    let mut cmd = Command::new(bin_path);
+    cmd.env(&ld_lib_path_envvar, {
+        let mut paths = vec![];
+        paths.push(PathBuf::from(env::var("TMPDIR").unwrap()));
+        for p in env::split_paths(&env::var("TARGET_RPATH_ENV").unwrap()) {
+            paths.push(p.to_path_buf());
+        }
+        for p in env::split_paths(&env::var(&ld_lib_path_envvar).unwrap()) {
+            paths.push(p.to_path_buf());
+        }
+        env::join_paths(paths.iter()).unwrap()
+    });
+
+    if target.contains("windows") {
+        let mut paths = vec![];
+        for p in env::split_paths(&std::env::var("PATH").unwrap_or(String::new())) {
+            paths.push(p.to_path_buf());
+        }
+        paths.push(Path::new(&std::env::var("TARGET_RPATH_DIR").unwrap()).to_path_buf());
+        cmd.env("PATH", env::join_paths(paths.iter()).unwrap());
+    }
+
+    let output = cmd.output().unwrap();
+    (cmd, output)
+}
+
+/// Run a built binary and make sure it succeeds.
+#[track_caller]
+pub fn run(bin_name: &str) -> Output {
+    let caller_location = std::panic::Location::caller();
+    let caller_line_number = caller_location.line();
+
+    let (cmd, output) = run_common(bin_name);
+    if !output.status.success() {
+        handle_failed_output(&format!("{:#?}", cmd), output, caller_line_number);
+    }
+    output
+}
+
+/// Run a built binary and make sure it fails.
+#[track_caller]
+pub fn run_fail(bin_name: &str) -> Output {
+    let caller_location = std::panic::Location::caller();
+    let caller_line_number = caller_location.line();
+
+    let (cmd, output) = run_common(bin_name);
+    if output.status.success() {
+        handle_failed_output(&format!("{:#?}", cmd), output, caller_line_number);
+    }
+    output
+}
diff --git a/src/tools/run-make-support/src/rustc.rs b/src/tools/run-make-support/src/rustc.rs
new file mode 100644
index 00000000000..d0ab8df42d2
--- /dev/null
+++ b/src/tools/run-make-support/src/rustc.rs
@@ -0,0 +1,147 @@
+use std::env;
+use std::path::Path;
+use std::process::{Command, Output};
+
+use crate::{handle_failed_output, tmp_dir};
+
+/// Construct a new `rustc` invocation.
+pub fn rustc() -> Rustc {
+    Rustc::new()
+}
+
+/// Construct a new `rustc` aux-build invocation.
+pub fn aux_build() -> Rustc {
+    Rustc::new_aux_build()
+}
+
+/// A `rustc` invocation builder.
+#[derive(Debug)]
+pub struct Rustc {
+    cmd: Command,
+}
+
+fn setup_common() -> Command {
+    let rustc = env::var("RUSTC").unwrap();
+    let mut cmd = Command::new(rustc);
+    cmd.arg("--out-dir").arg(tmp_dir()).arg("-L").arg(tmp_dir());
+    cmd
+}
+
+impl Rustc {
+    // `rustc` invocation constructor methods
+
+    /// Construct a new `rustc` invocation.
+    pub fn new() -> Self {
+        let cmd = setup_common();
+        Self { cmd }
+    }
+
+    /// Construct a new `rustc` invocation with `aux_build` preset (setting `--crate-type=lib`).
+    pub fn new_aux_build() -> Self {
+        let mut cmd = setup_common();
+        cmd.arg("--crate-type=lib");
+        Self { cmd }
+    }
+
+    // Argument provider methods
+
+    /// Configure the compilation environment.
+    pub fn cfg(&mut self, s: &str) -> &mut Self {
+        self.cmd.arg("--cfg");
+        self.cmd.arg(s);
+        self
+    }
+
+    /// Specify default optimization level `-O` (alias for `-C opt-level=2`).
+    pub fn opt(&mut self) -> &mut Self {
+        self.cmd.arg("-O");
+        self
+    }
+
+    /// Specify type(s) of output files to generate.
+    pub fn emit(&mut self, kinds: &str) -> &mut Self {
+        self.cmd.arg(format!("--emit={kinds}"));
+        self
+    }
+
+    /// Specify where an external library is located.
+    pub fn extern_<P: AsRef<Path>>(&mut self, crate_name: &str, path: P) -> &mut Self {
+        assert!(
+            !crate_name.contains(|c: char| c.is_whitespace() || c == '\\' || c == '/'),
+            "crate name cannot contain whitespace or path separators"
+        );
+
+        let path = path.as_ref().to_string_lossy();
+
+        self.cmd.arg("--extern");
+        self.cmd.arg(format!("{crate_name}={path}"));
+
+        self
+    }
+
+    /// Specify path to the input file.
+    pub fn input<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
+        self.cmd.arg(path.as_ref());
+        self
+    }
+
+    /// Specify target triple.
+    pub fn target(&mut self, target: &str) -> &mut Self {
+        assert!(!target.contains(char::is_whitespace), "target triple cannot contain spaces");
+        self.cmd.arg(format!("--target={target}"));
+        self
+    }
+
+    /// Generic command argument provider. Use `.arg("-Zname")` over `.arg("-Z").arg("arg")`.
+    /// This method will panic if a plain `-Z` or `-C` is passed, or if `-Z <name>` or `-C <name>`
+    /// is passed (note the space).
+    pub fn arg(&mut self, arg: &str) -> &mut Self {
+        assert!(
+            !(["-Z", "-C"].contains(&arg) || arg.starts_with("-Z ") || arg.starts_with("-C ")),
+            "use `-Zarg` or `-Carg` over split `-Z` `arg` or `-C` `arg`"
+        );
+        self.cmd.arg(arg);
+        self
+    }
+
+    /// Generic command arguments provider. Use `.arg("-Zname")` over `.arg("-Z").arg("arg")`.
+    /// This method will panic if a plain `-Z` or `-C` is passed, or if `-Z <name>` or `-C <name>`
+    /// is passed (note the space).
+    pub fn args(&mut self, args: &[&str]) -> &mut Self {
+        for arg in args {
+            assert!(
+                !(["-Z", "-C"].contains(&arg) || arg.starts_with("-Z ") || arg.starts_with("-C ")),
+                "use `-Zarg` or `-Carg` over split `-Z` `arg` or `-C` `arg`"
+            );
+        }
+
+        self.cmd.args(args);
+        self
+    }
+
+    // Command inspection, output and running helper methods
+
+    /// Get the [`Output`][std::process::Output] of the finished `rustc` process.
+    pub fn output(&mut self) -> Output {
+        self.cmd.output().unwrap()
+    }
+
+    /// Run the constructed `rustc` command and assert that it is successfully run.
+    #[track_caller]
+    pub fn run(&mut self) -> Output {
+        let caller_location = std::panic::Location::caller();
+        let caller_line_number = caller_location.line();
+
+        let output = self.cmd.output().unwrap();
+        if !output.status.success() {
+            handle_failed_output(&format!("{:#?}", self.cmd), output, caller_line_number);
+        }
+        output
+    }
+
+    /// Inspect what the underlying [`Command`] is up to the current construction.
+    pub fn inspect(&mut self, f: impl FnOnce(&Command)) -> &mut Self {
+        f(&self.cmd);
+        self
+    }
+}
diff --git a/src/tools/run-make-support/src/rustdoc.rs b/src/tools/run-make-support/src/rustdoc.rs
new file mode 100644
index 00000000000..9607ff02f96
--- /dev/null
+++ b/src/tools/run-make-support/src/rustdoc.rs
@@ -0,0 +1,80 @@
+use std::env;
+use std::path::Path;
+use std::process::{Command, Output};
+
+use crate::handle_failed_output;
+
+/// Construct a plain `rustdoc` invocation with no flags set.
+pub fn bare_rustdoc() -> Rustdoc {
+    Rustdoc::bare()
+}
+
+/// Construct a new `rustdoc` invocation with `-L $(TARGET_RPATH_DIR)` set.
+pub fn rustdoc() -> Rustdoc {
+    Rustdoc::new()
+}
+
+#[derive(Debug)]
+pub struct Rustdoc {
+    cmd: Command,
+}
+
+fn setup_common() -> Command {
+    let rustdoc = env::var("RUSTDOC").unwrap();
+    Command::new(rustdoc)
+}
+
+impl Rustdoc {
+    /// Construct a bare `rustdoc` invocation.
+    pub fn bare() -> Self {
+        let cmd = setup_common();
+        Self { cmd }
+    }
+
+    /// Construct a `rustdoc` invocation with `-L $(TARGET_RPATH_DIR)` set.
+    pub fn new() -> Self {
+        let mut cmd = setup_common();
+        let target_rpath_dir = env::var_os("TARGET_RPATH_DIR").unwrap();
+        cmd.arg(format!("-L{}", target_rpath_dir.to_string_lossy()));
+        Self { cmd }
+    }
+
+    /// Specify path to the input file.
+    pub fn input<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
+        self.cmd.arg(path.as_ref());
+        self
+    }
+
+    /// Specify output directory.
+    pub fn out_dir<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
+        self.cmd.arg("--out-dir").arg(path.as_ref());
+        self
+    }
+
+    /// Given a `path`, pass `@{path}` to `rustdoc` as an
+    /// [arg file](https://doc.rust-lang.org/rustdoc/command-line-arguments.html#path-load-command-line-flags-from-a-path).
+    pub fn arg_file<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
+        self.cmd.arg(format!("@{}", path.as_ref().display()));
+        self
+    }
+
+    /// Fallback argument provider. Consider adding meaningfully named methods instead of using
+    /// this method.
+    pub fn arg(&mut self, arg: &str) -> &mut Self {
+        self.cmd.arg(arg);
+        self
+    }
+
+    /// Run the build `rustdoc` command and assert that the run is successful.
+    #[track_caller]
+    pub fn run(&mut self) -> Output {
+        let caller_location = std::panic::Location::caller();
+        let caller_line_number = caller_location.line();
+
+        let output = self.cmd.output().unwrap();
+        if !output.status.success() {
+            handle_failed_output(&format!("{:#?}", self.cmd), output, caller_line_number);
+        }
+        output
+    }
+}
diff --git a/src/tools/tidy/src/allowed_run_make_makefiles.txt b/src/tools/tidy/src/allowed_run_make_makefiles.txt
new file mode 100644
index 00000000000..bdee3afa6b7
--- /dev/null
+++ b/src/tools/tidy/src/allowed_run_make_makefiles.txt
@@ -0,0 +1,337 @@
+run-make/alloc-no-oom-handling/Makefile
+run-make/alloc-no-rc/Makefile
+run-make/alloc-no-sync/Makefile
+run-make/allocator-shim-circular-deps/Makefile
+run-make/allow-non-lint-warnings-cmdline/Makefile
+run-make/allow-warnings-cmdline-stability/Makefile
+run-make/archive-duplicate-names/Makefile
+run-make/arguments-non-c-like-enum/Makefile
+run-make/atomic-lock-free/Makefile
+run-make/bare-outfile/Makefile
+run-make/branch-protection-check-IBT/Makefile
+run-make/c-dynamic-dylib/Makefile
+run-make/c-dynamic-rlib/Makefile
+run-make/c-link-to-rust-dylib/Makefile
+run-make/c-link-to-rust-staticlib/Makefile
+run-make/c-link-to-rust-va-list-fn/Makefile
+run-make/c-static-dylib/Makefile
+run-make/c-static-rlib/Makefile
+run-make/c-unwind-abi-catch-lib-panic/Makefile
+run-make/c-unwind-abi-catch-panic/Makefile
+run-make/cat-and-grep-sanity-check/Makefile
+run-make/cdylib-dylib-linkage/Makefile
+run-make/cdylib-fewer-symbols/Makefile
+run-make/cdylib/Makefile
+run-make/codegen-options-parsing/Makefile
+run-make/comment-section/Makefile
+run-make/compile-stdin/Makefile
+run-make/compiler-lookup-paths-2/Makefile
+run-make/compiler-lookup-paths/Makefile
+run-make/compiler-rt-works-on-mingw/Makefile
+run-make/compressed-debuginfo/Makefile
+run-make/const-prop-lint/Makefile
+run-make/const_fn_mir/Makefile
+run-make/core-no-fp-fmt-parse/Makefile
+run-make/core-no-oom-handling/Makefile
+run-make/crate-data-smoke/Makefile
+run-make/crate-hash-rustc-version/Makefile
+run-make/crate-name-priority/Makefile
+run-make/cross-lang-lto-clang/Makefile
+run-make/cross-lang-lto-pgo-smoketest/Makefile
+run-make/cross-lang-lto-upstream-rlibs/Makefile
+run-make/cross-lang-lto/Makefile
+run-make/debug-assertions/Makefile
+run-make/debugger-visualizer-dep-info/Makefile
+run-make/dep-graph/Makefile
+run-make/dep-info-doesnt-run-much/Makefile
+run-make/dep-info-spaces/Makefile
+run-make/dep-info/Makefile
+run-make/doctests-keep-binaries/Makefile
+run-make/doctests-runtool/Makefile
+run-make/dump-ice-to-disk/Makefile
+run-make/dump-mono-stats/Makefile
+run-make/duplicate-output-flavors/Makefile
+run-make/dylib-chain/Makefile
+run-make/emit-named-files/Makefile
+run-make/emit-path-unhashed/Makefile
+run-make/emit-shared-files/Makefile
+run-make/emit-stack-sizes/Makefile
+run-make/emit-to-stdout/Makefile
+run-make/emit/Makefile
+run-make/env-dep-info/Makefile
+run-make/error-found-staticlib-instead-crate/Makefile
+run-make/error-writing-dependencies/Makefile
+run-make/exit-code/Makefile
+run-make/export-executable-symbols/Makefile
+run-make/extern-diff-internal-name/Makefile
+run-make/extern-flag-disambiguates/Makefile
+run-make/extern-flag-fun/Makefile
+run-make/extern-flag-pathless/Makefile
+run-make/extern-flag-rename-transitive/Makefile
+run-make/extern-fn-explicit-align/Makefile
+run-make/extern-fn-generic/Makefile
+run-make/extern-fn-mangle/Makefile
+run-make/extern-fn-reachable/Makefile
+run-make/extern-fn-struct-passing-abi/Makefile
+run-make/extern-fn-with-extern-types/Makefile
+run-make/extern-fn-with-packed-struct/Makefile
+run-make/extern-fn-with-union/Makefile
+run-make/extern-multiple-copies/Makefile
+run-make/extern-multiple-copies2/Makefile
+run-make/extern-overrides-distribution/Makefile
+run-make/extra-filename-with-temp-outputs/Makefile
+run-make/fmt-write-bloat/Makefile
+run-make/forced-unwind-terminate-pof/Makefile
+run-make/foreign-double-unwind/Makefile
+run-make/foreign-exceptions/Makefile
+run-make/foreign-rust-exceptions/Makefile
+run-make/fpic/Makefile
+run-make/glibc-staticlib-args/Makefile
+run-make/hir-tree/Makefile
+run-make/inaccessible-temp-dir/Makefile
+run-make/include_bytes_deps/Makefile
+run-make/incr-add-rust-src-component/Makefile
+run-make/incr-foreign-head-span/Makefile
+run-make/incr-prev-body-beyond-eof/Makefile
+run-make/incremental-debugger-visualizer/Makefile
+run-make/incremental-session-fail/Makefile
+run-make/inline-always-many-cgu/Makefile
+run-make/interdependent-c-libraries/Makefile
+run-make/intrinsic-unreachable/Makefile
+run-make/invalid-library/Makefile
+run-make/invalid-so/Makefile
+run-make/invalid-staticlib/Makefile
+run-make/issue-107094/Makefile
+run-make/issue-10971-temps-dir/Makefile
+run-make/issue-109934-lto-debuginfo/Makefile
+run-make/issue-11908/Makefile
+run-make/issue-14500/Makefile
+run-make/issue-14698/Makefile
+run-make/issue-15460/Makefile
+run-make/issue-18943/Makefile
+run-make/issue-20626/Makefile
+run-make/issue-22131/Makefile
+run-make/issue-24445/Makefile
+run-make/issue-25581/Makefile
+run-make/issue-26006/Makefile
+run-make/issue-26092/Makefile
+run-make/issue-28595/Makefile
+run-make/issue-28766/Makefile
+run-make/issue-30063/Makefile
+run-make/issue-33329/Makefile
+run-make/issue-35164/Makefile
+run-make/issue-36710/Makefile
+run-make/issue-37839/Makefile
+run-make/issue-37893/Makefile
+run-make/issue-38237/Makefile
+run-make/issue-40535/Makefile
+run-make/issue-46239/Makefile
+run-make/issue-47384/Makefile
+run-make/issue-47551/Makefile
+run-make/issue-51671/Makefile
+run-make/issue-53964/Makefile
+run-make/issue-64153/Makefile
+run-make/issue-68794-textrel-on-minimal-lib/Makefile
+run-make/issue-69368/Makefile
+run-make/issue-7349/Makefile
+run-make/issue-83045/Makefile
+run-make/issue-83112-incr-test-moved-file/Makefile
+run-make/issue-84395-lto-embed-bitcode/Makefile
+run-make/issue-85019-moved-src-dir/Makefile
+run-make/issue-85401-static-mir/Makefile
+run-make/issue-85441/Makefile
+run-make/issue-88756-default-output/Makefile
+run-make/issue-97463-abi-param-passing/Makefile
+run-make/issue64319/Makefile
+run-make/jobserver-error/Makefile
+run-make/libs-through-symlinks/Makefile
+run-make/libtest-json/Makefile
+run-make/libtest-junit/Makefile
+run-make/libtest-padding/Makefile
+run-make/libtest-thread-limit/Makefile
+run-make/link-arg/Makefile
+run-make/link-args-order/Makefile
+run-make/link-cfg/Makefile
+run-make/link-dedup/Makefile
+run-make/link-framework/Makefile
+run-make/link-path-order/Makefile
+run-make/linkage-attr-on-static/Makefile
+run-make/llvm-ident/Makefile
+run-make/llvm-outputs/Makefile
+run-make/long-linker-command-lines-cmd-exe/Makefile
+run-make/long-linker-command-lines/Makefile
+run-make/longjmp-across-rust/Makefile
+run-make/ls-metadata/Makefile
+run-make/lto-dylib-dep/Makefile
+run-make/lto-empty/Makefile
+run-make/lto-linkage-used-attr/Makefile
+run-make/lto-no-link-whole-rlib/Makefile
+run-make/lto-readonly-lib/Makefile
+run-make/lto-smoke-c/Makefile
+run-make/lto-smoke/Makefile
+run-make/macos-deployment-target/Makefile
+run-make/macos-fat-archive/Makefile
+run-make/manual-crate-name/Makefile
+run-make/manual-link/Makefile
+run-make/many-crates-but-no-match/Makefile
+run-make/metadata-dep-info/Makefile
+run-make/metadata-flag-frobs-symbols/Makefile
+run-make/min-global-align/Makefile
+run-make/mingw-export-call-convention/Makefile
+run-make/mismatching-target-triples/Makefile
+run-make/missing-crate-dependency/Makefile
+run-make/mixing-deps/Makefile
+run-make/mixing-formats/Makefile
+run-make/mixing-libs/Makefile
+run-make/msvc-opt-minsize/Makefile
+run-make/multiple-emits/Makefile
+run-make/native-link-modifier-bundle/Makefile
+run-make/native-link-modifier-verbatim-linker/Makefile
+run-make/native-link-modifier-verbatim-rustc/Makefile
+run-make/native-link-modifier-whole-archive/Makefile
+run-make/no-alloc-shim/Makefile
+run-make/no-builtins-attribute/Makefile
+run-make/no-builtins-lto/Makefile
+run-make/no-cdylib-as-rdylib/Makefile
+run-make/no-duplicate-libs/Makefile
+run-make/no-input-file/Makefile
+run-make/no-intermediate-extras/Makefile
+run-make/obey-crate-type-flag/Makefile
+run-make/optimization-remarks-dir-pgo/Makefile
+run-make/optimization-remarks-dir/Makefile
+run-make/output-filename-conflicts-with-directory/Makefile
+run-make/output-filename-overwrites-input/Makefile
+run-make/output-type-permutations/Makefile
+run-make/output-with-hyphens/Makefile
+run-make/override-aliased-flags/Makefile
+run-make/overwrite-input/Makefile
+run-make/panic-abort-eh_frame/Makefile
+run-make/panic-impl-transitive/Makefile
+run-make/pass-linker-flags-flavor/Makefile
+run-make/pass-linker-flags-from-dep/Makefile
+run-make/pass-linker-flags/Makefile
+run-make/pass-non-c-like-enum-to-c/Makefile
+run-make/pdb-alt-path/Makefile
+run-make/pdb-buildinfo-cl-cmd/Makefile
+run-make/pgo-branch-weights/Makefile
+run-make/pgo-gen-lto/Makefile
+run-make/pgo-gen-no-imp-symbols/Makefile
+run-make/pgo-gen/Makefile
+run-make/pgo-indirect-call-promotion/Makefile
+run-make/pgo-use/Makefile
+run-make/pointer-auth-link-with-c/Makefile
+run-make/prefer-dylib/Makefile
+run-make/prefer-rlib/Makefile
+run-make/pretty-print-to-file/Makefile
+run-make/pretty-print-with-dep-file/Makefile
+run-make/print-calling-conventions/Makefile
+run-make/print-cfg/Makefile
+run-make/print-native-static-libs/Makefile
+run-make/print-target-list/Makefile
+run-make/profile/Makefile
+run-make/prune-link-args/Makefile
+run-make/raw-dylib-alt-calling-convention/Makefile
+run-make/raw-dylib-c/Makefile
+run-make/raw-dylib-cross-compilation/Makefile
+run-make/raw-dylib-custom-dlltool/Makefile
+run-make/raw-dylib-import-name-type/Makefile
+run-make/raw-dylib-inline-cross-dylib/Makefile
+run-make/raw-dylib-link-ordinal/Makefile
+run-make/raw-dylib-stdcall-ordinal/Makefile
+run-make/redundant-libs/Makefile
+run-make/relocation-model/Makefile
+run-make/relro-levels/Makefile
+run-make/remap-path-prefix-dwarf/Makefile
+run-make/remap-path-prefix/Makefile
+run-make/repr128-dwarf/Makefile
+run-make/reproducible-build-2/Makefile
+run-make/reproducible-build/Makefile
+run-make/resolve-rename/Makefile
+run-make/return-non-c-like-enum-from-c/Makefile
+run-make/return-non-c-like-enum/Makefile
+run-make/rlib-chain/Makefile
+run-make/rlib-format-packed-bundled-libs-2/Makefile
+run-make/rlib-format-packed-bundled-libs-3/Makefile
+run-make/rlib-format-packed-bundled-libs/Makefile
+run-make/rmeta-preferred/Makefile
+run-make/rust-lld-custom-target/Makefile
+run-make/rust-lld/Makefile
+run-make/rustc-macro-dep-files/Makefile
+run-make/rustdoc-determinism/Makefile
+run-make/rustdoc-error-lines/Makefile
+run-make/rustdoc-io-error/Makefile
+run-make/rustdoc-map-file/Makefile
+run-make/rustdoc-output-path/Makefile
+run-make/rustdoc-scrape-examples-invalid-expr/Makefile
+run-make/rustdoc-scrape-examples-macros/Makefile
+run-make/rustdoc-scrape-examples-multiple/Makefile
+run-make/rustdoc-scrape-examples-ordering/Makefile
+run-make/rustdoc-scrape-examples-remap/Makefile
+run-make/rustdoc-scrape-examples-test/Makefile
+run-make/rustdoc-scrape-examples-whitespace/Makefile
+run-make/rustdoc-shared-flags/Makefile
+run-make/rustdoc-target-spec-json-path/Makefile
+run-make/rustdoc-themes/Makefile
+run-make/rustdoc-verify-output-files/Makefile
+run-make/rustdoc-with-out-dir-option/Makefile
+run-make/rustdoc-with-output-option/Makefile
+run-make/rustdoc-with-short-out-dir-option/Makefile
+run-make/sanitizer-cdylib-link/Makefile
+run-make/sanitizer-dylib-link/Makefile
+run-make/sanitizer-staticlib-link/Makefile
+run-make/separate-link-fail/Makefile
+run-make/separate-link/Makefile
+run-make/sepcomp-cci-copies/Makefile
+run-make/sepcomp-inlining/Makefile
+run-make/sepcomp-separate/Makefile
+run-make/share-generics-dylib/Makefile
+run-make/short-ice/Makefile
+run-make/silly-file-names/Makefile
+run-make/simd-ffi/Makefile
+run-make/simple-dylib/Makefile
+run-make/simple-rlib/Makefile
+run-make/split-debuginfo/Makefile
+run-make/stable-symbol-names/Makefile
+run-make/static-dylib-by-default/Makefile
+run-make/static-extern-type/Makefile
+run-make/static-pie/Makefile
+run-make/static-unwinding/Makefile
+run-make/staticlib-blank-lib/Makefile
+run-make/staticlib-dylib-linkage/Makefile
+run-make/std-core-cycle/Makefile
+run-make/stdin-non-utf8/Makefile
+run-make/suspicious-library/Makefile
+run-make/symbol-mangling-hashed/Makefile
+run-make/symbol-visibility/Makefile
+run-make/symbols-include-type-name/Makefile
+run-make/symlinked-extern/Makefile
+run-make/symlinked-libraries/Makefile
+run-make/symlinked-rlib/Makefile
+run-make/sysroot-crates-are-unstable/Makefile
+run-make/target-cpu-native/Makefile
+run-make/target-specs/Makefile
+run-make/target-without-atomic-cas/Makefile
+run-make/test-benches/Makefile
+run-make/test-harness/Makefile
+run-make/thumb-none-cortex-m/Makefile
+run-make/thumb-none-qemu/Makefile
+run-make/track-path-dep-info/Makefile
+run-make/track-pgo-dep-info/Makefile
+run-make/translation/Makefile
+run-make/type-mismatch-same-crate-name/Makefile
+run-make/unknown-mod-stdin/Makefile
+run-make/unstable-flag-required/Makefile
+run-make/use-suggestions-rust-2018/Makefile
+run-make/used-cdylib-macos/Makefile
+run-make/used/Makefile
+run-make/valid-print-requests/Makefile
+run-make/version/Makefile
+run-make/volatile-intrinsics/Makefile
+run-make/wasm-exceptions-nostd/Makefile
+run-make/wasm-override-linker/Makefile
+run-make/weird-output-filenames/Makefile
+run-make/windows-binary-no-external-deps/Makefile
+run-make/windows-safeseh/Makefile
+run-make/windows-spawn/Makefile
+run-make/windows-subsystem/Makefile
+run-make/x86_64-fortanix-unknown-sgx-lvi/Makefile
diff --git a/src/tools/tidy/src/lib.rs b/src/tools/tidy/src/lib.rs
index 670b7eb2be9..57436e8d7fe 100644
--- a/src/tools/tidy/src/lib.rs
+++ b/src/tools/tidy/src/lib.rs
@@ -69,6 +69,7 @@ mod fluent_used;
 pub(crate) mod iter_header;
 pub mod mir_opt_tests;
 pub mod pal;
+pub mod run_make_tests;
 pub mod rustdoc_css_themes;
 pub mod rustdoc_gui_tests;
 pub mod style;
diff --git a/src/tools/tidy/src/main.rs b/src/tools/tidy/src/main.rs
index 2bce246c308..93be4d61a9a 100644
--- a/src/tools/tidy/src/main.rs
+++ b/src/tools/tidy/src/main.rs
@@ -103,6 +103,8 @@ fn main() {
         check!(tests_revision_unpaired_stdout_stderr, &tests_path);
         check!(debug_artifacts, &tests_path);
         check!(ui_tests, &root_path, bless);
+        // FIXME(jieyouxu): remove this check once all run-make tests are ported over to rmake.rs.
+        check!(run_make_tests, &tests_path, &src_path, bless);
         check!(mir_opt_tests, &tests_path, bless);
         check!(rustdoc_gui_tests, &tests_path);
         check!(rustdoc_css_themes, &librustdoc_path);
diff --git a/src/tools/tidy/src/run_make_tests.rs b/src/tools/tidy/src/run_make_tests.rs
new file mode 100644
index 00000000000..98021cec130
--- /dev/null
+++ b/src/tools/tidy/src/run_make_tests.rs
@@ -0,0 +1,89 @@
+//! Tidy check to ensure that no new Makefiles are added under `tests/run-make/`.
+
+use std::collections::BTreeSet;
+use std::fs::File;
+use std::io::Write;
+use std::path::{Path, PathBuf};
+
+pub fn check(tests_path: &Path, src_path: &Path, bless: bool, bad: &mut bool) {
+    let allowed_makefiles = {
+        let allowed_makefiles = include_str!("allowed_run_make_makefiles.txt");
+        let allowed_makefiles = allowed_makefiles.lines().collect::<Vec<_>>();
+        let is_sorted = allowed_makefiles.windows(2).all(|w| w[0] < w[1]);
+        if !is_sorted && !bless {
+            tidy_error!(
+                bad,
+                "`src/tools/tidy/src/allowed_run_make_makefiles.txt` is not in order, likely \
+                because you modified it manually, please only update it with command \
+                `x test tidy --bless`"
+            );
+        }
+        let allowed_makefiles_unique =
+            allowed_makefiles.iter().map(ToString::to_string).collect::<BTreeSet<String>>();
+        if allowed_makefiles_unique.len() != allowed_makefiles.len() {
+            tidy_error!(
+                bad,
+                "`src/tools/tidy/src/allowed_run_make_makefiles.txt` contains duplicate entries, \
+                likely because you modified it manually, please only update it with command \
+                `x test tidy --bless`"
+            );
+        }
+        allowed_makefiles_unique
+    };
+
+    let mut remaining_makefiles = allowed_makefiles.clone();
+
+    crate::walk::walk_no_read(
+        &[tests_path.join("run-make").as_ref()],
+        |_, _| false,
+        &mut |entry| {
+            if entry.file_type().map_or(true, |t| t.is_dir()) {
+                return;
+            }
+
+            if entry.file_name().to_str().map_or(true, |f| f != "Makefile") {
+                return;
+            }
+
+            let makefile_path = entry.path().strip_prefix(&tests_path).unwrap();
+            let makefile_path = makefile_path.to_str().unwrap().replace('\\', "/");
+
+            if !remaining_makefiles.remove(&makefile_path) {
+                tidy_error!(
+                    bad,
+                    "found run-make Makefile not permitted in \
+                `src/tools/tidy/src/allowed_run_make_makefiles.txt`, please write new run-make \
+                tests with `rmake.rs` instead: {}",
+                    entry.path().display()
+                );
+            }
+        },
+    );
+
+    // If there are any expected Makefiles remaining, they were moved or deleted.
+    // Our data must remain up to date, so they must be removed from
+    // `src/tools/tidy/src/allowed_run_make_makefiles.txt`.
+    // This can be done automatically on --bless, or else a tidy error will be issued.
+    if bless && !remaining_makefiles.is_empty() {
+        let tidy_src = src_path.join("tools").join("tidy").join("src");
+        let org_file_path = tidy_src.join("allowed_run_make_makefiles.txt");
+        let temp_file_path = tidy_src.join("blessed_allowed_run_make_makefiles.txt");
+        let mut temp_file = t!(File::create_new(&temp_file_path));
+        for file in allowed_makefiles.difference(&remaining_makefiles) {
+            t!(writeln!(temp_file, "{file}"));
+        }
+        t!(std::fs::rename(&temp_file_path, &org_file_path));
+    } else {
+        for file in remaining_makefiles {
+            let mut p = PathBuf::from(tests_path);
+            p.push(file);
+            tidy_error!(
+                bad,
+                "Makefile `{}` no longer exists and should be removed from the exclusions in \
+                `src/tools/tidy/src/allowed_run_make_makefiles.txt`, you can run --bless to update \
+                the allow list",
+                p.display()
+            );
+        }
+    }
+}