about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bootstrap/defaults/config.compiler.toml3
-rw-r--r--src/bootstrap/download-ci-llvm-stamp2
-rw-r--r--src/bootstrap/mk/Makefile.in8
-rw-r--r--src/bootstrap/src/core/build_steps/compile.rs14
-rw-r--r--src/bootstrap/src/core/build_steps/format.rs5
-rw-r--r--src/bootstrap/src/core/build_steps/llvm.rs4
-rw-r--r--src/bootstrap/src/core/build_steps/suggest.rs1
-rw-r--r--src/bootstrap/src/core/build_steps/test.rs1
-rw-r--r--src/bootstrap/src/core/build_steps/tool.rs7
-rw-r--r--src/bootstrap/src/core/config/config.rs24
-rw-r--r--src/bootstrap/src/utils/change_tracker.rs5
-rw-r--r--src/bootstrap/src/utils/helpers.rs23
-rw-r--r--src/doc/rustc/src/platform-support/apple-ios-macabi.md2
-rw-r--r--src/doc/rustc/src/platform-support/arm64e-apple-ios.md2
-rw-r--r--src/doc/unstable-book/src/compiler-flags/small-data-threshold.md20
-rw-r--r--src/librustdoc/clean/auto_trait.rs22
-rw-r--r--src/librustdoc/clean/blanket_impl.rs72
-rw-r--r--src/librustdoc/clean/inline.rs69
-rw-r--r--src/librustdoc/clean/mod.rs14
-rw-r--r--src/librustdoc/clean/types.rs63
-rw-r--r--src/librustdoc/clean/utils.rs6
-rw-r--r--src/librustdoc/config.rs125
-rw-r--r--src/librustdoc/core.rs4
-rw-r--r--src/librustdoc/doctest.rs10
-rw-r--r--src/librustdoc/doctest/markdown.rs11
-rw-r--r--src/librustdoc/fold.rs10
-rw-r--r--src/librustdoc/formats/cache.rs26
-rw-r--r--src/librustdoc/formats/item_type.rs2
-rw-r--r--src/librustdoc/formats/mod.rs2
-rw-r--r--src/librustdoc/formats/renderer.rs2
-rw-r--r--src/librustdoc/html/render/context.rs177
-rw-r--r--src/librustdoc/html/render/mod.rs23
-rw-r--r--src/librustdoc/html/render/print_item.rs38
-rw-r--r--src/librustdoc/html/render/search_index.rs4
-rw-r--r--src/librustdoc/html/render/sidebar.rs10
-rw-r--r--src/librustdoc/html/render/write_shared.rs180
-rw-r--r--src/librustdoc/html/render/write_shared/tests.rs10
-rw-r--r--src/librustdoc/json/conversions.rs6
-rw-r--r--src/librustdoc/json/import_finder.rs2
-rw-r--r--src/librustdoc/json/mod.rs4
-rw-r--r--src/librustdoc/lib.rs90
-rw-r--r--src/librustdoc/passes/calculate_doc_coverage.rs2
-rw-r--r--src/librustdoc/passes/check_doc_test_visibility.rs2
-rw-r--r--src/librustdoc/passes/collect_trait_impls.rs10
-rw-r--r--src/librustdoc/passes/lint.rs33
-rw-r--r--src/librustdoc/passes/lint/bare_urls.rs68
-rw-r--r--src/librustdoc/passes/lint/check_code_block_syntax.rs6
-rw-r--r--src/librustdoc/passes/lint/html_tags.rs248
-rw-r--r--src/librustdoc/passes/lint/redundant_explicit_links.rs7
-rw-r--r--src/librustdoc/passes/lint/unescaped_backticks.rs12
-rw-r--r--src/librustdoc/passes/lint/unportable_markdown.rs12
-rw-r--r--src/librustdoc/passes/propagate_doc_cfg.rs2
-rw-r--r--src/librustdoc/passes/strip_aliased_non_local.rs2
-rw-r--r--src/librustdoc/passes/strip_hidden.rs4
-rw-r--r--src/librustdoc/passes/stripper.rs8
-rw-r--r--src/librustdoc/visit.rs4
m---------src/llvm-project0
-rw-r--r--src/tools/build_helper/src/git.rs43
-rw-r--r--src/tools/clippy/clippy_utils/src/ast_utils.rs12
-rw-r--r--src/tools/compiletest/src/common.rs7
-rw-r--r--src/tools/compiletest/src/header/tests.rs1
-rw-r--r--src/tools/compiletest/src/lib.rs15
-rw-r--r--src/tools/miri/src/concurrency/data_race.rs2
-rw-r--r--src/tools/miri/src/helpers.rs8
-rw-r--r--src/tools/miri/src/intrinsics/mod.rs6
-rw-r--r--src/tools/miri/src/intrinsics/simd.rs15
-rw-r--r--src/tools/miri/src/machine.rs13
-rw-r--r--src/tools/miri/tests/fail/intrinsics/ptr_offset_unsigned_overflow.rs7
-rw-r--r--src/tools/miri/tests/fail/intrinsics/ptr_offset_unsigned_overflow.stderr15
-rw-r--r--src/tools/miri/tests/fail/intrinsics/simd-div-by-zero.rs6
-rw-r--r--src/tools/miri/tests/fail/intrinsics/simd-div-overflow.rs6
-rw-r--r--src/tools/miri/tests/fail/intrinsics/simd-reduce-invalid-bool.rs4
-rw-r--r--src/tools/miri/tests/fail/intrinsics/simd-rem-by-zero.rs6
-rw-r--r--src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.rs4
-rw-r--r--src/tools/miri/tests/fail/intrinsics/simd-select-invalid-bool.rs4
-rw-r--r--src/tools/miri/tests/fail/intrinsics/simd-shl-too-far.rs6
-rw-r--r--src/tools/miri/tests/fail/intrinsics/simd-shr-too-far.rs6
-rw-r--r--src/tools/miri/tests/fail/provenance/int_copy_looses_provenance0.rs10
-rw-r--r--src/tools/miri/tests/fail/provenance/int_copy_looses_provenance0.stderr15
-rw-r--r--src/tools/miri/tests/fail/provenance/int_copy_looses_provenance1.rs10
-rw-r--r--src/tools/miri/tests/fail/provenance/int_copy_looses_provenance1.stderr15
-rw-r--r--src/tools/miri/tests/fail/provenance/int_copy_looses_provenance2.rs12
-rw-r--r--src/tools/miri/tests/fail/provenance/int_copy_looses_provenance2.stderr15
-rw-r--r--src/tools/miri/tests/fail/provenance/int_copy_looses_provenance3.rs29
-rw-r--r--src/tools/miri/tests/fail/provenance/int_copy_looses_provenance3.stderr15
-rw-r--r--src/tools/miri/tests/fail/provenance/ptr_copy_loses_partial_provenance0.rs18
-rw-r--r--src/tools/miri/tests/fail/provenance/ptr_copy_loses_partial_provenance0.stderr15
-rw-r--r--src/tools/miri/tests/fail/provenance/ptr_copy_loses_partial_provenance1.rs18
-rw-r--r--src/tools/miri/tests/fail/provenance/ptr_copy_loses_partial_provenance1.stderr15
-rw-r--r--src/tools/miri/tests/fail/uninit/padding-enum.rs23
-rw-r--r--src/tools/miri/tests/fail/uninit/padding-enum.stderr15
-rw-r--r--src/tools/miri/tests/fail/uninit/padding-pair.rs25
-rw-r--r--src/tools/miri/tests/fail/uninit/padding-pair.stderr15
-rw-r--r--src/tools/miri/tests/fail/uninit/padding-struct-in-union.rs32
-rw-r--r--src/tools/miri/tests/fail/uninit/padding-struct-in-union.stderr15
-rw-r--r--src/tools/miri/tests/fail/uninit/padding-struct.rs11
-rw-r--r--src/tools/miri/tests/fail/uninit/padding-struct.stderr15
-rw-r--r--src/tools/miri/tests/fail/uninit/padding-union.rs14
-rw-r--r--src/tools/miri/tests/fail/uninit/padding-union.stderr15
-rw-r--r--src/tools/miri/tests/fail/uninit/padding-wide-ptr.rs18
-rw-r--r--src/tools/miri/tests/fail/uninit/padding-wide-ptr.stderr15
-rw-r--r--src/tools/miri/tests/fail/uninit/transmute-pair-uninit.rs (renamed from src/tools/miri/tests/fail/transmute-pair-uninit.rs)11
-rw-r--r--src/tools/miri/tests/fail/uninit/transmute-pair-uninit.stderr (renamed from src/tools/miri/tests/fail/transmute-pair-uninit.stderr)0
-rw-r--r--src/tools/miri/tests/pass/arrays.rs15
-rw-r--r--src/tools/miri/tests/pass/enums.rs38
-rw-r--r--src/tools/miri/tests/pass/intrinsics/portable-simd.rs8
-rw-r--r--src/tools/miri/tests/pass/intrinsics/simd-intrinsic-generic-elements.rs24
-rw-r--r--src/tools/miri/tests/pass/provenance.rs22
-rw-r--r--src/tools/miri/tests/pass/simd-intrinsic-generic-elements.rs24
-rw-r--r--src/tools/run-make-support/src/external_deps/cargo.rs7
-rw-r--r--src/tools/run-make-support/src/external_deps/llvm.rs6
-rw-r--r--src/tools/run-make-support/src/external_deps/mod.rs1
-rw-r--r--src/tools/run-make-support/src/external_deps/rustc.rs7
-rw-r--r--src/tools/run-make-support/src/lib.rs4
-rw-r--r--src/tools/suggest-tests/src/main.rs1
115 files changed, 1574 insertions, 703 deletions
diff --git a/src/bootstrap/defaults/config.compiler.toml b/src/bootstrap/defaults/config.compiler.toml
index 789586b58f7..147939d2047 100644
--- a/src/bootstrap/defaults/config.compiler.toml
+++ b/src/bootstrap/defaults/config.compiler.toml
@@ -27,4 +27,5 @@ assertions = false
 # Enable warnings during the LLVM compilation (when LLVM is changed, causing a compilation)
 enable-warnings = true
 # Will download LLVM from CI if available on your platform.
-download-ci-llvm = "if-unchanged"
+# If you intend to modify `src/llvm-project`, use `"if-unchanged"` or `false` instead.
+download-ci-llvm = true
diff --git a/src/bootstrap/download-ci-llvm-stamp b/src/bootstrap/download-ci-llvm-stamp
index 90901530501..42cecbf5df9 100644
--- a/src/bootstrap/download-ci-llvm-stamp
+++ b/src/bootstrap/download-ci-llvm-stamp
@@ -1,4 +1,4 @@
 Change this file to make users of the `download-ci-llvm` configuration download
 a new version of LLVM from CI, even if the LLVM submodule hasn’t changed.
 
-Last change is for: https://github.com/rust-lang/rust/pull/129116
+Last change is for: https://github.com/rust-lang/rust/pull/129788
diff --git a/src/bootstrap/mk/Makefile.in b/src/bootstrap/mk/Makefile.in
index 9acd85cddde..3fa2b3c2292 100644
--- a/src/bootstrap/mk/Makefile.in
+++ b/src/bootstrap/mk/Makefile.in
@@ -54,30 +54,34 @@ check-aux:
 		src/etc/test-float-parse \
 		$(BOOTSTRAP_ARGS)
 	# Run standard library tests in Miri.
-	$(Q)BOOTSTRAP_SKIP_TARGET_SANITY=1 \
-		$(BOOTSTRAP) miri --stage 2 \
+	$(Q)$(BOOTSTRAP) miri --stage 2 \
 		library/core \
 		library/alloc \
+		$(BOOTSTRAP_ARGS) \
 		--no-doc
 	# Some doctests use file system operations to demonstrate dealing with `Result`.
 	$(Q)MIRIFLAGS="-Zmiri-disable-isolation" \
 		$(BOOTSTRAP) miri --stage 2 \
 		library/core \
 		library/alloc \
+		$(BOOTSTRAP_ARGS) \
 		--doc
 	# In `std` we cannot test everything, so we skip some modules.
 	$(Q)MIRIFLAGS="-Zmiri-disable-isolation" \
 		$(BOOTSTRAP) miri --stage 2 library/std \
+		$(BOOTSTRAP_ARGS) \
 		--no-doc -- \
 		--skip fs:: --skip net:: --skip process:: --skip sys::pal::
 	$(Q)MIRIFLAGS="-Zmiri-disable-isolation" \
 		$(BOOTSTRAP) miri --stage 2 library/std \
+		$(BOOTSTRAP_ARGS) \
 		--doc -- \
 		--skip fs:: --skip net:: --skip process:: --skip sys::pal::
 	# Also test some very target-specific modules on other targets
 	# (making sure to cover an i686 target as well).
 	$(Q)MIRIFLAGS="-Zmiri-disable-isolation" BOOTSTRAP_SKIP_TARGET_SANITY=1 \
 		$(BOOTSTRAP) miri --stage 2 library/std \
+		$(BOOTSTRAP_ARGS) \
 		--target aarch64-apple-darwin,i686-pc-windows-msvc \
 		--no-doc -- \
 		time:: sync:: thread:: env::
diff --git a/src/bootstrap/src/core/build_steps/compile.rs b/src/bootstrap/src/core/build_steps/compile.rs
index 102c9fd2554..bb07d478f71 100644
--- a/src/bootstrap/src/core/build_steps/compile.rs
+++ b/src/bootstrap/src/core/build_steps/compile.rs
@@ -15,6 +15,7 @@ use std::path::{Path, PathBuf};
 use std::process::Stdio;
 use std::{env, fs, str};
 
+use build_helper::git::get_closest_merge_commit;
 use serde_derive::Deserialize;
 
 use crate::core::build_steps::tool::SourceType;
@@ -26,8 +27,7 @@ use crate::core::builder::{
 use crate::core::config::{DebuginfoLevel, LlvmLibunwind, RustcLto, TargetSelection};
 use crate::utils::exec::command;
 use crate::utils::helpers::{
-    self, exe, get_clang_cl_resource_dir, get_closest_merge_base_commit, is_debug_info, is_dylib,
-    symlink_dir, t, up_to_date,
+    self, exe, get_clang_cl_resource_dir, is_debug_info, is_dylib, symlink_dir, t, up_to_date,
 };
 use crate::{CLang, Compiler, DependencyType, GitRepo, Mode, LLVM_TOOLS};
 
@@ -127,13 +127,9 @@ impl Step for Std {
         // the `rust.download-rustc=true` option.
         let force_recompile =
             if builder.rust_info().is_managed_git_subrepository() && builder.download_rustc() {
-                let closest_merge_commit = get_closest_merge_base_commit(
-                    Some(&builder.src),
-                    &builder.config.git_config(),
-                    &builder.config.stage0_metadata.config.git_merge_commit_email,
-                    &[],
-                )
-                .unwrap();
+                let closest_merge_commit =
+                    get_closest_merge_commit(Some(&builder.src), &builder.config.git_config(), &[])
+                        .unwrap();
 
                 // Check if `library` has changes (returns false otherwise)
                 !t!(helpers::git(Some(&builder.src))
diff --git a/src/bootstrap/src/core/build_steps/format.rs b/src/bootstrap/src/core/build_steps/format.rs
index 91fbc57429a..bbd81fb570b 100644
--- a/src/bootstrap/src/core/build_steps/format.rs
+++ b/src/bootstrap/src/core/build_steps/format.rs
@@ -200,6 +200,11 @@ pub fn format(build: &Builder<'_>, check: bool, all: bool, paths: &[PathBuf]) {
                 adjective = Some("modified");
                 match get_modified_rs_files(build) {
                     Ok(Some(files)) => {
+                        if files.is_empty() {
+                            println!("fmt info: No modified files detected for formatting.");
+                            return;
+                        }
+
                         for file in files {
                             override_builder.add(&format!("/{file}")).expect(&file);
                         }
diff --git a/src/bootstrap/src/core/build_steps/llvm.rs b/src/bootstrap/src/core/build_steps/llvm.rs
index 442638d3203..94b03b1b138 100644
--- a/src/bootstrap/src/core/build_steps/llvm.rs
+++ b/src/bootstrap/src/core/build_steps/llvm.rs
@@ -16,6 +16,7 @@ use std::sync::OnceLock;
 use std::{env, io};
 
 use build_helper::ci::CiEnv;
+use build_helper::git::get_closest_merge_commit;
 
 use crate::core::builder::{Builder, RunConfig, ShouldRun, Step};
 use crate::core::config::{Config, TargetSelection};
@@ -153,10 +154,9 @@ pub fn prebuilt_llvm_config(builder: &Builder<'_>, target: TargetSelection) -> L
 /// This retrieves the LLVM sha we *want* to use, according to git history.
 pub(crate) fn detect_llvm_sha(config: &Config, is_git: bool) -> String {
     let llvm_sha = if is_git {
-        helpers::get_closest_merge_base_commit(
+        get_closest_merge_commit(
             Some(&config.src),
             &config.git_config(),
-            &config.stage0_metadata.config.git_merge_commit_email,
             &[
                 config.src.join("src/llvm-project"),
                 config.src.join("src/bootstrap/download-ci-llvm-stamp"),
diff --git a/src/bootstrap/src/core/build_steps/suggest.rs b/src/bootstrap/src/core/build_steps/suggest.rs
index 8aaffab514d..ba9b1b2fc33 100644
--- a/src/bootstrap/src/core/build_steps/suggest.rs
+++ b/src/bootstrap/src/core/build_steps/suggest.rs
@@ -17,6 +17,7 @@ pub fn suggest(builder: &Builder<'_>, run: bool) {
         .tool_cmd(Tool::SuggestTests)
         .env("SUGGEST_TESTS_GIT_REPOSITORY", git_config.git_repository)
         .env("SUGGEST_TESTS_NIGHTLY_BRANCH", git_config.nightly_branch)
+        .env("SUGGEST_TESTS_MERGE_COMMIT_EMAIL", git_config.git_merge_commit_email)
         .run_capture_stdout(builder)
         .stdout();
 
diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs
index 83f65615c8d..a7e9352bb1c 100644
--- a/src/bootstrap/src/core/build_steps/test.rs
+++ b/src/bootstrap/src/core/build_steps/test.rs
@@ -2098,6 +2098,7 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the
         let git_config = builder.config.git_config();
         cmd.arg("--git-repository").arg(git_config.git_repository);
         cmd.arg("--nightly-branch").arg(git_config.nightly_branch);
+        cmd.arg("--git-merge-commit-email").arg(git_config.git_merge_commit_email);
         cmd.force_coloring_in_ci();
 
         #[cfg(feature = "build-metrics")]
diff --git a/src/bootstrap/src/core/build_steps/tool.rs b/src/bootstrap/src/core/build_steps/tool.rs
index 3c2d791c209..a437f829ba5 100644
--- a/src/bootstrap/src/core/build_steps/tool.rs
+++ b/src/bootstrap/src/core/build_steps/tool.rs
@@ -1,6 +1,8 @@
 use std::path::PathBuf;
 use std::{env, fs};
 
+use build_helper::git::get_closest_merge_commit;
+
 use crate::core::build_steps::compile;
 use crate::core::build_steps::toolstate::ToolState;
 use crate::core::builder;
@@ -8,7 +10,7 @@ use crate::core::builder::{Builder, Cargo as CargoCommand, RunConfig, ShouldRun,
 use crate::core::config::TargetSelection;
 use crate::utils::channel::GitInfo;
 use crate::utils::exec::{command, BootstrapCommand};
-use crate::utils::helpers::{add_dylib_path, exe, get_closest_merge_base_commit, git, t};
+use crate::utils::helpers::{add_dylib_path, exe, git, t};
 use crate::{gha, Compiler, Kind, Mode};
 
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
@@ -576,10 +578,9 @@ impl Step for Rustdoc {
             && target_compiler.stage > 0
             && builder.rust_info().is_managed_git_subrepository()
         {
-            let commit = get_closest_merge_base_commit(
+            let commit = get_closest_merge_commit(
                 Some(&builder.config.src),
                 &builder.config.git_config(),
-                &builder.config.stage0_metadata.config.git_merge_commit_email,
                 &[],
             )
             .unwrap();
diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs
index de861c42c4c..9271e809853 100644
--- a/src/bootstrap/src/core/config/config.rs
+++ b/src/bootstrap/src/core/config/config.rs
@@ -14,7 +14,7 @@ use std::sync::OnceLock;
 use std::{cmp, env, fs};
 
 use build_helper::exit;
-use build_helper::git::{output_result, GitConfig};
+use build_helper::git::{get_closest_merge_commit, output_result, GitConfig};
 use serde::{Deserialize, Deserializer};
 use serde_derive::Deserialize;
 
@@ -24,7 +24,7 @@ pub use crate::core::config::flags::Subcommand;
 use crate::core::config::flags::{Color, Flags, Warnings};
 use crate::utils::cache::{Interned, INTERNER};
 use crate::utils::channel::{self, GitInfo};
-use crate::utils::helpers::{self, exe, get_closest_merge_base_commit, output, t};
+use crate::utils::helpers::{self, exe, output, t};
 
 macro_rules! check_ci_llvm {
     ($name:expr) => {
@@ -2512,6 +2512,7 @@ impl Config {
         GitConfig {
             git_repository: &self.stage0_metadata.config.git_repository,
             nightly_branch: &self.stage0_metadata.config.nightly_branch,
+            git_merge_commit_email: &self.stage0_metadata.config.git_merge_commit_email,
         }
     }
 
@@ -2688,13 +2689,7 @@ impl Config {
 
         // Look for a version to compare to based on the current commit.
         // Only commits merged by bors will have CI artifacts.
-        let commit = get_closest_merge_base_commit(
-            Some(&self.src),
-            &self.git_config(),
-            &self.stage0_metadata.config.git_merge_commit_email,
-            &[],
-        )
-        .unwrap();
+        let commit = get_closest_merge_commit(Some(&self.src), &self.git_config(), &[]).unwrap();
         if commit.is_empty() {
             println!("ERROR: could not find commit hash for downloading rustc");
             println!("HELP: maybe your repository history is too shallow?");
@@ -2766,7 +2761,8 @@ impl Config {
                     );
                 }
 
-                b
+                // If download-ci-llvm=true we also want to check that CI llvm is available
+                b && llvm::is_ci_llvm_available(self, asserts)
             }
             Some(StringOrBool::String(s)) if s == "if-unchanged" => if_unchanged(),
             Some(StringOrBool::String(other)) => {
@@ -2785,13 +2781,7 @@ impl Config {
     ) -> Option<String> {
         // Look for a version to compare to based on the current commit.
         // Only commits merged by bors will have CI artifacts.
-        let commit = get_closest_merge_base_commit(
-            Some(&self.src),
-            &self.git_config(),
-            &self.stage0_metadata.config.git_merge_commit_email,
-            &[],
-        )
-        .unwrap();
+        let commit = get_closest_merge_commit(Some(&self.src), &self.git_config(), &[]).unwrap();
         if commit.is_empty() {
             println!("error: could not find commit hash for downloading components from CI");
             println!("help: maybe your repository history is too shallow?");
diff --git a/src/bootstrap/src/utils/change_tracker.rs b/src/bootstrap/src/utils/change_tracker.rs
index 80ab09881fe..99bcc6e0787 100644
--- a/src/bootstrap/src/utils/change_tracker.rs
+++ b/src/bootstrap/src/utils/change_tracker.rs
@@ -250,4 +250,9 @@ pub const CONFIG_CHANGE_HISTORY: &[ChangeInfo] = &[
         severity: ChangeSeverity::Info,
         summary: "New option `llvm.enzyme` to control whether the llvm based autodiff tool (Enzyme) is built.",
     },
+    ChangeInfo {
+        change_id: 129473,
+        severity: ChangeSeverity::Warning,
+        summary: "`download-ci-llvm = true` now checks if CI llvm is available and has become the default for the compiler profile",
+    },
 ];
diff --git a/src/bootstrap/src/utils/helpers.rs b/src/bootstrap/src/utils/helpers.rs
index a856c99ff55..beb3c5fb098 100644
--- a/src/bootstrap/src/utils/helpers.rs
+++ b/src/bootstrap/src/utils/helpers.rs
@@ -10,7 +10,6 @@ use std::sync::OnceLock;
 use std::time::{Instant, SystemTime, UNIX_EPOCH};
 use std::{env, fs, io, str};
 
-use build_helper::git::{get_git_merge_base, output_result, GitConfig};
 use build_helper::util::fail;
 
 use crate::core::builder::Builder;
@@ -523,28 +522,6 @@ pub fn git(source_dir: Option<&Path>) -> BootstrapCommand {
     git
 }
 
-/// Returns the closest commit available from upstream for the given `author` and `target_paths`.
-///
-/// If it fails to find the commit from upstream using `git merge-base`, fallbacks to HEAD.
-pub fn get_closest_merge_base_commit(
-    source_dir: Option<&Path>,
-    config: &GitConfig<'_>,
-    author: &str,
-    target_paths: &[PathBuf],
-) -> Result<String, String> {
-    let mut git = git(source_dir);
-
-    let merge_base = get_git_merge_base(config, source_dir).unwrap_or_else(|_| "HEAD".into());
-
-    git.args(["rev-list", &format!("--author={author}"), "-n1", "--first-parent", &merge_base]);
-
-    if !target_paths.is_empty() {
-        git.arg("--").args(target_paths);
-    }
-
-    Ok(output_result(git.as_command_mut())?.trim().to_owned())
-}
-
 /// Sets the file times for a given file at `path`.
 pub fn set_file_times<P: AsRef<Path>>(path: P, times: fs::FileTimes) -> io::Result<()> {
     // Windows requires file to be writable to modify file times. But on Linux CI the file does not
diff --git a/src/doc/rustc/src/platform-support/apple-ios-macabi.md b/src/doc/rustc/src/platform-support/apple-ios-macabi.md
index 678630873b1..a54656190d1 100644
--- a/src/doc/rustc/src/platform-support/apple-ios-macabi.md
+++ b/src/doc/rustc/src/platform-support/apple-ios-macabi.md
@@ -24,7 +24,7 @@ environment variable.
 
 ### OS version
 
-The minimum supported version is iOS 13.1.
+The minimum supported version is iOS 13.1 on x86 and 14.0 on Aarch64.
 
 This can be raised per-binary by changing the deployment target. `rustc`
 respects the common environment variables used by Xcode to do so, in this
diff --git a/src/doc/rustc/src/platform-support/arm64e-apple-ios.md b/src/doc/rustc/src/platform-support/arm64e-apple-ios.md
index 3c878f7250e..fc4ec5e373f 100644
--- a/src/doc/rustc/src/platform-support/arm64e-apple-ios.md
+++ b/src/doc/rustc/src/platform-support/arm64e-apple-ios.md
@@ -2,7 +2,7 @@
 
 **Tier: 3**
 
-ARM64e iOS (12.0+)
+ARM64e iOS (14.0+)
 
 ## Target maintainers
 
diff --git a/src/doc/unstable-book/src/compiler-flags/small-data-threshold.md b/src/doc/unstable-book/src/compiler-flags/small-data-threshold.md
new file mode 100644
index 00000000000..1734433181e
--- /dev/null
+++ b/src/doc/unstable-book/src/compiler-flags/small-data-threshold.md
@@ -0,0 +1,20 @@
+# `small-data-threshold`
+
+-----------------------
+
+This flag controls the maximum static variable size that may be included in the
+"small data sections" (.sdata, .sbss) supported by some architectures (RISCV,
+MIPS, M68K, Hexagon).  Can be set to `0` to disable the use of small data
+sections.
+
+Target support is indicated by the `small_data_threshold_support` target
+option which can be:
+
+- `none` (`SmallDataThresholdSupport::None`) for no support
+- `default-for-arch` (`SmallDataThresholdSupport::DefaultForArch`) which
+  is automatically translated into an appropriate value for the target.
+- `llvm-module-flag=<flag_name>`
+  (`SmallDataThresholdSupport::LlvmModuleFlag`) for specifying the
+  threshold via an LLVM module flag
+- `llvm-arg=<arg_name>` (`SmallDataThresholdSupport::LlvmArg`) for
+  specifying the threshold via an LLVM argument.
diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs
index 65490108027..7e3881c798b 100644
--- a/src/librustdoc/clean/auto_trait.rs
+++ b/src/librustdoc/clean/auto_trait.rs
@@ -115,17 +115,19 @@ fn synthesize_auto_trait_impl<'tcx>(
 
     Some(clean::Item {
         name: None,
-        attrs: Default::default(),
+        inner: Box::new(clean::ItemInner {
+            attrs: Default::default(),
+            kind: clean::ImplItem(Box::new(clean::Impl {
+                safety: hir::Safety::Safe,
+                generics,
+                trait_: Some(clean_trait_ref_with_constraints(cx, trait_ref, ThinVec::new())),
+                for_: clean_middle_ty(ty::Binder::dummy(ty), cx, None, None),
+                items: Vec::new(),
+                polarity,
+                kind: clean::ImplKind::Auto,
+            })),
+        }),
         item_id: clean::ItemId::Auto { trait_: trait_def_id, for_: item_def_id },
-        kind: Box::new(clean::ImplItem(Box::new(clean::Impl {
-            safety: hir::Safety::Safe,
-            generics,
-            trait_: Some(clean_trait_ref_with_constraints(cx, trait_ref, ThinVec::new())),
-            for_: clean_middle_ty(ty::Binder::dummy(ty), cx, None, None),
-            items: Vec::new(),
-            polarity,
-            kind: clean::ImplKind::Auto,
-        }))),
         cfg: None,
         inline_stmt_id: None,
     })
diff --git a/src/librustdoc/clean/blanket_impl.rs b/src/librustdoc/clean/blanket_impl.rs
index 1f5f6602b9f..95f6616cec3 100644
--- a/src/librustdoc/clean/blanket_impl.rs
+++ b/src/librustdoc/clean/blanket_impl.rs
@@ -84,42 +84,44 @@ pub(crate) fn synthesize_blanket_impls(
 
             blanket_impls.push(clean::Item {
                 name: None,
-                attrs: Default::default(),
                 item_id: clean::ItemId::Blanket { impl_id: impl_def_id, for_: item_def_id },
-                kind: Box::new(clean::ImplItem(Box::new(clean::Impl {
-                    safety: hir::Safety::Safe,
-                    generics: clean_ty_generics(
-                        cx,
-                        tcx.generics_of(impl_def_id),
-                        tcx.explicit_predicates_of(impl_def_id),
-                    ),
-                    // FIXME(eddyb) compute both `trait_` and `for_` from
-                    // the post-inference `trait_ref`, as it's more accurate.
-                    trait_: Some(clean_trait_ref_with_constraints(
-                        cx,
-                        ty::Binder::dummy(trait_ref.instantiate_identity()),
-                        ThinVec::new(),
-                    )),
-                    for_: clean_middle_ty(
-                        ty::Binder::dummy(ty.instantiate_identity()),
-                        cx,
-                        None,
-                        None,
-                    ),
-                    items: tcx
-                        .associated_items(impl_def_id)
-                        .in_definition_order()
-                        .filter(|item| !item.is_impl_trait_in_trait())
-                        .map(|item| clean_middle_assoc_item(item, cx))
-                        .collect(),
-                    polarity: ty::ImplPolarity::Positive,
-                    kind: clean::ImplKind::Blanket(Box::new(clean_middle_ty(
-                        ty::Binder::dummy(trait_ref.instantiate_identity().self_ty()),
-                        cx,
-                        None,
-                        None,
-                    ))),
-                }))),
+                inner: Box::new(clean::ItemInner {
+                    attrs: Default::default(),
+                    kind: clean::ImplItem(Box::new(clean::Impl {
+                        safety: hir::Safety::Safe,
+                        generics: clean_ty_generics(
+                            cx,
+                            tcx.generics_of(impl_def_id),
+                            tcx.explicit_predicates_of(impl_def_id),
+                        ),
+                        // FIXME(eddyb) compute both `trait_` and `for_` from
+                        // the post-inference `trait_ref`, as it's more accurate.
+                        trait_: Some(clean_trait_ref_with_constraints(
+                            cx,
+                            ty::Binder::dummy(trait_ref.instantiate_identity()),
+                            ThinVec::new(),
+                        )),
+                        for_: clean_middle_ty(
+                            ty::Binder::dummy(ty.instantiate_identity()),
+                            cx,
+                            None,
+                            None,
+                        ),
+                        items: tcx
+                            .associated_items(impl_def_id)
+                            .in_definition_order()
+                            .filter(|item| !item.is_impl_trait_in_trait())
+                            .map(|item| clean_middle_assoc_item(item, cx))
+                            .collect(),
+                        polarity: ty::ImplPolarity::Positive,
+                        kind: clean::ImplKind::Blanket(Box::new(clean_middle_ty(
+                            ty::Binder::dummy(trait_ref.instantiate_identity().self_ty()),
+                            cx,
+                            None,
+                            None,
+                        ))),
+                    })),
+                }),
                 cfg: None,
                 inline_stmt_id: None,
             });
diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs
index d22c4cc4b76..8383012885f 100644
--- a/src/librustdoc/clean/inline.rs
+++ b/src/librustdoc/clean/inline.rs
@@ -5,7 +5,7 @@ use std::sync::Arc;
 
 use rustc_data_structures::fx::FxHashSet;
 use rustc_hir::def::{DefKind, Res};
-use rustc_hir::def_id::{DefId, DefIdSet, LocalModDefId};
+use rustc_hir::def_id::{DefId, DefIdSet, LocalDefId, LocalModDefId};
 use rustc_hir::Mutability;
 use rustc_metadata::creader::{CStore, LoadedMacro};
 use rustc_middle::ty::fast_reject::SimplifiedType;
@@ -43,7 +43,7 @@ pub(crate) fn try_inline(
     cx: &mut DocContext<'_>,
     res: Res,
     name: Symbol,
-    attrs: Option<(&[ast::Attribute], Option<DefId>)>,
+    attrs: Option<(&[ast::Attribute], Option<LocalDefId>)>,
     visited: &mut DefIdSet,
 ) -> Option<Vec<clean::Item>> {
     let did = res.opt_def_id()?;
@@ -152,14 +152,8 @@ pub(crate) fn try_inline(
     };
 
     cx.inlined.insert(did.into());
-    let mut item = crate::clean::generate_item_with_correct_attrs(
-        cx,
-        kind,
-        did,
-        name,
-        import_def_id.and_then(|def_id| def_id.as_local()),
-        None,
-    );
+    let mut item =
+        crate::clean::generate_item_with_correct_attrs(cx, kind, did, name, import_def_id, None);
     // The visibility needs to reflect the one from the reexport and not from the "source" DefId.
     item.inline_stmt_id = import_def_id;
     ret.push(item);
@@ -198,7 +192,7 @@ pub(crate) fn try_inline_glob(
                 visited,
                 inlined_names,
                 Some(&reexports),
-                Some((attrs, Some(import.owner_id.def_id.to_def_id()))),
+                Some((attrs, Some(import.owner_id.def_id))),
             );
             items.retain(|item| {
                 if let Some(name) = item.name {
@@ -372,7 +366,7 @@ fn build_type_alias(
 pub(crate) fn build_impls(
     cx: &mut DocContext<'_>,
     did: DefId,
-    attrs: Option<(&[ast::Attribute], Option<DefId>)>,
+    attrs: Option<(&[ast::Attribute], Option<LocalDefId>)>,
     ret: &mut Vec<clean::Item>,
 ) {
     let _prof_timer = cx.tcx.sess.prof.generic_activity("build_inherent_impls");
@@ -405,7 +399,7 @@ pub(crate) fn build_impls(
 pub(crate) fn merge_attrs(
     cx: &mut DocContext<'_>,
     old_attrs: &[ast::Attribute],
-    new_attrs: Option<(&[ast::Attribute], Option<DefId>)>,
+    new_attrs: Option<(&[ast::Attribute], Option<LocalDefId>)>,
 ) -> (clean::Attributes, Option<Arc<clean::cfg::Cfg>>) {
     // NOTE: If we have additional attributes (from a re-export),
     // always insert them first. This ensure that re-export
@@ -416,7 +410,7 @@ pub(crate) fn merge_attrs(
         both.extend_from_slice(old_attrs);
         (
             if let Some(item_id) = item_id {
-                Attributes::from_ast_with_additional(old_attrs, (inner, item_id))
+                Attributes::from_ast_with_additional(old_attrs, (inner, item_id.to_def_id()))
             } else {
                 Attributes::from_ast(&both)
             },
@@ -431,7 +425,7 @@ pub(crate) fn merge_attrs(
 pub(crate) fn build_impl(
     cx: &mut DocContext<'_>,
     did: DefId,
-    attrs: Option<(&[ast::Attribute], Option<DefId>)>,
+    attrs: Option<(&[ast::Attribute], Option<LocalDefId>)>,
     ret: &mut Vec<clean::Item>,
 ) {
     if !cx.inlined.insert(did.into()) {
@@ -623,7 +617,7 @@ pub(crate) fn build_impl(
                 ImplKind::Normal
             },
         })),
-        Box::new(merged_attrs),
+        merged_attrs,
         cfg,
     ));
 }
@@ -641,7 +635,7 @@ fn build_module_items(
     visited: &mut DefIdSet,
     inlined_names: &mut FxHashSet<(ItemType, Symbol)>,
     allowed_def_ids: Option<&DefIdSet>,
-    attrs: Option<(&[ast::Attribute], Option<DefId>)>,
+    attrs: Option<(&[ast::Attribute], Option<LocalDefId>)>,
 ) -> Vec<clean::Item> {
     let mut items = Vec::new();
 
@@ -673,27 +667,29 @@ fn build_module_items(
                 let prim_ty = clean::PrimitiveType::from(p);
                 items.push(clean::Item {
                     name: None,
-                    attrs: Box::default(),
                     // We can use the item's `DefId` directly since the only information ever used
                     // from it is `DefId.krate`.
                     item_id: ItemId::DefId(did),
-                    kind: Box::new(clean::ImportItem(clean::Import::new_simple(
-                        item.ident.name,
-                        clean::ImportSource {
-                            path: clean::Path {
-                                res,
-                                segments: thin_vec![clean::PathSegment {
-                                    name: prim_ty.as_sym(),
-                                    args: clean::GenericArgs::AngleBracketed {
-                                        args: Default::default(),
-                                        constraints: ThinVec::new(),
-                                    },
-                                }],
+                    inner: Box::new(clean::ItemInner {
+                        attrs: Default::default(),
+                        kind: clean::ImportItem(clean::Import::new_simple(
+                            item.ident.name,
+                            clean::ImportSource {
+                                path: clean::Path {
+                                    res,
+                                    segments: thin_vec![clean::PathSegment {
+                                        name: prim_ty.as_sym(),
+                                        args: clean::GenericArgs::AngleBracketed {
+                                            args: Default::default(),
+                                            constraints: ThinVec::new(),
+                                        },
+                                    }],
+                                },
+                                did: None,
                             },
-                            did: None,
-                        },
-                        true,
-                    ))),
+                            true,
+                        )),
+                    }),
                     cfg: None,
                     inline_stmt_id: None,
                 });
@@ -745,7 +741,7 @@ fn build_macro(
     cx: &mut DocContext<'_>,
     def_id: DefId,
     name: Symbol,
-    import_def_id: Option<DefId>,
+    import_def_id: Option<LocalDefId>,
     macro_kind: MacroKind,
     is_doc_hidden: bool,
 ) -> clean::ItemKind {
@@ -753,7 +749,8 @@ fn build_macro(
         LoadedMacro::MacroDef(item_def, _) => match macro_kind {
             MacroKind::Bang => {
                 if let ast::ItemKind::MacroDef(ref def) = item_def.kind {
-                    let vis = cx.tcx.visibility(import_def_id.unwrap_or(def_id));
+                    let vis =
+                        cx.tcx.visibility(import_def_id.map(|d| d.to_def_id()).unwrap_or(def_id));
                     clean::MacroItem(clean::Macro {
                         source: utils::display_macro_source(
                             cx,
diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs
index 710bd4a5fdf..e47ae7df77f 100644
--- a/src/librustdoc/clean/mod.rs
+++ b/src/librustdoc/clean/mod.rs
@@ -203,8 +203,8 @@ fn generate_item_with_correct_attrs(
     let attrs = Attributes::from_ast_iter(attrs.iter().map(|(attr, did)| (&**attr, *did)), false);
 
     let name = renamed.or(Some(name));
-    let mut item = Item::from_def_id_and_attrs_and_parts(def_id, name, kind, Box::new(attrs), cfg);
-    item.inline_stmt_id = import_id.map(|local| local.to_def_id());
+    let mut item = Item::from_def_id_and_attrs_and_parts(def_id, name, kind, attrs, cfg);
+    item.inline_stmt_id = import_id;
     item
 }
 
@@ -2927,7 +2927,7 @@ fn clean_extern_crate<'tcx>(
         })
         && !cx.output_format.is_json();
 
-    let krate_owner_def_id = krate.owner_id.to_def_id();
+    let krate_owner_def_id = krate.owner_id.def_id;
     if please_inline {
         if let Some(items) = inline::try_inline(
             cx,
@@ -2941,7 +2941,7 @@ fn clean_extern_crate<'tcx>(
     }
 
     vec![Item::from_def_id_and_parts(
-        krate_owner_def_id,
+        krate_owner_def_id.to_def_id(),
         Some(name),
         ExternCrateItem { src: orig_name },
         cx,
@@ -2988,7 +2988,7 @@ fn clean_use_statement_inner<'tcx>(
     let inline_attr = attrs.lists(sym::doc).get_word_attr(sym::inline);
     let pub_underscore = visibility.is_public() && name == kw::Underscore;
     let current_mod = cx.tcx.parent_module_from_def_id(import.owner_id.def_id);
-    let import_def_id = import.owner_id.def_id.to_def_id();
+    let import_def_id = import.owner_id.def_id;
 
     // The parent of the module in which this import resides. This
     // is the same as `current_mod` if that's already the top
@@ -3071,7 +3071,7 @@ fn clean_use_statement_inner<'tcx>(
             )
         {
             items.push(Item::from_def_id_and_parts(
-                import_def_id,
+                import_def_id.to_def_id(),
                 None,
                 ImportItem(Import::new_simple(name, resolve_use_source(cx, path), false)),
                 cx,
@@ -3081,7 +3081,7 @@ fn clean_use_statement_inner<'tcx>(
         Import::new_simple(name, resolve_use_source(cx, path), true)
     };
 
-    vec![Item::from_def_id_and_parts(import_def_id, None, ImportItem(inner), cx)]
+    vec![Item::from_def_id_and_parts(import_def_id.to_def_id(), None, ImportItem(inner), cx)]
 }
 
 fn clean_maybe_renamed_foreign_item<'tcx>(
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index 5666b229078..383efe568ae 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -320,16 +320,30 @@ pub(crate) struct Item {
     /// The name of this item.
     /// Optional because not every item has a name, e.g. impls.
     pub(crate) name: Option<Symbol>,
-    pub(crate) attrs: Box<Attributes>,
-    /// Information about this item that is specific to what kind of item it is.
-    /// E.g., struct vs enum vs function.
-    pub(crate) kind: Box<ItemKind>,
+    pub(crate) inner: Box<ItemInner>,
     pub(crate) item_id: ItemId,
-    /// This is the `DefId` of the `use` statement if the item was inlined.
-    pub(crate) inline_stmt_id: Option<DefId>,
+    /// This is the `LocalDefId` of the `use` statement if the item was inlined.
+    /// The crate metadata doesn't hold this information, so the `use` statement
+    /// always belongs to the current crate.
+    pub(crate) inline_stmt_id: Option<LocalDefId>,
     pub(crate) cfg: Option<Arc<Cfg>>,
 }
 
+#[derive(Clone)]
+pub(crate) struct ItemInner {
+    /// Information about this item that is specific to what kind of item it is.
+    /// E.g., struct vs enum vs function.
+    pub(crate) kind: ItemKind,
+    pub(crate) attrs: Attributes,
+}
+
+impl std::ops::Deref for Item {
+    type Target = ItemInner;
+    fn deref(&self) -> &ItemInner {
+        &*self.inner
+    }
+}
+
 /// NOTE: this does NOT unconditionally print every item, to avoid thousands of lines of logs.
 /// If you want to see the debug output for attributes and the `kind` as well, use `{:#?}` instead of `{:?}`.
 impl fmt::Debug for Item {
@@ -389,9 +403,9 @@ impl Item {
     }
 
     pub(crate) fn span(&self, tcx: TyCtxt<'_>) -> Option<Span> {
-        let kind = match &*self.kind {
-            ItemKind::StrippedItem(k) => k,
-            _ => &*self.kind,
+        let kind = match &self.kind {
+            ItemKind::StrippedItem(k) => &*k,
+            _ => &self.kind,
         };
         match kind {
             ItemKind::ModuleItem(Module { span, .. }) => Some(*span),
@@ -436,7 +450,7 @@ impl Item {
             def_id,
             name,
             kind,
-            Box::new(Attributes::from_ast(ast_attrs)),
+            Attributes::from_ast(ast_attrs),
             ast_attrs.cfg(cx.tcx, &cx.cache.hidden_cfg),
         )
     }
@@ -445,16 +459,15 @@ impl Item {
         def_id: DefId,
         name: Option<Symbol>,
         kind: ItemKind,
-        attrs: Box<Attributes>,
+        attrs: Attributes,
         cfg: Option<Arc<Cfg>>,
     ) -> Item {
         trace!("name={name:?}, def_id={def_id:?} cfg={cfg:?}");
 
         Item {
             item_id: def_id.into(),
-            kind: Box::new(kind),
+            inner: Box::new(ItemInner { kind, attrs }),
             name,
-            attrs,
             cfg,
             inline_stmt_id: None,
         }
@@ -522,16 +535,16 @@ impl Item {
         self.type_() == ItemType::Variant
     }
     pub(crate) fn is_associated_type(&self) -> bool {
-        matches!(&*self.kind, AssocTypeItem(..) | StrippedItem(box AssocTypeItem(..)))
+        matches!(self.kind, AssocTypeItem(..) | StrippedItem(box AssocTypeItem(..)))
     }
     pub(crate) fn is_ty_associated_type(&self) -> bool {
-        matches!(&*self.kind, TyAssocTypeItem(..) | StrippedItem(box TyAssocTypeItem(..)))
+        matches!(self.kind, TyAssocTypeItem(..) | StrippedItem(box TyAssocTypeItem(..)))
     }
     pub(crate) fn is_associated_const(&self) -> bool {
-        matches!(&*self.kind, AssocConstItem(..) | StrippedItem(box AssocConstItem(..)))
+        matches!(self.kind, AssocConstItem(..) | StrippedItem(box AssocConstItem(..)))
     }
     pub(crate) fn is_ty_associated_const(&self) -> bool {
-        matches!(&*self.kind, TyAssocConstItem(..) | StrippedItem(box TyAssocConstItem(..)))
+        matches!(self.kind, TyAssocConstItem(..) | StrippedItem(box TyAssocConstItem(..)))
     }
     pub(crate) fn is_method(&self) -> bool {
         self.type_() == ItemType::Method
@@ -555,14 +568,14 @@ impl Item {
         self.type_() == ItemType::Keyword
     }
     pub(crate) fn is_stripped(&self) -> bool {
-        match *self.kind {
+        match self.kind {
             StrippedItem(..) => true,
             ImportItem(ref i) => !i.should_be_displayed,
             _ => false,
         }
     }
     pub(crate) fn has_stripped_entries(&self) -> Option<bool> {
-        match *self.kind {
+        match self.kind {
             StructItem(ref struct_) => Some(struct_.has_stripped_entries()),
             UnionItem(ref union_) => Some(union_.has_stripped_entries()),
             EnumItem(ref enum_) => Some(enum_.has_stripped_entries()),
@@ -605,7 +618,7 @@ impl Item {
     }
 
     pub(crate) fn is_default(&self) -> bool {
-        match *self.kind {
+        match self.kind {
             ItemKind::MethodItem(_, Some(defaultness)) => {
                 defaultness.has_value() && !defaultness.is_final()
             }
@@ -633,7 +646,7 @@ impl Item {
             };
             hir::FnHeader { safety: sig.safety(), abi: sig.abi(), constness, asyncness }
         }
-        let header = match *self.kind {
+        let header = match self.kind {
             ItemKind::ForeignFunctionItem(_, safety) => {
                 let def_id = self.def_id().unwrap();
                 let abi = tcx.fn_sig(def_id).skip_binder().abi();
@@ -672,7 +685,7 @@ impl Item {
             ItemId::DefId(def_id) => def_id,
         };
 
-        match *self.kind {
+        match self.kind {
             // Primitives and Keywords are written in the source code as private modules.
             // The modules need to be private so that nobody actually uses them, but the
             // keywords and primitives that they are documenting are public.
@@ -702,7 +715,7 @@ impl Item {
             _ => {}
         }
         let def_id = match self.inline_stmt_id {
-            Some(inlined) => inlined,
+            Some(inlined) => inlined.to_def_id(),
             None => def_id,
         };
         Some(tcx.visibility(def_id))
@@ -2559,13 +2572,13 @@ mod size_asserts {
 
     use super::*;
     // tidy-alphabetical-start
-    static_assert_size!(Crate, 64); // frequently moved by-value
+    static_assert_size!(Crate, 56); // frequently moved by-value
     static_assert_size!(DocFragment, 32);
     static_assert_size!(GenericArg, 32);
     static_assert_size!(GenericArgs, 32);
     static_assert_size!(GenericParamDef, 40);
     static_assert_size!(Generics, 16);
-    static_assert_size!(Item, 56);
+    static_assert_size!(Item, 48);
     static_assert_size!(ItemKind, 48);
     static_assert_size!(PathSegment, 40);
     static_assert_size!(Type, 32);
diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs
index 5686307d83d..885758c17cf 100644
--- a/src/librustdoc/clean/utils.rs
+++ b/src/librustdoc/clean/utils.rs
@@ -36,7 +36,7 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate {
     // understood by rustdoc.
     let mut module = clean_doc_module(&module, cx);
 
-    match *module.kind {
+    match module.kind {
         ItemKind::ModuleItem(ref module) => {
             for it in &module.items {
                 // `compiler_builtins` should be masked too, but we can't apply
@@ -60,7 +60,7 @@ pub(crate) fn krate(cx: &mut DocContext<'_>) -> Crate {
     let primitives = local_crate.primitives(cx.tcx);
     let keywords = local_crate.keywords(cx.tcx);
     {
-        let ItemKind::ModuleItem(ref mut m) = *module.kind else { unreachable!() };
+        let ItemKind::ModuleItem(ref mut m) = &mut module.inner.kind else { unreachable!() };
         m.items.extend(primitives.iter().map(|&(def_id, prim)| {
             Item::from_def_id_and_parts(
                 def_id,
@@ -281,7 +281,7 @@ pub(crate) fn build_deref_target_impls(
     let tcx = cx.tcx;
 
     for item in items {
-        let target = match *item.kind {
+        let target = match item.kind {
             ItemKind::AssocTypeItem(ref t, _) => &t.type_,
             _ => continue,
         };
diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs
index 9e7b69ec45f..b3c87a72508 100644
--- a/src/librustdoc/config.rs
+++ b/src/librustdoc/config.rs
@@ -53,12 +53,18 @@ impl TryFrom<&str> for OutputFormat {
     }
 }
 
+/// Either an input crate, markdown file, or nothing (--merge=finalize).
+pub(crate) enum InputMode {
+    /// The `--merge=finalize` step does not need an input crate to rustdoc.
+    NoInputMergeFinalize,
+    /// A crate or markdown file.
+    HasFile(Input),
+}
+
 /// Configuration options for rustdoc.
 #[derive(Clone)]
 pub(crate) struct Options {
     // Basic options / Options passed directly to rustc
-    /// The crate root or Markdown file to load.
-    pub(crate) input: Input,
     /// The name of the crate being documented.
     pub(crate) crate_name: Option<String>,
     /// Whether or not this is a bin crate
@@ -179,7 +185,6 @@ impl fmt::Debug for Options {
         }
 
         f.debug_struct("Options")
-            .field("input", &self.input.source_name())
             .field("crate_name", &self.crate_name)
             .field("bin_crate", &self.bin_crate)
             .field("proc_macro_crate", &self.proc_macro_crate)
@@ -289,6 +294,12 @@ pub(crate) struct RenderOptions {
     /// This field is only used for the JSON output. If it's set to true, no file will be created
     /// and content will be displayed in stdout directly.
     pub(crate) output_to_stdout: bool,
+    /// Whether we should read or write rendered cross-crate info in the doc root.
+    pub(crate) should_merge: ShouldMerge,
+    /// Path to crate-info for external crates.
+    pub(crate) include_parts_dir: Vec<PathToParts>,
+    /// Where to write crate-info
+    pub(crate) parts_out_dir: Option<PathToParts>,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
@@ -348,7 +359,7 @@ impl Options {
         early_dcx: &mut EarlyDiagCtxt,
         matches: &getopts::Matches,
         args: Vec<String>,
-    ) -> Option<(Options, RenderOptions)> {
+    ) -> Option<(InputMode, Options, RenderOptions)> {
         // Check for unstable options.
         nightly_options::check_nightly_options(early_dcx, matches, &opts());
 
@@ -478,15 +489,17 @@ impl Options {
         let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches);
 
         let input = if describe_lints {
-            "" // dummy, this won't be used
+            InputMode::HasFile(make_input(early_dcx, ""))
         } else {
             match matches.free.as_slice() {
+                [] if matches.opt_str("merge").as_deref() == Some("finalize") => {
+                    InputMode::NoInputMergeFinalize
+                }
                 [] => dcx.fatal("missing file operand"),
-                [input] => input,
+                [input] => InputMode::HasFile(make_input(early_dcx, input)),
                 _ => dcx.fatal("too many file operands"),
             }
         };
-        let input = make_input(early_dcx, input);
 
         let externs = parse_externs(early_dcx, matches, &unstable_opts);
         let extern_html_root_urls = match parse_extern_html_roots(matches) {
@@ -494,6 +507,16 @@ impl Options {
             Err(err) => dcx.fatal(err),
         };
 
+        let parts_out_dir =
+            match matches.opt_str("parts-out-dir").map(|p| PathToParts::from_flag(p)).transpose() {
+                Ok(parts_out_dir) => parts_out_dir,
+                Err(e) => dcx.fatal(e),
+            };
+        let include_parts_dir = match parse_include_parts_dir(matches) {
+            Ok(include_parts_dir) => include_parts_dir,
+            Err(e) => dcx.fatal(e),
+        };
+
         let default_settings: Vec<Vec<(String, String)>> = vec![
             matches
                 .opt_str("default-theme")
@@ -735,6 +758,10 @@ impl Options {
         let extern_html_root_takes_precedence =
             matches.opt_present("extern-html-root-takes-precedence");
         let html_no_source = matches.opt_present("html-no-source");
+        let should_merge = match parse_merge(matches) {
+            Ok(result) => result,
+            Err(e) => dcx.fatal(format!("--merge option error: {e}")),
+        };
 
         if generate_link_to_definition && (show_coverage || output_format != OutputFormat::Html) {
             dcx.struct_warn(
@@ -751,7 +778,6 @@ impl Options {
         let unstable_features =
             rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref());
         let options = Options {
-            input,
             bin_crate,
             proc_macro_crate,
             error_format,
@@ -823,16 +849,17 @@ impl Options {
             no_emit_shared: false,
             html_no_source,
             output_to_stdout,
+            should_merge,
+            include_parts_dir,
+            parts_out_dir,
         };
-        Some((options, render_options))
+        Some((input, options, render_options))
     }
+}
 
-    /// Returns `true` if the file given as `self.input` is a Markdown file.
-    pub(crate) fn markdown_input(&self) -> Option<&Path> {
-        self.input
-            .opt_path()
-            .filter(|p| matches!(p.extension(), Some(e) if e == "md" || e == "markdown"))
-    }
+/// Returns `true` if the file given as `self.input` is a Markdown file.
+pub(crate) fn markdown_input(input: &Input) -> Option<&Path> {
+    input.opt_path().filter(|p| matches!(p.extension(), Some(e) if e == "md" || e == "markdown"))
 }
 
 fn parse_remap_path_prefix(
@@ -900,3 +927,71 @@ fn parse_extern_html_roots(
     }
     Ok(externs)
 }
+
+/// Path directly to crate-info file.
+///
+/// For example, `/home/user/project/target/doc.parts/<crate>/crate-info`.
+#[derive(Clone, Debug)]
+pub(crate) struct PathToParts(pub(crate) PathBuf);
+
+impl PathToParts {
+    fn from_flag(path: String) -> Result<PathToParts, String> {
+        let mut path = PathBuf::from(path);
+        // check here is for diagnostics
+        if path.exists() && !path.is_dir() {
+            Err(format!(
+                "--parts-out-dir and --include-parts-dir expect directories, found: {}",
+                path.display(),
+            ))
+        } else {
+            // if it doesn't exist, we'll create it. worry about that in write_shared
+            path.push("crate-info");
+            Ok(PathToParts(path))
+        }
+    }
+}
+
+/// Reports error if --include-parts-dir / crate-info is not a file
+fn parse_include_parts_dir(m: &getopts::Matches) -> Result<Vec<PathToParts>, String> {
+    let mut ret = Vec::new();
+    for p in m.opt_strs("include-parts-dir") {
+        let p = PathToParts::from_flag(p)?;
+        // this is just for diagnostic
+        if !p.0.is_file() {
+            return Err(format!("--include-parts-dir expected {} to be a file", p.0.display()));
+        }
+        ret.push(p);
+    }
+    Ok(ret)
+}
+
+/// Controls merging of cross-crate information
+#[derive(Debug, Clone)]
+pub(crate) struct ShouldMerge {
+    /// Should we append to existing cci in the doc root
+    pub(crate) read_rendered_cci: bool,
+    /// Should we write cci to the doc root
+    pub(crate) write_rendered_cci: bool,
+}
+
+/// Extracts read_rendered_cci and write_rendered_cci from command line arguments, or
+/// reports an error if an invalid option was provided
+fn parse_merge(m: &getopts::Matches) -> Result<ShouldMerge, &'static str> {
+    match m.opt_str("merge").as_deref() {
+        // default = read-write
+        None => Ok(ShouldMerge { read_rendered_cci: true, write_rendered_cci: true }),
+        Some("none") if m.opt_present("include-parts-dir") => {
+            Err("--include-parts-dir not allowed if --merge=none")
+        }
+        Some("none") => Ok(ShouldMerge { read_rendered_cci: false, write_rendered_cci: false }),
+        Some("shared") if m.opt_present("parts-out-dir") || m.opt_present("include-parts-dir") => {
+            Err("--parts-out-dir and --include-parts-dir not allowed if --merge=shared")
+        }
+        Some("shared") => Ok(ShouldMerge { read_rendered_cci: true, write_rendered_cci: true }),
+        Some("finalize") if m.opt_present("parts-out-dir") => {
+            Err("--parts-out-dir not allowed if --merge=finalize")
+        }
+        Some("finalize") => Ok(ShouldMerge { read_rendered_cci: false, write_rendered_cci: true }),
+        Some(_) => Err("argument to --merge must be `none`, `shared`, or `finalize`"),
+    }
+}
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 2cde0ac5c53..4fafef65792 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -20,7 +20,7 @@ use rustc_interface::interface;
 use rustc_lint::{late_lint_mod, MissingDoc};
 use rustc_middle::hir::nested_filter;
 use rustc_middle::ty::{ParamEnv, Ty, TyCtxt};
-use rustc_session::config::{self, CrateType, ErrorOutputType, ResolveDocLinks};
+use rustc_session::config::{self, CrateType, ErrorOutputType, Input, ResolveDocLinks};
 pub(crate) use rustc_session::config::{Options, UnstableOptions};
 use rustc_session::{lint, Session};
 use rustc_span::symbol::sym;
@@ -177,8 +177,8 @@ pub(crate) fn new_dcx(
 
 /// Parse, resolve, and typecheck the given crate.
 pub(crate) fn create_config(
+    input: Input,
     RustdocOptions {
-        input,
         crate_name,
         proc_macro_crate,
         error_format,
diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs
index 8b6588ea75c..05ef7289201 100644
--- a/src/librustdoc/doctest.rs
+++ b/src/librustdoc/doctest.rs
@@ -19,7 +19,7 @@ use rustc_errors::{ColorConfig, DiagCtxtHandle, ErrorGuaranteed, FatalError};
 use rustc_hir::def_id::LOCAL_CRATE;
 use rustc_hir::CRATE_HIR_ID;
 use rustc_interface::interface;
-use rustc_session::config::{self, CrateType, ErrorOutputType};
+use rustc_session::config::{self, CrateType, ErrorOutputType, Input};
 use rustc_session::lint;
 use rustc_span::edition::Edition;
 use rustc_span::symbol::sym;
@@ -88,7 +88,11 @@ fn get_doctest_dir() -> io::Result<TempDir> {
     TempFileBuilder::new().prefix("rustdoctest").tempdir()
 }
 
-pub(crate) fn run(dcx: DiagCtxtHandle<'_>, options: RustdocOptions) -> Result<(), ErrorGuaranteed> {
+pub(crate) fn run(
+    dcx: DiagCtxtHandle<'_>,
+    input: Input,
+    options: RustdocOptions,
+) -> Result<(), ErrorGuaranteed> {
     let invalid_codeblock_attributes_name = crate::lint::INVALID_CODEBLOCK_ATTRIBUTES.name;
 
     // See core::create_config for what's going on here.
@@ -135,7 +139,7 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, options: RustdocOptions) -> Result<()
         opts: sessopts,
         crate_cfg: cfgs,
         crate_check_cfg: options.check_cfgs.clone(),
-        input: options.input.clone(),
+        input: input.clone(),
         output_file: None,
         output_dir: None,
         file_loader: None,
diff --git a/src/librustdoc/doctest/markdown.rs b/src/librustdoc/doctest/markdown.rs
index 9a237f8684d..4f83bd5e882 100644
--- a/src/librustdoc/doctest/markdown.rs
+++ b/src/librustdoc/doctest/markdown.rs
@@ -3,6 +3,7 @@
 use std::fs::read_to_string;
 use std::sync::{Arc, Mutex};
 
+use rustc_session::config::Input;
 use rustc_span::FileName;
 use tempfile::tempdir;
 
@@ -69,9 +70,8 @@ impl DocTestVisitor for MdCollector {
 }
 
 /// Runs any tests/code examples in the markdown file `options.input`.
-pub(crate) fn test(options: Options) -> Result<(), String> {
-    use rustc_session::config::Input;
-    let input_str = match &options.input {
+pub(crate) fn test(input: &Input, options: Options) -> Result<(), String> {
+    let input_str = match input {
         Input::File(path) => {
             read_to_string(path).map_err(|err| format!("{}: {err}", path.display()))?
         }
@@ -79,7 +79,7 @@ pub(crate) fn test(options: Options) -> Result<(), String> {
     };
 
     // Obviously not a real crate name, but close enough for purposes of doctests.
-    let crate_name = options.input.filestem().to_string();
+    let crate_name = input.filestem().to_string();
     let temp_dir =
         tempdir().map_err(|error| format!("failed to create temporary directory: {error:?}"))?;
     let args_file = temp_dir.path().join("rustdoc-cfgs");
@@ -96,8 +96,7 @@ pub(crate) fn test(options: Options) -> Result<(), String> {
     let mut md_collector = MdCollector {
         tests: vec![],
         cur_path: vec![],
-        filename: options
-            .input
+        filename: input
             .opt_path()
             .map(ToOwned::to_owned)
             .map(FileName::from)
diff --git a/src/librustdoc/fold.rs b/src/librustdoc/fold.rs
index bf82c911f29..c25a4ddb6f3 100644
--- a/src/librustdoc/fold.rs
+++ b/src/librustdoc/fold.rs
@@ -1,8 +1,8 @@
 use crate::clean::*;
 
 pub(crate) fn strip_item(mut item: Item) -> Item {
-    if !matches!(*item.kind, StrippedItem(..)) {
-        item.kind = Box::new(StrippedItem(item.kind));
+    if !matches!(item.inner.kind, StrippedItem(..)) {
+        item.inner.kind = StrippedItem(Box::new(item.inner.kind));
     }
     item
 }
@@ -99,10 +99,10 @@ pub(crate) trait DocFolder: Sized {
 
     /// don't override!
     fn fold_item_recur(&mut self, mut item: Item) -> Item {
-        item.kind = Box::new(match *item.kind {
+        item.inner.kind = match item.inner.kind {
             StrippedItem(box i) => StrippedItem(Box::new(self.fold_inner_recur(i))),
-            _ => self.fold_inner_recur(*item.kind),
-        });
+            _ => self.fold_inner_recur(item.inner.kind),
+        };
         item
     }
 
diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs
index c5e2b33ccf8..1b3176e7918 100644
--- a/src/librustdoc/formats/cache.rs
+++ b/src/librustdoc/formats/cache.rs
@@ -216,7 +216,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
 
         // If this is a stripped module,
         // we don't want it or its children in the search index.
-        let orig_stripped_mod = match *item.kind {
+        let orig_stripped_mod = match item.kind {
             clean::StrippedItem(box clean::ModuleItem(..)) => {
                 mem::replace(&mut self.cache.stripped_mod, true)
             }
@@ -232,7 +232,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
 
         // If the impl is from a masked crate or references something from a
         // masked crate then remove it completely.
-        if let clean::ImplItem(ref i) = *item.kind
+        if let clean::ImplItem(ref i) = item.kind
             && (self.cache.masked_crates.contains(&item.item_id.krate())
                 || i.trait_
                     .as_ref()
@@ -246,9 +246,9 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
 
         // Propagate a trait method's documentation to all implementors of the
         // trait.
-        if let clean::TraitItem(ref t) = *item.kind {
+        if let clean::TraitItem(ref t) = item.kind {
             self.cache.traits.entry(item.item_id.expect_def_id()).or_insert_with(|| (**t).clone());
-        } else if let clean::ImplItem(ref i) = *item.kind
+        } else if let clean::ImplItem(ref i) = item.kind
             && let Some(trait_) = &i.trait_
             && !i.kind.is_blanket()
         {
@@ -263,7 +263,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
         // Index this method for searching later on.
         let search_name = if !item.is_stripped() {
             item.name.or_else(|| {
-                if let clean::ImportItem(ref i) = *item.kind
+                if let clean::ImportItem(ref i) = item.kind
                     && let clean::ImportKind::Simple(s) = i.kind
                 {
                     Some(s)
@@ -287,7 +287,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
             _ => false,
         };
 
-        match *item.kind {
+        match item.kind {
             clean::StructItem(..)
             | clean::EnumItem(..)
             | clean::TypeAliasItem(..)
@@ -350,7 +350,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
         }
 
         // Maintain the parent stack.
-        let (item, parent_pushed) = match *item.kind {
+        let (item, parent_pushed) = match item.kind {
             clean::TraitItem(..)
             | clean::EnumItem(..)
             | clean::ForeignTypeItem
@@ -367,7 +367,11 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
 
         // Once we've recursively found all the generics, hoard off all the
         // implementations elsewhere.
-        let ret = if let clean::Item { kind: box clean::ImplItem(ref i), .. } = item {
+        let ret = if let clean::Item {
+            inner: box clean::ItemInner { kind: clean::ImplItem(ref i), .. },
+            ..
+        } = item
+        {
             // Figure out the id of this impl. This may map to a
             // primitive rather than always to a struct/enum.
             // Note: matching twice to restrict the lifetime of the `i` borrow.
@@ -436,7 +440,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
 fn add_item_to_search_index(tcx: TyCtxt<'_>, cache: &mut Cache, item: &clean::Item, name: Symbol) {
     // Item has a name, so it must also have a DefId (can't be an impl, let alone a blanket or auto impl).
     let item_def_id = item.item_id.as_def_id().unwrap();
-    let (parent_did, parent_path) = match *item.kind {
+    let (parent_did, parent_path) = match item.kind {
         clean::StrippedItem(..) => return,
         clean::AssocConstItem(..) | clean::AssocTypeItem(..)
             if cache.parent_stack.last().is_some_and(|parent| parent.is_trait_impl()) =>
@@ -528,7 +532,7 @@ fn add_item_to_search_index(tcx: TyCtxt<'_>, cache: &mut Cache, item: &clean::It
     // - It's either an inline, or a true re-export
     // - It's got the same name
     // - Both of them have the same exact path
-    let defid = match &*item.kind {
+    let defid = match &item.kind {
         clean::ItemKind::ImportItem(import) => import.source.did.unwrap_or(item_def_id),
         _ => item_def_id,
     };
@@ -605,7 +609,7 @@ enum ParentStackItem {
 
 impl ParentStackItem {
     fn new(item: &clean::Item) -> Self {
-        match &*item.kind {
+        match &item.kind {
             clean::ItemKind::ImplItem(box clean::Impl { for_, trait_, generics, kind, .. }) => {
                 ParentStackItem::Impl {
                     for_: for_.clone(),
diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs
index 9754e1e6f74..383e3135faa 100644
--- a/src/librustdoc/formats/item_type.rs
+++ b/src/librustdoc/formats/item_type.rs
@@ -70,7 +70,7 @@ impl Serialize for ItemType {
 
 impl<'a> From<&'a clean::Item> for ItemType {
     fn from(item: &'a clean::Item) -> ItemType {
-        let kind = match *item.kind {
+        let kind = match item.kind {
             clean::StrippedItem(box ref item) => item,
             ref kind => kind,
         };
diff --git a/src/librustdoc/formats/mod.rs b/src/librustdoc/formats/mod.rs
index 84adca8efa9..0bdea36043c 100644
--- a/src/librustdoc/formats/mod.rs
+++ b/src/librustdoc/formats/mod.rs
@@ -16,7 +16,7 @@ pub(crate) struct Impl {
 
 impl Impl {
     pub(crate) fn inner_impl(&self) -> &clean::Impl {
-        match *self.impl_item.kind {
+        match self.impl_item.kind {
             clean::ImplItem(ref impl_) => impl_,
             _ => panic!("non-impl item found in impl"),
         }
diff --git a/src/librustdoc/formats/renderer.rs b/src/librustdoc/formats/renderer.rs
index cc57dca86c3..edd7d56b179 100644
--- a/src/librustdoc/formats/renderer.rs
+++ b/src/librustdoc/formats/renderer.rs
@@ -77,7 +77,7 @@ pub(crate) fn run_format<'tcx, T: FormatRenderer<'tcx>>(
 
             cx.mod_item_in(&item)?;
             let (clean::StrippedItem(box clean::ModuleItem(module)) | clean::ModuleItem(module)) =
-                *item.kind
+                item.inner.kind
             else {
                 unreachable!()
             };
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 9fe43e428f2..a9b9377c0b9 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -16,12 +16,11 @@ use tracing::info;
 
 use super::print_item::{full_path, item_path, print_item};
 use super::sidebar::{print_sidebar, sidebar_module_like, ModuleLike, Sidebar};
-use super::write_shared::write_shared;
 use super::{collect_spans_and_sources, scrape_examples_help, AllTypes, LinkFromSrc, StylePath};
 use crate::clean::types::ExternalLocation;
 use crate::clean::utils::has_doc_flag;
 use crate::clean::{self, ExternalCrate};
-use crate::config::{ModuleSorting, RenderOptions};
+use crate::config::{ModuleSorting, RenderOptions, ShouldMerge};
 use crate::docfs::{DocFS, PathError};
 use crate::error::Error;
 use crate::formats::cache::Cache;
@@ -30,6 +29,7 @@ use crate::formats::FormatRenderer;
 use crate::html::escape::Escape;
 use crate::html::format::{join_with_double_colon, Buffer};
 use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap};
+use crate::html::render::write_shared::write_shared;
 use crate::html::url_parts_builder::UrlPartsBuilder;
 use crate::html::{layout, sources, static_files};
 use crate::scrape_examples::AllCallLocations;
@@ -128,8 +128,10 @@ pub(crate) struct SharedContext<'tcx> {
     pub(crate) span_correspondence_map: FxHashMap<rustc_span::Span, LinkFromSrc>,
     /// The [`Cache`] used during rendering.
     pub(crate) cache: Cache,
-
     pub(crate) call_locations: AllCallLocations,
+    /// Controls whether we read / write to cci files in the doc root. Defaults read=true,
+    /// write=true
+    should_merge: ShouldMerge,
 }
 
 impl SharedContext<'_> {
@@ -551,6 +553,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             span_correspondence_map: matches,
             cache,
             call_locations,
+            should_merge: options.should_merge,
         };
 
         let dst = output;
@@ -640,92 +643,96 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
         );
         shared.fs.write(final_file, v)?;
 
-        // Generating settings page.
-        page.title = "Settings";
-        page.description = "Settings of Rustdoc";
-        page.root_path = "./";
-        page.rust_logo = true;
+        // if to avoid writing help, settings files to doc root unless we're on the final invocation
+        if shared.should_merge.write_rendered_cci {
+            // Generating settings page.
+            page.title = "Settings";
+            page.description = "Settings of Rustdoc";
+            page.root_path = "./";
+            page.rust_logo = true;
 
-        let sidebar = "<h2 class=\"location\">Settings</h2><div class=\"sidebar-elems\"></div>";
-        let v = layout::render(
-            &shared.layout,
-            &page,
-            sidebar,
-            |buf: &mut Buffer| {
-                write!(
-                    buf,
-                    "<div class=\"main-heading\">\
-                     <h1>Rustdoc settings</h1>\
-                     <span class=\"out-of-band\">\
-                         <a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\
-                            Back\
-                        </a>\
-                     </span>\
-                     </div>\
-                     <noscript>\
-                        <section>\
-                            You need to enable JavaScript be able to update your settings.\
-                        </section>\
-                     </noscript>\
-                     <script defer src=\"{static_root_path}{settings_js}\"></script>",
-                    static_root_path = page.get_static_root_path(),
-                    settings_js = static_files::STATIC_FILES.settings_js,
-                );
-                // Pre-load all theme CSS files, so that switching feels seamless.
-                //
-                // When loading settings.html as a popover, the equivalent HTML is
-                // generated in main.js.
-                for file in &shared.style_files {
-                    if let Ok(theme) = file.basename() {
-                        write!(
-                            buf,
-                            "<link rel=\"preload\" href=\"{root_path}{theme}{suffix}.css\" \
-                                as=\"style\">",
-                            root_path = page.static_root_path.unwrap_or(""),
-                            suffix = page.resource_suffix,
-                        );
+            let sidebar = "<h2 class=\"location\">Settings</h2><div class=\"sidebar-elems\"></div>";
+            let v = layout::render(
+                &shared.layout,
+                &page,
+                sidebar,
+                |buf: &mut Buffer| {
+                    write!(
+                        buf,
+                        "<div class=\"main-heading\">\
+                         <h1>Rustdoc settings</h1>\
+                         <span class=\"out-of-band\">\
+                             <a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\
+                                Back\
+                            </a>\
+                         </span>\
+                         </div>\
+                         <noscript>\
+                            <section>\
+                                You need to enable JavaScript be able to update your settings.\
+                            </section>\
+                         </noscript>\
+                         <script defer src=\"{static_root_path}{settings_js}\"></script>",
+                        static_root_path = page.get_static_root_path(),
+                        settings_js = static_files::STATIC_FILES.settings_js,
+                    );
+                    // Pre-load all theme CSS files, so that switching feels seamless.
+                    //
+                    // When loading settings.html as a popover, the equivalent HTML is
+                    // generated in main.js.
+                    for file in &shared.style_files {
+                        if let Ok(theme) = file.basename() {
+                            write!(
+                                buf,
+                                "<link rel=\"preload\" href=\"{root_path}{theme}{suffix}.css\" \
+                                    as=\"style\">",
+                                root_path = page.static_root_path.unwrap_or(""),
+                                suffix = page.resource_suffix,
+                            );
+                        }
                     }
-                }
-            },
-            &shared.style_files,
-        );
-        shared.fs.write(settings_file, v)?;
+                },
+                &shared.style_files,
+            );
+            shared.fs.write(settings_file, v)?;
 
-        // Generating help page.
-        page.title = "Help";
-        page.description = "Documentation for Rustdoc";
-        page.root_path = "./";
-        page.rust_logo = true;
+            // Generating help page.
+            page.title = "Help";
+            page.description = "Documentation for Rustdoc";
+            page.root_path = "./";
+            page.rust_logo = true;
 
-        let sidebar = "<h2 class=\"location\">Help</h2><div class=\"sidebar-elems\"></div>";
-        let v = layout::render(
-            &shared.layout,
-            &page,
-            sidebar,
-            |buf: &mut Buffer| {
-                write!(
-                    buf,
-                    "<div class=\"main-heading\">\
-                     <h1>Rustdoc help</h1>\
-                     <span class=\"out-of-band\">\
-                         <a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\
-                            Back\
-                        </a>\
-                     </span>\
-                     </div>\
-                     <noscript>\
-                        <section>\
-                            <p>You need to enable JavaScript to use keyboard commands or search.</p>\
-                            <p>For more information, browse the <a href=\"https://doc.rust-lang.org/rustdoc/\">rustdoc handbook</a>.</p>\
-                        </section>\
-                     </noscript>",
-                )
-            },
-            &shared.style_files,
-        );
-        shared.fs.write(help_file, v)?;
+            let sidebar = "<h2 class=\"location\">Help</h2><div class=\"sidebar-elems\"></div>";
+            let v = layout::render(
+                &shared.layout,
+                &page,
+                sidebar,
+                |buf: &mut Buffer| {
+                    write!(
+                        buf,
+                        "<div class=\"main-heading\">\
+                         <h1>Rustdoc help</h1>\
+                         <span class=\"out-of-band\">\
+                             <a id=\"back\" href=\"javascript:void(0)\" onclick=\"history.back();\">\
+                                Back\
+                            </a>\
+                         </span>\
+                         </div>\
+                         <noscript>\
+                            <section>\
+                                <p>You need to enable JavaScript to use keyboard commands or search.</p>\
+                                <p>For more information, browse the <a href=\"https://doc.rust-lang.org/rustdoc/\">rustdoc handbook</a>.</p>\
+                            </section>\
+                         </noscript>",
+                    )
+                },
+                &shared.style_files,
+            );
+            shared.fs.write(help_file, v)?;
+        }
 
-        if shared.layout.scrape_examples_extension {
+        // if to avoid writing files to doc root unless we're on the final invocation
+        if shared.layout.scrape_examples_extension && shared.should_merge.write_rendered_cci {
             page.title = "About scraped examples";
             page.description = "How the scraped examples feature works in Rustdoc";
             let v = layout::render(
@@ -800,7 +807,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
         // Render sidebar-items.js used throughout this module.
         if !self.render_redirect_pages {
             let (clean::StrippedItem(box clean::ModuleItem(ref module))
-            | clean::ModuleItem(ref module)) = *item.kind
+            | clean::ModuleItem(ref module)) = item.kind
             else {
                 unreachable!()
             };
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index f67d006116c..a402d799504 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -61,6 +61,7 @@ use tracing::{debug, info};
 
 pub(crate) use self::context::*;
 pub(crate) use self::span_map::{collect_spans_and_sources, LinkFromSrc};
+pub(crate) use self::write_shared::*;
 use crate::clean::{self, ItemId, RenderedLink};
 use crate::error::Error;
 use crate::formats::cache::Cache;
@@ -620,7 +621,7 @@ fn document_full_inner<'a, 'cx: 'a>(
             }
         }
 
-        let kind = match &*item.kind {
+        let kind = match &item.kind {
             clean::ItemKind::StrippedItem(box kind) | kind => kind,
         };
 
@@ -1072,7 +1073,7 @@ fn render_assoc_item(
     cx: &mut Context<'_>,
     render_mode: RenderMode,
 ) {
-    match &*item.kind {
+    match &item.kind {
         clean::StrippedItem(..) => {}
         clean::TyMethodItem(m) => {
             assoc_method(w, item, &m.generics, &m.decl, link, parent, cx, render_mode)
@@ -1350,7 +1351,7 @@ fn render_deref_methods(
         .inner_impl()
         .items
         .iter()
-        .find_map(|item| match *item.kind {
+        .find_map(|item| match item.kind {
             clean::AssocTypeItem(box ref t, _) => Some(match *t {
                 clean::TypeAlias { item_type: Some(ref type_), .. } => (type_, &t.type_),
                 _ => (&t.type_, &t.type_),
@@ -1381,7 +1382,7 @@ fn render_deref_methods(
 }
 
 fn should_render_item(item: &clean::Item, deref_mut_: bool, tcx: TyCtxt<'_>) -> bool {
-    let self_type_opt = match *item.kind {
+    let self_type_opt = match item.kind {
         clean::MethodItem(ref method, _) => method.decl.receiver_type(),
         clean::TyMethodItem(ref method) => method.decl.receiver_type(),
         _ => None,
@@ -1491,7 +1492,7 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) {
 
                 write!(&mut out, "<div class=\"where\">{}</div>", impl_.print(false, cx));
                 for it in &impl_.items {
-                    if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind {
+                    if let clean::AssocTypeItem(ref tydef, ref _bounds) = it.kind {
                         out.push_str("<div class=\"where\">    ");
                         let empty_set = FxHashSet::default();
                         let src_link = AssocItemLink::GotoSource(trait_did.into(), &empty_set);
@@ -1659,7 +1660,7 @@ fn render_impl(
             let method_toggle_class = if item_type.is_method() { " method-toggle" } else { "" };
             write!(w, "<details class=\"toggle{method_toggle_class}\" open><summary>");
         }
-        match &*item.kind {
+        match &item.kind {
             clean::MethodItem(..) | clean::TyMethodItem(_) => {
                 // Only render when the method is not static or we allow static methods
                 if render_method_item {
@@ -1690,7 +1691,7 @@ fn render_impl(
                     w.write_str("</h4></section>");
                 }
             }
-            clean::TyAssocConstItem(generics, ty) => {
+            clean::TyAssocConstItem(ref generics, ref ty) => {
                 let source_id = format!("{item_type}.{name}");
                 let id = cx.derive_id(&source_id);
                 write!(w, "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">");
@@ -1734,7 +1735,7 @@ fn render_impl(
                 );
                 w.write_str("</h4></section>");
             }
-            clean::TyAssocTypeItem(generics, bounds) => {
+            clean::TyAssocTypeItem(ref generics, ref bounds) => {
                 let source_id = format!("{item_type}.{name}");
                 let id = cx.derive_id(&source_id);
                 write!(w, "<section id=\"{id}\" class=\"{item_type}{in_trait_class}\">");
@@ -1808,7 +1809,7 @@ fn render_impl(
 
     if !impl_.is_negative_trait_impl() {
         for trait_item in &impl_.items {
-            match *trait_item.kind {
+            match trait_item.kind {
                 clean::MethodItem(..) | clean::TyMethodItem(_) => methods.push(trait_item),
                 clean::TyAssocTypeItem(..) | clean::AssocTypeItem(..) => {
                     assoc_types.push(trait_item)
@@ -2051,7 +2052,7 @@ pub(crate) fn render_impl_summary(
         write!(w, "{}", inner_impl.print(use_absolute, cx));
         if show_def_docs {
             for it in &inner_impl.items {
-                if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind {
+                if let clean::AssocTypeItem(ref tydef, ref _bounds) = it.kind {
                     w.write_str("<div class=\"where\">  ");
                     assoc_type(
                         w,
@@ -2172,7 +2173,7 @@ fn get_id_for_impl<'tcx>(tcx: TyCtxt<'tcx>, impl_id: ItemId) -> String {
 }
 
 fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String, String)> {
-    match *item.kind {
+    match item.kind {
         clean::ItemKind::ImplItem(ref i) if i.trait_.is_some() => {
             // Alternative format produces no URLs,
             // so this parameter does nothing.
diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs
index cda5409a460..52e25152770 100644
--- a/src/librustdoc/html/render/print_item.rs
+++ b/src/librustdoc/html/render/print_item.rs
@@ -178,7 +178,7 @@ fn print_where_clause_and_check<'a, 'tcx: 'a>(
 
 pub(super) fn print_item(cx: &mut Context<'_>, item: &clean::Item, buf: &mut Buffer) {
     debug_assert!(!item.is_stripped());
-    let typ = match *item.kind {
+    let typ = match item.kind {
         clean::ModuleItem(_) => {
             if item.is_crate() {
                 "Crate "
@@ -252,7 +252,7 @@ pub(super) fn print_item(cx: &mut Context<'_>, item: &clean::Item, buf: &mut Buf
 
     item_vars.render_into(buf).unwrap();
 
-    match &*item.kind {
+    match &item.kind {
         clean::ModuleItem(ref m) => item_module(buf, cx, item, &m.items),
         clean::FunctionItem(ref f) | clean::ForeignFunctionItem(ref f, _) => {
             item_function(buf, cx, item, f)
@@ -411,7 +411,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
             );
         }
 
-        match *myitem.kind {
+        match myitem.kind {
             clean::ExternCrateItem { ref src } => {
                 use crate::html::format::anchor;
 
@@ -477,7 +477,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items:
                     continue;
                 }
 
-                let unsafety_flag = match *myitem.kind {
+                let unsafety_flag = match myitem.kind {
                     clean::FunctionItem(_) | clean::ForeignFunctionItem(..)
                         if myitem.fn_header(tcx).unwrap().safety == hir::Safety::Unsafe =>
                     {
@@ -1439,7 +1439,7 @@ fn item_union(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, s: &clean:
             self.s
                 .fields
                 .iter()
-                .filter_map(|f| match *f.kind {
+                .filter_map(|f| match f.kind {
                     clean::StructFieldItem(ref ty) => Some((f, ty)),
                     _ => None,
                 })
@@ -1457,7 +1457,7 @@ fn print_tuple_struct_fields<'a, 'cx: 'a>(
     display_fn(|f| {
         if !s.is_empty()
             && s.iter().all(|field| {
-                matches!(*field.kind, clean::StrippedItem(box clean::StructFieldItem(..)))
+                matches!(field.kind, clean::StrippedItem(box clean::StructFieldItem(..)))
             })
         {
             return f.write_str("<span class=\"comment\">/* private fields */</span>");
@@ -1467,7 +1467,7 @@ fn print_tuple_struct_fields<'a, 'cx: 'a>(
             if i > 0 {
                 f.write_str(", ")?;
             }
-            match *ty.kind {
+            match ty.kind {
                 clean::StrippedItem(box clean::StructFieldItem(_)) => f.write_str("_")?,
                 clean::StructFieldItem(ref ty) => write!(f, "{}", ty.print(cx))?,
                 _ => unreachable!(),
@@ -1521,7 +1521,7 @@ fn should_show_enum_discriminant(
 ) -> bool {
     let mut has_variants_with_value = false;
     for variant in variants {
-        if let clean::VariantItem(ref var) = *variant.kind
+        if let clean::VariantItem(ref var) = variant.kind
             && matches!(var.kind, clean::VariantKind::CLike)
         {
             has_variants_with_value |= var.discriminant.is_some();
@@ -1592,7 +1592,7 @@ fn render_enum_fields(
                 continue;
             }
             w.write_str(TAB);
-            match *v.kind {
+            match v.kind {
                 clean::VariantItem(ref var) => match var.kind {
                     clean::VariantKind::CLike => display_c_like_variant(
                         w,
@@ -1659,7 +1659,7 @@ fn item_variants(
             " rightside",
         );
         w.write_str("<h3 class=\"code-header\">");
-        if let clean::VariantItem(ref var) = *variant.kind
+        if let clean::VariantItem(ref var) = variant.kind
             && let clean::VariantKind::CLike = var.kind
         {
             display_c_like_variant(
@@ -1675,7 +1675,7 @@ fn item_variants(
             w.write_str(variant.name.unwrap().as_str());
         }
 
-        let clean::VariantItem(variant_data) = &*variant.kind else { unreachable!() };
+        let clean::VariantItem(variant_data) = &variant.kind else { unreachable!() };
 
         if let clean::VariantKind::Tuple(ref s) = variant_data.kind {
             write!(w, "({})", print_tuple_struct_fields(cx, s));
@@ -1716,7 +1716,7 @@ fn item_variants(
                 document_non_exhaustive(variant)
             );
             for field in fields {
-                match *field.kind {
+                match field.kind {
                     clean::StrippedItem(box clean::StructFieldItem(_)) => {}
                     clean::StructFieldItem(ref ty) => {
                         let id = cx.derive_id(format!(
@@ -1886,7 +1886,7 @@ fn item_fields(
 ) {
     let mut fields = fields
         .iter()
-        .filter_map(|f| match *f.kind {
+        .filter_map(|f| match f.kind {
             clean::StructFieldItem(ref ty) => Some((f, ty)),
             _ => None,
         })
@@ -2196,14 +2196,14 @@ fn render_union<'a, 'cx: 'a>(
 
         write!(f, "{{\n")?;
         let count_fields =
-            fields.iter().filter(|field| matches!(*field.kind, clean::StructFieldItem(..))).count();
+            fields.iter().filter(|field| matches!(field.kind, clean::StructFieldItem(..))).count();
         let toggle = should_hide_fields(count_fields);
         if toggle {
             toggle_open(&mut f, format_args!("{count_fields} fields"));
         }
 
         for field in fields {
-            if let clean::StructFieldItem(ref ty) = *field.kind {
+            if let clean::StructFieldItem(ref ty) = field.kind {
                 write!(
                     f,
                     "    {}{}: {},\n",
@@ -2279,14 +2279,14 @@ fn render_struct_fields(
                 w.write_str("{");
             }
             let count_fields =
-                fields.iter().filter(|f| matches!(*f.kind, clean::StructFieldItem(..))).count();
+                fields.iter().filter(|f| matches!(f.kind, clean::StructFieldItem(..))).count();
             let has_visible_fields = count_fields > 0;
             let toggle = should_hide_fields(count_fields);
             if toggle {
                 toggle_open(&mut w, format_args!("{count_fields} fields"));
             }
             for field in fields {
-                if let clean::StructFieldItem(ref ty) = *field.kind {
+                if let clean::StructFieldItem(ref ty) = field.kind {
                     write!(
                         w,
                         "\n{tab}    {vis}{name}: {ty},",
@@ -2314,7 +2314,7 @@ fn render_struct_fields(
             w.write_str("(");
             if !fields.is_empty()
                 && fields.iter().all(|field| {
-                    matches!(*field.kind, clean::StrippedItem(box clean::StructFieldItem(..)))
+                    matches!(field.kind, clean::StrippedItem(box clean::StructFieldItem(..)))
                 })
             {
                 write!(w, "<span class=\"comment\">/* private fields */</span>");
@@ -2323,7 +2323,7 @@ fn render_struct_fields(
                     if i > 0 {
                         w.write_str(", ");
                     }
-                    match *field.kind {
+                    match field.kind {
                         clean::StrippedItem(box clean::StructFieldItem(..)) => write!(w, "_"),
                         clean::StructFieldItem(ref ty) => {
                             write!(w, "{}{}", visibility_print_with_space(field, cx), ty.print(cx),)
diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs
index bfdf457c529..b84c22ac746 100644
--- a/src/librustdoc/html/render/search_index.rs
+++ b/src/librustdoc/html/render/search_index.rs
@@ -758,7 +758,7 @@ pub(crate) fn get_function_type_for_search<'tcx>(
             None
         }
     });
-    let (mut inputs, mut output, where_clause) = match *item.kind {
+    let (mut inputs, mut output, where_clause) = match item.kind {
         clean::FunctionItem(ref f) | clean::MethodItem(ref f, _) | clean::TyMethodItem(ref f) => {
             get_fn_inputs_and_outputs(f, tcx, impl_or_trait_generics, cache)
         }
@@ -1132,7 +1132,7 @@ fn simplify_fn_type<'tcx, 'a>(
                 && trait_.items.iter().any(|at| at.is_ty_associated_type())
             {
                 for assoc_ty in &trait_.items {
-                    if let clean::ItemKind::TyAssocTypeItem(_generics, bounds) = &*assoc_ty.kind
+                    if let clean::ItemKind::TyAssocTypeItem(_generics, bounds) = &assoc_ty.kind
                         && let Some(name) = assoc_ty.name
                     {
                         let idx = -isize::try_from(rgen.len() + 1).unwrap();
diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs
index 06cc57b2a55..842ee81624e 100644
--- a/src/librustdoc/html/render/sidebar.rs
+++ b/src/librustdoc/html/render/sidebar.rs
@@ -119,7 +119,7 @@ pub(crate) mod filters {
 pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) {
     let mut ids = IdMap::new();
     let mut blocks: Vec<LinkBlock<'_>> = docblock_toc(cx, it, &mut ids).into_iter().collect();
-    match *it.kind {
+    match it.kind {
         clean::StructItem(ref s) => sidebar_struct(cx, it, s, &mut blocks),
         clean::TraitItem(ref t) => sidebar_trait(cx, it, t, &mut blocks),
         clean::PrimitiveItem(_) => sidebar_primitive(cx, it, &mut blocks),
@@ -143,7 +143,7 @@ pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buf
     // crate title is displayed as part of logo lockup
     let (title_prefix, title) = if !blocks.is_empty() && !it.is_crate() {
         (
-            match *it.kind {
+            match it.kind {
                 clean::ModuleItem(..) => "Module ",
                 _ => "",
             },
@@ -181,7 +181,7 @@ pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buf
 fn get_struct_fields_name<'a>(fields: &'a [clean::Item]) -> Vec<Link<'a>> {
     let mut fields = fields
         .iter()
-        .filter(|f| matches!(*f.kind, clean::StructFieldItem(..)))
+        .filter(|f| matches!(f.kind, clean::StructFieldItem(..)))
         .filter_map(|f| {
             f.name.as_ref().map(|name| Link::new(format!("structfield.{name}"), name.as_str()))
         })
@@ -467,7 +467,7 @@ fn sidebar_deref_methods<'a>(
 
     debug!("found Deref: {impl_:?}");
     if let Some((target, real_target)) =
-        impl_.inner_impl().items.iter().find_map(|item| match *item.kind {
+        impl_.inner_impl().items.iter().find_map(|item| match item.kind {
             clean::AssocTypeItem(box ref t, _) => Some(match *t {
                 clean::TypeAlias { item_type: Some(ref type_), .. } => (type_, &t.type_),
                 _ => (&t.type_, &t.type_),
@@ -587,7 +587,7 @@ fn sidebar_module(
                 && it
                     .name
                     .or_else(|| {
-                        if let clean::ImportItem(ref i) = *it.kind
+                        if let clean::ImportItem(ref i) = it.kind
                             && let clean::ImportKind::Simple(s) = i.kind
                         {
                             Some(s)
diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs
index b0af8d8c23e..dc1a8cca6bc 100644
--- a/src/librustdoc/html/render/write_shared.rs
+++ b/src/librustdoc/html/render/write_shared.rs
@@ -39,7 +39,7 @@ use serde::{Deserialize, Serialize, Serializer};
 
 use super::{collect_paths_for_type, ensure_trailing_slash, Context, RenderMode};
 use crate::clean::{Crate, Item, ItemId, ItemKind};
-use crate::config::{EmitType, RenderOptions};
+use crate::config::{EmitType, PathToParts, RenderOptions, ShouldMerge};
 use crate::docfs::PathError;
 use crate::error::Error;
 use crate::formats::cache::Cache;
@@ -50,12 +50,11 @@ use crate::html::layout;
 use crate::html::render::ordered_json::{EscapedJson, OrderedJson};
 use crate::html::render::search_index::{build_index, SerializedSearchIndex};
 use crate::html::render::sorted_template::{self, FileFormat, SortedTemplate};
-use crate::html::render::{AssocItemLink, ImplRenderingParameters};
+use crate::html::render::{AssocItemLink, ImplRenderingParameters, StylePath};
 use crate::html::static_files::{self, suffix_path};
 use crate::visit::DocVisitor;
 use crate::{try_err, try_none};
 
-/// Write cross-crate information files, static files, invocation-specific files, etc. to disk
 pub(crate) fn write_shared(
     cx: &mut Context<'_>,
     krate: &Crate,
@@ -70,13 +69,14 @@ pub(crate) fn write_shared(
 
     let SerializedSearchIndex { index, desc } =
         build_index(&krate, &mut Rc::get_mut(&mut cx.shared).unwrap().cache, tcx);
-    write_search_desc(cx, &krate, &desc)?; // does not need to be merged; written unconditionally
+    write_search_desc(cx, &krate, &desc)?; // does not need to be merged
 
     let crate_name = krate.name(cx.tcx());
     let crate_name = crate_name.as_str(); // rand
     let crate_name_json = OrderedJson::serialize(crate_name).unwrap(); // "rand"
     let external_crates = hack_get_external_crate_names(&cx.dst, &cx.shared.resource_suffix)?;
     let info = CrateInfo {
+        version: CrateInfoVersion::V1,
         src_files_js: SourcesPart::get(cx, &crate_name_json)?,
         search_index_js: SearchIndexPart::get(index, &cx.shared.resource_suffix)?,
         all_crates: AllCratesPart::get(crate_name_json.clone(), &cx.shared.resource_suffix)?,
@@ -85,47 +85,103 @@ pub(crate) fn write_shared(
         type_impl: TypeAliasPart::get(cx, krate, &crate_name_json)?,
     };
 
-    let crates = vec![info]; // we have info from just one crate. rest will found in out dir
+    if let Some(parts_out_dir) = &opt.parts_out_dir {
+        create_parents(&parts_out_dir.0)?;
+        try_err!(
+            fs::write(&parts_out_dir.0, serde_json::to_string(&info).unwrap()),
+            &parts_out_dir.0
+        );
+    }
 
-    write_static_files(cx, &opt)?;
-    let dst = &cx.dst;
-    if opt.emit.is_empty() || opt.emit.contains(&EmitType::InvocationSpecific) {
-        if cx.include_sources {
-            write_rendered_cci::<SourcesPart, _>(SourcesPart::blank, dst, &crates)?;
-        }
-        write_rendered_cci::<SearchIndexPart, _>(SearchIndexPart::blank, dst, &crates)?;
-        write_rendered_cci::<AllCratesPart, _>(AllCratesPart::blank, dst, &crates)?;
-    }
-    write_rendered_cci::<TraitAliasPart, _>(TraitAliasPart::blank, dst, &crates)?;
-    write_rendered_cci::<TypeAliasPart, _>(TypeAliasPart::blank, dst, &crates)?;
-    match &opt.index_page {
-        Some(index_page) if opt.enable_index_page => {
-            let mut md_opts = opt.clone();
-            md_opts.output = cx.dst.clone();
-            md_opts.external_html = cx.shared.layout.external_html.clone();
-            try_err!(
-                crate::markdown::render(&index_page, md_opts, cx.shared.edition()),
-                &index_page
-            );
-        }
-        None if opt.enable_index_page => {
-            write_rendered_cci::<CratesIndexPart, _>(|| CratesIndexPart::blank(cx), dst, &crates)?;
+    let mut crates = CrateInfo::read_many(&opt.include_parts_dir)?;
+    crates.push(info);
+
+    if opt.should_merge.write_rendered_cci {
+        write_not_crate_specific(
+            &crates,
+            &cx.dst,
+            opt,
+            &cx.shared.style_files,
+            cx.shared.layout.css_file_extension.as_deref(),
+            &cx.shared.resource_suffix,
+            cx.include_sources,
+        )?;
+        match &opt.index_page {
+            Some(index_page) if opt.enable_index_page => {
+                let mut md_opts = opt.clone();
+                md_opts.output = cx.dst.clone();
+                md_opts.external_html = cx.shared.layout.external_html.clone();
+                try_err!(
+                    crate::markdown::render(&index_page, md_opts, cx.shared.edition()),
+                    &index_page
+                );
+            }
+            None if opt.enable_index_page => {
+                write_rendered_cci::<CratesIndexPart, _>(
+                    || CratesIndexPart::blank(cx),
+                    &cx.dst,
+                    &crates,
+                    &opt.should_merge,
+                )?;
+            }
+            _ => {} // they don't want an index page
         }
-        _ => {} // they don't want an index page
     }
 
     Rc::get_mut(&mut cx.shared).unwrap().fs.set_sync_only(false);
     Ok(())
 }
 
-/// Writes the static files, the style files, and the css extensions
-fn write_static_files(cx: &mut Context<'_>, options: &RenderOptions) -> Result<(), Error> {
-    let static_dir = cx.dst.join("static.files");
+/// Writes files that are written directly to the `--out-dir`, without the prefix from the current
+/// crate. These are the rendered cross-crate files that encode info from multiple crates (e.g.
+/// search index), and the static files.
+pub(crate) fn write_not_crate_specific(
+    crates: &[CrateInfo],
+    dst: &Path,
+    opt: &RenderOptions,
+    style_files: &[StylePath],
+    css_file_extension: Option<&Path>,
+    resource_suffix: &str,
+    include_sources: bool,
+) -> Result<(), Error> {
+    write_rendered_cross_crate_info(crates, dst, opt, include_sources)?;
+    write_static_files(dst, opt, style_files, css_file_extension, resource_suffix)?;
+    Ok(())
+}
 
-    cx.shared.fs.create_dir_all(&static_dir).map_err(|e| PathError::new(e, "static.files"))?;
+fn write_rendered_cross_crate_info(
+    crates: &[CrateInfo],
+    dst: &Path,
+    opt: &RenderOptions,
+    include_sources: bool,
+) -> Result<(), Error> {
+    let m = &opt.should_merge;
+    if opt.emit.is_empty() || opt.emit.contains(&EmitType::InvocationSpecific) {
+        if include_sources {
+            write_rendered_cci::<SourcesPart, _>(SourcesPart::blank, dst, &crates, m)?;
+        }
+        write_rendered_cci::<SearchIndexPart, _>(SearchIndexPart::blank, dst, &crates, m)?;
+        write_rendered_cci::<AllCratesPart, _>(AllCratesPart::blank, dst, &crates, m)?;
+    }
+    write_rendered_cci::<TraitAliasPart, _>(TraitAliasPart::blank, dst, &crates, m)?;
+    write_rendered_cci::<TypeAliasPart, _>(TypeAliasPart::blank, dst, &crates, m)?;
+    Ok(())
+}
+
+/// Writes the static files, the style files, and the css extensions.
+/// Have to be careful about these, because they write to the root out dir.
+fn write_static_files(
+    dst: &Path,
+    opt: &RenderOptions,
+    style_files: &[StylePath],
+    css_file_extension: Option<&Path>,
+    resource_suffix: &str,
+) -> Result<(), Error> {
+    let static_dir = dst.join("static.files");
+    try_err!(fs::create_dir_all(&static_dir), &static_dir);
 
     // Handle added third-party themes
-    for entry in &cx.shared.style_files {
+    for entry in style_files {
         let theme = entry.basename()?;
         let extension =
             try_none!(try_none!(entry.path.extension(), &entry.path).to_str(), &entry.path);
@@ -136,22 +192,24 @@ fn write_static_files(cx: &mut Context<'_>, options: &RenderOptions) -> Result<(
         }
 
         let bytes = try_err!(fs::read(&entry.path), &entry.path);
-        let filename = format!("{theme}{suffix}.{extension}", suffix = cx.shared.resource_suffix);
-        cx.shared.fs.write(cx.dst.join(filename), bytes)?;
+        let filename = format!("{theme}{resource_suffix}.{extension}");
+        let dst_filename = dst.join(filename);
+        try_err!(fs::write(&dst_filename, bytes), &dst_filename);
     }
 
     // When the user adds their own CSS files with --extend-css, we write that as an
     // invocation-specific file (that is, with a resource suffix).
-    if let Some(ref css) = cx.shared.layout.css_file_extension {
+    if let Some(css) = css_file_extension {
         let buffer = try_err!(fs::read_to_string(css), css);
-        let path = static_files::suffix_path("theme.css", &cx.shared.resource_suffix);
-        cx.shared.fs.write(cx.dst.join(path), buffer)?;
+        let path = static_files::suffix_path("theme.css", resource_suffix);
+        let dst_path = dst.join(path);
+        try_err!(fs::write(&dst_path, buffer), &dst_path);
     }
 
-    if options.emit.is_empty() || options.emit.contains(&EmitType::Toolchain) {
+    if opt.emit.is_empty() || opt.emit.contains(&EmitType::Toolchain) {
         static_files::for_each(|f: &static_files::StaticFile| {
             let filename = static_dir.join(f.output_filename());
-            cx.shared.fs.write(filename, f.minified())
+            fs::write(&filename, f.minified()).map_err(|e| PathError::new(e, &filename))
         })?;
     }
 
@@ -186,7 +244,8 @@ fn write_search_desc(
 
 /// Contains pre-rendered contents to insert into the CCI template
 #[derive(Serialize, Deserialize, Clone, Debug)]
-struct CrateInfo {
+pub(crate) struct CrateInfo {
+    version: CrateInfoVersion,
     src_files_js: PartsAndLocations<SourcesPart>,
     search_index_js: PartsAndLocations<SearchIndexPart>,
     all_crates: PartsAndLocations<AllCratesPart>,
@@ -195,6 +254,33 @@ struct CrateInfo {
     type_impl: PartsAndLocations<TypeAliasPart>,
 }
 
+impl CrateInfo {
+    /// Read all of the crate info from its location on the filesystem
+    pub(crate) fn read_many(parts_paths: &[PathToParts]) -> Result<Vec<Self>, Error> {
+        parts_paths
+            .iter()
+            .map(|parts_path| {
+                let path = &parts_path.0;
+                let parts = try_err!(fs::read(&path), &path);
+                let parts: CrateInfo = try_err!(serde_json::from_slice(&parts), &path);
+                Ok::<_, Error>(parts)
+            })
+            .collect::<Result<Vec<CrateInfo>, Error>>()
+    }
+}
+
+/// Version for the format of the crate-info file.
+///
+/// This enum should only ever have one variant, representing the current version.
+/// Gives pretty good error message about expecting the current version on deserialize.
+///
+/// Must be incremented (V2, V3, etc.) upon any changes to the search index or CrateInfo,
+/// to provide better diagnostics about including an invalid file.
+#[derive(Serialize, Deserialize, Clone, Debug)]
+enum CrateInfoVersion {
+    V1,
+}
+
 /// Paths (relative to the doc root) and their pre-merge contents
 #[derive(Serialize, Deserialize, Debug, Clone)]
 #[serde(transparent)]
@@ -783,7 +869,7 @@ impl<'cx, 'cache> DocVisitor for TypeImplCollector<'cx, 'cache> {
     fn visit_item(&mut self, it: &Item) {
         self.visit_item_recur(it);
         let cache = self.cache;
-        let ItemKind::TypeAliasItem(ref t) = *it.kind else { return };
+        let ItemKind::TypeAliasItem(ref t) = it.kind else { return };
         let Some(self_did) = it.item_id.as_def_id() else { return };
         if !self.visited_aliases.insert(self_did) {
             return;
@@ -900,10 +986,14 @@ fn create_parents(path: &Path) -> Result<(), Error> {
 fn read_template_or_blank<F, T: FileFormat>(
     mut make_blank: F,
     path: &Path,
+    should_merge: &ShouldMerge,
 ) -> Result<SortedTemplate<T>, Error>
 where
     F: FnMut() -> SortedTemplate<T>,
 {
+    if !should_merge.read_rendered_cci {
+        return Ok(make_blank());
+    }
     match fs::read_to_string(&path) {
         Ok(template) => Ok(try_err!(SortedTemplate::from_str(&template), &path)),
         Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(make_blank()),
@@ -916,6 +1006,7 @@ fn write_rendered_cci<T: CciPart, F>(
     mut make_blank: F,
     dst: &Path,
     crates_info: &[CrateInfo],
+    should_merge: &ShouldMerge,
 ) -> Result<(), Error>
 where
     F: FnMut() -> SortedTemplate<T::FileFormat>,
@@ -924,7 +1015,8 @@ where
     for (path, parts) in get_path_parts::<T>(dst, crates_info) {
         create_parents(&path)?;
         // read previous rendered cci from storage, append to them
-        let mut template = read_template_or_blank::<_, T::FileFormat>(&mut make_blank, &path)?;
+        let mut template =
+            read_template_or_blank::<_, T::FileFormat>(&mut make_blank, &path, should_merge)?;
         for part in parts {
             template.append(part);
         }
diff --git a/src/librustdoc/html/render/write_shared/tests.rs b/src/librustdoc/html/render/write_shared/tests.rs
index e282cd99e43..a235f1d3724 100644
--- a/src/librustdoc/html/render/write_shared/tests.rs
+++ b/src/librustdoc/html/render/write_shared/tests.rs
@@ -1,3 +1,4 @@
+use crate::config::ShouldMerge;
 use crate::html::render::ordered_json::{EscapedJson, OrderedJson};
 use crate::html::render::sorted_template::{Html, SortedTemplate};
 use crate::html::render::write_shared::*;
@@ -192,16 +193,17 @@ fn read_template_test() {
     let path = path.path().join("file.html");
     let make_blank = || SortedTemplate::<Html>::from_before_after("<div>", "</div>");
 
-    let template = read_template_or_blank(make_blank, &path).unwrap();
+    let should_merge = ShouldMerge { read_rendered_cci: true, write_rendered_cci: true };
+    let template = read_template_or_blank(make_blank, &path, &should_merge).unwrap();
     assert_eq!(but_last_line(&template.to_string()), "<div></div>");
     fs::write(&path, template.to_string()).unwrap();
-    let mut template = read_template_or_blank(make_blank, &path).unwrap();
+    let mut template = read_template_or_blank(make_blank, &path, &should_merge).unwrap();
     template.append("<img/>".to_string());
     fs::write(&path, template.to_string()).unwrap();
-    let mut template = read_template_or_blank(make_blank, &path).unwrap();
+    let mut template = read_template_or_blank(make_blank, &path, &should_merge).unwrap();
     template.append("<br/>".to_string());
     fs::write(&path, template.to_string()).unwrap();
-    let template = read_template_or_blank(make_blank, &path).unwrap();
+    let template = read_template_or_blank(make_blank, &path, &should_merge).unwrap();
 
     assert_eq!(but_last_line(&template.to_string()), "<div><br/><img/></div>");
 }
diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs
index 59e4b072a12..82ce619a19d 100644
--- a/src/librustdoc/json/conversions.rs
+++ b/src/librustdoc/json/conversions.rs
@@ -49,7 +49,7 @@ impl JsonRenderer<'_> {
         let visibility = item.visibility(self.tcx);
         let clean::Item { name, item_id, .. } = item;
         let id = id_from_item(&item, self.tcx);
-        let inner = match *item.kind {
+        let inner = match item.kind {
             clean::KeywordItem => return None,
             clean::StrippedItem(ref inner) => {
                 match &**inner {
@@ -294,7 +294,7 @@ pub(crate) fn id_from_item_inner(
 }
 
 pub(crate) fn id_from_item(item: &clean::Item, tcx: TyCtxt<'_>) -> Id {
-    match *item.kind {
+    match item.kind {
         clean::ItemKind::ImportItem(ref import) => {
             let extra =
                 import.source.did.map(ItemId::from).map(|i| id_from_item_inner(i, tcx, None, None));
@@ -310,7 +310,7 @@ fn from_clean_item(item: clean::Item, tcx: TyCtxt<'_>) -> ItemEnum {
     let is_crate = item.is_crate();
     let header = item.fn_header(tcx);
 
-    match *item.kind {
+    match item.inner.kind {
         ModuleItem(m) => {
             ItemEnum::Module(Module { is_crate, items: ids(m.items, tcx), is_stripped: false })
         }
diff --git a/src/librustdoc/json/import_finder.rs b/src/librustdoc/json/import_finder.rs
index 976a7fd69ae..e21fe9668d4 100644
--- a/src/librustdoc/json/import_finder.rs
+++ b/src/librustdoc/json/import_finder.rs
@@ -24,7 +24,7 @@ struct ImportFinder {
 
 impl DocFolder for ImportFinder {
     fn fold_item(&mut self, i: Item) -> Option<Item> {
-        match *i.kind {
+        match i.kind {
             clean::ImportItem(Import { source: ImportSource { did: Some(did), .. }, .. }) => {
                 self.imported.insert(did);
                 Some(i)
diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs
index 03a012864ef..f30a34cdccf 100644
--- a/src/librustdoc/json/mod.rs
+++ b/src/librustdoc/json/mod.rs
@@ -85,7 +85,7 @@ impl<'tcx> JsonRenderer<'tcx> {
                         // document primitive items in an arbitrary crate by using
                         // `rustc_doc_primitive`.
                         let mut is_primitive_impl = false;
-                        if let clean::types::ItemKind::ImplItem(ref impl_) = *item.kind
+                        if let clean::types::ItemKind::ImplItem(ref impl_) = item.kind
                             && impl_.trait_.is_none()
                             && let clean::types::Type::Primitive(_) = impl_.for_
                         {
@@ -164,7 +164,7 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> {
 
         // Flatten items that recursively store other items. We include orphaned items from
         // stripped modules and etc that are otherwise reachable.
-        if let ItemKind::StrippedItem(inner) = &*item.kind {
+        if let ItemKind::StrippedItem(inner) = &item.kind {
             inner.inner_items().for_each(|i| self.item(i.clone()).unwrap());
         }
 
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index f25acbe080a..6649e1721a4 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -603,6 +603,33 @@ fn opts() -> Vec<RustcOptGroup> {
                 "path to function call information (for displaying examples in the documentation)",
             )
         }),
+        unstable("merge", |o| {
+            o.optopt(
+                "",
+                "merge",
+                "Controls how rustdoc handles files from previously documented crates in the doc root
+                      none = Do not write cross-crate information to the --out-dir
+                      shared = Append current crate's info to files found in the --out-dir
+                      finalize = Write current crate's info and --include-parts-dir info to the --out-dir, overwriting conflicting files",
+                "none|shared|finalize",
+            )
+        }),
+        unstable("parts-out-dir", |o| {
+            o.optopt(
+                "",
+                "parts-out-dir",
+                "Writes trait implementations and other info for the current crate to provided path. Only use with --merge=none",
+                "path/to/doc.parts/<crate-name>",
+            )
+        }),
+        unstable("include-parts-dir", |o| {
+            o.optmulti(
+                "",
+                "include-parts-dir",
+                "Includes trait implementations and other crate info from provided path. Only use with --merge=finalize",
+                "path/to/doc.parts/<crate-name>",
+            )
+        }),
         // deprecated / removed options
         unstable("disable-minification", |o| o.optflagmulti("", "disable-minification", "removed")),
         stable("plugin-path", |o| {
@@ -697,6 +724,32 @@ fn run_renderer<'tcx, T: formats::FormatRenderer<'tcx>>(
     }
 }
 
+/// Renders and writes cross-crate info files, like the search index. This function exists so that
+/// we can run rustdoc without a crate root in the `--merge=finalize` mode. Cross-crate info files
+/// discovered via `--include-parts-dir` are combined and written to the doc root.
+fn run_merge_finalize(opt: config::RenderOptions) -> Result<(), error::Error> {
+    assert!(
+        opt.should_merge.write_rendered_cci,
+        "config.rs only allows us to return InputMode::NoInputMergeFinalize if --merge=finalize"
+    );
+    assert!(
+        !opt.should_merge.read_rendered_cci,
+        "config.rs only allows us to return InputMode::NoInputMergeFinalize if --merge=finalize"
+    );
+    let crates = html::render::CrateInfo::read_many(&opt.include_parts_dir)?;
+    let include_sources = !opt.html_no_source;
+    html::render::write_not_crate_specific(
+        &crates,
+        &opt.output,
+        &opt,
+        &opt.themes,
+        opt.extension_css.as_deref(),
+        &opt.resource_suffix,
+        include_sources,
+    )?;
+    Ok(())
+}
+
 fn main_args(
     early_dcx: &mut EarlyDiagCtxt,
     at_args: &[String],
@@ -727,22 +780,35 @@ fn main_args(
 
     // Note that we discard any distinction between different non-zero exit
     // codes from `from_matches` here.
-    let (options, render_options) = match config::Options::from_matches(early_dcx, &matches, args) {
-        Some(opts) => opts,
-        None => return Ok(()),
-    };
+    let (input, options, render_options) =
+        match config::Options::from_matches(early_dcx, &matches, args) {
+            Some(opts) => opts,
+            None => return Ok(()),
+        };
 
     let dcx =
         core::new_dcx(options.error_format, None, options.diagnostic_width, &options.unstable_opts);
     let dcx = dcx.handle();
 
-    match (options.should_test, options.markdown_input()) {
-        (true, Some(_)) => return wrap_return(dcx, doctest::test_markdown(options)),
-        (true, None) => return doctest::run(dcx, options),
-        (false, Some(input)) => {
-            let input = input.to_owned();
+    let input = match input {
+        config::InputMode::HasFile(input) => input,
+        config::InputMode::NoInputMergeFinalize => {
+            return wrap_return(
+                dcx,
+                run_merge_finalize(render_options)
+                    .map_err(|e| format!("could not write merged cross-crate info: {e}")),
+            );
+        }
+    };
+
+    match (options.should_test, config::markdown_input(&input)) {
+        (true, Some(_)) => return wrap_return(dcx, doctest::test_markdown(&input, options)),
+        (true, None) => return doctest::run(dcx, input, options),
+        (false, Some(md_input)) => {
+            let md_input = md_input.to_owned();
             let edition = options.edition;
-            let config = core::create_config(options, &render_options, using_internal_features);
+            let config =
+                core::create_config(input, options, &render_options, using_internal_features);
 
             // `markdown::render` can invoke `doctest::make_test`, which
             // requires session globals and a thread pool, so we use
@@ -750,7 +816,7 @@ fn main_args(
             return wrap_return(
                 dcx,
                 interface::run_compiler(config, |_compiler| {
-                    markdown::render(&input, render_options, edition)
+                    markdown::render(&md_input, render_options, edition)
                 }),
             );
         }
@@ -775,7 +841,7 @@ fn main_args(
     let scrape_examples_options = options.scrape_examples_options.clone();
     let bin_crate = options.bin_crate;
 
-    let config = core::create_config(options, &render_options, using_internal_features);
+    let config = core::create_config(input, options, &render_options, using_internal_features);
 
     interface::run_compiler(config, |compiler| {
         let sess = &compiler.sess;
diff --git a/src/librustdoc/passes/calculate_doc_coverage.rs b/src/librustdoc/passes/calculate_doc_coverage.rs
index 1d0b41fc429..7391909406e 100644
--- a/src/librustdoc/passes/calculate_doc_coverage.rs
+++ b/src/librustdoc/passes/calculate_doc_coverage.rs
@@ -195,7 +195,7 @@ impl<'a, 'b> DocVisitor for CoverageCalculator<'a, 'b> {
             return;
         }
 
-        match *i.kind {
+        match i.kind {
             clean::StrippedItem(..) => {
                 // don't count items in stripped modules
                 return;
diff --git a/src/librustdoc/passes/check_doc_test_visibility.rs b/src/librustdoc/passes/check_doc_test_visibility.rs
index d456e8e24e6..733fd919e71 100644
--- a/src/librustdoc/passes/check_doc_test_visibility.rs
+++ b/src/librustdoc/passes/check_doc_test_visibility.rs
@@ -57,7 +57,7 @@ impl crate::doctest::DocTestVisitor for Tests {
 pub(crate) fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -> bool {
     if !cx.cache.effective_visibilities.is_directly_public(cx.tcx, item.item_id.expect_def_id())
         || matches!(
-            *item.kind,
+            item.kind,
             clean::StructFieldItem(_)
                 | clean::VariantItem(_)
                 | clean::AssocConstItem(..)
diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs
index 394a64df4bc..a9edc485d5e 100644
--- a/src/librustdoc/passes/collect_trait_impls.rs
+++ b/src/librustdoc/passes/collect_trait_impls.rs
@@ -160,13 +160,13 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) ->
 
     // scan through included items ahead of time to splice in Deref targets to the "valid" sets
     for it in new_items_external.iter().chain(new_items_local.iter()) {
-        if let ImplItem(box Impl { ref for_, ref trait_, ref items, .. }) = *it.kind
+        if let ImplItem(box Impl { ref for_, ref trait_, ref items, .. }) = it.kind
             && trait_.as_ref().map(|t| t.def_id()) == tcx.lang_items().deref_trait()
             && cleaner.keep_impl(for_, true)
         {
             let target = items
                 .iter()
-                .find_map(|item| match *item.kind {
+                .find_map(|item| match item.kind {
                     AssocTypeItem(ref t, _) => Some(&t.type_),
                     _ => None,
                 })
@@ -200,7 +200,7 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) ->
 
     // Filter out external items that are not needed
     new_items_external.retain(|it| {
-        if let ImplItem(box Impl { ref for_, ref trait_, ref kind, .. }) = *it.kind {
+        if let ImplItem(box Impl { ref for_, ref trait_, ref kind, .. }) = it.kind {
             cleaner.keep_impl(
                 for_,
                 trait_.as_ref().map(|t| t.def_id()) == tcx.lang_items().deref_trait(),
@@ -211,7 +211,7 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) ->
         }
     });
 
-    if let ModuleItem(Module { items, .. }) = &mut *krate.module.kind {
+    if let ModuleItem(Module { items, .. }) = &mut krate.module.inner.kind {
         items.extend(synth_impls);
         items.extend(new_items_external);
         items.extend(new_items_local);
@@ -258,7 +258,7 @@ impl<'cache> DocVisitor for ItemAndAliasCollector<'cache> {
     fn visit_item(&mut self, i: &Item) {
         self.items.insert(i.item_id);
 
-        if let TypeAliasItem(alias) = &*i.kind
+        if let TypeAliasItem(alias) = &i.inner.kind
             && let Some(did) = alias.type_.def_id(self.cache)
         {
             self.items.insert(ItemId::DefId(did));
diff --git a/src/librustdoc/passes/lint.rs b/src/librustdoc/passes/lint.rs
index bc804a340bf..4da5d8f0e06 100644
--- a/src/librustdoc/passes/lint.rs
+++ b/src/librustdoc/passes/lint.rs
@@ -27,12 +27,33 @@ pub(crate) fn run_lints(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
 
 impl<'a, 'tcx> DocVisitor for Linter<'a, 'tcx> {
     fn visit_item(&mut self, item: &Item) {
-        bare_urls::visit_item(self.cx, item);
-        check_code_block_syntax::visit_item(self.cx, item);
-        html_tags::visit_item(self.cx, item);
-        unescaped_backticks::visit_item(self.cx, item);
-        redundant_explicit_links::visit_item(self.cx, item);
-        unportable_markdown::visit_item(self.cx, item);
+        let Some(hir_id) = DocContext::as_local_hir_id(self.cx.tcx, item.item_id) else {
+            // If non-local, no need to check anything.
+            return;
+        };
+        let dox = item.doc_value();
+        if !dox.is_empty() {
+            let may_have_link = dox.contains(&[':', '['][..]);
+            let may_have_block_comment_or_html = dox.contains(&['<', '>']);
+            // ~~~rust
+            // // This is a real, supported commonmark syntax for block code
+            // ~~~
+            let may_have_code = dox.contains(&['~', '`', '\t'][..]) || dox.contains("    ");
+            if may_have_link {
+                bare_urls::visit_item(self.cx, item, hir_id, &dox);
+                redundant_explicit_links::visit_item(self.cx, item, hir_id);
+            }
+            if may_have_code {
+                check_code_block_syntax::visit_item(self.cx, item, &dox);
+                unescaped_backticks::visit_item(self.cx, item, hir_id, &dox);
+            }
+            if may_have_block_comment_or_html {
+                html_tags::visit_item(self.cx, item, hir_id, &dox);
+                unportable_markdown::visit_item(self.cx, item, hir_id, &dox);
+            } else if may_have_link {
+                unportable_markdown::visit_item(self.cx, item, hir_id, &dox);
+            }
+        }
 
         self.visit_item_recur(item)
     }
diff --git a/src/librustdoc/passes/lint/bare_urls.rs b/src/librustdoc/passes/lint/bare_urls.rs
index 22051dd954d..bac0e07f1c1 100644
--- a/src/librustdoc/passes/lint/bare_urls.rs
+++ b/src/librustdoc/passes/lint/bare_urls.rs
@@ -8,6 +8,7 @@ use std::sync::LazyLock;
 use pulldown_cmark::{Event, Parser, Tag};
 use regex::Regex;
 use rustc_errors::Applicability;
+use rustc_hir::HirId;
 use rustc_resolve::rustdoc::source_span_for_markdown_range;
 use tracing::trace;
 
@@ -15,50 +16,43 @@ use crate::clean::*;
 use crate::core::DocContext;
 use crate::html::markdown::main_body_opts;
 
-pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item) {
-    let Some(hir_id) = DocContext::as_local_hir_id(cx.tcx, item.item_id) else {
-        // If non-local, no need to check anything.
-        return;
+pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &str) {
+    let report_diag = |cx: &DocContext<'_>, msg: &'static str, range: Range<usize>| {
+        let sp = source_span_for_markdown_range(cx.tcx, &dox, &range, &item.attrs.doc_strings)
+            .unwrap_or_else(|| item.attr_span(cx.tcx));
+        cx.tcx.node_span_lint(crate::lint::BARE_URLS, hir_id, sp, |lint| {
+            lint.primary_message(msg)
+                .note("bare URLs are not automatically turned into clickable links")
+                .multipart_suggestion(
+                    "use an automatic link instead",
+                    vec![
+                        (sp.shrink_to_lo(), "<".to_string()),
+                        (sp.shrink_to_hi(), ">".to_string()),
+                    ],
+                    Applicability::MachineApplicable,
+                );
+        });
     };
-    let dox = item.doc_value();
-    if !dox.is_empty() {
-        let report_diag = |cx: &DocContext<'_>, msg: &'static str, range: Range<usize>| {
-            let sp = source_span_for_markdown_range(cx.tcx, &dox, &range, &item.attrs.doc_strings)
-                .unwrap_or_else(|| item.attr_span(cx.tcx));
-            cx.tcx.node_span_lint(crate::lint::BARE_URLS, hir_id, sp, |lint| {
-                lint.primary_message(msg)
-                    .note("bare URLs are not automatically turned into clickable links")
-                    .multipart_suggestion(
-                        "use an automatic link instead",
-                        vec![
-                            (sp.shrink_to_lo(), "<".to_string()),
-                            (sp.shrink_to_hi(), ">".to_string()),
-                        ],
-                        Applicability::MachineApplicable,
-                    );
-            });
-        };
 
-        let mut p = Parser::new_ext(&dox, main_body_opts()).into_offset_iter();
+    let mut p = Parser::new_ext(&dox, main_body_opts()).into_offset_iter();
 
-        while let Some((event, range)) = p.next() {
-            match event {
-                Event::Text(s) => find_raw_urls(cx, &s, range, &report_diag),
-                // We don't want to check the text inside code blocks or links.
-                Event::Start(tag @ (Tag::CodeBlock(_) | Tag::Link { .. })) => {
-                    while let Some((event, _)) = p.next() {
-                        match event {
-                            Event::End(end)
-                                if mem::discriminant(&end) == mem::discriminant(&tag.to_end()) =>
-                            {
-                                break;
-                            }
-                            _ => {}
+    while let Some((event, range)) = p.next() {
+        match event {
+            Event::Text(s) => find_raw_urls(cx, &s, range, &report_diag),
+            // We don't want to check the text inside code blocks or links.
+            Event::Start(tag @ (Tag::CodeBlock(_) | Tag::Link { .. })) => {
+                while let Some((event, _)) = p.next() {
+                    match event {
+                        Event::End(end)
+                            if mem::discriminant(&end) == mem::discriminant(&tag.to_end()) =>
+                        {
+                            break;
                         }
+                        _ => {}
                     }
                 }
-                _ => {}
             }
+            _ => {}
         }
     }
 }
diff --git a/src/librustdoc/passes/lint/check_code_block_syntax.rs b/src/librustdoc/passes/lint/check_code_block_syntax.rs
index 977c0953336..1b2431a629b 100644
--- a/src/librustdoc/passes/lint/check_code_block_syntax.rs
+++ b/src/librustdoc/passes/lint/check_code_block_syntax.rs
@@ -15,10 +15,8 @@ use crate::clean;
 use crate::core::DocContext;
 use crate::html::markdown::{self, RustCodeBlock};
 
-pub(crate) fn visit_item(cx: &DocContext<'_>, item: &clean::Item) {
-    if let Some(def_id) = item.item_id.as_local_def_id()
-        && let Some(dox) = &item.opt_doc_value()
-    {
+pub(crate) fn visit_item(cx: &DocContext<'_>, item: &clean::Item, dox: &str) {
+    if let Some(def_id) = item.item_id.as_local_def_id() {
         let sp = item.attr_span(cx.tcx);
         let extra = crate::html::markdown::ExtraInfo::new(cx.tcx, def_id, sp);
         for code_block in markdown::rust_code_blocks(dox, &extra) {
diff --git a/src/librustdoc/passes/lint/html_tags.rs b/src/librustdoc/passes/lint/html_tags.rs
index 6f9e9d36a5c..223174838ad 100644
--- a/src/librustdoc/passes/lint/html_tags.rs
+++ b/src/librustdoc/passes/lint/html_tags.rs
@@ -5,159 +5,149 @@ use std::ops::Range;
 use std::str::CharIndices;
 
 use pulldown_cmark::{BrokenLink, Event, LinkType, Parser, Tag, TagEnd};
+use rustc_hir::HirId;
 use rustc_resolve::rustdoc::source_span_for_markdown_range;
 
 use crate::clean::*;
 use crate::core::DocContext;
 use crate::html::markdown::main_body_opts;
 
-pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
+pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &str) {
     let tcx = cx.tcx;
-    let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id)
-    // If non-local, no need to check anything.
-    else {
-        return;
-    };
-    let dox = item.doc_value();
-    if !dox.is_empty() {
-        let report_diag = |msg: String, range: &Range<usize>, is_open_tag: bool| {
-            let sp = match source_span_for_markdown_range(tcx, &dox, range, &item.attrs.doc_strings)
-            {
-                Some(sp) => sp,
-                None => item.attr_span(tcx),
-            };
-            tcx.node_span_lint(crate::lint::INVALID_HTML_TAGS, hir_id, sp, |lint| {
-                use rustc_lint_defs::Applicability;
+    let report_diag = |msg: String, range: &Range<usize>, is_open_tag: bool| {
+        let sp = match source_span_for_markdown_range(tcx, &dox, range, &item.attrs.doc_strings) {
+            Some(sp) => sp,
+            None => item.attr_span(tcx),
+        };
+        tcx.node_span_lint(crate::lint::INVALID_HTML_TAGS, hir_id, sp, |lint| {
+            use rustc_lint_defs::Applicability;
 
-                lint.primary_message(msg);
+            lint.primary_message(msg);
 
-                // If a tag looks like `<this>`, it might actually be a generic.
-                // We don't try to detect stuff `<like, this>` because that's not valid HTML,
-                // and we don't try to detect stuff `<like this>` because that's not valid Rust.
-                let mut generics_end = range.end;
-                if let Some(Some(mut generics_start)) = (is_open_tag
-                    && dox[..generics_end].ends_with('>'))
-                .then(|| extract_path_backwards(&dox, range.start))
+            // If a tag looks like `<this>`, it might actually be a generic.
+            // We don't try to detect stuff `<like, this>` because that's not valid HTML,
+            // and we don't try to detect stuff `<like this>` because that's not valid Rust.
+            let mut generics_end = range.end;
+            if let Some(Some(mut generics_start)) = (is_open_tag
+                && dox[..generics_end].ends_with('>'))
+            .then(|| extract_path_backwards(&dox, range.start))
+            {
+                while generics_start != 0
+                    && generics_end < dox.len()
+                    && dox.as_bytes()[generics_start - 1] == b'<'
+                    && dox.as_bytes()[generics_end] == b'>'
                 {
-                    while generics_start != 0
-                        && generics_end < dox.len()
-                        && dox.as_bytes()[generics_start - 1] == b'<'
-                        && dox.as_bytes()[generics_end] == b'>'
-                    {
-                        generics_end += 1;
-                        generics_start -= 1;
-                        if let Some(new_start) = extract_path_backwards(&dox, generics_start) {
-                            generics_start = new_start;
-                        }
-                        if let Some(new_end) = extract_path_forward(&dox, generics_end) {
-                            generics_end = new_end;
-                        }
+                    generics_end += 1;
+                    generics_start -= 1;
+                    if let Some(new_start) = extract_path_backwards(&dox, generics_start) {
+                        generics_start = new_start;
                     }
                     if let Some(new_end) = extract_path_forward(&dox, generics_end) {
                         generics_end = new_end;
                     }
-                    let generics_sp = match source_span_for_markdown_range(
-                        tcx,
-                        &dox,
-                        &(generics_start..generics_end),
-                        &item.attrs.doc_strings,
-                    ) {
-                        Some(sp) => sp,
-                        None => item.attr_span(tcx),
-                    };
-                    // Sometimes, we only extract part of a path. For example, consider this:
-                    //
-                    //     <[u32] as IntoIter<u32>>::Item
-                    //                       ^^^^^ unclosed HTML tag `u32`
-                    //
-                    // We don't have any code for parsing fully-qualified trait paths.
-                    // In theory, we could add it, but doing it correctly would require
-                    // parsing the entire path grammar, which is problematic because of
-                    // overlap between the path grammar and Markdown.
-                    //
-                    // The example above shows that ambiguity. Is `[u32]` intended to be an
-                    // intra-doc link to the u32 primitive, or is it intended to be a slice?
-                    //
-                    // If the below conditional were removed, we would suggest this, which is
-                    // not what the user probably wants.
-                    //
-                    //     <[u32] as `IntoIter<u32>`>::Item
-                    //
-                    // We know that the user actually wants to wrap the whole thing in a code
-                    // block, but the only reason we know that is because `u32` does not, in
-                    // fact, implement IntoIter. If the example looks like this:
-                    //
-                    //     <[Vec<i32>] as IntoIter<i32>::Item
-                    //
-                    // The ideal fix would be significantly different.
-                    if (generics_start > 0 && dox.as_bytes()[generics_start - 1] == b'<')
-                        || (generics_end < dox.len() && dox.as_bytes()[generics_end] == b'>')
-                    {
-                        return;
-                    }
-                    // multipart form is chosen here because ``Vec<i32>`` would be confusing.
-                    lint.multipart_suggestion(
-                        "try marking as source code",
-                        vec![
-                            (generics_sp.shrink_to_lo(), String::from("`")),
-                            (generics_sp.shrink_to_hi(), String::from("`")),
-                        ],
-                        Applicability::MaybeIncorrect,
-                    );
                 }
-            });
-        };
+                if let Some(new_end) = extract_path_forward(&dox, generics_end) {
+                    generics_end = new_end;
+                }
+                let generics_sp = match source_span_for_markdown_range(
+                    tcx,
+                    &dox,
+                    &(generics_start..generics_end),
+                    &item.attrs.doc_strings,
+                ) {
+                    Some(sp) => sp,
+                    None => item.attr_span(tcx),
+                };
+                // Sometimes, we only extract part of a path. For example, consider this:
+                //
+                //     <[u32] as IntoIter<u32>>::Item
+                //                       ^^^^^ unclosed HTML tag `u32`
+                //
+                // We don't have any code for parsing fully-qualified trait paths.
+                // In theory, we could add it, but doing it correctly would require
+                // parsing the entire path grammar, which is problematic because of
+                // overlap between the path grammar and Markdown.
+                //
+                // The example above shows that ambiguity. Is `[u32]` intended to be an
+                // intra-doc link to the u32 primitive, or is it intended to be a slice?
+                //
+                // If the below conditional were removed, we would suggest this, which is
+                // not what the user probably wants.
+                //
+                //     <[u32] as `IntoIter<u32>`>::Item
+                //
+                // We know that the user actually wants to wrap the whole thing in a code
+                // block, but the only reason we know that is because `u32` does not, in
+                // fact, implement IntoIter. If the example looks like this:
+                //
+                //     <[Vec<i32>] as IntoIter<i32>::Item
+                //
+                // The ideal fix would be significantly different.
+                if (generics_start > 0 && dox.as_bytes()[generics_start - 1] == b'<')
+                    || (generics_end < dox.len() && dox.as_bytes()[generics_end] == b'>')
+                {
+                    return;
+                }
+                // multipart form is chosen here because ``Vec<i32>`` would be confusing.
+                lint.multipart_suggestion(
+                    "try marking as source code",
+                    vec![
+                        (generics_sp.shrink_to_lo(), String::from("`")),
+                        (generics_sp.shrink_to_hi(), String::from("`")),
+                    ],
+                    Applicability::MaybeIncorrect,
+                );
+            }
+        });
+    };
 
-        let mut tags = Vec::new();
-        let mut is_in_comment = None;
-        let mut in_code_block = false;
+    let mut tags = Vec::new();
+    let mut is_in_comment = None;
+    let mut in_code_block = false;
 
-        let link_names = item.link_names(&cx.cache);
+    let link_names = item.link_names(&cx.cache);
 
-        let mut replacer = |broken_link: BrokenLink<'_>| {
-            if let Some(link) =
-                link_names.iter().find(|link| *link.original_text == *broken_link.reference)
-            {
-                Some((link.href.as_str().into(), link.new_text.to_string().into()))
-            } else if matches!(
-                &broken_link.link_type,
-                LinkType::Reference | LinkType::ReferenceUnknown
-            ) {
-                // If the link is shaped [like][this], suppress any broken HTML in the [this] part.
-                // The `broken_intra_doc_links` will report typos in there anyway.
-                Some((
-                    broken_link.reference.to_string().into(),
-                    broken_link.reference.to_string().into(),
-                ))
-            } else {
-                None
-            }
-        };
+    let mut replacer = |broken_link: BrokenLink<'_>| {
+        if let Some(link) =
+            link_names.iter().find(|link| *link.original_text == *broken_link.reference)
+        {
+            Some((link.href.as_str().into(), link.new_text.to_string().into()))
+        } else if matches!(&broken_link.link_type, LinkType::Reference | LinkType::ReferenceUnknown)
+        {
+            // If the link is shaped [like][this], suppress any broken HTML in the [this] part.
+            // The `broken_intra_doc_links` will report typos in there anyway.
+            Some((
+                broken_link.reference.to_string().into(),
+                broken_link.reference.to_string().into(),
+            ))
+        } else {
+            None
+        }
+    };
 
-        let p = Parser::new_with_broken_link_callback(&dox, main_body_opts(), Some(&mut replacer))
-            .into_offset_iter();
+    let p = Parser::new_with_broken_link_callback(&dox, main_body_opts(), Some(&mut replacer))
+        .into_offset_iter();
 
-        for (event, range) in p {
-            match event {
-                Event::Start(Tag::CodeBlock(_)) => in_code_block = true,
-                Event::Html(text) | Event::InlineHtml(text) if !in_code_block => {
-                    extract_tags(&mut tags, &text, range, &mut is_in_comment, &report_diag)
-                }
-                Event::End(TagEnd::CodeBlock) => in_code_block = false,
-                _ => {}
+    for (event, range) in p {
+        match event {
+            Event::Start(Tag::CodeBlock(_)) => in_code_block = true,
+            Event::Html(text) | Event::InlineHtml(text) if !in_code_block => {
+                extract_tags(&mut tags, &text, range, &mut is_in_comment, &report_diag)
             }
+            Event::End(TagEnd::CodeBlock) => in_code_block = false,
+            _ => {}
         }
+    }
 
-        for (tag, range) in tags.iter().filter(|(t, _)| {
-            let t = t.to_lowercase();
-            !ALLOWED_UNCLOSED.contains(&t.as_str())
-        }) {
-            report_diag(format!("unclosed HTML tag `{tag}`"), range, true);
-        }
+    for (tag, range) in tags.iter().filter(|(t, _)| {
+        let t = t.to_lowercase();
+        !ALLOWED_UNCLOSED.contains(&t.as_str())
+    }) {
+        report_diag(format!("unclosed HTML tag `{tag}`"), range, true);
+    }
 
-        if let Some(range) = is_in_comment {
-            report_diag("Unclosed HTML comment".to_string(), &range, false);
-        }
+    if let Some(range) = is_in_comment {
+        report_diag("Unclosed HTML comment".to_string(), &range, false);
     }
 }
 
diff --git a/src/librustdoc/passes/lint/redundant_explicit_links.rs b/src/librustdoc/passes/lint/redundant_explicit_links.rs
index 0a90c039dfb..9c37e11349a 100644
--- a/src/librustdoc/passes/lint/redundant_explicit_links.rs
+++ b/src/librustdoc/passes/lint/redundant_explicit_links.rs
@@ -24,12 +24,7 @@ struct LinkData {
     display_link: String,
 }
 
-pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
-    let Some(hir_id) = DocContext::as_local_hir_id(cx.tcx, item.item_id) else {
-        // If non-local, no need to check anything.
-        return;
-    };
-
+pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId) {
     let hunks = prepare_to_doc_link_resolution(&item.attrs.doc_strings);
     for (item_id, doc) in hunks {
         if let Some(item_id) = item_id.or(item.def_id())
diff --git a/src/librustdoc/passes/lint/unescaped_backticks.rs b/src/librustdoc/passes/lint/unescaped_backticks.rs
index a6c8db16f82..d79f682a580 100644
--- a/src/librustdoc/passes/lint/unescaped_backticks.rs
+++ b/src/librustdoc/passes/lint/unescaped_backticks.rs
@@ -4,6 +4,7 @@ use std::ops::Range;
 
 use pulldown_cmark::{BrokenLink, Event, Parser};
 use rustc_errors::Diag;
+use rustc_hir::HirId;
 use rustc_lint_defs::Applicability;
 use rustc_resolve::rustdoc::source_span_for_markdown_range;
 
@@ -11,17 +12,8 @@ use crate::clean::Item;
 use crate::core::DocContext;
 use crate::html::markdown::main_body_opts;
 
-pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
+pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &str) {
     let tcx = cx.tcx;
-    let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id) else {
-        // If non-local, no need to check anything.
-        return;
-    };
-
-    let dox = item.doc_value();
-    if dox.is_empty() {
-        return;
-    }
 
     let link_names = item.link_names(&cx.cache);
     let mut replacer = |broken_link: BrokenLink<'_>| {
diff --git a/src/librustdoc/passes/lint/unportable_markdown.rs b/src/librustdoc/passes/lint/unportable_markdown.rs
index 87fe0055883..f8368a866c8 100644
--- a/src/librustdoc/passes/lint/unportable_markdown.rs
+++ b/src/librustdoc/passes/lint/unportable_markdown.rs
@@ -12,6 +12,7 @@
 
 use std::collections::{BTreeMap, BTreeSet};
 
+use rustc_hir::HirId;
 use rustc_lint_defs::Applicability;
 use rustc_resolve::rustdoc::source_span_for_markdown_range;
 use {pulldown_cmark as cmarkn, pulldown_cmark_old as cmarko};
@@ -19,17 +20,8 @@ use {pulldown_cmark as cmarkn, pulldown_cmark_old as cmarko};
 use crate::clean::Item;
 use crate::core::DocContext;
 
-pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
+pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: &str) {
     let tcx = cx.tcx;
-    let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id) else {
-        // If non-local, no need to check anything.
-        return;
-    };
-
-    let dox = item.doc_value();
-    if dox.is_empty() {
-        return;
-    }
 
     // P1: unintended strikethrough was fixed by requiring single-tildes to flank
     // the same way underscores do, so nothing is done here
diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs
index 5aa6a5ee604..6be51dd1560 100644
--- a/src/librustdoc/passes/propagate_doc_cfg.rs
+++ b/src/librustdoc/passes/propagate_doc_cfg.rs
@@ -31,7 +31,7 @@ impl<'a, 'tcx> CfgPropagator<'a, 'tcx> {
     // Some items need to merge their attributes with their parents' otherwise a few of them
     // (mostly `cfg` ones) will be missing.
     fn merge_with_parent_attributes(&mut self, item: &mut Item) {
-        let check_parent = match &*item.kind {
+        let check_parent = match &item.kind {
             // impl blocks can be in different modules with different cfg and we need to get them
             // as well.
             ItemKind::ImplItem(_) => false,
diff --git a/src/librustdoc/passes/strip_aliased_non_local.rs b/src/librustdoc/passes/strip_aliased_non_local.rs
index e1dd2d3af0f..6078ab36528 100644
--- a/src/librustdoc/passes/strip_aliased_non_local.rs
+++ b/src/librustdoc/passes/strip_aliased_non_local.rs
@@ -23,7 +23,7 @@ struct AliasedNonLocalStripper<'tcx> {
 
 impl<'tcx> DocFolder for AliasedNonLocalStripper<'tcx> {
     fn fold_item(&mut self, i: Item) -> Option<Item> {
-        Some(match *i.kind {
+        Some(match i.kind {
             clean::TypeAliasItem(..) => {
                 let mut stripper = NonLocalStripper { tcx: self.tcx };
                 // don't call `fold_item` as that could strip the type-alias it-self
diff --git a/src/librustdoc/passes/strip_hidden.rs b/src/librustdoc/passes/strip_hidden.rs
index 86f14ddbe85..7c5fbac94e4 100644
--- a/src/librustdoc/passes/strip_hidden.rs
+++ b/src/librustdoc/passes/strip_hidden.rs
@@ -89,7 +89,7 @@ impl<'a, 'tcx> Stripper<'a, 'tcx> {
 impl<'a, 'tcx> DocFolder for Stripper<'a, 'tcx> {
     fn fold_item(&mut self, i: Item) -> Option<Item> {
         let has_doc_hidden = i.is_doc_hidden();
-        let is_impl_or_exported_macro = match *i.kind {
+        let is_impl_or_exported_macro = match i.kind {
             clean::ImplItem(..) => true,
             // If the macro has the `#[macro_export]` attribute, it means it's accessible at the
             // crate level so it should be handled differently.
@@ -138,7 +138,7 @@ impl<'a, 'tcx> DocFolder for Stripper<'a, 'tcx> {
         // module it's defined in. Both of these are marked "stripped," and
         // not included in the final docs, but since they still have an effect
         // on the final doc, cannot be completely removed from the Clean IR.
-        match *i.kind {
+        match i.kind {
             clean::StructFieldItem(..) | clean::ModuleItem(..) | clean::VariantItem(..) => {
                 // We need to recurse into stripped modules to
                 // strip things like impl methods but when doing so
diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs
index 3f706ac951f..a85428f8742 100644
--- a/src/librustdoc/passes/stripper.rs
+++ b/src/librustdoc/passes/stripper.rs
@@ -39,7 +39,7 @@ fn is_item_reachable(
 
 impl<'a, 'tcx> DocFolder for Stripper<'a, 'tcx> {
     fn fold_item(&mut self, i: Item) -> Option<Item> {
-        match *i.kind {
+        match i.kind {
             clean::StrippedItem(..) => {
                 // We need to recurse into stripped modules to strip things
                 // like impl methods but when doing so we must not add any
@@ -130,7 +130,7 @@ impl<'a, 'tcx> DocFolder for Stripper<'a, 'tcx> {
             clean::KeywordItem => {}
         }
 
-        let fastreturn = match *i.kind {
+        let fastreturn = match i.kind {
             // nothing left to do for traits (don't want to filter their
             // methods out, visibility controlled by the trait)
             clean::TraitItem(..) => true,
@@ -195,7 +195,7 @@ impl<'a> ImplStripper<'a, '_> {
 
 impl<'a> DocFolder for ImplStripper<'a, '_> {
     fn fold_item(&mut self, i: Item) -> Option<Item> {
-        if let clean::ImplItem(ref imp) = *i.kind {
+        if let clean::ImplItem(ref imp) = i.kind {
             // Impl blocks can be skipped if they are: empty; not a trait impl; and have no
             // documentation.
             //
@@ -272,7 +272,7 @@ impl<'tcx> ImportStripper<'tcx> {
 
 impl<'tcx> DocFolder for ImportStripper<'tcx> {
     fn fold_item(&mut self, i: Item) -> Option<Item> {
-        match *i.kind {
+        match &i.kind {
             clean::ImportItem(imp)
                 if !self.document_hidden && self.import_should_be_hidden(&i, &imp) =>
             {
diff --git a/src/librustdoc/visit.rs b/src/librustdoc/visit.rs
index 430bbe991ea..7fb0a32cc94 100644
--- a/src/librustdoc/visit.rs
+++ b/src/librustdoc/visit.rs
@@ -48,8 +48,8 @@ pub(crate) trait DocVisitor: Sized {
 
     /// don't override!
     fn visit_item_recur(&mut self, item: &Item) {
-        match &*item.kind {
-            StrippedItem(i) => self.visit_inner_recur(i),
+        match &item.kind {
+            StrippedItem(i) => self.visit_inner_recur(&*i),
             _ => self.visit_inner_recur(&item.kind),
         }
     }
diff --git a/src/llvm-project b/src/llvm-project
-Subproject 2b259b3c201f939f2ed8d2fb0a0e9b37dd2d132
+Subproject 4b8d29c585687084bbcf21471e04f279d1eddc0
diff --git a/src/tools/build_helper/src/git.rs b/src/tools/build_helper/src/git.rs
index cc48a8964a3..15d863caf0c 100644
--- a/src/tools/build_helper/src/git.rs
+++ b/src/tools/build_helper/src/git.rs
@@ -1,9 +1,10 @@
-use std::path::Path;
+use std::path::{Path, PathBuf};
 use std::process::{Command, Stdio};
 
 pub struct GitConfig<'a> {
     pub git_repository: &'a str,
     pub nightly_branch: &'a str,
+    pub git_merge_commit_email: &'a str,
 }
 
 /// Runs a command and returns the output
@@ -95,7 +96,11 @@ pub fn updated_master_branch(
     Err("Cannot find any suitable upstream master branch".to_owned())
 }
 
-pub fn get_git_merge_base(
+/// Finds the nearest merge commit by comparing the local `HEAD` with the upstream branch's state.
+/// To work correctly, the upstream remote must be properly configured using `git remote add <name> <url>`.
+/// In most cases `get_closest_merge_commit` is the function you are looking for as it doesn't require remote
+/// to be configured.
+fn git_upstream_merge_base(
     config: &GitConfig<'_>,
     git_dir: Option<&Path>,
 ) -> Result<String, String> {
@@ -107,6 +112,38 @@ pub fn get_git_merge_base(
     Ok(output_result(git.arg("merge-base").arg(&updated_master).arg("HEAD"))?.trim().to_owned())
 }
 
+/// Searches for the nearest merge commit in the repository that also exists upstream.
+///
+/// If it fails to find the upstream remote, it then looks for the most recent commit made
+/// by the merge bot by matching the author's email address with the merge bot's email.
+pub fn get_closest_merge_commit(
+    git_dir: Option<&Path>,
+    config: &GitConfig<'_>,
+    target_paths: &[PathBuf],
+) -> Result<String, String> {
+    let mut git = Command::new("git");
+
+    if let Some(git_dir) = git_dir {
+        git.current_dir(git_dir);
+    }
+
+    let merge_base = git_upstream_merge_base(config, git_dir).unwrap_or_else(|_| "HEAD".into());
+
+    git.args([
+        "rev-list",
+        &format!("--author={}", config.git_merge_commit_email),
+        "-n1",
+        "--first-parent",
+        &merge_base,
+    ]);
+
+    if !target_paths.is_empty() {
+        git.arg("--").args(target_paths);
+    }
+
+    Ok(output_result(&mut git)?.trim().to_owned())
+}
+
 /// Returns the files that have been modified in the current branch compared to the master branch.
 /// The `extensions` parameter can be used to filter the files by their extension.
 /// Does not include removed files.
@@ -116,7 +153,7 @@ pub fn get_git_modified_files(
     git_dir: Option<&Path>,
     extensions: &[&str],
 ) -> Result<Option<Vec<String>>, String> {
-    let merge_base = get_git_merge_base(config, git_dir)?;
+    let merge_base = get_closest_merge_commit(git_dir, config, &[])?;
 
     let mut git = Command::new("git");
     if let Some(git_dir) = git_dir {
diff --git a/src/tools/clippy/clippy_utils/src/ast_utils.rs b/src/tools/clippy/clippy_utils/src/ast_utils.rs
index 181bbdde8e5..de8bbb619f8 100644
--- a/src/tools/clippy/clippy_utils/src/ast_utils.rs
+++ b/src/tools/clippy/clippy_utils/src/ast_utils.rs
@@ -221,7 +221,7 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
         ) => {
             eq_closure_binder(lb, rb)
                 && lc == rc
-                && la.map_or(false, CoroutineKind::is_async) == ra.map_or(false, CoroutineKind::is_async)
+                && eq_coroutine_kind(*la, *ra)
                 && lm == rm
                 && eq_fn_decl(lf, rf)
                 && eq_expr(le, re)
@@ -241,6 +241,16 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
     }
 }
 
+fn eq_coroutine_kind(a: Option<CoroutineKind>, b: Option<CoroutineKind>) -> bool {
+    match (a, b) {
+        (Some(CoroutineKind::Async { .. }), Some(CoroutineKind::Async { .. }))
+        | (Some(CoroutineKind::Gen { .. }), Some(CoroutineKind::Gen { .. }))
+        | (Some(CoroutineKind::AsyncGen { .. }), Some(CoroutineKind::AsyncGen { .. }))
+        | (None, None) => true,
+        _ => false,
+    }
+}
+
 pub fn eq_field(l: &ExprField, r: &ExprField) -> bool {
     l.is_placeholder == r.is_placeholder
         && eq_id(l.ident, r.ident)
diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs
index 773d795f75a..5c18179b6fe 100644
--- a/src/tools/compiletest/src/common.rs
+++ b/src/tools/compiletest/src/common.rs
@@ -384,6 +384,7 @@ pub struct Config {
     // Needed both to construct build_helper::git::GitConfig
     pub git_repository: String,
     pub nightly_branch: String,
+    pub git_merge_commit_email: String,
 
     /// True if the profiler runtime is enabled for this target.
     /// Used by the "needs-profiler-support" header in test files.
@@ -461,7 +462,11 @@ impl Config {
     }
 
     pub fn git_config(&self) -> GitConfig<'_> {
-        GitConfig { git_repository: &self.git_repository, nightly_branch: &self.nightly_branch }
+        GitConfig {
+            git_repository: &self.git_repository,
+            nightly_branch: &self.nightly_branch,
+            git_merge_commit_email: &self.git_merge_commit_email,
+        }
     }
 }
 
diff --git a/src/tools/compiletest/src/header/tests.rs b/src/tools/compiletest/src/header/tests.rs
index 29e11e77f1c..3a9a7eb9118 100644
--- a/src/tools/compiletest/src/header/tests.rs
+++ b/src/tools/compiletest/src/header/tests.rs
@@ -148,6 +148,7 @@ impl ConfigBuilder {
             self.target.as_deref().unwrap_or("x86_64-unknown-linux-gnu"),
             "--git-repository=",
             "--nightly-branch=",
+            "--git-merge-commit-email=",
         ];
         let mut args: Vec<String> = args.iter().map(ToString::to_string).collect();
 
diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs
index 33687a3dad3..250b5084d13 100644
--- a/src/tools/compiletest/src/lib.rs
+++ b/src/tools/compiletest/src/lib.rs
@@ -163,7 +163,13 @@ pub fn parse_config(args: Vec<String>) -> Config {
         )
         .optopt("", "edition", "default Rust edition", "EDITION")
         .reqopt("", "git-repository", "name of the git repository", "ORG/REPO")
-        .reqopt("", "nightly-branch", "name of the git branch for nightly", "BRANCH");
+        .reqopt("", "nightly-branch", "name of the git branch for nightly", "BRANCH")
+        .reqopt(
+            "",
+            "git-merge-commit-email",
+            "email address used for finding merge commits",
+            "EMAIL",
+        );
 
     let (argv0, args_) = args.split_first().unwrap();
     if args.len() == 1 || args[1] == "-h" || args[1] == "--help" {
@@ -346,6 +352,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
 
         git_repository: matches.opt_str("git-repository").unwrap(),
         nightly_branch: matches.opt_str("nightly-branch").unwrap(),
+        git_merge_commit_email: matches.opt_str("git-merge-commit-email").unwrap(),
 
         profiler_support: matches.opt_present("profiler-support"),
     }
@@ -640,6 +647,12 @@ fn common_inputs_stamp(config: &Config) -> Stamp {
         stamp.add_path(&rust_src_dir.join("src/etc/htmldocck.py"));
     }
 
+    // Re-run coverage tests if the `coverage-dump` tool was modified,
+    // because its output format might have changed.
+    if let Some(coverage_dump_path) = &config.coverage_dump_path {
+        stamp.add_path(coverage_dump_path)
+    }
+
     stamp.add_dir(&rust_src_dir.join("src/tools/run-make-support"));
 
     // Compiletest itself.
diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs
index 6fd207c92b9..b604fd868a0 100644
--- a/src/tools/miri/src/concurrency/data_race.rs
+++ b/src/tools/miri/src/concurrency/data_race.rs
@@ -637,7 +637,7 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> {
         // The program didn't actually do a read, so suppress the memory access hooks.
         // This is also a very special exception where we just ignore an error -- if this read
         // was UB e.g. because the memory is uninitialized, we don't want to know!
-        let old_val = this.run_for_validation(|| this.read_scalar(dest)).ok();
+        let old_val = this.run_for_validation(|this| this.read_scalar(dest)).ok();
         this.allow_data_races_mut(move |this| this.write_scalar(val, dest))?;
         this.validate_atomic_store(dest, atomic)?;
         this.buffered_atomic_write(val, dest, atomic, old_val)
diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs
index a546ad20fef..cba99c0bd7a 100644
--- a/src/tools/miri/src/helpers.rs
+++ b/src/tools/miri/src/helpers.rs
@@ -869,7 +869,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     /// Dereference a pointer operand to a place using `layout` instead of the pointer's declared type
     fn deref_pointer_as(
         &self,
-        op: &impl Readable<'tcx, Provenance>,
+        op: &impl Projectable<'tcx, Provenance>,
         layout: TyAndLayout<'tcx>,
     ) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
         let this = self.eval_context_ref();
@@ -880,7 +880,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     /// Calculates the MPlaceTy given the offset and layout of an access on an operand
     fn deref_pointer_and_offset(
         &self,
-        op: &impl Readable<'tcx, Provenance>,
+        op: &impl Projectable<'tcx, Provenance>,
         offset: u64,
         base_layout: TyAndLayout<'tcx>,
         value_layout: TyAndLayout<'tcx>,
@@ -897,7 +897,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
     fn deref_pointer_and_read(
         &self,
-        op: &impl Readable<'tcx, Provenance>,
+        op: &impl Projectable<'tcx, Provenance>,
         offset: u64,
         base_layout: TyAndLayout<'tcx>,
         value_layout: TyAndLayout<'tcx>,
@@ -909,7 +909,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
     fn deref_pointer_and_write(
         &mut self,
-        op: &impl Readable<'tcx, Provenance>,
+        op: &impl Projectable<'tcx, Provenance>,
         offset: u64,
         value: impl Into<Scalar>,
         base_layout: TyAndLayout<'tcx>,
diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs
index 18b22827bdb..0ab1b9dfb61 100644
--- a/src/tools/miri/src/intrinsics/mod.rs
+++ b/src/tools/miri/src/intrinsics/mod.rs
@@ -152,8 +152,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // ```
             // Would not be considered UB, or the other way around (`is_val_statically_known(0)`).
             "is_val_statically_known" => {
-                let [arg] = check_arg_count(args)?;
-                this.validate_operand(arg, /*recursive*/ false)?;
+                let [_arg] = check_arg_count(args)?;
+                // FIXME: should we check for validity here? It's tricky because we do not have a
+                // place. Codegen does not seem to set any attributes like `noundef` for intrinsic
+                // calls, so we don't *have* to do anything.
                 let branch: bool = this.machine.rng.get_mut().gen();
                 this.write_scalar(Scalar::from_bool(branch), dest)?;
             }
diff --git a/src/tools/miri/src/intrinsics/simd.rs b/src/tools/miri/src/intrinsics/simd.rs
index a5afd029b65..85d7459bb45 100644
--- a/src/tools/miri/src/intrinsics/simd.rs
+++ b/src/tools/miri/src/intrinsics/simd.rs
@@ -666,22 +666,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let (right, right_len) = this.operand_to_simd(right)?;
                 let (dest, dest_len) = this.mplace_to_simd(dest)?;
 
-                // `index` is an array, not a SIMD type
-                let ty::Array(_, index_len) = index.layout.ty.kind() else {
-                    span_bug!(
-                        this.cur_span(),
-                        "simd_shuffle index argument has non-array type {}",
-                        index.layout.ty
-                    )
+                // `index` is an array or a SIMD type
+                let (index, index_len) = match index.layout.ty.kind() {
+                    // FIXME: remove this once `index` must always be a SIMD vector.
+                    ty::Array(..) => (index.assert_mem_place(), index.len(this)?),
+                    _ => this.operand_to_simd(index)?,
                 };
-                let index_len = index_len.eval_target_usize(*this.tcx, this.param_env());
 
                 assert_eq!(left_len, right_len);
                 assert_eq!(index_len, dest_len);
 
                 for i in 0..dest_len {
                     let src_index: u64 = this
-                        .read_immediate(&this.project_index(index, i)?)?
+                        .read_immediate(&this.project_index(&index, i)?)?
                         .to_scalar()
                         .to_u32()?
                         .into();
diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs
index 2cd57e72871..76b4366476d 100644
--- a/src/tools/miri/src/machine.rs
+++ b/src/tools/miri/src/machine.rs
@@ -572,6 +572,9 @@ pub struct MiriMachine<'tcx> {
     /// Invariant: the promised alignment will never be less than the native alignment of the
     /// allocation.
     pub(crate) symbolic_alignment: RefCell<FxHashMap<AllocId, (Size, Align)>>,
+
+    /// A cache of "data range" computations for unions (i.e., the offsets of non-padding bytes).
+    union_data_ranges: FxHashMap<Ty<'tcx>, RangeSet>,
 }
 
 impl<'tcx> MiriMachine<'tcx> {
@@ -714,6 +717,7 @@ impl<'tcx> MiriMachine<'tcx> {
             allocation_spans: RefCell::new(FxHashMap::default()),
             const_cache: RefCell::new(FxHashMap::default()),
             symbolic_alignment: RefCell::new(FxHashMap::default()),
+            union_data_ranges: FxHashMap::default(),
         }
     }
 
@@ -826,6 +830,7 @@ impl VisitProvenance for MiriMachine<'_> {
             allocation_spans: _,
             const_cache: _,
             symbolic_alignment: _,
+            union_data_ranges: _,
         } = self;
 
         threads.visit_provenance(visit);
@@ -1627,4 +1632,12 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
             ecx.machine.rng.borrow_mut().gen::<usize>() % ADDRS_PER_ANON_GLOBAL
         }
     }
+
+    fn cached_union_data_range<'e>(
+        ecx: &'e mut InterpCx<'tcx, Self>,
+        ty: Ty<'tcx>,
+        compute_range: impl FnOnce() -> RangeSet,
+    ) -> Cow<'e, RangeSet> {
+        Cow::Borrowed(ecx.machine.union_data_ranges.entry(ty).or_insert_with(compute_range))
+    }
 }
diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_unsigned_overflow.rs b/src/tools/miri/tests/fail/intrinsics/ptr_offset_unsigned_overflow.rs
new file mode 100644
index 00000000000..a2739842bc1
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_unsigned_overflow.rs
@@ -0,0 +1,7 @@
+fn main() {
+    let x = &[0i32; 2];
+    let x = x.as_ptr().wrapping_add(1);
+    // If the `!0` is interpreted as `isize`, it is just `-1` and hence harmless.
+    // However, this is unsigned arithmetic, so really this is `usize::MAX` and hence UB.
+    unsafe { x.byte_add(!0).read() }; //~ERROR: does not fit in an `isize`
+}
diff --git a/src/tools/miri/tests/fail/intrinsics/ptr_offset_unsigned_overflow.stderr b/src/tools/miri/tests/fail/intrinsics/ptr_offset_unsigned_overflow.stderr
new file mode 100644
index 00000000000..43cd80a6d3e
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsics/ptr_offset_unsigned_overflow.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: overflowing pointer arithmetic: the total offset in bytes does not fit in an `isize`
+  --> $DIR/ptr_offset_unsigned_overflow.rs:LL:CC
+   |
+LL |     unsafe { x.byte_add(!0).read() };
+   |              ^^^^^^^^^^^^^^ overflowing pointer arithmetic: the total offset in bytes does not fit in an `isize`
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/ptr_offset_unsigned_overflow.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/intrinsics/simd-div-by-zero.rs b/src/tools/miri/tests/fail/intrinsics/simd-div-by-zero.rs
index ba474332b81..57a9b66d8ec 100644
--- a/src/tools/miri/tests/fail/intrinsics/simd-div-by-zero.rs
+++ b/src/tools/miri/tests/fail/intrinsics/simd-div-by-zero.rs
@@ -4,12 +4,12 @@ use std::intrinsics::simd::simd_div;
 
 #[repr(simd)]
 #[allow(non_camel_case_types)]
-struct i32x2(i32, i32);
+struct i32x2([i32; 2]);
 
 fn main() {
     unsafe {
-        let x = i32x2(1, 1);
-        let y = i32x2(1, 0);
+        let x = i32x2([1, 1]);
+        let y = i32x2([1, 0]);
         simd_div(x, y); //~ERROR: Undefined Behavior: dividing by zero
     }
 }
diff --git a/src/tools/miri/tests/fail/intrinsics/simd-div-overflow.rs b/src/tools/miri/tests/fail/intrinsics/simd-div-overflow.rs
index d01e41de0e4..8ffc2669828 100644
--- a/src/tools/miri/tests/fail/intrinsics/simd-div-overflow.rs
+++ b/src/tools/miri/tests/fail/intrinsics/simd-div-overflow.rs
@@ -4,12 +4,12 @@ use std::intrinsics::simd::simd_div;
 
 #[repr(simd)]
 #[allow(non_camel_case_types)]
-struct i32x2(i32, i32);
+struct i32x2([i32; 2]);
 
 fn main() {
     unsafe {
-        let x = i32x2(1, i32::MIN);
-        let y = i32x2(1, -1);
+        let x = i32x2([1, i32::MIN]);
+        let y = i32x2([1, -1]);
         simd_div(x, y); //~ERROR: Undefined Behavior: overflow in signed division
     }
 }
diff --git a/src/tools/miri/tests/fail/intrinsics/simd-reduce-invalid-bool.rs b/src/tools/miri/tests/fail/intrinsics/simd-reduce-invalid-bool.rs
index a194f0dd18a..ea0f908d996 100644
--- a/src/tools/miri/tests/fail/intrinsics/simd-reduce-invalid-bool.rs
+++ b/src/tools/miri/tests/fail/intrinsics/simd-reduce-invalid-bool.rs
@@ -4,11 +4,11 @@ use std::intrinsics::simd::simd_reduce_any;
 
 #[repr(simd)]
 #[allow(non_camel_case_types)]
-struct i32x2(i32, i32);
+struct i32x2([i32; 2]);
 
 fn main() {
     unsafe {
-        let x = i32x2(0, 1);
+        let x = i32x2([0, 1]);
         simd_reduce_any(x); //~ERROR: must be all-0-bits or all-1-bits
     }
 }
diff --git a/src/tools/miri/tests/fail/intrinsics/simd-rem-by-zero.rs b/src/tools/miri/tests/fail/intrinsics/simd-rem-by-zero.rs
index cd1e4b8162b..21c9520efc4 100644
--- a/src/tools/miri/tests/fail/intrinsics/simd-rem-by-zero.rs
+++ b/src/tools/miri/tests/fail/intrinsics/simd-rem-by-zero.rs
@@ -4,12 +4,12 @@ use std::intrinsics::simd::simd_rem;
 
 #[repr(simd)]
 #[allow(non_camel_case_types)]
-struct i32x2(i32, i32);
+struct i32x2([i32; 2]);
 
 fn main() {
     unsafe {
-        let x = i32x2(1, 1);
-        let y = i32x2(1, 0);
+        let x = i32x2([1, 1]);
+        let y = i32x2([1, 0]);
         simd_rem(x, y); //~ERROR: Undefined Behavior: calculating the remainder with a divisor of zero
     }
 }
diff --git a/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.rs b/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.rs
index 96802fae49c..409098ac3b5 100644
--- a/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.rs
+++ b/src/tools/miri/tests/fail/intrinsics/simd-select-bitmask-invalid.rs
@@ -5,11 +5,11 @@ use std::intrinsics::simd::simd_select_bitmask;
 #[repr(simd)]
 #[allow(non_camel_case_types)]
 #[derive(Copy, Clone)]
-struct i32x2(i32, i32);
+struct i32x2([i32; 2]);
 
 fn main() {
     unsafe {
-        let x = i32x2(0, 1);
+        let x = i32x2([0, 1]);
         simd_select_bitmask(0b11111111u8, x, x); //~ERROR: bitmask less than 8 bits long must be filled with 0s for the remaining bits
     }
 }
diff --git a/src/tools/miri/tests/fail/intrinsics/simd-select-invalid-bool.rs b/src/tools/miri/tests/fail/intrinsics/simd-select-invalid-bool.rs
index 388fb2e2a84..a81ce95ada6 100644
--- a/src/tools/miri/tests/fail/intrinsics/simd-select-invalid-bool.rs
+++ b/src/tools/miri/tests/fail/intrinsics/simd-select-invalid-bool.rs
@@ -5,11 +5,11 @@ use std::intrinsics::simd::simd_select;
 #[repr(simd)]
 #[allow(non_camel_case_types)]
 #[derive(Copy, Clone)]
-struct i32x2(i32, i32);
+struct i32x2([i32; 2]);
 
 fn main() {
     unsafe {
-        let x = i32x2(0, 1);
+        let x = i32x2([0, 1]);
         simd_select(x, x, x); //~ERROR: must be all-0-bits or all-1-bits
     }
 }
diff --git a/src/tools/miri/tests/fail/intrinsics/simd-shl-too-far.rs b/src/tools/miri/tests/fail/intrinsics/simd-shl-too-far.rs
index 12aa7c10af4..ed317254ee6 100644
--- a/src/tools/miri/tests/fail/intrinsics/simd-shl-too-far.rs
+++ b/src/tools/miri/tests/fail/intrinsics/simd-shl-too-far.rs
@@ -4,12 +4,12 @@ use std::intrinsics::simd::simd_shl;
 
 #[repr(simd)]
 #[allow(non_camel_case_types)]
-struct i32x2(i32, i32);
+struct i32x2([i32; 2]);
 
 fn main() {
     unsafe {
-        let x = i32x2(1, 1);
-        let y = i32x2(100, 0);
+        let x = i32x2([1, 1]);
+        let y = i32x2([100, 0]);
         simd_shl(x, y); //~ERROR: overflowing shift by 100 in `simd_shl` in lane 0
     }
 }
diff --git a/src/tools/miri/tests/fail/intrinsics/simd-shr-too-far.rs b/src/tools/miri/tests/fail/intrinsics/simd-shr-too-far.rs
index ada7cf408c4..5d2ff1b82ed 100644
--- a/src/tools/miri/tests/fail/intrinsics/simd-shr-too-far.rs
+++ b/src/tools/miri/tests/fail/intrinsics/simd-shr-too-far.rs
@@ -4,12 +4,12 @@ use std::intrinsics::simd::simd_shr;
 
 #[repr(simd)]
 #[allow(non_camel_case_types)]
-struct i32x2(i32, i32);
+struct i32x2([i32; 2]);
 
 fn main() {
     unsafe {
-        let x = i32x2(1, 1);
-        let y = i32x2(20, 40);
+        let x = i32x2([1, 1]);
+        let y = i32x2([20, 40]);
         simd_shr(x, y); //~ERROR: overflowing shift by 40 in `simd_shr` in lane 1
     }
 }
diff --git a/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance0.rs b/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance0.rs
new file mode 100644
index 00000000000..fd0773ed916
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance0.rs
@@ -0,0 +1,10 @@
+use std::mem;
+
+// Doing a copy at integer type should lose provenance.
+// This tests the unoptimized base case.
+fn main() {
+    let ptrs = [(&42, true)];
+    let ints: [(usize, bool); 1] = unsafe { mem::transmute(ptrs) };
+    let ptr = (&raw const ints[0].0).cast::<&i32>();
+    let _val = unsafe { *ptr.read() }; //~ERROR: dangling
+}
diff --git a/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance0.stderr b/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance0.stderr
new file mode 100644
index 00000000000..fc012af3ad8
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance0.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value: encountered a dangling reference ($HEX[noalloc] has no provenance)
+  --> $DIR/int_copy_looses_provenance0.rs:LL:CC
+   |
+LL |     let _val = unsafe { *ptr.read() };
+   |                          ^^^^^^^^^^ constructing invalid value: encountered a dangling reference ($HEX[noalloc] has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/int_copy_looses_provenance0.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance1.rs b/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance1.rs
new file mode 100644
index 00000000000..ce64dcc1a07
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance1.rs
@@ -0,0 +1,10 @@
+use std::mem;
+
+// Doing a copy at integer type should lose provenance.
+// This tests the optimized-array case of integer copies.
+fn main() {
+    let ptrs = [&42];
+    let ints: [usize; 1] = unsafe { mem::transmute(ptrs) };
+    let ptr = (&raw const ints[0]).cast::<&i32>();
+    let _val = unsafe { *ptr.read() }; //~ERROR: dangling
+}
diff --git a/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance1.stderr b/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance1.stderr
new file mode 100644
index 00000000000..375262655d0
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value: encountered a dangling reference ($HEX[noalloc] has no provenance)
+  --> $DIR/int_copy_looses_provenance1.rs:LL:CC
+   |
+LL |     let _val = unsafe { *ptr.read() };
+   |                          ^^^^^^^^^^ constructing invalid value: encountered a dangling reference ($HEX[noalloc] has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/int_copy_looses_provenance1.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance2.rs b/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance2.rs
new file mode 100644
index 00000000000..e8966c53d70
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance2.rs
@@ -0,0 +1,12 @@
+use std::mem;
+
+// Doing a copy at integer type should lose provenance.
+// This tests the case where provenacne is hiding in the metadata of a pointer.
+fn main() {
+    let ptrs = [(&42, &42)];
+    // Typed copy at wide pointer type (with integer-typed metadata).
+    let ints: [*const [usize]; 1] = unsafe { mem::transmute(ptrs) };
+    // Get a pointer to the metadata field.
+    let ptr = (&raw const ints[0]).wrapping_byte_add(mem::size_of::<*const ()>()).cast::<&i32>();
+    let _val = unsafe { *ptr.read() }; //~ERROR: dangling
+}
diff --git a/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance2.stderr b/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance2.stderr
new file mode 100644
index 00000000000..8402c7b5e13
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance2.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value: encountered a dangling reference ($HEX[noalloc] has no provenance)
+  --> $DIR/int_copy_looses_provenance2.rs:LL:CC
+   |
+LL |     let _val = unsafe { *ptr.read() };
+   |                          ^^^^^^^^^^ constructing invalid value: encountered a dangling reference ($HEX[noalloc] has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/int_copy_looses_provenance2.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance3.rs b/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance3.rs
new file mode 100644
index 00000000000..48a48ce4587
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance3.rs
@@ -0,0 +1,29 @@
+#![feature(strict_provenance)]
+use std::mem;
+
+#[repr(C, usize)]
+#[allow(unused)]
+enum E {
+    Var1(usize),
+    Var2(usize),
+}
+
+// Doing a copy at integer type should lose provenance.
+// This tests the case where provenacne is hiding in the discriminant of an enum.
+fn main() {
+    assert_eq!(mem::size_of::<E>(), 2*mem::size_of::<usize>());
+
+    // We want to store provenance in the enum discriminant, but the value still needs to
+    // be valid atfor the type. So we split provenance and data.
+    let ptr = &42;
+    let ptr = ptr as *const i32;
+    let ptrs = [(ptr.with_addr(0), ptr)];
+    // Typed copy at the enum type.
+    let ints: [E; 1] = unsafe { mem::transmute(ptrs) };
+    // Read the discriminant.
+    let discr = unsafe { (&raw const ints[0]).cast::<*const i32>().read() };
+    // Take the provenance from there, together with the original address.
+    let ptr = discr.with_addr(ptr.addr());
+    // There should be no provenance is `discr`, so this should be UB.
+    let _val = unsafe { *ptr }; //~ERROR: dangling
+}
diff --git a/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance3.stderr b/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance3.stderr
new file mode 100644
index 00000000000..b50e23da96a
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/int_copy_looses_provenance3.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: memory access failed: expected a pointer to 4 bytes of memory, but got $HEX[noalloc] which is a dangling pointer (it has no provenance)
+  --> $DIR/int_copy_looses_provenance3.rs:LL:CC
+   |
+LL |     let _val = unsafe { *ptr };
+   |                         ^^^^ memory access failed: expected a pointer to 4 bytes of memory, but got $HEX[noalloc] which is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/int_copy_looses_provenance3.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/provenance/ptr_copy_loses_partial_provenance0.rs b/src/tools/miri/tests/fail/provenance/ptr_copy_loses_partial_provenance0.rs
new file mode 100644
index 00000000000..ff94f2263c5
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/ptr_copy_loses_partial_provenance0.rs
@@ -0,0 +1,18 @@
+fn main() {
+    let half_ptr = std::mem::size_of::<*const ()>() / 2;
+    let mut bytes = [1u8; 16];
+    let bytes = bytes.as_mut_ptr();
+
+    unsafe {
+        // Put a pointer in the middle.
+        bytes.add(half_ptr).cast::<&i32>().write_unaligned(&42);
+        // Typed copy of the entire thing as two pointers, but not perfectly
+        // overlapping with the pointer we have in there.
+        let copy = bytes.cast::<[*const (); 2]>().read_unaligned();
+        let copy_bytes = copy.as_ptr().cast::<u8>();
+        // Now go to the middle of the copy and get the pointer back out.
+        let ptr = copy_bytes.add(half_ptr).cast::<*const i32>().read_unaligned();
+        // Dereferencing this should fail as the copy has removed the provenance.
+        let _val = *ptr; //~ERROR: dangling
+    }
+}
diff --git a/src/tools/miri/tests/fail/provenance/ptr_copy_loses_partial_provenance0.stderr b/src/tools/miri/tests/fail/provenance/ptr_copy_loses_partial_provenance0.stderr
new file mode 100644
index 00000000000..ed38572a5f3
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/ptr_copy_loses_partial_provenance0.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: memory access failed: expected a pointer to 4 bytes of memory, but got $HEX[noalloc] which is a dangling pointer (it has no provenance)
+  --> $DIR/ptr_copy_loses_partial_provenance0.rs:LL:CC
+   |
+LL |         let _val = *ptr;
+   |                    ^^^^ memory access failed: expected a pointer to 4 bytes of memory, but got $HEX[noalloc] which is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/ptr_copy_loses_partial_provenance0.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/provenance/ptr_copy_loses_partial_provenance1.rs b/src/tools/miri/tests/fail/provenance/ptr_copy_loses_partial_provenance1.rs
new file mode 100644
index 00000000000..d0e3dac7792
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/ptr_copy_loses_partial_provenance1.rs
@@ -0,0 +1,18 @@
+fn main() {
+    let half_ptr = std::mem::size_of::<*const ()>() / 2;
+    let mut bytes = [1u8; 16];
+    let bytes = bytes.as_mut_ptr();
+
+    unsafe {
+        // Put a pointer in the middle.
+        bytes.add(half_ptr).cast::<&i32>().write_unaligned(&42);
+        // Typed copy of the entire thing as two *function* pointers, but not perfectly
+        // overlapping with the pointer we have in there.
+        let copy = bytes.cast::<[fn(); 2]>().read_unaligned();
+        let copy_bytes = copy.as_ptr().cast::<u8>();
+        // Now go to the middle of the copy and get the pointer back out.
+        let ptr = copy_bytes.add(half_ptr).cast::<*const i32>().read_unaligned();
+        // Dereferencing this should fail as the copy has removed the provenance.
+        let _val = *ptr; //~ERROR: dangling
+    }
+}
diff --git a/src/tools/miri/tests/fail/provenance/ptr_copy_loses_partial_provenance1.stderr b/src/tools/miri/tests/fail/provenance/ptr_copy_loses_partial_provenance1.stderr
new file mode 100644
index 00000000000..2e11687175a
--- /dev/null
+++ b/src/tools/miri/tests/fail/provenance/ptr_copy_loses_partial_provenance1.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: memory access failed: expected a pointer to 4 bytes of memory, but got $HEX[noalloc] which is a dangling pointer (it has no provenance)
+  --> $DIR/ptr_copy_loses_partial_provenance1.rs:LL:CC
+   |
+LL |         let _val = *ptr;
+   |                    ^^^^ memory access failed: expected a pointer to 4 bytes of memory, but got $HEX[noalloc] which is a dangling pointer (it has no provenance)
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/ptr_copy_loses_partial_provenance1.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/uninit/padding-enum.rs b/src/tools/miri/tests/fail/uninit/padding-enum.rs
new file mode 100644
index 00000000000..3852ac5c477
--- /dev/null
+++ b/src/tools/miri/tests/fail/uninit/padding-enum.rs
@@ -0,0 +1,23 @@
+use std::mem;
+
+// We have three fields to avoid the ScalarPair optimization.
+#[allow(unused)]
+enum E {
+    None,
+    Some(&'static (), &'static (), usize),
+}
+
+fn main() { unsafe {
+    let mut p: mem::MaybeUninit<E> = mem::MaybeUninit::zeroed();
+    // The copy when `E` is returned from `transmute` should destroy padding
+    // (even when we use `write_unaligned`, which under the hood uses an untyped copy).
+    p.as_mut_ptr().write_unaligned(mem::transmute((0usize, 0usize, 0usize)));
+    // This is a `None`, so everything but the discriminant is padding.
+    assert!(matches!(*p.as_ptr(), E::None));
+
+    // Turns out the discriminant is (currently) stored
+    // in the 2nd pointer, so the first half is padding.
+    let c = &p as *const _ as *const u8;
+    let _val = *c.add(0); // Get a padding byte.
+    //~^ERROR: uninitialized
+} }
diff --git a/src/tools/miri/tests/fail/uninit/padding-enum.stderr b/src/tools/miri/tests/fail/uninit/padding-enum.stderr
new file mode 100644
index 00000000000..c571f188740
--- /dev/null
+++ b/src/tools/miri/tests/fail/uninit/padding-enum.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
+  --> $DIR/padding-enum.rs:LL:CC
+   |
+LL |     let _val = *c.add(0); // Get a padding byte.
+   |                ^^^^^^^^^ using uninitialized data, but this operation requires initialized memory
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/padding-enum.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/uninit/padding-pair.rs b/src/tools/miri/tests/fail/uninit/padding-pair.rs
new file mode 100644
index 00000000000..c8c00b3c65a
--- /dev/null
+++ b/src/tools/miri/tests/fail/uninit/padding-pair.rs
@@ -0,0 +1,25 @@
+#![feature(core_intrinsics)]
+
+use std::mem::{self, MaybeUninit};
+
+fn main() {
+    // This constructs a `(usize, bool)` pair: 9 bytes initialized, the rest not.
+    // Ensure that these 9 bytes are indeed initialized, and the rest is indeed not.
+    // This should be the case even if we write into previously initialized storage.
+    let mut x: MaybeUninit<Box<[u8]>> = MaybeUninit::zeroed();
+    let z = std::intrinsics::add_with_overflow(0usize, 0usize);
+    unsafe { x.as_mut_ptr().cast::<(usize, bool)>().write(z) };
+    // Now read this bytewise. There should be (`ptr_size + 1`) def bytes followed by
+    // (`ptr_size - 1`) undef bytes (the padding after the bool) in there.
+    let z: *const u8 = &x as *const _ as *const _;
+    let first_undef = mem::size_of::<usize>() as isize + 1;
+    for i in 0..first_undef {
+        let byte = unsafe { *z.offset(i) };
+        assert_eq!(byte, 0);
+    }
+    let v = unsafe { *z.offset(first_undef) };
+    //~^ ERROR: uninitialized
+    if v == 0 {
+        println!("it is zero");
+    }
+}
diff --git a/src/tools/miri/tests/fail/uninit/padding-pair.stderr b/src/tools/miri/tests/fail/uninit/padding-pair.stderr
new file mode 100644
index 00000000000..d35934d83d5
--- /dev/null
+++ b/src/tools/miri/tests/fail/uninit/padding-pair.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
+  --> $DIR/padding-pair.rs:LL:CC
+   |
+LL |     let v = unsafe { *z.offset(first_undef) };
+   |                      ^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/padding-pair.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/uninit/padding-struct-in-union.rs b/src/tools/miri/tests/fail/uninit/padding-struct-in-union.rs
new file mode 100644
index 00000000000..132b8582836
--- /dev/null
+++ b/src/tools/miri/tests/fail/uninit/padding-struct-in-union.rs
@@ -0,0 +1,32 @@
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+struct Foo {
+    val16: u16,
+    // Padding bytes go here!
+    val32: u32,
+}
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+struct Bar {
+    bytes: [u8; 8],
+}
+
+#[repr(C)]
+union FooBar {
+    foo: Foo,
+    bar: Bar,
+}
+
+pub fn main() {
+    // Initialize as u8 to ensure padding bytes are zeroed.
+    let mut foobar = FooBar { bar: Bar { bytes: [0u8; 8] } };
+    // Reading either field is ok.
+    let _val = unsafe { (foobar.foo, foobar.bar) };
+    // Does this assignment copy the uninitialized padding bytes
+    // over the initialized padding bytes? miri doesn't seem to think so.
+    foobar.foo = Foo { val16: 1, val32: 2 };
+    // This resets the padding to uninit.
+    let _val = unsafe { (foobar.foo, foobar.bar) };
+    //~^ ERROR: uninitialized
+}
diff --git a/src/tools/miri/tests/fail/uninit/padding-struct-in-union.stderr b/src/tools/miri/tests/fail/uninit/padding-struct-in-union.stderr
new file mode 100644
index 00000000000..e122249af16
--- /dev/null
+++ b/src/tools/miri/tests/fail/uninit/padding-struct-in-union.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value at .bytes[2]: encountered uninitialized memory, but expected an integer
+  --> $DIR/padding-struct-in-union.rs:LL:CC
+   |
+LL |     let _val = unsafe { (foobar.foo, foobar.bar) };
+   |                                      ^^^^^^^^^^ constructing invalid value at .bytes[2]: encountered uninitialized memory, but expected an integer
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/padding-struct-in-union.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/uninit/padding-struct.rs b/src/tools/miri/tests/fail/uninit/padding-struct.rs
new file mode 100644
index 00000000000..dd3be503439
--- /dev/null
+++ b/src/tools/miri/tests/fail/uninit/padding-struct.rs
@@ -0,0 +1,11 @@
+use std::mem;
+
+#[repr(C)]
+struct Pair(u8, u16);
+
+fn main() { unsafe {
+    let p: Pair = mem::transmute(0u32); // The copy when `Pair` is returned from `transmute` should destroy padding.
+    let c = &p as *const _ as *const u8;
+    let _val = *c.add(1); // Get the padding byte.
+    //~^ERROR: uninitialized
+} }
diff --git a/src/tools/miri/tests/fail/uninit/padding-struct.stderr b/src/tools/miri/tests/fail/uninit/padding-struct.stderr
new file mode 100644
index 00000000000..8dc40a482ac
--- /dev/null
+++ b/src/tools/miri/tests/fail/uninit/padding-struct.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
+  --> $DIR/padding-struct.rs:LL:CC
+   |
+LL |     let _val = *c.add(1); // Get the padding byte.
+   |                ^^^^^^^^^ using uninitialized data, but this operation requires initialized memory
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/padding-struct.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/uninit/padding-union.rs b/src/tools/miri/tests/fail/uninit/padding-union.rs
new file mode 100644
index 00000000000..2e9e0a40d6c
--- /dev/null
+++ b/src/tools/miri/tests/fail/uninit/padding-union.rs
@@ -0,0 +1,14 @@
+use std::mem;
+
+#[allow(unused)]
+#[repr(C)]
+union U {
+    field: (u8, u16),
+}
+
+fn main() { unsafe {
+    let p: U = mem::transmute(0u32); // The copy when `U` is returned from `transmute` should destroy padding.
+    let c = &p as *const _ as *const [u8; 4];
+    let _val = *c; // Read the entire thing, definitely contains the padding byte.
+    //~^ERROR: uninitialized
+} }
diff --git a/src/tools/miri/tests/fail/uninit/padding-union.stderr b/src/tools/miri/tests/fail/uninit/padding-union.stderr
new file mode 100644
index 00000000000..04002da4f19
--- /dev/null
+++ b/src/tools/miri/tests/fail/uninit/padding-union.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: constructing invalid value at [1]: encountered uninitialized memory, but expected an integer
+  --> $DIR/padding-union.rs:LL:CC
+   |
+LL |     let _val = *c; // Read the entire thing, definitely contains the padding byte.
+   |                ^^ constructing invalid value at [1]: encountered uninitialized memory, but expected an integer
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/padding-union.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/uninit/padding-wide-ptr.rs b/src/tools/miri/tests/fail/uninit/padding-wide-ptr.rs
new file mode 100644
index 00000000000..0403a9caba6
--- /dev/null
+++ b/src/tools/miri/tests/fail/uninit/padding-wide-ptr.rs
@@ -0,0 +1,18 @@
+use std::mem;
+
+// If this is `None`, the metadata becomes padding.
+type T = Option<&'static str>;
+
+fn main() { unsafe {
+    let mut p: mem::MaybeUninit<T> = mem::MaybeUninit::zeroed();
+    // The copy when `T` is returned from `transmute` should destroy padding
+    // (even when we use `write_unaligned`, which under the hood uses an untyped copy).
+    p.as_mut_ptr().write_unaligned(mem::transmute((0usize, 0usize)));
+    // Null epresents `None`.
+    assert!(matches!(*p.as_ptr(), None));
+
+    // The second part, with the length, becomes padding.
+    let c = &p as *const _ as *const u8;
+    let _val = *c.add(mem::size_of::<*const u8>()); // Get a padding byte.
+    //~^ERROR: uninitialized
+} }
diff --git a/src/tools/miri/tests/fail/uninit/padding-wide-ptr.stderr b/src/tools/miri/tests/fail/uninit/padding-wide-ptr.stderr
new file mode 100644
index 00000000000..0da72550b2e
--- /dev/null
+++ b/src/tools/miri/tests/fail/uninit/padding-wide-ptr.stderr
@@ -0,0 +1,15 @@
+error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
+  --> $DIR/padding-wide-ptr.rs:LL:CC
+   |
+LL |     let _val = *c.add(mem::size_of::<*const u8>()); // Get a padding byte.
+   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory
+   |
+   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
+   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/padding-wide-ptr.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/transmute-pair-uninit.rs b/src/tools/miri/tests/fail/uninit/transmute-pair-uninit.rs
index bc95f3cb7ad..0ba5520a544 100644
--- a/src/tools/miri/tests/fail/transmute-pair-uninit.rs
+++ b/src/tools/miri/tests/fail/uninit/transmute-pair-uninit.rs
@@ -1,16 +1,17 @@
 #![feature(core_intrinsics)]
 
-use std::mem;
+use std::mem::{self, MaybeUninit};
 
 fn main() {
-    let x: Option<Box<[u8]>> = unsafe {
+    // This constructs a `(usize, bool)` pair: 9 bytes initialized, the rest not.
+    // Ensure that these 9 bytes are indeed initialized, and the rest is indeed not.
+    let x: MaybeUninit<Box<[u8]>> = unsafe {
         let z = std::intrinsics::add_with_overflow(0usize, 0usize);
-        std::mem::transmute::<(usize, bool), Option<Box<[u8]>>>(z)
+        std::mem::transmute::<(usize, bool), MaybeUninit<Box<[u8]>>>(z)
     };
-    let y = &x;
     // Now read this bytewise. There should be (`ptr_size + 1`) def bytes followed by
     // (`ptr_size - 1`) undef bytes (the padding after the bool) in there.
-    let z: *const u8 = y as *const _ as *const _;
+    let z: *const u8 = &x as *const _ as *const _;
     let first_undef = mem::size_of::<usize>() as isize + 1;
     for i in 0..first_undef {
         let byte = unsafe { *z.offset(i) };
diff --git a/src/tools/miri/tests/fail/transmute-pair-uninit.stderr b/src/tools/miri/tests/fail/uninit/transmute-pair-uninit.stderr
index 30306054fbb..30306054fbb 100644
--- a/src/tools/miri/tests/fail/transmute-pair-uninit.stderr
+++ b/src/tools/miri/tests/fail/uninit/transmute-pair-uninit.stderr
diff --git a/src/tools/miri/tests/pass/arrays.rs b/src/tools/miri/tests/pass/arrays.rs
index 61b44453e9b..b0c6f54cab8 100644
--- a/src/tools/miri/tests/pass/arrays.rs
+++ b/src/tools/miri/tests/pass/arrays.rs
@@ -61,6 +61,20 @@ fn debug() {
     println!("{:?}", array);
 }
 
+fn huge_zst() {
+    fn id<T>(x: T) -> T { x }
+
+    // A "huge" zero-sized array. Make sure we don't loop over it in any part of Miri.
+    let val = [(); usize::MAX];
+    id(val); // make a copy
+
+    let val = [val; 2];
+    id(val);
+
+    // Also wrap it in a union (which, in particular, hits the logic for computing union padding).
+    let _copy = std::mem::MaybeUninit::new(val);
+}
+
 fn main() {
     assert_eq!(empty_array(), []);
     assert_eq!(index_unsafe(), 20);
@@ -73,4 +87,5 @@ fn main() {
     from();
     eq();
     debug();
+    huge_zst();
 }
diff --git a/src/tools/miri/tests/pass/enums.rs b/src/tools/miri/tests/pass/enums.rs
index 1dafef025e9..9fc61f07c04 100644
--- a/src/tools/miri/tests/pass/enums.rs
+++ b/src/tools/miri/tests/pass/enums.rs
@@ -132,6 +132,43 @@ fn overaligned_casts() {
     assert_eq!(aligned as u8, 0);
 }
 
+// This hits a corner case in the logic for clearing padding on typed copies.
+fn padding_clear_corner_case() {
+    #[allow(unused)]
+    #[derive(Copy, Clone)]
+    #[repr(C)]
+    pub struct Decoded {
+        /// The scaled mantissa.
+        pub mant: u64,
+        /// The lower error range.
+        pub minus: u64,
+        /// The upper error range.
+        pub plus: u64,
+        /// The shared exponent in base 2.
+        pub exp: i16,
+        /// True when the error range is inclusive.
+        ///
+        /// In IEEE 754, this is true when the original mantissa was even.
+        pub inclusive: bool,
+    }
+
+    #[allow(unused)]
+    #[derive(Copy, Clone)]
+    pub enum FullDecoded {
+        /// Not-a-number.
+        Nan,
+        /// Infinities, either positive or negative.
+        Infinite,
+        /// Zero, either positive or negative.
+        Zero,
+        /// Finite numbers with further decoded fields.
+        Finite(Decoded),
+    }
+
+    let val = FullDecoded::Finite(Decoded { mant: 0, minus: 0, plus: 0, exp: 0, inclusive: false });
+    let _val2 = val; // trigger typed copy
+}
+
 fn main() {
     test(MyEnum::MyEmptyVariant);
     test(MyEnum::MyNewtypeVariant(42));
@@ -141,4 +178,5 @@ fn main() {
     discriminant_overflow();
     more_discriminant_overflow();
     overaligned_casts();
+    padding_clear_corner_case();
 }
diff --git a/src/tools/miri/tests/pass/intrinsics/portable-simd.rs b/src/tools/miri/tests/pass/intrinsics/portable-simd.rs
index c4ba11d0a43..daf75fee8fe 100644
--- a/src/tools/miri/tests/pass/intrinsics/portable-simd.rs
+++ b/src/tools/miri/tests/pass/intrinsics/portable-simd.rs
@@ -621,6 +621,10 @@ fn simd_intrinsics() {
         assert_eq!(simd_shuffle_generic::<_, i32x4, { &[3, 1, 0, 2] }>(a, b), a,);
         assert_eq!(simd_shuffle::<_, _, i32x4>(a, b, const { [3u32, 1, 0, 2] }), a,);
         assert_eq!(
+            simd_shuffle::<_, _, i32x4>(a, b, const { u32x4::from_array([3u32, 1, 0, 2]) }),
+            a,
+        );
+        assert_eq!(
             simd_shuffle_generic::<_, i32x4, { &[7, 5, 4, 6] }>(a, b),
             i32x4::from_array([4, 2, 1, 10]),
         );
@@ -628,6 +632,10 @@ fn simd_intrinsics() {
             simd_shuffle::<_, _, i32x4>(a, b, const { [7u32, 5, 4, 6] }),
             i32x4::from_array([4, 2, 1, 10]),
         );
+        assert_eq!(
+            simd_shuffle::<_, _, i32x4>(a, b, const { u32x4::from_array([7u32, 5, 4, 6]) }),
+            i32x4::from_array([4, 2, 1, 10]),
+        );
     }
 }
 
diff --git a/src/tools/miri/tests/pass/intrinsics/simd-intrinsic-generic-elements.rs b/src/tools/miri/tests/pass/intrinsics/simd-intrinsic-generic-elements.rs
new file mode 100644
index 00000000000..9cf0c2ddef3
--- /dev/null
+++ b/src/tools/miri/tests/pass/intrinsics/simd-intrinsic-generic-elements.rs
@@ -0,0 +1,24 @@
+#![feature(repr_simd)]
+
+#[repr(simd)]
+#[derive(Copy, Clone, Debug, PartialEq)]
+#[allow(non_camel_case_types)]
+struct i32x2([i32; 2]);
+#[repr(simd)]
+#[derive(Copy, Clone, Debug, PartialEq)]
+#[allow(non_camel_case_types)]
+struct i32x4([i32; 4]);
+#[repr(simd)]
+#[derive(Copy, Clone, Debug, PartialEq)]
+#[allow(non_camel_case_types)]
+struct i32x8([i32; 8]);
+
+fn main() {
+    let _x2 = i32x2([20, 21]);
+    let _x4 = i32x4([40, 41, 42, 43]);
+    let _x8 = i32x8([80, 81, 82, 83, 84, 85, 86, 87]);
+
+    let _y2 = i32x2([120, 121]);
+    let _y4 = i32x4([140, 141, 142, 143]);
+    let _y8 = i32x8([180, 181, 182, 183, 184, 185, 186, 187]);
+}
diff --git a/src/tools/miri/tests/pass/provenance.rs b/src/tools/miri/tests/pass/provenance.rs
index 9e8a9651b3d..2e4d240cc48 100644
--- a/src/tools/miri/tests/pass/provenance.rs
+++ b/src/tools/miri/tests/pass/provenance.rs
@@ -12,6 +12,7 @@ fn main() {
     bytewise_custom_memcpy();
     bytewise_custom_memcpy_chunked();
     int_load_strip_provenance();
+    maybe_uninit_preserves_partial_provenance();
 }
 
 /// Some basic smoke tests for provenance.
@@ -145,3 +146,24 @@ fn int_load_strip_provenance() {
     let ints: [usize; 1] = unsafe { mem::transmute(ptrs) };
     assert_eq!(ptrs[0] as *const _ as usize, ints[0]);
 }
+
+fn maybe_uninit_preserves_partial_provenance() {
+    // This is the same test as ptr_copy_loses_partial_provenance.rs, but using MaybeUninit and thus
+    // properly preserving partial provenance.
+    unsafe {
+        let mut bytes = [1u8; 16];
+        let bytes = bytes.as_mut_ptr();
+
+        // Put a pointer in the middle.
+        bytes.add(4).cast::<&i32>().write_unaligned(&42);
+        // Copy the entire thing as two pointers but not perfectly
+        // overlapping with the pointer we have in there.
+        let copy = bytes.cast::<[mem::MaybeUninit<*const ()>; 2]>().read_unaligned();
+        let copy_bytes = copy.as_ptr().cast::<u8>();
+        // Now go to the middle of the copy and get the pointer back out.
+        let ptr = copy_bytes.add(4).cast::<*const i32>().read_unaligned();
+        // And deref this to ensure we get the right value.
+        let val = *ptr;
+        assert_eq!(val, 42);
+    }
+}
diff --git a/src/tools/miri/tests/pass/simd-intrinsic-generic-elements.rs b/src/tools/miri/tests/pass/simd-intrinsic-generic-elements.rs
deleted file mode 100644
index 4a87f8c3ca7..00000000000
--- a/src/tools/miri/tests/pass/simd-intrinsic-generic-elements.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-#![feature(repr_simd)]
-
-#[repr(simd)]
-#[derive(Copy, Clone, Debug, PartialEq)]
-#[allow(non_camel_case_types)]
-struct i32x2(i32, i32);
-#[repr(simd)]
-#[derive(Copy, Clone, Debug, PartialEq)]
-#[allow(non_camel_case_types)]
-struct i32x4(i32, i32, i32, i32);
-#[repr(simd)]
-#[derive(Copy, Clone, Debug, PartialEq)]
-#[allow(non_camel_case_types)]
-struct i32x8(i32, i32, i32, i32, i32, i32, i32, i32);
-
-fn main() {
-    let _x2 = i32x2(20, 21);
-    let _x4 = i32x4(40, 41, 42, 43);
-    let _x8 = i32x8(80, 81, 82, 83, 84, 85, 86, 87);
-
-    let _y2 = i32x2(120, 121);
-    let _y4 = i32x4(140, 141, 142, 143);
-    let _y8 = i32x8(180, 181, 182, 183, 184, 185, 186, 187);
-}
diff --git a/src/tools/run-make-support/src/external_deps/cargo.rs b/src/tools/run-make-support/src/external_deps/cargo.rs
new file mode 100644
index 00000000000..b0e045dc80b
--- /dev/null
+++ b/src/tools/run-make-support/src/external_deps/cargo.rs
@@ -0,0 +1,7 @@
+use crate::command::Command;
+use crate::env_var;
+
+/// Returns a command that can be used to invoke Cargo.
+pub fn cargo() -> Command {
+    Command::new(env_var("BOOTSTRAP_CARGO"))
+}
diff --git a/src/tools/run-make-support/src/external_deps/llvm.rs b/src/tools/run-make-support/src/external_deps/llvm.rs
index 16c4251998f..38a9ac923b4 100644
--- a/src/tools/run-make-support/src/external_deps/llvm.rs
+++ b/src/tools/run-make-support/src/external_deps/llvm.rs
@@ -54,6 +54,12 @@ pub fn llvm_dwarfdump() -> LlvmDwarfdump {
     LlvmDwarfdump::new()
 }
 
+/// Construct a new `llvm-pdbutil` invocation. This assumes that `llvm-pdbutil` is available
+/// at `$LLVM_BIN_DIR/llvm-pdbutil`.
+pub fn llvm_pdbutil() -> LlvmPdbutil {
+    LlvmPdbutil::new()
+}
+
 /// A `llvm-readobj` invocation builder.
 #[derive(Debug)]
 #[must_use]
diff --git a/src/tools/run-make-support/src/external_deps/mod.rs b/src/tools/run-make-support/src/external_deps/mod.rs
index f7c84724d0e..80c34a9070f 100644
--- a/src/tools/run-make-support/src/external_deps/mod.rs
+++ b/src/tools/run-make-support/src/external_deps/mod.rs
@@ -2,6 +2,7 @@
 //! such as `cc` or `python`.
 
 pub mod c_build;
+pub mod cargo;
 pub mod cc;
 pub mod clang;
 pub mod htmldocck;
diff --git a/src/tools/run-make-support/src/external_deps/rustc.rs b/src/tools/run-make-support/src/external_deps/rustc.rs
index f60ea972839..35d983dc607 100644
--- a/src/tools/run-make-support/src/external_deps/rustc.rs
+++ b/src/tools/run-make-support/src/external_deps/rustc.rs
@@ -36,10 +36,13 @@ pub struct Rustc {
 
 crate::macros::impl_common_helpers!(Rustc);
 
+pub fn rustc_path() -> String {
+    env_var("RUSTC")
+}
+
 #[track_caller]
 fn setup_common() -> Command {
-    let rustc = env_var("RUSTC");
-    let mut cmd = Command::new(rustc);
+    let mut cmd = Command::new(rustc_path());
     set_host_rpath(&mut cmd);
     cmd
 }
diff --git a/src/tools/run-make-support/src/lib.rs b/src/tools/run-make-support/src/lib.rs
index 980bd37dca8..15d813ccf53 100644
--- a/src/tools/run-make-support/src/lib.rs
+++ b/src/tools/run-make-support/src/lib.rs
@@ -50,6 +50,7 @@ pub use external_deps::{c_build, cc, clang, htmldocck, llvm, python, rustc, rust
 // These rely on external dependencies.
 pub use cc::{cc, cxx, extra_c_flags, extra_cxx_flags, Cc};
 pub use c_build::{build_native_dynamic_lib, build_native_static_lib, build_native_static_lib_optimized, build_native_static_lib_cxx};
+pub use cargo::cargo;
 pub use clang::{clang, Clang};
 pub use htmldocck::htmldocck;
 pub use llvm::{
@@ -58,7 +59,7 @@ pub use llvm::{
     LlvmProfdata, LlvmReadobj,
 };
 pub use python::python_command;
-pub use rustc::{aux_build, bare_rustc, rustc, Rustc};
+pub use rustc::{aux_build, bare_rustc, rustc, rustc_path, Rustc};
 pub use rustdoc::{bare_rustdoc, rustdoc, Rustdoc};
 
 /// [`diff`][mod@diff] is implemented in terms of the [similar] library.
@@ -98,3 +99,4 @@ pub use assertion_helpers::{
 pub use string::{
     count_regex_matches_in_files_with_extension, invalid_utf8_contains, invalid_utf8_not_contains,
 };
+use crate::external_deps::cargo;
diff --git a/src/tools/suggest-tests/src/main.rs b/src/tools/suggest-tests/src/main.rs
index 8e3625c2449..6f09bddcf60 100644
--- a/src/tools/suggest-tests/src/main.rs
+++ b/src/tools/suggest-tests/src/main.rs
@@ -8,6 +8,7 @@ fn main() -> ExitCode {
         &GitConfig {
             git_repository: &env("SUGGEST_TESTS_GIT_REPOSITORY"),
             nightly_branch: &env("SUGGEST_TESTS_NIGHTLY_BRANCH"),
+            git_merge_commit_email: &env("SUGGEST_TESTS_MERGE_COMMIT_EMAIL"),
         },
         None,
         &Vec::new(),