about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock139
-rw-r--r--bootstrap.example.toml3
-rw-r--r--compiler/rustc_codegen_llvm/src/debuginfo/metadata/type_map.rs4
-rw-r--r--compiler/rustc_codegen_ssa/Cargo.toml4
-rw-r--r--compiler/rustc_errors/src/emitter.rs50
-rw-r--r--compiler/rustc_llvm/Cargo.toml4
-rw-r--r--compiler/rustc_mir_build/src/thir/cx/expr.rs21
-rw-r--r--compiler/rustc_parse/src/parser/tests.rs48
-rw-r--r--library/core/src/ops/range.rs30
-rw-r--r--library/core/src/range.rs18
-rw-r--r--library/std/src/sync/mod.rs2
-rw-r--r--library/std/src/sync/nonpoison.rs37
-rw-r--r--library/std/src/sync/nonpoison/mutex.rs611
-rw-r--r--library/std/src/sync/poison.rs6
-rw-r--r--library/std/src/sync/poison/condvar.rs2
-rw-r--r--library/std/src/sync/poison/mutex.rs2
-rw-r--r--library/std/src/sys/pal/hermit/thread.rs6
-rw-r--r--library/std/src/sys/pal/itron/thread.rs6
-rw-r--r--library/std/src/sys/pal/sgx/thread.rs6
-rw-r--r--library/std/src/sys/pal/teeos/thread.rs6
-rw-r--r--library/std/src/sys/pal/uefi/thread.rs6
-rw-r--r--library/std/src/sys/pal/unix/stack_overflow.rs23
-rw-r--r--library/std/src/sys/pal/unix/thread.rs24
-rw-r--r--library/std/src/sys/pal/unsupported/thread.rs6
-rw-r--r--library/std/src/sys/pal/wasi/thread.rs4
-rw-r--r--library/std/src/sys/pal/wasm/atomics/thread.rs6
-rw-r--r--library/std/src/sys/pal/windows/thread.rs6
-rw-r--r--library/std/src/sys/pal/xous/thread.rs6
-rw-r--r--library/std/src/thread/mod.rs2
-rw-r--r--library/std/tests/sync/lib.rs55
-rw-r--r--library/std/tests/sync/mutex.rs525
-rw-r--r--src/bootstrap/defaults/bootstrap.tools.toml2
-rw-r--r--src/bootstrap/src/core/config/config.rs28
-rw-r--r--src/bootstrap/src/core/download.rs1108
-rw-r--r--src/bootstrap/src/utils/proc_macro_deps.rs5
-rw-r--r--src/librustdoc/config.rs20
-rw-r--r--src/librustdoc/externalfiles.rs23
-rw-r--r--src/librustdoc/lib.rs8
-rw-r--r--src/librustdoc/scrape_examples.rs2
-rw-r--r--src/tools/miri/.github/workflows/ci.yml51
-rw-r--r--src/tools/miri/.gitignore1
-rw-r--r--src/tools/miri/CONTRIBUTING.md14
-rw-r--r--src/tools/miri/Cargo.lock549
-rw-r--r--src/tools/miri/Cargo.toml6
-rw-r--r--src/tools/miri/cargo-miri/src/phases.rs8
-rwxr-xr-xsrc/tools/miri/ci/ci.sh12
-rw-r--r--src/tools/miri/doc/genmc.md62
-rw-r--r--src/tools/miri/etc/rust_analyzer_helix.toml1
-rw-r--r--src/tools/miri/etc/rust_analyzer_vscode.json1
-rw-r--r--src/tools/miri/genmc-sys/.gitignore1
-rw-r--r--src/tools/miri/genmc-sys/Cargo.toml17
-rw-r--r--src/tools/miri/genmc-sys/build.rs269
-rw-r--r--src/tools/miri/genmc-sys/src/lib.rs30
-rw-r--r--src/tools/miri/genmc-sys/src_cpp/MiriInterface.cpp50
-rw-r--r--src/tools/miri/genmc-sys/src_cpp/MiriInterface.hpp44
-rw-r--r--src/tools/miri/josh-sync.toml2
-rw-r--r--src/tools/miri/miri-script/Cargo.lock90
-rw-r--r--src/tools/miri/miri-script/Cargo.toml1
-rw-r--r--src/tools/miri/miri-script/src/commands.rs233
-rw-r--r--src/tools/miri/miri-script/src/main.rs22
-rw-r--r--src/tools/miri/rust-version2
-rw-r--r--src/tools/miri/src/bin/log/setup.rs2
-rw-r--r--src/tools/miri/src/bin/log/tracing_chrome.rs13
-rw-r--r--src/tools/miri/src/bin/miri.rs35
-rw-r--r--src/tools/miri/src/concurrency/genmc/config.rs26
-rw-r--r--src/tools/miri/src/concurrency/genmc/dummy.rs15
-rw-r--r--src/tools/miri/src/concurrency/genmc/mod.rs17
-rw-r--r--src/tools/miri/src/concurrency/mod.rs12
-rw-r--r--src/tools/miri/src/concurrency/thread.rs2
-rw-r--r--src/tools/miri/src/diagnostics.rs2
-rw-r--r--src/tools/miri/src/eval.rs10
-rw-r--r--src/tools/miri/src/helpers.rs233
-rw-r--r--src/tools/miri/src/intrinsics/atomic.rs2
-rw-r--r--src/tools/miri/src/intrinsics/mod.rs22
-rw-r--r--src/tools/miri/src/intrinsics/simd.rs5
-rw-r--r--src/tools/miri/src/lib.rs2
-rw-r--r--src/tools/miri/src/machine.rs14
-rw-r--r--src/tools/miri/src/shims/aarch64.rs5
-rw-r--r--src/tools/miri/src/shims/backtrace.rs8
-rw-r--r--src/tools/miri/src/shims/files.rs16
-rw-r--r--src/tools/miri/src/shims/foreign_items.rs90
-rw-r--r--src/tools/miri/src/shims/mod.rs1
-rw-r--r--src/tools/miri/src/shims/sig.rs266
-rw-r--r--src/tools/miri/src/shims/time.rs100
-rw-r--r--src/tools/miri/src/shims/unix/android/foreign_items.rs11
-rw-r--r--src/tools/miri/src/shims/unix/android/thread.rs4
-rw-r--r--src/tools/miri/src/shims/unix/fd.rs23
-rw-r--r--src/tools/miri/src/shims/unix/foreign_items.rs451
-rw-r--r--src/tools/miri/src/shims/unix/freebsd/foreign_items.rs26
-rw-r--r--src/tools/miri/src/shims/unix/freebsd/sync.rs26
-rw-r--r--src/tools/miri/src/shims/unix/fs.rs2
-rw-r--r--src/tools/miri/src/shims/unix/linux/foreign_items.rs39
-rw-r--r--src/tools/miri/src/shims/unix/linux_like/eventfd.rs5
-rw-r--r--src/tools/miri/src/shims/unix/linux_like/sync.rs2
-rw-r--r--src/tools/miri/src/shims/unix/linux_like/syscall.rs4
-rw-r--r--src/tools/miri/src/shims/unix/macos/foreign_items.rs71
-rw-r--r--src/tools/miri/src/shims/unix/solarish/foreign_items.rs34
-rw-r--r--src/tools/miri/src/shims/unix/sync.rs65
-rw-r--r--src/tools/miri/src/shims/unwind.rs6
-rw-r--r--src/tools/miri/src/shims/wasi/foreign_items.rs6
-rw-r--r--src/tools/miri/src/shims/windows/foreign_items.rs147
-rw-r--r--src/tools/miri/src/shims/windows/fs.rs22
-rw-r--r--src/tools/miri/src/shims/x86/aesni.rs14
-rw-r--r--src/tools/miri/src/shims/x86/avx.rs50
-rw-r--r--src/tools/miri/src/shims/x86/avx2.rs57
-rw-r--r--src/tools/miri/src/shims/x86/bmi.rs2
-rw-r--r--src/tools/miri/src/shims/x86/gfni.rs9
-rw-r--r--src/tools/miri/src/shims/x86/mod.rs11
-rw-r--r--src/tools/miri/src/shims/x86/sha.rs6
-rw-r--r--src/tools/miri/src/shims/x86/sse.rs24
-rw-r--r--src/tools/miri/src/shims/x86/sse2.rs40
-rw-r--r--src/tools/miri/src/shims/x86/sse3.rs5
-rw-r--r--src/tools/miri/src/shims/x86/sse41.rs28
-rw-r--r--src/tools/miri/src/shims/x86/sse42.rs14
-rw-r--r--src/tools/miri/src/shims/x86/ssse3.rs17
-rw-r--r--src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs7
-rw-r--r--src/tools/miri/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs8
-rw-r--r--src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs3
-rw-r--r--src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.rs25
-rw-r--r--src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.stderr4
-rw-r--r--src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs29
-rw-r--r--src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.stderr4
-rw-r--r--src/tools/miri/tests/fail/function_pointers/abi_mismatch_return_type.stderr2
-rw-r--r--src/tools/miri/tests/fail/shims/ctor_wrong_ret_type.rs39
-rw-r--r--src/tools/miri/tests/fail/shims/ctor_wrong_ret_type.stderr12
-rw-r--r--src/tools/miri/tests/fail/shims/return_type_mismatch.stderr2
-rw-r--r--src/tools/miri/tests/genmc/pass/test_cxx_build.rs8
-rw-r--r--src/tools/miri/tests/genmc/pass/test_cxx_build.stderr5
-rw-r--r--src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs7
-rw-r--r--src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs44
-rw-r--r--src/tools/miri/tests/pass-dep/libc/libc-fs.rs49
-rw-r--r--src/tools/miri/tests/pass-dep/libc/libc-pipe.rs42
-rw-r--r--src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs81
-rw-r--r--src/tools/miri/tests/pass/shims/ctor.rs15
-rw-r--r--src/tools/miri/tests/pass/shims/fs.rs32
-rw-r--r--src/tools/miri/tests/pass/shims/pipe.rs4
-rw-r--r--src/tools/miri/tests/ui.rs14
-rw-r--r--src/tools/miri/tests/utils/fs.rs2
-rw-r--r--src/tools/miri/tests/utils/libc.rs44
-rw-r--r--src/tools/miri/tests/x86_64-unknown-kernel.json2
-rw-r--r--src/tools/tidy/src/extra_checks/mod.rs30
-rw-r--r--src/tools/tidy/src/lib.rs55
-rw-r--r--tests/codegen-llvm/debuginfo-cyclic-structure.rs32
-rw-r--r--tests/run-make/crate-loading-crate-depends-on-itself/foo.stderr12
-rw-r--r--tests/run-make/rustdoc-dep-info/after.md1
-rw-r--r--tests/run-make/rustdoc-dep-info/before.html0
-rw-r--r--tests/run-make/rustdoc-dep-info/extend.css0
-rw-r--r--tests/run-make/rustdoc-dep-info/rmake.rs15
-rw-r--r--tests/run-make/rustdoc-dep-info/theme.css1
-rw-r--r--tests/rustdoc-ui/doctest/standalone-warning-2024.stderr2
-rw-r--r--tests/ui/async-await/issue-64130-non-send-future-diags.stderr4
-rw-r--r--tests/ui/async-await/issue-71137.stderr4
-rw-r--r--tests/ui/async-await/issues/issue-67893.rs2
-rw-r--r--tests/ui/async-await/issues/issue-67893.stderr6
-rw-r--r--tests/ui/compiletest-self-test/ui-testing-optout.stderr2
-rw-r--r--tests/ui/const-generics/generic_const_exprs/auxiliary/feature-attribute-missing-in-dependent-crate-ice-aux.rs9
-rw-r--r--tests/ui/const-generics/generic_const_exprs/feature-attribute-missing-in-dependent-crate-ice.rs19
-rw-r--r--tests/ui/const-generics/generic_const_exprs/feature-attribute-missing-in-dependent-crate-ice.stderr14
-rw-r--r--tests/ui/lint/must_not_suspend/mutex.rs2
-rw-r--r--tests/ui/lint/must_not_suspend/mutex.stderr2
-rw-r--r--tests/ui/loop-match/upvar-scrutinee.rs81
-rw-r--r--tests/ui/loop-match/upvar-scrutinee.stderr18
-rw-r--r--tests/ui/modules/issue-107649.stderr4
-rw-r--r--tests/ui/privacy/private-field-access-in-mutex-54062.rs2
-rw-r--r--tests/ui/privacy/private-field-access-in-mutex-54062.stderr2
-rw-r--r--tests/ui/runtime/out-of-stack.rs12
-rw-r--r--tests/ui/suggestions/inner_type.fixed2
-rw-r--r--tests/ui/suggestions/inner_type.rs2
-rw-r--r--tests/ui/suggestions/inner_type.stderr4
-rw-r--r--tests/ui/sync/mutexguard-sync.stderr2
-rw-r--r--tests/ui/traits/const-traits/span-bug-issue-121418.stderr6
-rw-r--r--tests/ui/typeck/assign-non-lval-derefmut.fixed4
-rw-r--r--tests/ui/typeck/assign-non-lval-derefmut.rs4
-rw-r--r--tests/ui/typeck/assign-non-lval-derefmut.stderr14
-rw-r--r--tests/ui/typeck/deref-multi.stderr2
175 files changed, 4977 insertions, 2327 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b8ccf270104..c1076f05ef1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -466,6 +466,8 @@ version = "1.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
 dependencies = [
+ "jobserver",
+ "libc",
  "shlex",
 ]
 
@@ -656,6 +658,26 @@ dependencies = [
 ]
 
 [[package]]
+name = "cmake"
+version = "0.1.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "codespan-reporting"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81"
+dependencies = [
+ "serde",
+ "termcolor",
+ "unicode-width 0.2.1",
+]
+
+[[package]]
 name = "collect-license-metadata"
 version = "0.1.0"
 dependencies = [
@@ -914,6 +936,68 @@ dependencies = [
 ]
 
 [[package]]
+name = "cxx"
+version = "1.0.161"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3523cc02ad831111491dd64b27ad999f1ae189986728e477604e61b81f828df"
+dependencies = [
+ "cc",
+ "cxxbridge-cmd",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "foldhash",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.161"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "212b754247a6f07b10fa626628c157593f0abf640a3dd04cce2760eca970f909"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "indexmap",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn 2.0.104",
+]
+
+[[package]]
+name = "cxxbridge-cmd"
+version = "1.0.161"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f426a20413ec2e742520ba6837c9324b55ffac24ead47491a6e29f933c5b135a"
+dependencies = [
+ "clap",
+ "codespan-reporting",
+ "indexmap",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.104",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.161"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258b6069020b4e5da6415df94a50ee4f586a6c38b037a180e940a43d06a070d"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.161"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8dec184b52be5008d6eaf7e62fc1802caf1ad1227d11b3b7df2c409c7ffc3f4"
+dependencies = [
+ "indexmap",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn 2.0.104",
+]
+
+[[package]]
 name = "darling"
 version = "0.20.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1374,6 +1458,17 @@ dependencies = [
 ]
 
 [[package]]
+name = "genmc-sys"
+version = "0.1.0"
+dependencies = [
+ "cc",
+ "cmake",
+ "cxx",
+ "cxx-build",
+ "git2",
+]
+
+[[package]]
 name = "getopts"
 version = "0.2.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1428,6 +1523,21 @@ dependencies = [
 ]
 
 [[package]]
+name = "git2"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110"
+dependencies = [
+ "bitflags",
+ "libc",
+ "libgit2-sys",
+ "log",
+ "openssl-probe",
+ "openssl-sys",
+ "url",
+]
+
+[[package]]
 name = "glob"
 version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2061,6 +2171,19 @@ dependencies = [
 ]
 
 [[package]]
+name = "libgit2-sys"
+version = "0.18.2+1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c42fe03df2bd3c53a3a9c7317ad91d80c81cd1fb0caec8d7cc4cd2bfa10c222"
+dependencies = [
+ "cc",
+ "libc",
+ "libz-sys",
+ "openssl-sys",
+ "pkg-config",
+]
+
+[[package]]
 name = "libloading"
 version = "0.8.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2100,6 +2223,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "link-cplusplus"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a6f6da007f968f9def0d65a05b187e2960183de70c160204ecfccf0ee330212"
+dependencies = [
+ "cc",
+]
+
+[[package]]
 name = "linkchecker"
 version = "0.1.0"
 dependencies = [
@@ -2308,6 +2440,7 @@ dependencies = [
  "chrono-tz",
  "colored 3.0.0",
  "directories",
+ "genmc-sys",
  "getrandom 0.3.3",
  "ipc-channel",
  "libc",
@@ -4878,6 +5011,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
+name = "scratch"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f6280af86e5f559536da57a45ebc84948833b3bee313a7dd25232e09c878a52"
+
+[[package]]
 name = "self_cell"
 version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/bootstrap.example.toml b/bootstrap.example.toml
index 73e93ccbe42..ef49113b70f 100644
--- a/bootstrap.example.toml
+++ b/bootstrap.example.toml
@@ -475,6 +475,9 @@
 # Note that if any value is manually given to bootstrap such as
 # `./x test tidy --extra-checks=js`, this value is ignored.
 # Use `--extra-checks=''` to temporarily disable all extra checks.
+#
+# Automatically enabled in the "tools" profile.
+# Set to the empty string to force disable (recommeded for hdd systems).
 #build.tidy-extra-checks = ""
 
 # Indicates whether ccache is used when building certain artifacts (e.g. LLVM).
diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/metadata/type_map.rs b/compiler/rustc_codegen_llvm/src/debuginfo/metadata/type_map.rs
index 56fb12d3c22..d1502d2b1e6 100644
--- a/compiler/rustc_codegen_llvm/src/debuginfo/metadata/type_map.rs
+++ b/compiler/rustc_codegen_llvm/src/debuginfo/metadata/type_map.rs
@@ -285,8 +285,8 @@ pub(super) fn build_type_with_children<'ll, 'tcx>(
         //     Item(T),
         // }
         // ```
-        let is_expanding_recursive =
-            debug_context(cx).adt_stack.borrow().iter().any(|(parent_def_id, parent_args)| {
+        let is_expanding_recursive = adt_def.is_enum()
+            && debug_context(cx).adt_stack.borrow().iter().any(|(parent_def_id, parent_args)| {
                 if def_id == *parent_def_id {
                     args.iter().zip(parent_args.iter()).any(|(arg, parent_arg)| {
                         if let (Some(arg), Some(parent_arg)) = (arg.as_type(), parent_arg.as_type())
diff --git a/compiler/rustc_codegen_ssa/Cargo.toml b/compiler/rustc_codegen_ssa/Cargo.toml
index cfae1b3ec98..94501da69a7 100644
--- a/compiler/rustc_codegen_ssa/Cargo.toml
+++ b/compiler/rustc_codegen_ssa/Cargo.toml
@@ -8,8 +8,8 @@ edition = "2024"
 ar_archive_writer = "0.4.2"
 bitflags = "2.4.1"
 bstr = "1.11.3"
-# Pinned so `cargo update` bumps don't cause breakage. Please also update the
-# `cc` in `rustc_llvm` if you update the `cc` here.
+# `cc` updates often break things, so we pin it here. Cargo enforces "max 1 semver-compat version
+# per crate", so if you change this, you need to also change it in `rustc_llvm`.
 cc = "=1.2.16"
 itertools = "0.12"
 pathdiff = "0.2.0"
diff --git a/compiler/rustc_errors/src/emitter.rs b/compiler/rustc_errors/src/emitter.rs
index 95400ac2ca3..46a4a186824 100644
--- a/compiler/rustc_errors/src/emitter.rs
+++ b/compiler/rustc_errors/src/emitter.rs
@@ -713,8 +713,7 @@ impl HumanEmitter {
                 Style::LineNumber,
             );
         }
-        buffer.puts(line_offset, 0, &self.maybe_anonymized(line_index), Style::LineNumber);
-
+        self.draw_line_num(buffer, line_index, line_offset, width_offset - 3);
         self.draw_col_separator_no_space(buffer, line_offset, width_offset - 2);
         left
     }
@@ -2128,11 +2127,11 @@ impl HumanEmitter {
                 // Account for a suggestion to completely remove a line(s) with whitespace (#94192).
                 let line_end = sm.lookup_char_pos(parts[0].span.hi()).line;
                 for line in line_start..=line_end {
-                    buffer.puts(
+                    self.draw_line_num(
+                        &mut buffer,
+                        line,
                         row_num - 1 + line - line_start,
-                        0,
-                        &self.maybe_anonymized(line),
-                        Style::LineNumber,
+                        max_line_num_len,
                     );
                     buffer.puts(
                         row_num - 1 + line - line_start,
@@ -2612,12 +2611,7 @@ impl HumanEmitter {
             // For more info: https://github.com/rust-lang/rust/issues/92741
             let lines_to_remove = file_lines.lines.iter().take(file_lines.lines.len() - 1);
             for (index, line_to_remove) in lines_to_remove.enumerate() {
-                buffer.puts(
-                    *row_num - 1,
-                    0,
-                    &self.maybe_anonymized(line_num + index),
-                    Style::LineNumber,
-                );
+                self.draw_line_num(buffer, line_num + index, *row_num - 1, max_line_num_len);
                 buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
                 let line = normalize_whitespace(
                     &file_lines.file.get_line(line_to_remove.line_index).unwrap(),
@@ -2634,11 +2628,11 @@ impl HumanEmitter {
             let last_line_index = file_lines.lines[file_lines.lines.len() - 1].line_index;
             let last_line = &file_lines.file.get_line(last_line_index).unwrap();
             if last_line != line_to_add {
-                buffer.puts(
+                self.draw_line_num(
+                    buffer,
+                    line_num + file_lines.lines.len() - 1,
                     *row_num - 1,
-                    0,
-                    &self.maybe_anonymized(line_num + file_lines.lines.len() - 1),
-                    Style::LineNumber,
+                    max_line_num_len,
                 );
                 buffer.puts(*row_num - 1, max_line_num_len + 1, "- ", Style::Removal);
                 buffer.puts(
@@ -2661,7 +2655,7 @@ impl HumanEmitter {
                     // 2 -     .await
                     //   |
                     // *row_num -= 1;
-                    buffer.puts(*row_num, 0, &self.maybe_anonymized(line_num), Style::LineNumber);
+                    self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
                     buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
                     buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
                 } else {
@@ -2671,7 +2665,7 @@ impl HumanEmitter {
                 *row_num -= 2;
             }
         } else if is_multiline {
-            buffer.puts(*row_num, 0, &self.maybe_anonymized(line_num), Style::LineNumber);
+            self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
             match &highlight_parts {
                 [SubstitutionHighlight { start: 0, end }] if *end == line_to_add.len() => {
                     buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
@@ -2702,11 +2696,11 @@ impl HumanEmitter {
                 Style::NoStyle,
             );
         } else if let DisplaySuggestion::Add = show_code_change {
-            buffer.puts(*row_num, 0, &self.maybe_anonymized(line_num), Style::LineNumber);
+            self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
             buffer.puts(*row_num, max_line_num_len + 1, "+ ", Style::Addition);
             buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
         } else {
-            buffer.puts(*row_num, 0, &self.maybe_anonymized(line_num), Style::LineNumber);
+            self.draw_line_num(buffer, line_num, *row_num, max_line_num_len);
             self.draw_col_separator(buffer, *row_num, max_line_num_len + 1);
             buffer.append(*row_num, &normalize_whitespace(line_to_add), Style::NoStyle);
         }
@@ -3016,6 +3010,22 @@ impl HumanEmitter {
             OutputTheme::Unicode => "…",
         }
     }
+
+    fn draw_line_num(
+        &self,
+        buffer: &mut StyledBuffer,
+        line_num: usize,
+        line_offset: usize,
+        max_line_num_len: usize,
+    ) {
+        let line_num = self.maybe_anonymized(line_num);
+        buffer.puts(
+            line_offset,
+            max_line_num_len.saturating_sub(str_width(&line_num)),
+            &line_num,
+            Style::LineNumber,
+        );
+    }
 }
 
 #[derive(Debug, Clone, Copy)]
diff --git a/compiler/rustc_llvm/Cargo.toml b/compiler/rustc_llvm/Cargo.toml
index 39de4783238..85a2a9c09f0 100644
--- a/compiler/rustc_llvm/Cargo.toml
+++ b/compiler/rustc_llvm/Cargo.toml
@@ -10,8 +10,8 @@ libc = "0.2.73"
 
 [build-dependencies]
 # tidy-alphabetical-start
-# Pinned so `cargo update` bumps don't cause breakage. Please also update the
-# pinned `cc` in `rustc_codegen_ssa` if you update `cc` here.
+# `cc` updates often break things, so we pin it here. Cargo enforces "max 1 semver-compat version
+# per crate", so if you change this, you need to also change it in `rustc_codegen_ssa`.
 cc = "=1.2.16"
 # tidy-alphabetical-end
 
diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs
index 33bf4e3e29f..16df58cd76d 100644
--- a/compiler/rustc_mir_build/src/thir/cx/expr.rs
+++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs
@@ -955,9 +955,13 @@ impl<'tcx> ThirBuildCx<'tcx> {
                         dcx.emit_fatal(LoopMatchBadRhs { span: block_body_expr.span })
                     };
 
-                    fn local(expr: &rustc_hir::Expr<'_>) -> Option<hir::HirId> {
+                    fn local(
+                        cx: &mut ThirBuildCx<'_>,
+                        expr: &rustc_hir::Expr<'_>,
+                    ) -> Option<hir::HirId> {
                         if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = expr.kind
                             && let Res::Local(hir_id) = path.res
+                            && !cx.is_upvar(hir_id)
                         {
                             return Some(hir_id);
                         }
@@ -965,11 +969,11 @@ impl<'tcx> ThirBuildCx<'tcx> {
                         None
                     }
 
-                    let Some(scrutinee_hir_id) = local(scrutinee) else {
+                    let Some(scrutinee_hir_id) = local(self, scrutinee) else {
                         dcx.emit_fatal(LoopMatchInvalidMatch { span: scrutinee.span })
                     };
 
-                    if local(state) != Some(scrutinee_hir_id) {
+                    if local(self, state) != Some(scrutinee_hir_id) {
                         dcx.emit_fatal(LoopMatchInvalidUpdate {
                             scrutinee: scrutinee.span,
                             lhs: state.span,
@@ -1260,10 +1264,7 @@ impl<'tcx> ThirBuildCx<'tcx> {
     fn convert_var(&mut self, var_hir_id: hir::HirId) -> ExprKind<'tcx> {
         // We want upvars here not captures.
         // Captures will be handled in MIR.
-        let is_upvar = self
-            .tcx
-            .upvars_mentioned(self.body_owner)
-            .is_some_and(|upvars| upvars.contains_key(&var_hir_id));
+        let is_upvar = self.is_upvar(var_hir_id);
 
         debug!(
             "convert_var({:?}): is_upvar={}, body_owner={:?}",
@@ -1443,6 +1444,12 @@ impl<'tcx> ThirBuildCx<'tcx> {
         }
     }
 
+    fn is_upvar(&mut self, var_hir_id: hir::HirId) -> bool {
+        self.tcx
+            .upvars_mentioned(self.body_owner)
+            .is_some_and(|upvars| upvars.contains_key(&var_hir_id))
+    }
+
     /// Converts a list of named fields (i.e., for struct-like struct/enum ADTs) into FieldExpr.
     fn field_refs(&mut self, fields: &'tcx [hir::ExprField<'tcx>]) -> Box<[FieldExpr]> {
         fields
diff --git a/compiler/rustc_parse/src/parser/tests.rs b/compiler/rustc_parse/src/parser/tests.rs
index 15679d23bc5..43a1d779a75 100644
--- a/compiler/rustc_parse/src/parser/tests.rs
+++ b/compiler/rustc_parse/src/parser/tests.rs
@@ -2114,15 +2114,15 @@ fn foo() {
 error: foo
   --> test.rs:3:6
    |
-3  |      X0 Y0 Z0
+ 3 |      X0 Y0 Z0
    |  _______^
-4  | |    X1 Y1 Z1
+ 4 | |    X1 Y1 Z1
    | | ____^____-
    | ||____|
    |  |    `X` is a good letter
-5  |  | 1
-6  |  | 2
-7  |  | 3
+ 5 |  | 1
+ 6 |  | 2
+ 7 |  | 3
 ...   |
 15 |  |   X2 Y2 Z2
 16 |  |   X3 Y3 Z3
@@ -2133,15 +2133,15 @@ error: foo
 error: foo
    ╭▸ test.rs:3:6

-3  │      X0 Y0 Z0
+ 3 │      X0 Y0 Z0
    │ ┏━━━━━━━┛
-4  │ ┃    X1 Y1 Z1
+ 4 │ ┃    X1 Y1 Z1
    │ ┃┌────╿────┘
    │ ┗│━━━━┥
    │  │    `X` is a good letter
-5  │  │ 1
-6  │  │ 2
-7  │  │ 3
+ 5 │  │ 1
+ 6 │  │ 2
+ 7 │  │ 3
    ‡  │
 15 │  │   X2 Y2 Z2
 16 │  │   X3 Y3 Z3
@@ -2189,15 +2189,15 @@ fn foo() {
 error: foo
   --> test.rs:3:6
    |
-3  |      X0 Y0 Z0
+ 3 |      X0 Y0 Z0
    |  _______^
-4  | |  1
-5  | |  2
-6  | |  3
-7  | |    X1 Y1 Z1
+ 4 | |  1
+ 5 | |  2
+ 6 | |  3
+ 7 | |    X1 Y1 Z1
    | | _________-
-8  | || 4
-9  | || 5
+ 8 | || 4
+ 9 | || 5
 10 | || 6
 11 | ||   X2 Y2 Z2
    | ||__________- `Z` is a good letter too
@@ -2211,15 +2211,15 @@ error: foo
 error: foo
    ╭▸ test.rs:3:6

-3  │      X0 Y0 Z0
+ 3 │      X0 Y0 Z0
    │ ┏━━━━━━━┛
-4  │ ┃  1
-5  │ ┃  2
-6  │ ┃  3
-7  │ ┃    X1 Y1 Z1
+ 4 │ ┃  1
+ 5 │ ┃  2
+ 6 │ ┃  3
+ 7 │ ┃    X1 Y1 Z1
    │ ┃┌─────────┘
-8  │ ┃│ 4
-9  │ ┃│ 5
+ 8 │ ┃│ 4
+ 9 │ ┃│ 5
 10 │ ┃│ 6
 11 │ ┃│   X2 Y2 Z2
    │ ┃└──────────┘ `Z` is a good letter too
diff --git a/library/core/src/ops/range.rs b/library/core/src/ops/range.rs
index ad3b6439a61..f33a33e6b75 100644
--- a/library/core/src/ops/range.rs
+++ b/library/core/src/ops/range.rs
@@ -1141,6 +1141,12 @@ impl<'a, T: ?Sized + 'a> RangeBounds<T> for (Bound<&'a T>, Bound<&'a T>) {
     }
 }
 
+// This impl intentionally does not have `T: ?Sized`;
+// see https://github.com/rust-lang/rust/pull/61584 for discussion of why.
+//
+/// If you need to use this implementation where `T` is unsized,
+/// consider using the `RangeBounds` impl for a 2-tuple of [`Bound<&T>`][Bound],
+/// i.e. replace `start..` with `(Bound::Included(start), Bound::Unbounded)`.
 #[stable(feature = "collections_range", since = "1.28.0")]
 impl<T> RangeBounds<T> for RangeFrom<&T> {
     fn start_bound(&self) -> Bound<&T> {
@@ -1151,6 +1157,12 @@ impl<T> RangeBounds<T> for RangeFrom<&T> {
     }
 }
 
+// This impl intentionally does not have `T: ?Sized`;
+// see https://github.com/rust-lang/rust/pull/61584 for discussion of why.
+//
+/// If you need to use this implementation where `T` is unsized,
+/// consider using the `RangeBounds` impl for a 2-tuple of [`Bound<&T>`][Bound],
+/// i.e. replace `..end` with `(Bound::Unbounded, Bound::Excluded(end))`.
 #[stable(feature = "collections_range", since = "1.28.0")]
 impl<T> RangeBounds<T> for RangeTo<&T> {
     fn start_bound(&self) -> Bound<&T> {
@@ -1161,6 +1173,12 @@ impl<T> RangeBounds<T> for RangeTo<&T> {
     }
 }
 
+// This impl intentionally does not have `T: ?Sized`;
+// see https://github.com/rust-lang/rust/pull/61584 for discussion of why.
+//
+/// If you need to use this implementation where `T` is unsized,
+/// consider using the `RangeBounds` impl for a 2-tuple of [`Bound<&T>`][Bound],
+/// i.e. replace `start..end` with `(Bound::Included(start), Bound::Excluded(end))`.
 #[stable(feature = "collections_range", since = "1.28.0")]
 impl<T> RangeBounds<T> for Range<&T> {
     fn start_bound(&self) -> Bound<&T> {
@@ -1171,6 +1189,12 @@ impl<T> RangeBounds<T> for Range<&T> {
     }
 }
 
+// This impl intentionally does not have `T: ?Sized`;
+// see https://github.com/rust-lang/rust/pull/61584 for discussion of why.
+//
+/// If you need to use this implementation where `T` is unsized,
+/// consider using the `RangeBounds` impl for a 2-tuple of [`Bound<&T>`][Bound],
+/// i.e. replace `start..=end` with `(Bound::Included(start), Bound::Included(end))`.
 #[stable(feature = "collections_range", since = "1.28.0")]
 impl<T> RangeBounds<T> for RangeInclusive<&T> {
     fn start_bound(&self) -> Bound<&T> {
@@ -1181,6 +1205,12 @@ impl<T> RangeBounds<T> for RangeInclusive<&T> {
     }
 }
 
+// This impl intentionally does not have `T: ?Sized`;
+// see https://github.com/rust-lang/rust/pull/61584 for discussion of why.
+//
+/// If you need to use this implementation where `T` is unsized,
+/// consider using the `RangeBounds` impl for a 2-tuple of [`Bound<&T>`][Bound],
+/// i.e. replace `..=end` with `(Bound::Unbounded, Bound::Included(end))`.
 #[stable(feature = "collections_range", since = "1.28.0")]
 impl<T> RangeBounds<T> for RangeToInclusive<&T> {
     fn start_bound(&self) -> Bound<&T> {
diff --git a/library/core/src/range.rs b/library/core/src/range.rs
index 5cd7956291c..7158fa0fcf0 100644
--- a/library/core/src/range.rs
+++ b/library/core/src/range.rs
@@ -167,6 +167,12 @@ impl<T> RangeBounds<T> for Range<T> {
     }
 }
 
+// This impl intentionally does not have `T: ?Sized`;
+// see https://github.com/rust-lang/rust/pull/61584 for discussion of why.
+//
+/// If you need to use this implementation where `T` is unsized,
+/// consider using the `RangeBounds` impl for a 2-tuple of [`Bound<&T>`][Bound],
+/// i.e. replace `start..end` with `(Bound::Included(start), Bound::Excluded(end))`.
 #[unstable(feature = "new_range_api", issue = "125687")]
 impl<T> RangeBounds<T> for Range<&T> {
     fn start_bound(&self) -> Bound<&T> {
@@ -346,6 +352,12 @@ impl<T> RangeBounds<T> for RangeInclusive<T> {
     }
 }
 
+// This impl intentionally does not have `T: ?Sized`;
+// see https://github.com/rust-lang/rust/pull/61584 for discussion of why.
+//
+/// If you need to use this implementation where `T` is unsized,
+/// consider using the `RangeBounds` impl for a 2-tuple of [`Bound<&T>`][Bound],
+/// i.e. replace `start..=end` with `(Bound::Included(start), Bound::Included(end))`.
 #[unstable(feature = "new_range_api", issue = "125687")]
 impl<T> RangeBounds<T> for RangeInclusive<&T> {
     fn start_bound(&self) -> Bound<&T> {
@@ -491,6 +503,12 @@ impl<T> RangeBounds<T> for RangeFrom<T> {
     }
 }
 
+// This impl intentionally does not have `T: ?Sized`;
+// see https://github.com/rust-lang/rust/pull/61584 for discussion of why.
+//
+/// If you need to use this implementation where `T` is unsized,
+/// consider using the `RangeBounds` impl for a 2-tuple of [`Bound<&T>`][Bound],
+/// i.e. replace `start..` with `(Bound::Included(start), Bound::Unbounded)`.
 #[unstable(feature = "new_range_api", issue = "125687")]
 impl<T> RangeBounds<T> for RangeFrom<&T> {
     fn start_bound(&self) -> Bound<&T> {
diff --git a/library/std/src/sync/mod.rs b/library/std/src/sync/mod.rs
index e67b4f6f22f..6ef3bf25cf6 100644
--- a/library/std/src/sync/mod.rs
+++ b/library/std/src/sync/mod.rs
@@ -225,6 +225,8 @@ pub use self::poison::{MappedMutexGuard, MappedRwLockReadGuard, MappedRwLockWrit
 pub mod mpmc;
 pub mod mpsc;
 
+#[unstable(feature = "sync_nonpoison", issue = "134645")]
+pub mod nonpoison;
 #[unstable(feature = "sync_poison_mod", issue = "134646")]
 pub mod poison;
 
diff --git a/library/std/src/sync/nonpoison.rs b/library/std/src/sync/nonpoison.rs
new file mode 100644
index 00000000000..2bbf226dc2c
--- /dev/null
+++ b/library/std/src/sync/nonpoison.rs
@@ -0,0 +1,37 @@
+//! Non-poisoning synchronous locks.
+//!
+//! The difference from the locks in the [`poison`] module is that the locks in this module will not
+//! become poisoned when a thread panics while holding a guard.
+//!
+//! [`poison`]: super::poison
+
+use crate::fmt;
+
+/// A type alias for the result of a nonblocking locking method.
+#[unstable(feature = "sync_nonpoison", issue = "134645")]
+pub type TryLockResult<Guard> = Result<Guard, WouldBlock>;
+
+/// A lock could not be acquired at this time because the operation would otherwise block.
+#[unstable(feature = "sync_nonpoison", issue = "134645")]
+pub struct WouldBlock;
+
+#[unstable(feature = "sync_nonpoison", issue = "134645")]
+impl fmt::Debug for WouldBlock {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        "WouldBlock".fmt(f)
+    }
+}
+
+#[unstable(feature = "sync_nonpoison", issue = "134645")]
+impl fmt::Display for WouldBlock {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        "try_lock failed because the operation would block".fmt(f)
+    }
+}
+
+#[unstable(feature = "mapped_lock_guards", issue = "117108")]
+pub use self::mutex::MappedMutexGuard;
+#[unstable(feature = "nonpoison_mutex", issue = "134645")]
+pub use self::mutex::{Mutex, MutexGuard};
+
+mod mutex;
diff --git a/library/std/src/sync/nonpoison/mutex.rs b/library/std/src/sync/nonpoison/mutex.rs
new file mode 100644
index 00000000000..b6861c78f00
--- /dev/null
+++ b/library/std/src/sync/nonpoison/mutex.rs
@@ -0,0 +1,611 @@
+use crate::cell::UnsafeCell;
+use crate::fmt;
+use crate::marker::PhantomData;
+use crate::mem::{self, ManuallyDrop};
+use crate::ops::{Deref, DerefMut};
+use crate::ptr::NonNull;
+use crate::sync::nonpoison::{TryLockResult, WouldBlock};
+use crate::sys::sync as sys;
+
+/// A mutual exclusion primitive useful for protecting shared data that does not keep track of
+/// lock poisoning.
+///
+/// For more information about mutexes, check out the documentation for the poisoning variant of
+/// this lock at [`poison::Mutex`].
+///
+/// [`poison::Mutex`]: crate::sync::poison::Mutex
+///
+/// # Examples
+///
+/// Note that this `Mutex` does **not** propagate threads that panic while holding the lock via
+/// poisoning. If you need this functionality, see [`poison::Mutex`].
+///
+/// ```
+/// #![feature(nonpoison_mutex)]
+///
+/// use std::thread;
+/// use std::sync::{Arc, nonpoison::Mutex};
+///
+/// let mutex = Arc::new(Mutex::new(0u32));
+/// let mut handles = Vec::new();
+///
+/// for n in 0..10 {
+///     let m = Arc::clone(&mutex);
+///     let handle = thread::spawn(move || {
+///         let mut guard = m.lock();
+///         *guard += 1;
+///         panic!("panic from thread {n} {guard}")
+///     });
+///     handles.push(handle);
+/// }
+///
+/// for h in handles {
+///     let _ = h.join();
+/// }
+///
+/// println!("Finished, locked {} times", mutex.lock());
+/// ```
+#[unstable(feature = "nonpoison_mutex", issue = "134645")]
+#[cfg_attr(not(test), rustc_diagnostic_item = "NonPoisonMutex")]
+pub struct Mutex<T: ?Sized> {
+    inner: sys::Mutex,
+    data: UnsafeCell<T>,
+}
+
+/// `T` must be `Send` for a [`Mutex`] to be `Send` because it is possible to acquire
+/// the owned `T` from the `Mutex` via [`into_inner`].
+///
+/// [`into_inner`]: Mutex::into_inner
+#[unstable(feature = "nonpoison_mutex", issue = "134645")]
+unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
+
+/// `T` must be `Send` for [`Mutex`] to be `Sync`.
+/// This ensures that the protected data can be accessed safely from multiple threads
+/// without causing data races or other unsafe behavior.
+///
+/// [`Mutex<T>`] provides mutable access to `T` to one thread at a time. However, it's essential
+/// for `T` to be `Send` because it's not safe for non-`Send` structures to be accessed in
+/// this manner. For instance, consider [`Rc`], a non-atomic reference counted smart pointer,
+/// which is not `Send`. With `Rc`, we can have multiple copies pointing to the same heap
+/// allocation with a non-atomic reference count. If we were to use `Mutex<Rc<_>>`, it would
+/// only protect one instance of `Rc` from shared access, leaving other copies vulnerable
+/// to potential data races.
+///
+/// Also note that it is not necessary for `T` to be `Sync` as `&T` is only made available
+/// to one thread at a time if `T` is not `Sync`.
+///
+/// [`Rc`]: crate::rc::Rc
+#[unstable(feature = "nonpoison_mutex", issue = "134645")]
+unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
+
+/// An RAII implementation of a "scoped lock" of a mutex. When this structure is
+/// dropped (falls out of scope), the lock will be unlocked.
+///
+/// The data protected by the mutex can be accessed through this guard via its
+/// [`Deref`] and [`DerefMut`] implementations.
+///
+/// This structure is created by the [`lock`] and [`try_lock`] methods on
+/// [`Mutex`].
+///
+/// [`lock`]: Mutex::lock
+/// [`try_lock`]: Mutex::try_lock
+#[must_use = "if unused the Mutex will immediately unlock"]
+#[must_not_suspend = "holding a MutexGuard across suspend \
+                      points can cause deadlocks, delays, \
+                      and cause Futures to not implement `Send`"]
+#[unstable(feature = "nonpoison_mutex", issue = "134645")]
+#[clippy::has_significant_drop]
+#[cfg_attr(not(test), rustc_diagnostic_item = "NonPoisonMutexGuard")]
+pub struct MutexGuard<'a, T: ?Sized + 'a> {
+    lock: &'a Mutex<T>,
+}
+
+/// A [`MutexGuard`] is not `Send` to maximize platform portablity.
+///
+/// On platforms that use POSIX threads (commonly referred to as pthreads) there is a requirement to
+/// release mutex locks on the same thread they were acquired.
+/// For this reason, [`MutexGuard`] must not implement `Send` to prevent it being dropped from
+/// another thread.
+#[unstable(feature = "nonpoison_mutex", issue = "134645")]
+impl<T: ?Sized> !Send for MutexGuard<'_, T> {}
+
+/// `T` must be `Sync` for a [`MutexGuard<T>`] to be `Sync`
+/// because it is possible to get a `&T` from `&MutexGuard` (via `Deref`).
+#[unstable(feature = "nonpoison_mutex", issue = "134645")]
+unsafe impl<T: ?Sized + Sync> Sync for MutexGuard<'_, T> {}
+
+// FIXME(nonpoison_condvar): Use this link instead: [`Condvar`]: crate::sync::nonpoison::Condvar
+/// An RAII mutex guard returned by `MutexGuard::map`, which can point to a
+/// subfield of the protected data. When this structure is dropped (falls out
+/// of scope), the lock will be unlocked.
+///
+/// The main difference between `MappedMutexGuard` and [`MutexGuard`] is that the
+/// former cannot be used with [`Condvar`], since that could introduce soundness issues if the
+/// locked object is modified by another thread while the `Mutex` is unlocked.
+///
+/// The data protected by the mutex can be accessed through this guard via its
+/// [`Deref`] and [`DerefMut`] implementations.
+///
+/// This structure is created by the [`map`] and [`filter_map`] methods on
+/// [`MutexGuard`].
+///
+/// [`map`]: MutexGuard::map
+/// [`filter_map`]: MutexGuard::filter_map
+/// [`Condvar`]: crate::sync::Condvar
+#[must_use = "if unused the Mutex will immediately unlock"]
+#[must_not_suspend = "holding a MappedMutexGuard across suspend \
+                      points can cause deadlocks, delays, \
+                      and cause Futures to not implement `Send`"]
+#[unstable(feature = "mapped_lock_guards", issue = "117108")]
+// #[unstable(feature = "nonpoison_mutex", issue = "134645")]
+#[clippy::has_significant_drop]
+pub struct MappedMutexGuard<'a, T: ?Sized + 'a> {
+    // NB: we use a pointer instead of `&'a mut T` to avoid `noalias` violations, because a
+    // `MappedMutexGuard` argument doesn't hold uniqueness for its whole scope, only until it drops.
+    // `NonNull` is covariant over `T`, so we add a `PhantomData<&'a mut T>` field
+    // below for the correct variance over `T` (invariance).
+    data: NonNull<T>,
+    inner: &'a sys::Mutex,
+    _variance: PhantomData<&'a mut T>,
+}
+
+#[unstable(feature = "mapped_lock_guards", issue = "117108")]
+// #[unstable(feature = "nonpoison_mutex", issue = "134645")]
+impl<T: ?Sized> !Send for MappedMutexGuard<'_, T> {}
+#[unstable(feature = "mapped_lock_guards", issue = "117108")]
+// #[unstable(feature = "nonpoison_mutex", issue = "134645")]
+unsafe impl<T: ?Sized + Sync> Sync for MappedMutexGuard<'_, T> {}
+
+impl<T> Mutex<T> {
+    /// Creates a new mutex in an unlocked state ready for use.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(nonpoison_mutex)]
+    ///
+    /// use std::sync::nonpoison::Mutex;
+    ///
+    /// let mutex = Mutex::new(0);
+    /// ```
+    #[unstable(feature = "nonpoison_mutex", issue = "134645")]
+    #[inline]
+    pub const fn new(t: T) -> Mutex<T> {
+        Mutex { inner: sys::Mutex::new(), data: UnsafeCell::new(t) }
+    }
+
+    /// Returns the contained value by cloning it.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(nonpoison_mutex)]
+    /// #![feature(lock_value_accessors)]
+    ///
+    /// use std::sync::nonpoison::Mutex;
+    ///
+    /// let mut mutex = Mutex::new(7);
+    ///
+    /// assert_eq!(mutex.get_cloned(), 7);
+    /// ```
+    #[unstable(feature = "lock_value_accessors", issue = "133407")]
+    // #[unstable(feature = "nonpoison_mutex", issue = "134645")]
+    pub fn get_cloned(&self) -> T
+    where
+        T: Clone,
+    {
+        self.lock().clone()
+    }
+
+    /// Sets the contained value.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(nonpoison_mutex)]
+    /// #![feature(lock_value_accessors)]
+    ///
+    /// use std::sync::nonpoison::Mutex;
+    ///
+    /// let mut mutex = Mutex::new(7);
+    ///
+    /// assert_eq!(mutex.get_cloned(), 7);
+    /// mutex.set(11);
+    /// assert_eq!(mutex.get_cloned(), 11);
+    /// ```
+    #[unstable(feature = "lock_value_accessors", issue = "133407")]
+    // #[unstable(feature = "nonpoison_mutex", issue = "134645")]
+    pub fn set(&self, value: T) {
+        if mem::needs_drop::<T>() {
+            // If the contained value has a non-trivial destructor, we
+            // call that destructor after the lock has been released.
+            drop(self.replace(value))
+        } else {
+            *self.lock() = value;
+        }
+    }
+
+    /// Replaces the contained value with `value`, and returns the old contained value.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(nonpoison_mutex)]
+    /// #![feature(lock_value_accessors)]
+    ///
+    /// use std::sync::nonpoison::Mutex;
+    ///
+    /// let mut mutex = Mutex::new(7);
+    ///
+    /// assert_eq!(mutex.replace(11), 7);
+    /// assert_eq!(mutex.get_cloned(), 11);
+    /// ```
+    #[unstable(feature = "lock_value_accessors", issue = "133407")]
+    // #[unstable(feature = "nonpoison_mutex", issue = "134645")]
+    pub fn replace(&self, value: T) -> T {
+        let mut guard = self.lock();
+        mem::replace(&mut *guard, value)
+    }
+}
+
+impl<T: ?Sized> Mutex<T> {
+    /// Acquires a mutex, blocking the current thread until it is able to do so.
+    ///
+    /// This function will block the local thread until it is available to acquire
+    /// the mutex. Upon returning, the thread is the only thread with the lock
+    /// held. An RAII guard is returned to allow scoped unlock of the lock. When
+    /// the guard goes out of scope, the mutex will be unlocked.
+    ///
+    /// The exact behavior on locking a mutex in the thread which already holds
+    /// the lock is left unspecified. However, this function will not return on
+    /// the second call (it might panic or deadlock, for example).
+    ///
+    /// # Panics
+    ///
+    /// This function might panic when called if the lock is already held by
+    /// the current thread.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(nonpoison_mutex)]
+    ///
+    /// use std::sync::{Arc, nonpoison::Mutex};
+    /// use std::thread;
+    ///
+    /// let mutex = Arc::new(Mutex::new(0));
+    /// let c_mutex = Arc::clone(&mutex);
+    ///
+    /// thread::spawn(move || {
+    ///     *c_mutex.lock() = 10;
+    /// }).join().expect("thread::spawn failed");
+    /// assert_eq!(*mutex.lock(), 10);
+    /// ```
+    #[unstable(feature = "nonpoison_mutex", issue = "134645")]
+    pub fn lock(&self) -> MutexGuard<'_, T> {
+        unsafe {
+            self.inner.lock();
+            MutexGuard::new(self)
+        }
+    }
+
+    /// Attempts to acquire this lock.
+    ///
+    /// This function does not block. If the lock could not be acquired at this time, then
+    /// [`WouldBlock`] is returned. Otherwise, an RAII guard is returned.
+    ///
+    /// The lock will be unlocked when the guard is dropped.
+    ///
+    /// # Errors
+    ///
+    /// If the mutex could not be acquired because it is already locked, then this call will return
+    /// the [`WouldBlock`] error.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use std::sync::{Arc, Mutex};
+    /// use std::thread;
+    ///
+    /// let mutex = Arc::new(Mutex::new(0));
+    /// let c_mutex = Arc::clone(&mutex);
+    ///
+    /// thread::spawn(move || {
+    ///     let mut lock = c_mutex.try_lock();
+    ///     if let Ok(ref mut mutex) = lock {
+    ///         **mutex = 10;
+    ///     } else {
+    ///         println!("try_lock failed");
+    ///     }
+    /// }).join().expect("thread::spawn failed");
+    /// assert_eq!(*mutex.lock().unwrap(), 10);
+    /// ```
+    #[unstable(feature = "nonpoison_mutex", issue = "134645")]
+    pub fn try_lock(&self) -> TryLockResult<MutexGuard<'_, T>> {
+        unsafe { if self.inner.try_lock() { Ok(MutexGuard::new(self)) } else { Err(WouldBlock) } }
+    }
+
+    /// Consumes this mutex, returning the underlying data.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(nonpoison_mutex)]
+    ///
+    /// use std::sync::nonpoison::Mutex;
+    ///
+    /// let mutex = Mutex::new(0);
+    /// assert_eq!(mutex.into_inner(), 0);
+    /// ```
+    #[unstable(feature = "nonpoison_mutex", issue = "134645")]
+    pub fn into_inner(self) -> T
+    where
+        T: Sized,
+    {
+        self.data.into_inner()
+    }
+
+    /// Returns a mutable reference to the underlying data.
+    ///
+    /// Since this call borrows the `Mutex` mutably, no actual locking needs to
+    /// take place -- the mutable borrow statically guarantees no locks exist.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(nonpoison_mutex)]
+    ///
+    /// use std::sync::nonpoison::Mutex;
+    ///
+    /// let mut mutex = Mutex::new(0);
+    /// *mutex.get_mut() = 10;
+    /// assert_eq!(*mutex.lock(), 10);
+    /// ```
+    #[unstable(feature = "nonpoison_mutex", issue = "134645")]
+    pub fn get_mut(&mut self) -> &mut T {
+        self.data.get_mut()
+    }
+
+    /// Returns a raw pointer to the underlying data.
+    ///
+    /// The returned pointer is always non-null and properly aligned, but it is
+    /// the user's responsibility to ensure that any reads and writes through it
+    /// are properly synchronized to avoid data races, and that it is not read
+    /// or written through after the mutex is dropped.
+    #[unstable(feature = "mutex_data_ptr", issue = "140368")]
+    // #[unstable(feature = "nonpoison_mutex", issue = "134645")]
+    pub fn data_ptr(&self) -> *mut T {
+        self.data.get()
+    }
+}
+
+#[unstable(feature = "nonpoison_mutex", issue = "134645")]
+impl<T> From<T> for Mutex<T> {
+    /// Creates a new mutex in an unlocked state ready for use.
+    /// This is equivalent to [`Mutex::new`].
+    fn from(t: T) -> Self {
+        Mutex::new(t)
+    }
+}
+
+#[unstable(feature = "nonpoison_mutex", issue = "134645")]
+impl<T: ?Sized + Default> Default for Mutex<T> {
+    /// Creates a `Mutex<T>`, with the `Default` value for T.
+    fn default() -> Mutex<T> {
+        Mutex::new(Default::default())
+    }
+}
+
+#[unstable(feature = "nonpoison_mutex", issue = "134645")]
+impl<T: ?Sized + fmt::Debug> fmt::Debug for Mutex<T> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let mut d = f.debug_struct("Mutex");
+        match self.try_lock() {
+            Ok(guard) => {
+                d.field("data", &&*guard);
+            }
+            Err(WouldBlock) => {
+                d.field("data", &"<locked>");
+            }
+        }
+        d.finish_non_exhaustive()
+    }
+}
+
+impl<'mutex, T: ?Sized> MutexGuard<'mutex, T> {
+    unsafe fn new(lock: &'mutex Mutex<T>) -> MutexGuard<'mutex, T> {
+        return MutexGuard { lock };
+    }
+}
+
+#[unstable(feature = "nonpoison_mutex", issue = "134645")]
+impl<T: ?Sized> Deref for MutexGuard<'_, T> {
+    type Target = T;
+
+    fn deref(&self) -> &T {
+        unsafe { &*self.lock.data.get() }
+    }
+}
+
+#[unstable(feature = "nonpoison_mutex", issue = "134645")]
+impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
+    fn deref_mut(&mut self) -> &mut T {
+        unsafe { &mut *self.lock.data.get() }
+    }
+}
+
+#[unstable(feature = "nonpoison_mutex", issue = "134645")]
+impl<T: ?Sized> Drop for MutexGuard<'_, T> {
+    #[inline]
+    fn drop(&mut self) {
+        unsafe {
+            self.lock.inner.unlock();
+        }
+    }
+}
+
+#[unstable(feature = "nonpoison_mutex", issue = "134645")]
+impl<T: ?Sized + fmt::Debug> fmt::Debug for MutexGuard<'_, T> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt::Debug::fmt(&**self, f)
+    }
+}
+
+#[unstable(feature = "nonpoison_mutex", issue = "134645")]
+impl<T: ?Sized + fmt::Display> fmt::Display for MutexGuard<'_, T> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        (**self).fmt(f)
+    }
+}
+
+impl<'a, T: ?Sized> MutexGuard<'a, T> {
+    /// Makes a [`MappedMutexGuard`] for a component of the borrowed data, e.g.
+    /// an enum variant.
+    ///
+    /// The `Mutex` is already locked, so this cannot fail.
+    ///
+    /// This is an associated function that needs to be used as
+    /// `MutexGuard::map(...)`. A method would interfere with methods of the
+    /// same name on the contents of the `MutexGuard` used through `Deref`.
+    #[unstable(feature = "mapped_lock_guards", issue = "117108")]
+    // #[unstable(feature = "nonpoison_mutex", issue = "134645")]
+    pub fn map<U, F>(orig: Self, f: F) -> MappedMutexGuard<'a, U>
+    where
+        F: FnOnce(&mut T) -> &mut U,
+        U: ?Sized,
+    {
+        // SAFETY: the conditions of `MutexGuard::new` were satisfied when the original guard
+        // was created, and have been upheld throughout `map` and/or `filter_map`.
+        // The signature of the closure guarantees that it will not "leak" the lifetime of the reference
+        // passed to it. If the closure panics, the guard will be dropped.
+        let data = NonNull::from(f(unsafe { &mut *orig.lock.data.get() }));
+        let orig = ManuallyDrop::new(orig);
+        MappedMutexGuard { data, inner: &orig.lock.inner, _variance: PhantomData }
+    }
+
+    /// Makes a [`MappedMutexGuard`] for a component of the borrowed data. The
+    /// original guard is returned as an `Err(...)` if the closure returns
+    /// `None`.
+    ///
+    /// The `Mutex` is already locked, so this cannot fail.
+    ///
+    /// This is an associated function that needs to be used as
+    /// `MutexGuard::filter_map(...)`. A method would interfere with methods of the
+    /// same name on the contents of the `MutexGuard` used through `Deref`.
+    #[unstable(feature = "mapped_lock_guards", issue = "117108")]
+    // #[unstable(feature = "nonpoison_mutex", issue = "134645")]
+    pub fn filter_map<U, F>(orig: Self, f: F) -> Result<MappedMutexGuard<'a, U>, Self>
+    where
+        F: FnOnce(&mut T) -> Option<&mut U>,
+        U: ?Sized,
+    {
+        // SAFETY: the conditions of `MutexGuard::new` were satisfied when the original guard
+        // was created, and have been upheld throughout `map` and/or `filter_map`.
+        // The signature of the closure guarantees that it will not "leak" the lifetime of the reference
+        // passed to it. If the closure panics, the guard will be dropped.
+        match f(unsafe { &mut *orig.lock.data.get() }) {
+            Some(data) => {
+                let data = NonNull::from(data);
+                let orig = ManuallyDrop::new(orig);
+                Ok(MappedMutexGuard { data, inner: &orig.lock.inner, _variance: PhantomData })
+            }
+            None => Err(orig),
+        }
+    }
+}
+
+#[unstable(feature = "mapped_lock_guards", issue = "117108")]
+impl<T: ?Sized> Deref for MappedMutexGuard<'_, T> {
+    type Target = T;
+
+    fn deref(&self) -> &T {
+        unsafe { self.data.as_ref() }
+    }
+}
+
+#[unstable(feature = "mapped_lock_guards", issue = "117108")]
+impl<T: ?Sized> DerefMut for MappedMutexGuard<'_, T> {
+    fn deref_mut(&mut self) -> &mut T {
+        unsafe { self.data.as_mut() }
+    }
+}
+
+#[unstable(feature = "mapped_lock_guards", issue = "117108")]
+impl<T: ?Sized> Drop for MappedMutexGuard<'_, T> {
+    #[inline]
+    fn drop(&mut self) {
+        unsafe {
+            self.inner.unlock();
+        }
+    }
+}
+
+#[unstable(feature = "mapped_lock_guards", issue = "117108")]
+impl<T: ?Sized + fmt::Debug> fmt::Debug for MappedMutexGuard<'_, T> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt::Debug::fmt(&**self, f)
+    }
+}
+
+#[unstable(feature = "mapped_lock_guards", issue = "117108")]
+impl<T: ?Sized + fmt::Display> fmt::Display for MappedMutexGuard<'_, T> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        (**self).fmt(f)
+    }
+}
+
+impl<'a, T: ?Sized> MappedMutexGuard<'a, T> {
+    /// Makes a [`MappedMutexGuard`] for a component of the borrowed data, e.g.
+    /// an enum variant.
+    ///
+    /// The `Mutex` is already locked, so this cannot fail.
+    ///
+    /// This is an associated function that needs to be used as
+    /// `MappedMutexGuard::map(...)`. A method would interfere with methods of the
+    /// same name on the contents of the `MutexGuard` used through `Deref`.
+    #[unstable(feature = "mapped_lock_guards", issue = "117108")]
+    // #[unstable(feature = "nonpoison_mutex", issue = "134645")]
+    pub fn map<U, F>(mut orig: Self, f: F) -> MappedMutexGuard<'a, U>
+    where
+        F: FnOnce(&mut T) -> &mut U,
+        U: ?Sized,
+    {
+        // SAFETY: the conditions of `MutexGuard::new` were satisfied when the original guard
+        // was created, and have been upheld throughout `map` and/or `filter_map`.
+        // The signature of the closure guarantees that it will not "leak" the lifetime of the reference
+        // passed to it. If the closure panics, the guard will be dropped.
+        let data = NonNull::from(f(unsafe { orig.data.as_mut() }));
+        let orig = ManuallyDrop::new(orig);
+        MappedMutexGuard { data, inner: orig.inner, _variance: PhantomData }
+    }
+
+    /// Makes a [`MappedMutexGuard`] for a component of the borrowed data. The
+    /// original guard is returned as an `Err(...)` if the closure returns
+    /// `None`.
+    ///
+    /// The `Mutex` is already locked, so this cannot fail.
+    ///
+    /// This is an associated function that needs to be used as
+    /// `MappedMutexGuard::filter_map(...)`. A method would interfere with methods of the
+    /// same name on the contents of the `MutexGuard` used through `Deref`.
+    #[unstable(feature = "mapped_lock_guards", issue = "117108")]
+    // #[unstable(feature = "nonpoison_mutex", issue = "134645")]
+    pub fn filter_map<U, F>(mut orig: Self, f: F) -> Result<MappedMutexGuard<'a, U>, Self>
+    where
+        F: FnOnce(&mut T) -> Option<&mut U>,
+        U: ?Sized,
+    {
+        // SAFETY: the conditions of `MutexGuard::new` were satisfied when the original guard
+        // was created, and have been upheld throughout `map` and/or `filter_map`.
+        // The signature of the closure guarantees that it will not "leak" the lifetime of the reference
+        // passed to it. If the closure panics, the guard will be dropped.
+        match f(unsafe { orig.data.as_mut() }) {
+            Some(data) => {
+                let data = NonNull::from(data);
+                let orig = ManuallyDrop::new(orig);
+                Ok(MappedMutexGuard { data, inner: orig.inner, _variance: PhantomData })
+            }
+            None => Err(orig),
+        }
+    }
+}
diff --git a/library/std/src/sync/poison.rs b/library/std/src/sync/poison.rs
index 0c05f152ef8..b901a5701a4 100644
--- a/library/std/src/sync/poison.rs
+++ b/library/std/src/sync/poison.rs
@@ -13,7 +13,9 @@
 //! depend on the primitive. See [#Overview] below.
 //!
 //! For the alternative implementations that do not employ poisoning,
-//! see `std::sync::nonpoisoning`.
+//! see [`std::sync::nonpoison`].
+//!
+//! [`std::sync::nonpoison`]: crate::sync::nonpoison
 //!
 //! # Overview
 //!
@@ -56,8 +58,6 @@
 //!   while it is locked exclusively (write mode). If a panic occurs in any reader,
 //!   then the lock will not be poisoned.
 
-// FIXME(sync_nonpoison) add links to sync::nonpoison to the doc comment above.
-
 #[stable(feature = "rust1", since = "1.0.0")]
 pub use self::condvar::{Condvar, WaitTimeoutResult};
 #[unstable(feature = "mapped_lock_guards", issue = "117108")]
diff --git a/library/std/src/sync/poison/condvar.rs b/library/std/src/sync/poison/condvar.rs
index 7f0f3f652bc..0e9d4233c65 100644
--- a/library/std/src/sync/poison/condvar.rs
+++ b/library/std/src/sync/poison/condvar.rs
@@ -13,7 +13,7 @@ use crate::time::{Duration, Instant};
 #[stable(feature = "wait_timeout", since = "1.5.0")]
 pub struct WaitTimeoutResult(bool);
 
-// FIXME(sync_nonpoison): `WaitTimeoutResult` is actually poisoning-agnostic, it seems.
+// FIXME(nonpoison_condvar): `WaitTimeoutResult` is actually poisoning-agnostic, it seems.
 // Should we take advantage of this fact?
 impl WaitTimeoutResult {
     /// Returns `true` if the wait was known to have timed out.
diff --git a/library/std/src/sync/poison/mutex.rs b/library/std/src/sync/poison/mutex.rs
index 30325be685c..64744f18c74 100644
--- a/library/std/src/sync/poison/mutex.rs
+++ b/library/std/src/sync/poison/mutex.rs
@@ -650,7 +650,7 @@ impl<T: ?Sized + fmt::Debug> fmt::Debug for Mutex<T> {
                 d.field("data", &&**err.get_ref());
             }
             Err(TryLockError::WouldBlock) => {
-                d.field("data", &format_args!("<locked>"));
+                d.field("data", &"<locked>");
             }
         }
         d.field("poisoned", &self.poison.get());
diff --git a/library/std/src/sys/pal/hermit/thread.rs b/library/std/src/sys/pal/hermit/thread.rs
index 9bc5a16b800..95fe4f902d3 100644
--- a/library/std/src/sys/pal/hermit/thread.rs
+++ b/library/std/src/sys/pal/hermit/thread.rs
@@ -58,7 +58,11 @@ impl Thread {
         }
     }
 
-    pub unsafe fn new(stack: usize, p: Box<dyn FnOnce()>) -> io::Result<Thread> {
+    pub unsafe fn new(
+        stack: usize,
+        _name: Option<&str>,
+        p: Box<dyn FnOnce()>,
+    ) -> io::Result<Thread> {
         unsafe {
             Thread::new_with_coreid(stack, p, -1 /* = no specific core */)
         }
diff --git a/library/std/src/sys/pal/itron/thread.rs b/library/std/src/sys/pal/itron/thread.rs
index 813e1cbcd58..0d28051fcc4 100644
--- a/library/std/src/sys/pal/itron/thread.rs
+++ b/library/std/src/sys/pal/itron/thread.rs
@@ -86,7 +86,11 @@ impl Thread {
     /// # Safety
     ///
     /// See `thread::Builder::spawn_unchecked` for safety requirements.
-    pub unsafe fn new(stack: usize, p: Box<dyn FnOnce()>) -> io::Result<Thread> {
+    pub unsafe fn new(
+        stack: usize,
+        _name: Option<&str>,
+        p: Box<dyn FnOnce()>,
+    ) -> io::Result<Thread> {
         let inner = Box::new(ThreadInner {
             start: UnsafeCell::new(ManuallyDrop::new(p)),
             lifecycle: AtomicUsize::new(LIFECYCLE_INIT),
diff --git a/library/std/src/sys/pal/sgx/thread.rs b/library/std/src/sys/pal/sgx/thread.rs
index 85f6dcd96b4..a236c362706 100644
--- a/library/std/src/sys/pal/sgx/thread.rs
+++ b/library/std/src/sys/pal/sgx/thread.rs
@@ -96,7 +96,11 @@ pub mod wait_notify {
 
 impl Thread {
     // unsafe: see thread::Builder::spawn_unchecked for safety requirements
-    pub unsafe fn new(_stack: usize, p: Box<dyn FnOnce() + Send>) -> io::Result<Thread> {
+    pub unsafe fn new(
+        _stack: usize,
+        _name: Option<&str>,
+        p: Box<dyn FnOnce() + Send>,
+    ) -> io::Result<Thread> {
         let mut queue_lock = task_queue::lock();
         unsafe { usercalls::launch_thread()? };
         let (task, handle) = task_queue::Task::new(p);
diff --git a/library/std/src/sys/pal/teeos/thread.rs b/library/std/src/sys/pal/teeos/thread.rs
index b9cdc7a2a58..a91d95626e7 100644
--- a/library/std/src/sys/pal/teeos/thread.rs
+++ b/library/std/src/sys/pal/teeos/thread.rs
@@ -22,7 +22,11 @@ unsafe extern "C" {
 
 impl Thread {
     // unsafe: see thread::Builder::spawn_unchecked for safety requirements
-    pub unsafe fn new(stack: usize, p: Box<dyn FnOnce()>) -> io::Result<Thread> {
+    pub unsafe fn new(
+        stack: usize,
+        _name: Option<&str>,
+        p: Box<dyn FnOnce()>,
+    ) -> io::Result<Thread> {
         let p = Box::into_raw(Box::new(p));
         let mut native: libc::pthread_t = unsafe { mem::zeroed() };
         let mut attr: libc::pthread_attr_t = unsafe { mem::zeroed() };
diff --git a/library/std/src/sys/pal/uefi/thread.rs b/library/std/src/sys/pal/uefi/thread.rs
index e4776ec42fb..75c364362b2 100644
--- a/library/std/src/sys/pal/uefi/thread.rs
+++ b/library/std/src/sys/pal/uefi/thread.rs
@@ -11,7 +11,11 @@ pub const DEFAULT_MIN_STACK_SIZE: usize = 64 * 1024;
 
 impl Thread {
     // unsafe: see thread::Builder::spawn_unchecked for safety requirements
-    pub unsafe fn new(_stack: usize, _p: Box<dyn FnOnce()>) -> io::Result<Thread> {
+    pub unsafe fn new(
+        _stack: usize,
+        _name: Option<&str>,
+        _p: Box<dyn FnOnce()>,
+    ) -> io::Result<Thread> {
         unsupported()
     }
 
diff --git a/library/std/src/sys/pal/unix/stack_overflow.rs b/library/std/src/sys/pal/unix/stack_overflow.rs
index a3be2cdf738..d89100e6919 100644
--- a/library/std/src/sys/pal/unix/stack_overflow.rs
+++ b/library/std/src/sys/pal/unix/stack_overflow.rs
@@ -8,8 +8,8 @@ pub struct Handler {
 }
 
 impl Handler {
-    pub unsafe fn new() -> Handler {
-        make_handler(false)
+    pub unsafe fn new(thread_name: Option<Box<str>>) -> Handler {
+        make_handler(false, thread_name)
     }
 
     fn null() -> Handler {
@@ -72,7 +72,6 @@ mod imp {
     use crate::sync::OnceLock;
     use crate::sync::atomic::{Atomic, AtomicBool, AtomicPtr, AtomicUsize, Ordering};
     use crate::sys::pal::unix::os;
-    use crate::thread::with_current_name;
     use crate::{io, mem, panic, ptr};
 
     // Signal handler for the SIGSEGV and SIGBUS handlers. We've got guard pages
@@ -158,13 +157,12 @@ mod imp {
                 if !NEED_ALTSTACK.load(Ordering::Relaxed) {
                     // haven't set up our sigaltstack yet
                     NEED_ALTSTACK.store(true, Ordering::Release);
-                    let handler = unsafe { make_handler(true) };
+                    let handler = unsafe { make_handler(true, None) };
                     MAIN_ALTSTACK.store(handler.data, Ordering::Relaxed);
                     mem::forget(handler);
 
                     if let Some(guard_page_range) = guard_page_range.take() {
-                        let thread_name = with_current_name(|name| name.map(Box::from));
-                        set_current_info(guard_page_range, thread_name);
+                        set_current_info(guard_page_range, Some(Box::from("main")));
                     }
                 }
 
@@ -230,14 +228,13 @@ mod imp {
     /// # Safety
     /// Mutates the alternate signal stack
     #[forbid(unsafe_op_in_unsafe_fn)]
-    pub unsafe fn make_handler(main_thread: bool) -> Handler {
+    pub unsafe fn make_handler(main_thread: bool, thread_name: Option<Box<str>>) -> Handler {
         if !NEED_ALTSTACK.load(Ordering::Acquire) {
             return Handler::null();
         }
 
         if !main_thread {
             if let Some(guard_page_range) = unsafe { current_guard() } {
-                let thread_name = with_current_name(|name| name.map(Box::from));
                 set_current_info(guard_page_range, thread_name);
             }
         }
@@ -634,7 +631,10 @@ mod imp {
 
     pub unsafe fn cleanup() {}
 
-    pub unsafe fn make_handler(_main_thread: bool) -> super::Handler {
+    pub unsafe fn make_handler(
+        _main_thread: bool,
+        _thread_name: Option<Box<str>>,
+    ) -> super::Handler {
         super::Handler::null()
     }
 
@@ -717,7 +717,10 @@ mod imp {
 
     pub unsafe fn cleanup() {}
 
-    pub unsafe fn make_handler(main_thread: bool) -> super::Handler {
+    pub unsafe fn make_handler(
+        main_thread: bool,
+        _thread_name: Option<Box<str>>,
+    ) -> super::Handler {
         if !main_thread {
             reserve_stack();
         }
diff --git a/library/std/src/sys/pal/unix/thread.rs b/library/std/src/sys/pal/unix/thread.rs
index e4f5520d8a3..7f6440152d4 100644
--- a/library/std/src/sys/pal/unix/thread.rs
+++ b/library/std/src/sys/pal/unix/thread.rs
@@ -22,6 +22,11 @@ pub const DEFAULT_MIN_STACK_SIZE: usize = 256 * 1024;
 #[cfg(any(target_os = "espidf", target_os = "nuttx"))]
 pub const DEFAULT_MIN_STACK_SIZE: usize = 0; // 0 indicates that the stack size configured in the ESP-IDF/NuttX menuconfig system should be used
 
+struct ThreadData {
+    name: Option<Box<str>>,
+    f: Box<dyn FnOnce()>,
+}
+
 pub struct Thread {
     id: libc::pthread_t,
 }
@@ -34,8 +39,12 @@ unsafe impl Sync for Thread {}
 impl Thread {
     // unsafe: see thread::Builder::spawn_unchecked for safety requirements
     #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
-    pub unsafe fn new(stack: usize, p: Box<dyn FnOnce()>) -> io::Result<Thread> {
-        let p = Box::into_raw(Box::new(p));
+    pub unsafe fn new(
+        stack: usize,
+        name: Option<&str>,
+        f: Box<dyn FnOnce()>,
+    ) -> io::Result<Thread> {
+        let data = Box::into_raw(Box::new(ThreadData { name: name.map(Box::from), f }));
         let mut native: libc::pthread_t = mem::zeroed();
         let mut attr: mem::MaybeUninit<libc::pthread_attr_t> = mem::MaybeUninit::uninit();
         assert_eq!(libc::pthread_attr_init(attr.as_mut_ptr()), 0);
@@ -73,7 +82,7 @@ impl Thread {
             };
         }
 
-        let ret = libc::pthread_create(&mut native, attr.as_ptr(), thread_start, p as *mut _);
+        let ret = libc::pthread_create(&mut native, attr.as_ptr(), thread_start, data as *mut _);
         // Note: if the thread creation fails and this assert fails, then p will
         // be leaked. However, an alternative design could cause double-free
         // which is clearly worse.
@@ -82,19 +91,20 @@ impl Thread {
         return if ret != 0 {
             // The thread failed to start and as a result p was not consumed. Therefore, it is
             // safe to reconstruct the box so that it gets deallocated.
-            drop(Box::from_raw(p));
+            drop(Box::from_raw(data));
             Err(io::Error::from_raw_os_error(ret))
         } else {
             Ok(Thread { id: native })
         };
 
-        extern "C" fn thread_start(main: *mut libc::c_void) -> *mut libc::c_void {
+        extern "C" fn thread_start(data: *mut libc::c_void) -> *mut libc::c_void {
             unsafe {
+                let data = Box::from_raw(data as *mut ThreadData);
                 // Next, set up our stack overflow handler which may get triggered if we run
                 // out of stack.
-                let _handler = stack_overflow::Handler::new();
+                let _handler = stack_overflow::Handler::new(data.name);
                 // Finally, let's run some code.
-                Box::from_raw(main as *mut Box<dyn FnOnce()>)();
+                (data.f)();
             }
             ptr::null_mut()
         }
diff --git a/library/std/src/sys/pal/unsupported/thread.rs b/library/std/src/sys/pal/unsupported/thread.rs
index 8a3119fa292..5a1e3fde986 100644
--- a/library/std/src/sys/pal/unsupported/thread.rs
+++ b/library/std/src/sys/pal/unsupported/thread.rs
@@ -10,7 +10,11 @@ pub const DEFAULT_MIN_STACK_SIZE: usize = 64 * 1024;
 
 impl Thread {
     // unsafe: see thread::Builder::spawn_unchecked for safety requirements
-    pub unsafe fn new(_stack: usize, _p: Box<dyn FnOnce()>) -> io::Result<Thread> {
+    pub unsafe fn new(
+        _stack: usize,
+        _name: Option<&str>,
+        _p: Box<dyn FnOnce()>,
+    ) -> io::Result<Thread> {
         unsupported()
     }
 
diff --git a/library/std/src/sys/pal/wasi/thread.rs b/library/std/src/sys/pal/wasi/thread.rs
index 5f21a553673..a46c74630c9 100644
--- a/library/std/src/sys/pal/wasi/thread.rs
+++ b/library/std/src/sys/pal/wasi/thread.rs
@@ -73,7 +73,7 @@ impl Thread {
     // unsafe: see thread::Builder::spawn_unchecked for safety requirements
     cfg_if::cfg_if! {
         if #[cfg(target_feature = "atomics")] {
-            pub unsafe fn new(stack: usize, p: Box<dyn FnOnce()>) -> io::Result<Thread> {
+            pub unsafe fn new(stack: usize, _name: Option<&str>, p: Box<dyn FnOnce()>) -> io::Result<Thread> {
                 let p = Box::into_raw(Box::new(p));
                 let mut native: libc::pthread_t = unsafe { mem::zeroed() };
                 let mut attr: libc::pthread_attr_t = unsafe { mem::zeroed() };
@@ -120,7 +120,7 @@ impl Thread {
                 }
             }
         } else {
-            pub unsafe fn new(_stack: usize, _p: Box<dyn FnOnce()>) -> io::Result<Thread> {
+            pub unsafe fn new(_stack: usize, _name: Option<&str>, _p: Box<dyn FnOnce()>) -> io::Result<Thread> {
                 crate::sys::unsupported()
             }
         }
diff --git a/library/std/src/sys/pal/wasm/atomics/thread.rs b/library/std/src/sys/pal/wasm/atomics/thread.rs
index 44ce3eab109..ebfabaafc79 100644
--- a/library/std/src/sys/pal/wasm/atomics/thread.rs
+++ b/library/std/src/sys/pal/wasm/atomics/thread.rs
@@ -10,7 +10,11 @@ pub const DEFAULT_MIN_STACK_SIZE: usize = 1024 * 1024;
 
 impl Thread {
     // unsafe: see thread::Builder::spawn_unchecked for safety requirements
-    pub unsafe fn new(_stack: usize, _p: Box<dyn FnOnce()>) -> io::Result<Thread> {
+    pub unsafe fn new(
+        _stack: usize,
+        _name: Option<&str>,
+        _p: Box<dyn FnOnce()>,
+    ) -> io::Result<Thread> {
         unsupported()
     }
 
diff --git a/library/std/src/sys/pal/windows/thread.rs b/library/std/src/sys/pal/windows/thread.rs
index 14785171755..b45f76fb546 100644
--- a/library/std/src/sys/pal/windows/thread.rs
+++ b/library/std/src/sys/pal/windows/thread.rs
@@ -20,7 +20,11 @@ pub struct Thread {
 impl Thread {
     // unsafe: see thread::Builder::spawn_unchecked for safety requirements
     #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
-    pub unsafe fn new(stack: usize, p: Box<dyn FnOnce()>) -> io::Result<Thread> {
+    pub unsafe fn new(
+        stack: usize,
+        _name: Option<&str>,
+        p: Box<dyn FnOnce()>,
+    ) -> io::Result<Thread> {
         let p = Box::into_raw(Box::new(p));
 
         // CreateThread rounds up values for the stack size to the nearest page size (at least 4kb).
diff --git a/library/std/src/sys/pal/xous/thread.rs b/library/std/src/sys/pal/xous/thread.rs
index 1b344e984dc..f2404a62abf 100644
--- a/library/std/src/sys/pal/xous/thread.rs
+++ b/library/std/src/sys/pal/xous/thread.rs
@@ -20,7 +20,11 @@ pub const GUARD_PAGE_SIZE: usize = 4096;
 
 impl Thread {
     // unsafe: see thread::Builder::spawn_unchecked for safety requirements
-    pub unsafe fn new(stack: usize, p: Box<dyn FnOnce()>) -> io::Result<Thread> {
+    pub unsafe fn new(
+        stack: usize,
+        _name: Option<&str>,
+        p: Box<dyn FnOnce()>,
+    ) -> io::Result<Thread> {
         let p = Box::into_raw(Box::new(p));
         let mut stack_size = crate::cmp::max(stack, MIN_STACK_SIZE);
 
diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs
index 8cd1e0163a1..dff981c900c 100644
--- a/library/std/src/thread/mod.rs
+++ b/library/std/src/thread/mod.rs
@@ -595,7 +595,7 @@ impl Builder {
             // Similarly, the `sys` implementation must guarantee that no references to the closure
             // exist after the thread has terminated, which is signaled by `Thread::join`
             // returning.
-            native: unsafe { imp::Thread::new(stack_size, main)? },
+            native: unsafe { imp::Thread::new(stack_size, my_thread.name(), main)? },
             thread: my_thread,
             packet: my_packet,
         })
diff --git a/library/std/tests/sync/lib.rs b/library/std/tests/sync/lib.rs
index 51190f0894f..94f1fe96b6a 100644
--- a/library/std/tests/sync/lib.rs
+++ b/library/std/tests/sync/lib.rs
@@ -6,7 +6,10 @@
 #![feature(reentrant_lock)]
 #![feature(rwlock_downgrade)]
 #![feature(std_internals)]
+#![feature(sync_nonpoison)]
+#![feature(nonpoison_mutex)]
 #![allow(internal_features)]
+#![feature(macro_metavar_expr_concat)] // For concatenating identifiers in macros.
 
 mod barrier;
 mod condvar;
@@ -29,3 +32,55 @@ mod rwlock;
 
 #[path = "../common/mod.rs"]
 mod common;
+
+#[track_caller]
+fn result_unwrap<T, E: std::fmt::Debug>(x: Result<T, E>) -> T {
+    x.unwrap()
+}
+
+/// A macro that generates two test cases for both the poison and nonpoison locks.
+///
+/// To write a test that tests both `poison` and `nonpoison` locks, import any of the types
+/// under both `poison` and `nonpoison` using the module name `locks` instead. For example, write
+/// `use locks::Mutex;` instead of `use std::sync::poiosn::Mutex`. This will import the correct type
+/// for each test variant.
+///
+/// Write a test as normal in the `test_body`, but instead of calling `unwrap` on `poison` methods
+/// that return a `LockResult` or similar, call the function `maybe_unwrap(...)` on the result.
+///
+/// For example, call `maybe_unwrap(mutex.lock())` instead of `mutex.lock().unwrap()` or
+/// `maybe_unwrap(rwlock.read())` instead of `rwlock.read().unwrap()`.
+///
+/// For the `poison` types, `maybe_unwrap` will simply unwrap the `Result` (usually this is a form
+/// of `LockResult`, but it could also be other kinds of results). For the `nonpoison` types, it is
+/// a no-op (the identity function).
+///
+/// The test names will be prefiex with `poison_` or `nonpoison_`.
+macro_rules! nonpoison_and_poison_unwrap_test {
+    (
+        name: $name:ident,
+        test_body: {$($test_body:tt)*}
+    ) => {
+        // Creates the nonpoison test.
+        #[test]
+        fn ${concat(nonpoison_, $name)}() {
+            #[allow(unused_imports)]
+            use ::std::convert::identity as maybe_unwrap;
+            use ::std::sync::nonpoison as locks;
+
+            $($test_body)*
+        }
+
+        // Creates the poison test with the suffix `_unwrap_poisoned`.
+        #[test]
+        fn ${concat(poison_, $name)}() {
+            #[allow(unused_imports)]
+            use super::result_unwrap as maybe_unwrap;
+            use ::std::sync::poison as locks;
+
+            $($test_body)*
+        }
+    }
+}
+
+use nonpoison_and_poison_unwrap_test;
diff --git a/library/std/tests/sync/mutex.rs b/library/std/tests/sync/mutex.rs
index ac82914d6de..90cefc0d594 100644
--- a/library/std/tests/sync/mutex.rs
+++ b/library/std/tests/sync/mutex.rs
@@ -6,7 +6,71 @@ use std::sync::mpsc::channel;
 use std::sync::{Arc, Condvar, MappedMutexGuard, Mutex, MutexGuard, TryLockError};
 use std::{hint, mem, thread};
 
-struct Packet<T>(Arc<(Mutex<T>, Condvar)>);
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Nonpoison & Poison Tests
+////////////////////////////////////////////////////////////////////////////////////////////////////
+use super::nonpoison_and_poison_unwrap_test;
+
+nonpoison_and_poison_unwrap_test!(
+    name: smoke,
+    test_body: {
+        use locks::Mutex;
+
+        let m = Mutex::new(());
+        drop(maybe_unwrap(m.lock()));
+        drop(maybe_unwrap(m.lock()));
+    }
+);
+
+nonpoison_and_poison_unwrap_test!(
+    name: lots_and_lots,
+    test_body: {
+        use locks::Mutex;
+
+        const J: u32 = 1000;
+        const K: u32 = 3;
+
+        let m = Arc::new(Mutex::new(0));
+
+        fn inc(m: &Mutex<u32>) {
+            for _ in 0..J {
+                *maybe_unwrap(m.lock()) += 1;
+            }
+        }
+
+        let (tx, rx) = channel();
+        for _ in 0..K {
+            let tx2 = tx.clone();
+            let m2 = m.clone();
+            thread::spawn(move || {
+                inc(&m2);
+                tx2.send(()).unwrap();
+            });
+            let tx2 = tx.clone();
+            let m2 = m.clone();
+            thread::spawn(move || {
+                inc(&m2);
+                tx2.send(()).unwrap();
+            });
+        }
+
+        drop(tx);
+        for _ in 0..2 * K {
+            rx.recv().unwrap();
+        }
+        assert_eq!(*maybe_unwrap(m.lock()), J * K * 2);
+    }
+);
+
+nonpoison_and_poison_unwrap_test!(
+    name: try_lock,
+    test_body: {
+        use locks::Mutex;
+
+        let m = Mutex::new(());
+        *m.try_lock().unwrap() = ();
+    }
+);
 
 #[derive(Eq, PartialEq, Debug)]
 struct NonCopy(i32);
@@ -26,58 +90,278 @@ fn test_needs_drop() {
     assert!(mem::needs_drop::<NonCopyNeedsDrop>());
 }
 
-#[derive(Clone, Eq, PartialEq, Debug)]
-struct Cloneable(i32);
+nonpoison_and_poison_unwrap_test!(
+    name: test_into_inner,
+    test_body: {
+        use locks::Mutex;
 
-#[test]
-fn smoke() {
-    let m = Mutex::new(());
-    drop(m.lock().unwrap());
-    drop(m.lock().unwrap());
-}
+        let m = Mutex::new(NonCopy(10));
+        assert_eq!(maybe_unwrap(m.into_inner()), NonCopy(10));
+    }
+);
 
-#[test]
-fn lots_and_lots() {
-    const J: u32 = 1000;
-    const K: u32 = 3;
+nonpoison_and_poison_unwrap_test!(
+    name: test_into_inner_drop,
+    test_body: {
+        use locks::Mutex;
 
-    let m = Arc::new(Mutex::new(0));
+        struct Foo(Arc<AtomicUsize>);
+        impl Drop for Foo {
+            fn drop(&mut self) {
+                self.0.fetch_add(1, Ordering::SeqCst);
+            }
+        }
 
-    fn inc(m: &Mutex<u32>) {
-        for _ in 0..J {
-            *m.lock().unwrap() += 1;
+        let num_drops = Arc::new(AtomicUsize::new(0));
+        let m = Mutex::new(Foo(num_drops.clone()));
+        assert_eq!(num_drops.load(Ordering::SeqCst), 0);
+        {
+            let _inner = maybe_unwrap(m.into_inner());
+            assert_eq!(num_drops.load(Ordering::SeqCst), 0);
         }
+        assert_eq!(num_drops.load(Ordering::SeqCst), 1);
     }
+);
 
-    let (tx, rx) = channel();
-    for _ in 0..K {
-        let tx2 = tx.clone();
-        let m2 = m.clone();
-        thread::spawn(move || {
-            inc(&m2);
-            tx2.send(()).unwrap();
-        });
-        let tx2 = tx.clone();
-        let m2 = m.clone();
-        thread::spawn(move || {
-            inc(&m2);
-            tx2.send(()).unwrap();
-        });
+nonpoison_and_poison_unwrap_test!(
+    name: test_get_mut,
+    test_body: {
+        use locks::Mutex;
+
+        let mut m = Mutex::new(NonCopy(10));
+        *maybe_unwrap(m.get_mut()) = NonCopy(20);
+        assert_eq!(maybe_unwrap(m.into_inner()), NonCopy(20));
     }
+);
 
-    drop(tx);
-    for _ in 0..2 * K {
-        rx.recv().unwrap();
+nonpoison_and_poison_unwrap_test!(
+    name: test_get_cloned,
+    test_body: {
+        use locks::Mutex;
+
+        #[derive(Clone, Eq, PartialEq, Debug)]
+        struct Cloneable(i32);
+
+        let m = Mutex::new(Cloneable(10));
+
+        assert_eq!(maybe_unwrap(m.get_cloned()), Cloneable(10));
     }
-    assert_eq!(*m.lock().unwrap(), J * K * 2);
-}
+);
+
+nonpoison_and_poison_unwrap_test!(
+    name: test_set,
+    test_body: {
+        use locks::Mutex;
+
+        fn inner<T>(mut init: impl FnMut() -> T, mut value: impl FnMut() -> T)
+        where
+            T: Debug + Eq,
+        {
+            let m = Mutex::new(init());
+
+            assert_eq!(*maybe_unwrap(m.lock()), init());
+            maybe_unwrap(m.set(value()));
+            assert_eq!(*maybe_unwrap(m.lock()), value());
+        }
+
+        inner(|| NonCopy(10), || NonCopy(20));
+        inner(|| NonCopyNeedsDrop(10), || NonCopyNeedsDrop(20));
+    }
+);
+
+// Ensure that old values that are replaced by `set` are correctly dropped.
+nonpoison_and_poison_unwrap_test!(
+    name: test_set_drop,
+    test_body: {
+        use locks::Mutex;
+
+        struct Foo(Arc<AtomicUsize>);
+        impl Drop for Foo {
+            fn drop(&mut self) {
+                self.0.fetch_add(1, Ordering::SeqCst);
+            }
+        }
+
+        let num_drops = Arc::new(AtomicUsize::new(0));
+        let m = Mutex::new(Foo(num_drops.clone()));
+        assert_eq!(num_drops.load(Ordering::SeqCst), 0);
+
+        let different = Foo(Arc::new(AtomicUsize::new(42)));
+        maybe_unwrap(m.set(different));
+        assert_eq!(num_drops.load(Ordering::SeqCst), 1);
+    }
+);
+
+nonpoison_and_poison_unwrap_test!(
+    name: test_replace,
+    test_body: {
+        use locks::Mutex;
+
+        fn inner<T>(mut init: impl FnMut() -> T, mut value: impl FnMut() -> T)
+        where
+            T: Debug + Eq,
+        {
+            let m = Mutex::new(init());
+
+            assert_eq!(*maybe_unwrap(m.lock()), init());
+            assert_eq!(maybe_unwrap(m.replace(value())), init());
+            assert_eq!(*maybe_unwrap(m.lock()), value());
+        }
 
+        inner(|| NonCopy(10), || NonCopy(20));
+        inner(|| NonCopyNeedsDrop(10), || NonCopyNeedsDrop(20));
+    }
+);
+
+// FIXME(nonpoison_condvar): Move this to the `condvar.rs` test file once `nonpoison::condvar` gets
+// implemented.
 #[test]
-fn try_lock() {
-    let m = Mutex::new(());
-    *m.try_lock().unwrap() = ();
+fn test_mutex_arc_condvar() {
+    struct Packet<T>(Arc<(Mutex<T>, Condvar)>);
+
+    let packet = Packet(Arc::new((Mutex::new(false), Condvar::new())));
+    let packet2 = Packet(packet.0.clone());
+
+    let (tx, rx) = channel();
+
+    let _t = thread::spawn(move || {
+        // Wait until our parent has taken the lock.
+        rx.recv().unwrap();
+        let &(ref lock, ref cvar) = &*packet2.0;
+
+        // Set the data to `true` and wake up our parent.
+        let mut guard = lock.lock().unwrap();
+        *guard = true;
+        cvar.notify_one();
+    });
+
+    let &(ref lock, ref cvar) = &*packet.0;
+    let mut guard = lock.lock().unwrap();
+    // Wake up our child.
+    tx.send(()).unwrap();
+
+    // Wait until our child has set the data to `true`.
+    assert!(!*guard);
+    while !*guard {
+        guard = cvar.wait(guard).unwrap();
+    }
 }
 
+nonpoison_and_poison_unwrap_test!(
+    name: test_mutex_arc_nested,
+    test_body: {
+        use locks::Mutex;
+
+        // Tests nested mutexes and access
+        // to underlying data.
+        let arc = Arc::new(Mutex::new(1));
+        let arc2 = Arc::new(Mutex::new(arc));
+        let (tx, rx) = channel();
+        let _t = thread::spawn(move || {
+            let lock = maybe_unwrap(arc2.lock());
+            let lock2 = maybe_unwrap(lock.lock());
+            assert_eq!(*lock2, 1);
+            tx.send(()).unwrap();
+        });
+        rx.recv().unwrap();
+    }
+);
+
+nonpoison_and_poison_unwrap_test!(
+    name: test_mutex_unsized,
+    test_body: {
+        use locks::Mutex;
+
+        let mutex: &Mutex<[i32]> = &Mutex::new([1, 2, 3]);
+        {
+            let b = &mut *maybe_unwrap(mutex.lock());
+            b[0] = 4;
+            b[2] = 5;
+        }
+        let comp: &[i32] = &[4, 2, 5];
+        assert_eq!(&*maybe_unwrap(mutex.lock()), comp);
+    }
+);
+
+nonpoison_and_poison_unwrap_test!(
+    name: test_mapping_mapped_guard,
+    test_body: {
+        use locks::{Mutex, MutexGuard, MappedMutexGuard};
+
+        let arr = [0; 4];
+        let lock = Mutex::new(arr);
+        let guard = maybe_unwrap(lock.lock());
+        let guard = MutexGuard::map(guard, |arr| &mut arr[..2]);
+        let mut guard = MappedMutexGuard::map(guard, |slice| &mut slice[1..]);
+        assert_eq!(guard.len(), 1);
+        guard[0] = 42;
+        drop(guard);
+        assert_eq!(*maybe_unwrap(lock.lock()), [0, 42, 0, 0]);
+    }
+);
+
+#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
+nonpoison_and_poison_unwrap_test!(
+    name: test_panics,
+    test_body: {
+        use locks::Mutex;
+
+        let mutex = Mutex::new(42);
+
+        let catch_unwind_result1 = panic::catch_unwind(AssertUnwindSafe(|| {
+            let _guard1 = maybe_unwrap(mutex.lock());
+
+            panic!("test panic with mutex once");
+        }));
+        assert!(catch_unwind_result1.is_err());
+
+        let catch_unwind_result2 = panic::catch_unwind(AssertUnwindSafe(|| {
+            let _guard2 = maybe_unwrap(mutex.lock());
+
+            panic!("test panic with mutex twice");
+        }));
+        assert!(catch_unwind_result2.is_err());
+
+        let catch_unwind_result3 = panic::catch_unwind(AssertUnwindSafe(|| {
+            let _guard3 = maybe_unwrap(mutex.lock());
+
+            panic!("test panic with mutex thrice");
+        }));
+        assert!(catch_unwind_result3.is_err());
+    }
+);
+
+#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
+nonpoison_and_poison_unwrap_test!(
+    name: test_mutex_arc_access_in_unwind,
+    test_body: {
+        use locks::Mutex;
+
+        let arc = Arc::new(Mutex::new(1));
+        let arc2 = arc.clone();
+        let _ = thread::spawn(move || -> () {
+            struct Unwinder {
+                i: Arc<Mutex<i32>>,
+            }
+            impl Drop for Unwinder {
+                fn drop(&mut self) {
+                    *maybe_unwrap(self.i.lock()) += 1;
+                }
+            }
+            let _u = Unwinder { i: arc2 };
+            panic!();
+        })
+        .join();
+        let lock = maybe_unwrap(arc.lock());
+        assert_eq!(*lock, 2);
+    }
+);
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Poison Tests
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/// Creates a mutex that is immediately poisoned.
 fn new_poisoned_mutex<T>(value: T) -> Mutex<T> {
     let mutex = Mutex::new(value);
 
@@ -94,30 +378,6 @@ fn new_poisoned_mutex<T>(value: T) -> Mutex<T> {
 }
 
 #[test]
-fn test_into_inner() {
-    let m = Mutex::new(NonCopy(10));
-    assert_eq!(m.into_inner().unwrap(), NonCopy(10));
-}
-
-#[test]
-fn test_into_inner_drop() {
-    struct Foo(Arc<AtomicUsize>);
-    impl Drop for Foo {
-        fn drop(&mut self) {
-            self.0.fetch_add(1, Ordering::SeqCst);
-        }
-    }
-    let num_drops = Arc::new(AtomicUsize::new(0));
-    let m = Mutex::new(Foo(num_drops.clone()));
-    assert_eq!(num_drops.load(Ordering::SeqCst), 0);
-    {
-        let _inner = m.into_inner().unwrap();
-        assert_eq!(num_drops.load(Ordering::SeqCst), 0);
-    }
-    assert_eq!(num_drops.load(Ordering::SeqCst), 1);
-}
-
-#[test]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn test_into_inner_poison() {
     let m = new_poisoned_mutex(NonCopy(10));
@@ -129,15 +389,11 @@ fn test_into_inner_poison() {
 }
 
 #[test]
-fn test_get_cloned() {
-    let m = Mutex::new(Cloneable(10));
-
-    assert_eq!(m.get_cloned().unwrap(), Cloneable(10));
-}
-
-#[test]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn test_get_cloned_poison() {
+    #[derive(Clone, Eq, PartialEq, Debug)]
+    struct Cloneable(i32);
+
     let m = new_poisoned_mutex(Cloneable(10));
 
     match m.get_cloned() {
@@ -147,13 +403,6 @@ fn test_get_cloned_poison() {
 }
 
 #[test]
-fn test_get_mut() {
-    let mut m = Mutex::new(NonCopy(10));
-    *m.get_mut().unwrap() = NonCopy(20);
-    assert_eq!(m.into_inner().unwrap(), NonCopy(20));
-}
-
-#[test]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn test_get_mut_poison() {
     let mut m = new_poisoned_mutex(NonCopy(10));
@@ -165,23 +414,6 @@ fn test_get_mut_poison() {
 }
 
 #[test]
-fn test_set() {
-    fn inner<T>(mut init: impl FnMut() -> T, mut value: impl FnMut() -> T)
-    where
-        T: Debug + Eq,
-    {
-        let m = Mutex::new(init());
-
-        assert_eq!(*m.lock().unwrap(), init());
-        m.set(value()).unwrap();
-        assert_eq!(*m.lock().unwrap(), value());
-    }
-
-    inner(|| NonCopy(10), || NonCopy(20));
-    inner(|| NonCopyNeedsDrop(10), || NonCopyNeedsDrop(20));
-}
-
-#[test]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn test_set_poison() {
     fn inner<T>(mut init: impl FnMut() -> T, mut value: impl FnMut() -> T)
@@ -204,23 +436,6 @@ fn test_set_poison() {
 }
 
 #[test]
-fn test_replace() {
-    fn inner<T>(mut init: impl FnMut() -> T, mut value: impl FnMut() -> T)
-    where
-        T: Debug + Eq,
-    {
-        let m = Mutex::new(init());
-
-        assert_eq!(*m.lock().unwrap(), init());
-        assert_eq!(m.replace(value()).unwrap(), init());
-        assert_eq!(*m.lock().unwrap(), value());
-    }
-
-    inner(|| NonCopy(10), || NonCopy(20));
-    inner(|| NonCopyNeedsDrop(10), || NonCopyNeedsDrop(20));
-}
-
-#[test]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn test_replace_poison() {
     fn inner<T>(mut init: impl FnMut() -> T, mut value: impl FnMut() -> T)
@@ -243,31 +458,10 @@ fn test_replace_poison() {
 }
 
 #[test]
-fn test_mutex_arc_condvar() {
-    let packet = Packet(Arc::new((Mutex::new(false), Condvar::new())));
-    let packet2 = Packet(packet.0.clone());
-    let (tx, rx) = channel();
-    let _t = thread::spawn(move || {
-        // wait until parent gets in
-        rx.recv().unwrap();
-        let &(ref lock, ref cvar) = &*packet2.0;
-        let mut lock = lock.lock().unwrap();
-        *lock = true;
-        cvar.notify_one();
-    });
-
-    let &(ref lock, ref cvar) = &*packet.0;
-    let mut lock = lock.lock().unwrap();
-    tx.send(()).unwrap();
-    assert!(!*lock);
-    while !*lock {
-        lock = cvar.wait(lock).unwrap();
-    }
-}
-
-#[test]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn test_arc_condvar_poison() {
+    struct Packet<T>(Arc<(Mutex<T>, Condvar)>);
+
     let packet = Packet(Arc::new((Mutex::new(1), Condvar::new())));
     let packet2 = Packet(packet.0.clone());
     let (tx, rx) = channel();
@@ -327,69 +521,6 @@ fn test_mutex_arc_poison_mapped() {
 }
 
 #[test]
-fn test_mutex_arc_nested() {
-    // Tests nested mutexes and access
-    // to underlying data.
-    let arc = Arc::new(Mutex::new(1));
-    let arc2 = Arc::new(Mutex::new(arc));
-    let (tx, rx) = channel();
-    let _t = thread::spawn(move || {
-        let lock = arc2.lock().unwrap();
-        let lock2 = lock.lock().unwrap();
-        assert_eq!(*lock2, 1);
-        tx.send(()).unwrap();
-    });
-    rx.recv().unwrap();
-}
-
-#[test]
-#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
-fn test_mutex_arc_access_in_unwind() {
-    let arc = Arc::new(Mutex::new(1));
-    let arc2 = arc.clone();
-    let _ = thread::spawn(move || -> () {
-        struct Unwinder {
-            i: Arc<Mutex<i32>>,
-        }
-        impl Drop for Unwinder {
-            fn drop(&mut self) {
-                *self.i.lock().unwrap() += 1;
-            }
-        }
-        let _u = Unwinder { i: arc2 };
-        panic!();
-    })
-    .join();
-    let lock = arc.lock().unwrap();
-    assert_eq!(*lock, 2);
-}
-
-#[test]
-fn test_mutex_unsized() {
-    let mutex: &Mutex<[i32]> = &Mutex::new([1, 2, 3]);
-    {
-        let b = &mut *mutex.lock().unwrap();
-        b[0] = 4;
-        b[2] = 5;
-    }
-    let comp: &[i32] = &[4, 2, 5];
-    assert_eq!(&*mutex.lock().unwrap(), comp);
-}
-
-#[test]
-fn test_mapping_mapped_guard() {
-    let arr = [0; 4];
-    let mut lock = Mutex::new(arr);
-    let guard = lock.lock().unwrap();
-    let guard = MutexGuard::map(guard, |arr| &mut arr[..2]);
-    let mut guard = MappedMutexGuard::map(guard, |slice| &mut slice[1..]);
-    assert_eq!(guard.len(), 1);
-    guard[0] = 42;
-    drop(guard);
-    assert_eq!(*lock.get_mut().unwrap(), [0, 42, 0, 0]);
-}
-
-#[test]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn panic_while_mapping_unlocked_poison() {
     let lock = Mutex::new(());
diff --git a/src/bootstrap/defaults/bootstrap.tools.toml b/src/bootstrap/defaults/bootstrap.tools.toml
index 57c2706f60a..5abe636bd96 100644
--- a/src/bootstrap/defaults/bootstrap.tools.toml
+++ b/src/bootstrap/defaults/bootstrap.tools.toml
@@ -14,6 +14,8 @@ test-stage = 2
 doc-stage = 2
 # Contributors working on tools will probably expect compiler docs to be generated, so they can figure out how to use the API.
 compiler-docs = true
+# Contributors working on tools are the most likely to change non-rust programs.
+tidy-extra-checks = "auto:js,auto:py,auto:cpp,auto:spellcheck"
 
 [llvm]
 # Will download LLVM from CI if available on your platform.
diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs
index 6e04f115424..9644ade00b3 100644
--- a/src/bootstrap/src/core/config/config.rs
+++ b/src/bootstrap/src/core/config/config.rs
@@ -45,7 +45,9 @@ use crate::core::config::{
     DebuginfoLevel, DryRun, GccCiMode, LlvmLibunwind, Merge, ReplaceOpt, RustcLto, SplitDebuginfo,
     StringOrBool, set, threads_from_config,
 };
-use crate::core::download::is_download_ci_available;
+use crate::core::download::{
+    DownloadContext, download_beta_toolchain, is_download_ci_available, maybe_download_rustfmt,
+};
 use crate::utils::channel;
 use crate::utils::exec::{ExecutionContext, command};
 use crate::utils::helpers::{exe, get_host_target};
@@ -795,13 +797,19 @@ impl Config {
             );
         }
 
+        config.patch_binaries_for_nix = patch_binaries_for_nix;
+        config.bootstrap_cache_path = bootstrap_cache_path;
+        config.llvm_assertions =
+            toml.llvm.as_ref().is_some_and(|llvm| llvm.assertions.unwrap_or(false));
+
         config.initial_rustc = if let Some(rustc) = rustc {
             if !flags_skip_stage0_validation {
                 config.check_stage0_version(&rustc, "rustc");
             }
             rustc
         } else {
-            config.download_beta_toolchain();
+            let dwn_ctx = DownloadContext::from(&config);
+            download_beta_toolchain(dwn_ctx);
             config
                 .out
                 .join(config.host_target)
@@ -827,7 +835,8 @@ impl Config {
             }
             cargo
         } else {
-            config.download_beta_toolchain();
+            let dwn_ctx = DownloadContext::from(&config);
+            download_beta_toolchain(dwn_ctx);
             config.initial_sysroot.join("bin").join(exe("cargo", config.host_target))
         };
 
@@ -863,7 +872,6 @@ impl Config {
         config.reuse = reuse.map(PathBuf::from);
         config.submodules = submodules;
         config.android_ndk = android_ndk;
-        config.bootstrap_cache_path = bootstrap_cache_path;
         set(&mut config.low_priority, low_priority);
         set(&mut config.compiler_docs, compiler_docs);
         set(&mut config.library_docs_private_items, library_docs_private_items);
@@ -882,7 +890,6 @@ impl Config {
         set(&mut config.local_rebuild, local_rebuild);
         set(&mut config.print_step_timings, print_step_timings);
         set(&mut config.print_step_rusage, print_step_rusage);
-        config.patch_binaries_for_nix = patch_binaries_for_nix;
 
         config.verbose = cmp::max(config.verbose, flags_verbose as usize);
 
@@ -891,9 +898,6 @@ impl Config {
 
         config.apply_install_config(toml.install);
 
-        config.llvm_assertions =
-            toml.llvm.as_ref().is_some_and(|llvm| llvm.assertions.unwrap_or(false));
-
         let file_content = t!(fs::read_to_string(config.src.join("src/ci/channel")));
         let ci_channel = file_content.trim_end();
 
@@ -994,8 +998,12 @@ impl Config {
 
         config.apply_dist_config(toml.dist);
 
-        config.initial_rustfmt =
-            if let Some(r) = rustfmt { Some(r) } else { config.maybe_download_rustfmt() };
+        config.initial_rustfmt = if let Some(r) = rustfmt {
+            Some(r)
+        } else {
+            let dwn_ctx = DownloadContext::from(&config);
+            maybe_download_rustfmt(dwn_ctx)
+        };
 
         if matches!(config.lld_mode, LldMode::SelfContained)
             && !config.lld_enabled
diff --git a/src/bootstrap/src/core/download.rs b/src/bootstrap/src/core/download.rs
index 373fcd52052..7ec6c62a07d 100644
--- a/src/bootstrap/src/core/download.rs
+++ b/src/bootstrap/src/core/download.rs
@@ -7,9 +7,9 @@ use std::sync::OnceLock;
 
 use xz2::bufread::XzDecoder;
 
-use crate::core::config::BUILDER_CONFIG_FILENAME;
+use crate::core::config::{BUILDER_CONFIG_FILENAME, TargetSelection};
 use crate::utils::build_stamp::BuildStamp;
-use crate::utils::exec::command;
+use crate::utils::exec::{ExecutionContext, command};
 use crate::utils::helpers::{exe, hex_encode, move_file};
 use crate::{Config, t};
 
@@ -24,17 +24,6 @@ fn extract_curl_version(out: String) -> semver::Version {
         .unwrap_or(semver::Version::new(1, 0, 0))
 }
 
-fn curl_version(config: &Config) -> semver::Version {
-    let mut curl = command("curl");
-    curl.arg("-V");
-    let curl = curl.run_capture_stdout(config);
-    if curl.is_failure() {
-        return semver::Version::new(1, 0, 0);
-    }
-    let output = curl.stdout();
-    extract_curl_version(output)
-}
-
 /// Generic helpers that are useful anywhere in bootstrap.
 impl Config {
     pub fn is_verbose(&self) -> bool {
@@ -49,10 +38,7 @@ impl Config {
     }
 
     pub(crate) fn remove(&self, f: &Path) {
-        if self.dry_run() {
-            return;
-        }
-        fs::remove_file(f).unwrap_or_else(|_| panic!("failed to remove {f:?}"));
+        remove(&self.exec_ctx, f);
     }
 
     /// Create a temporary directory in `out` and return its path.
@@ -68,49 +54,7 @@ impl Config {
     /// Whether or not `fix_bin_or_dylib` needs to be run; can only be true
     /// on NixOS
     fn should_fix_bins_and_dylibs(&self) -> bool {
-        let val = *SHOULD_FIX_BINS_AND_DYLIBS.get_or_init(|| {
-            let uname = command("uname").allow_failure().arg("-s").run_capture_stdout(self);
-            if uname.is_failure() {
-                return false;
-            }
-            let output = uname.stdout();
-            if !output.starts_with("Linux") {
-                return false;
-            }
-            // If the user has asked binaries to be patched for Nix, then
-            // don't check for NixOS or `/lib`.
-            // NOTE: this intentionally comes after the Linux check:
-            // - patchelf only works with ELF files, so no need to run it on Mac or Windows
-            // - On other Unix systems, there is no stable syscall interface, so Nix doesn't manage the global libc.
-            if let Some(explicit_value) = self.patch_binaries_for_nix {
-                return explicit_value;
-            }
-
-            // Use `/etc/os-release` instead of `/etc/NIXOS`.
-            // The latter one does not exist on NixOS when using tmpfs as root.
-            let is_nixos = match File::open("/etc/os-release") {
-                Err(e) if e.kind() == ErrorKind::NotFound => false,
-                Err(e) => panic!("failed to access /etc/os-release: {e}"),
-                Ok(os_release) => BufReader::new(os_release).lines().any(|l| {
-                    let l = l.expect("reading /etc/os-release");
-                    matches!(l.trim(), "ID=nixos" | "ID='nixos'" | "ID=\"nixos\"")
-                }),
-            };
-            if !is_nixos {
-                let in_nix_shell = env::var("IN_NIX_SHELL");
-                if let Ok(in_nix_shell) = in_nix_shell {
-                    eprintln!(
-                        "The IN_NIX_SHELL environment variable is `{in_nix_shell}`; \
-                         you may need to set `patch-binaries-for-nix=true` in bootstrap.toml"
-                    );
-                }
-            }
-            is_nixos
-        });
-        if val {
-            eprintln!("INFO: You seem to be using Nix.");
-        }
-        val
+        should_fix_bins_and_dylibs(self.patch_binaries_for_nix, &self.exec_ctx)
     }
 
     /// Modifies the interpreter section of 'fname' to fix the dynamic linker,
@@ -121,259 +65,22 @@ impl Config {
     ///
     /// Please see <https://nixos.org/patchelf.html> for more information
     fn fix_bin_or_dylib(&self, fname: &Path) {
-        assert_eq!(SHOULD_FIX_BINS_AND_DYLIBS.get(), Some(&true));
-        println!("attempting to patch {}", fname.display());
-
-        // Only build `.nix-deps` once.
-        static NIX_DEPS_DIR: OnceLock<PathBuf> = OnceLock::new();
-        let mut nix_build_succeeded = true;
-        let nix_deps_dir = NIX_DEPS_DIR.get_or_init(|| {
-            // Run `nix-build` to "build" each dependency (which will likely reuse
-            // the existing `/nix/store` copy, or at most download a pre-built copy).
-            //
-            // Importantly, we create a gc-root called `.nix-deps` in the `build/`
-            // directory, but still reference the actual `/nix/store` path in the rpath
-            // as it makes it significantly more robust against changes to the location of
-            // the `.nix-deps` location.
-            //
-            // bintools: Needed for the path of `ld-linux.so` (via `nix-support/dynamic-linker`).
-            // zlib: Needed as a system dependency of `libLLVM-*.so`.
-            // patchelf: Needed for patching ELF binaries (see doc comment above).
-            let nix_deps_dir = self.out.join(".nix-deps");
-            const NIX_EXPR: &str = "
-            with (import <nixpkgs> {});
-            symlinkJoin {
-                name = \"rust-stage0-dependencies\";
-                paths = [
-                    zlib
-                    patchelf
-                    stdenv.cc.bintools
-                ];
-            }
-            ";
-            nix_build_succeeded = command("nix-build")
-                .allow_failure()
-                .args([Path::new("-E"), Path::new(NIX_EXPR), Path::new("-o"), &nix_deps_dir])
-                .run_capture_stdout(self)
-                .is_success();
-            nix_deps_dir
-        });
-        if !nix_build_succeeded {
-            return;
-        }
-
-        let mut patchelf = command(nix_deps_dir.join("bin/patchelf"));
-        patchelf.args(&[
-            OsString::from("--add-rpath"),
-            OsString::from(t!(fs::canonicalize(nix_deps_dir)).join("lib")),
-        ]);
-        if !path_is_dylib(fname) {
-            // Finally, set the correct .interp for binaries
-            let dynamic_linker_path = nix_deps_dir.join("nix-support/dynamic-linker");
-            let dynamic_linker = t!(fs::read_to_string(dynamic_linker_path));
-            patchelf.args(["--set-interpreter", dynamic_linker.trim_end()]);
-        }
-        patchelf.arg(fname);
-        let _ = patchelf.allow_failure().run_capture_stdout(self);
+        fix_bin_or_dylib(&self.out, fname, &self.exec_ctx);
     }
 
     fn download_file(&self, url: &str, dest_path: &Path, help_on_error: &str) {
-        self.verbose(|| println!("download {url}"));
-        // Use a temporary file in case we crash while downloading, to avoid a corrupt download in cache/.
-        let tempfile = self.tempdir().join(dest_path.file_name().unwrap());
-        // While bootstrap itself only supports http and https downloads, downstream forks might
-        // need to download components from other protocols. The match allows them adding more
-        // protocols without worrying about merge conflicts if we change the HTTP implementation.
-        match url.split_once("://").map(|(proto, _)| proto) {
-            Some("http") | Some("https") => {
-                self.download_http_with_retries(&tempfile, url, help_on_error)
-            }
-            Some(other) => panic!("unsupported protocol {other} in {url}"),
-            None => panic!("no protocol in {url}"),
-        }
-        t!(
-            move_file(&tempfile, dest_path),
-            format!("failed to rename {tempfile:?} to {dest_path:?}")
-        );
-    }
-
-    fn download_http_with_retries(&self, tempfile: &Path, url: &str, help_on_error: &str) {
-        println!("downloading {url}");
-        // Try curl. If that fails and we are on windows, fallback to PowerShell.
-        // options should be kept in sync with
-        // src/bootstrap/src/core/download.rs
-        // for consistency
-        let mut curl = command("curl").allow_failure();
-        curl.args([
-            // follow redirect
-            "--location",
-            // timeout if speed is < 10 bytes/sec for > 30 seconds
-            "--speed-time",
-            "30",
-            "--speed-limit",
-            "10",
-            // timeout if cannot connect within 30 seconds
-            "--connect-timeout",
-            "30",
-            // output file
-            "--output",
-            tempfile.to_str().unwrap(),
-            // if there is an error, don't restart the download,
-            // instead continue where it left off.
-            "--continue-at",
-            "-",
-            // retry up to 3 times.  note that this means a maximum of 4
-            // attempts will be made, since the first attempt isn't a *re*try.
-            "--retry",
-            "3",
-            // show errors, even if --silent is specified
-            "--show-error",
-            // set timestamp of downloaded file to that of the server
-            "--remote-time",
-            // fail on non-ok http status
-            "--fail",
-        ]);
-        // Don't print progress in CI; the \r wrapping looks bad and downloads don't take long enough for progress to be useful.
-        if self.is_running_on_ci {
-            curl.arg("--silent");
-        } else {
-            curl.arg("--progress-bar");
-        }
-        // --retry-all-errors was added in 7.71.0, don't use it if curl is old.
-        if curl_version(self) >= semver::Version::new(7, 71, 0) {
-            curl.arg("--retry-all-errors");
-        }
-        curl.arg(url);
-        if !curl.run(self) {
-            if self.host_target.contains("windows-msvc") {
-                eprintln!("Fallback to PowerShell");
-                for _ in 0..3 {
-                    let powershell = command("PowerShell.exe").allow_failure().args([
-                        "/nologo",
-                        "-Command",
-                        "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;",
-                        &format!(
-                            "(New-Object System.Net.WebClient).DownloadFile('{}', '{}')",
-                            url, tempfile.to_str().expect("invalid UTF-8 not supported with powershell downloads"),
-                        ),
-                    ]).run_capture_stdout(self);
-
-                    if powershell.is_failure() {
-                        return;
-                    }
-
-                    eprintln!("\nspurious failure, trying again");
-                }
-            }
-            if !help_on_error.is_empty() {
-                eprintln!("{help_on_error}");
-            }
-            crate::exit!(1);
-        }
+        let dwn_ctx: DownloadContext<'_> = self.into();
+        download_file(dwn_ctx, url, dest_path, help_on_error);
     }
 
     fn unpack(&self, tarball: &Path, dst: &Path, pattern: &str) {
-        eprintln!("extracting {} to {}", tarball.display(), dst.display());
-        if !dst.exists() {
-            t!(fs::create_dir_all(dst));
-        }
-
-        // `tarball` ends with `.tar.xz`; strip that suffix
-        // example: `rust-dev-nightly-x86_64-unknown-linux-gnu`
-        let uncompressed_filename =
-            Path::new(tarball.file_name().expect("missing tarball filename")).file_stem().unwrap();
-        let directory_prefix = Path::new(Path::new(uncompressed_filename).file_stem().unwrap());
-
-        // decompress the file
-        let data = t!(File::open(tarball), format!("file {} not found", tarball.display()));
-        let decompressor = XzDecoder::new(BufReader::new(data));
-
-        let mut tar = tar::Archive::new(decompressor);
-
-        let is_ci_rustc = dst.ends_with("ci-rustc");
-        let is_ci_llvm = dst.ends_with("ci-llvm");
-
-        // `compile::Sysroot` needs to know the contents of the `rustc-dev` tarball to avoid adding
-        // it to the sysroot unless it was explicitly requested. But parsing the 100 MB tarball is slow.
-        // Cache the entries when we extract it so we only have to read it once.
-        let mut recorded_entries = if is_ci_rustc { recorded_entries(dst, pattern) } else { None };
-
-        for member in t!(tar.entries()) {
-            let mut member = t!(member);
-            let original_path = t!(member.path()).into_owned();
-            // skip the top-level directory
-            if original_path == directory_prefix {
-                continue;
-            }
-            let mut short_path = t!(original_path.strip_prefix(directory_prefix));
-            let is_builder_config = short_path.to_str() == Some(BUILDER_CONFIG_FILENAME);
-
-            if !(short_path.starts_with(pattern)
-                || ((is_ci_rustc || is_ci_llvm) && is_builder_config))
-            {
-                continue;
-            }
-            short_path = short_path.strip_prefix(pattern).unwrap_or(short_path);
-            let dst_path = dst.join(short_path);
-            self.verbose(|| {
-                println!("extracting {} to {}", original_path.display(), dst.display())
-            });
-            if !t!(member.unpack_in(dst)) {
-                panic!("path traversal attack ??");
-            }
-            if let Some(record) = &mut recorded_entries {
-                t!(writeln!(record, "{}", short_path.to_str().unwrap()));
-            }
-            let src_path = dst.join(original_path);
-            if src_path.is_dir() && dst_path.exists() {
-                continue;
-            }
-            t!(move_file(src_path, dst_path));
-        }
-        let dst_dir = dst.join(directory_prefix);
-        if dst_dir.exists() {
-            t!(fs::remove_dir_all(&dst_dir), format!("failed to remove {}", dst_dir.display()));
-        }
+        unpack(&self.exec_ctx, tarball, dst, pattern);
     }
 
     /// Returns whether the SHA256 checksum of `path` matches `expected`.
+    #[cfg(test)]
     pub(crate) fn verify(&self, path: &Path, expected: &str) -> bool {
-        use sha2::Digest;
-
-        self.verbose(|| println!("verifying {}", path.display()));
-
-        if self.dry_run() {
-            return false;
-        }
-
-        let mut hasher = sha2::Sha256::new();
-
-        let file = t!(File::open(path));
-        let mut reader = BufReader::new(file);
-
-        loop {
-            let buffer = t!(reader.fill_buf());
-            let l = buffer.len();
-            // break if EOF
-            if l == 0 {
-                break;
-            }
-            hasher.update(buffer);
-            reader.consume(l);
-        }
-
-        let checksum = hex_encode(hasher.finalize().as_slice());
-        let verified = checksum == expected;
-
-        if !verified {
-            println!(
-                "invalid checksum: \n\
-                found:    {checksum}\n\
-                expected: {expected}",
-            );
-        }
-
-        verified
+        verify(&self.exec_ctx, path, expected)
     }
 }
 
@@ -388,6 +95,7 @@ fn recorded_entries(dst: &Path, pattern: &str) -> Option<BufWriter<File>> {
     Some(BufWriter::new(t!(File::create(dst.join(name)))))
 }
 
+#[derive(Clone)]
 enum DownloadSource {
     CI,
     Dist,
@@ -420,63 +128,6 @@ impl Config {
         cargo_clippy
     }
 
-    #[cfg(test)]
-    pub(crate) fn maybe_download_rustfmt(&self) -> Option<PathBuf> {
-        Some(PathBuf::new())
-    }
-
-    /// NOTE: rustfmt is a completely different toolchain than the bootstrap compiler, so it can't
-    /// reuse target directories or artifacts
-    #[cfg(not(test))]
-    pub(crate) fn maybe_download_rustfmt(&self) -> Option<PathBuf> {
-        use build_helper::stage0_parser::VersionMetadata;
-
-        if self.dry_run() {
-            return Some(PathBuf::new());
-        }
-
-        let VersionMetadata { date, version } = self.stage0_metadata.rustfmt.as_ref()?;
-        let channel = format!("{version}-{date}");
-
-        let host = self.host_target;
-        let bin_root = self.out.join(host).join("rustfmt");
-        let rustfmt_path = bin_root.join("bin").join(exe("rustfmt", host));
-        let rustfmt_stamp = BuildStamp::new(&bin_root).with_prefix("rustfmt").add_stamp(channel);
-        if rustfmt_path.exists() && rustfmt_stamp.is_up_to_date() {
-            return Some(rustfmt_path);
-        }
-
-        self.download_component(
-            DownloadSource::Dist,
-            format!("rustfmt-{version}-{build}.tar.xz", build = host.triple),
-            "rustfmt-preview",
-            date,
-            "rustfmt",
-        );
-        self.download_component(
-            DownloadSource::Dist,
-            format!("rustc-{version}-{build}.tar.xz", build = host.triple),
-            "rustc",
-            date,
-            "rustfmt",
-        );
-
-        if self.should_fix_bins_and_dylibs() {
-            self.fix_bin_or_dylib(&bin_root.join("bin").join("rustfmt"));
-            self.fix_bin_or_dylib(&bin_root.join("bin").join("cargo-fmt"));
-            let lib_dir = bin_root.join("lib");
-            for lib in t!(fs::read_dir(&lib_dir), lib_dir.display().to_string()) {
-                let lib = t!(lib);
-                if path_is_dylib(&lib.path()) {
-                    self.fix_bin_or_dylib(&lib.path());
-                }
-            }
-        }
-
-        t!(rustfmt_stamp.write());
-        Some(rustfmt_path)
-    }
-
     pub(crate) fn ci_rust_std_contents(&self) -> Vec<String> {
         self.ci_component_contents(".rust-std-contents")
     }
@@ -514,30 +165,6 @@ impl Config {
         );
     }
 
-    #[cfg(test)]
-    pub(crate) fn download_beta_toolchain(&self) {}
-
-    #[cfg(not(test))]
-    pub(crate) fn download_beta_toolchain(&self) {
-        self.verbose(|| println!("downloading stage0 beta artifacts"));
-
-        let date = &self.stage0_metadata.compiler.date;
-        let version = &self.stage0_metadata.compiler.version;
-        let extra_components = ["cargo"];
-
-        let download_beta_component = |config: &Config, filename, prefix: &_, date: &_| {
-            config.download_component(DownloadSource::Dist, filename, prefix, date, "stage0")
-        };
-
-        self.download_toolchain(
-            version,
-            "stage0",
-            date,
-            &extra_components,
-            download_beta_component,
-        );
-    }
-
     fn download_toolchain(
         &self,
         version: &str,
@@ -607,91 +234,8 @@ impl Config {
         key: &str,
         destination: &str,
     ) {
-        if self.dry_run() {
-            return;
-        }
-
-        let cache_dst =
-            self.bootstrap_cache_path.as_ref().cloned().unwrap_or_else(|| self.out.join("cache"));
-
-        let cache_dir = cache_dst.join(key);
-        if !cache_dir.exists() {
-            t!(fs::create_dir_all(&cache_dir));
-        }
-
-        let bin_root = self.out.join(self.host_target).join(destination);
-        let tarball = cache_dir.join(&filename);
-        let (base_url, url, should_verify) = match mode {
-            DownloadSource::CI => {
-                let dist_server = if self.llvm_assertions {
-                    self.stage0_metadata.config.artifacts_with_llvm_assertions_server.clone()
-                } else {
-                    self.stage0_metadata.config.artifacts_server.clone()
-                };
-                let url = format!(
-                    "{}/{filename}",
-                    key.strip_suffix(&format!("-{}", self.llvm_assertions)).unwrap()
-                );
-                (dist_server, url, false)
-            }
-            DownloadSource::Dist => {
-                let dist_server = env::var("RUSTUP_DIST_SERVER")
-                    .unwrap_or(self.stage0_metadata.config.dist_server.to_string());
-                // NOTE: make `dist` part of the URL because that's how it's stored in src/stage0
-                (dist_server, format!("dist/{key}/{filename}"), true)
-            }
-        };
-
-        // For the stage0 compiler, put special effort into ensuring the checksums are valid.
-        let checksum = if should_verify {
-            let error = format!(
-                "src/stage0 doesn't contain a checksum for {url}. \
-                Pre-built artifacts might not be available for this \
-                target at this time, see https://doc.rust-lang.org/nightly\
-                /rustc/platform-support.html for more information."
-            );
-            let sha256 = self.stage0_metadata.checksums_sha256.get(&url).expect(&error);
-            if tarball.exists() {
-                if self.verify(&tarball, sha256) {
-                    self.unpack(&tarball, &bin_root, prefix);
-                    return;
-                } else {
-                    self.verbose(|| {
-                        println!(
-                            "ignoring cached file {} due to failed verification",
-                            tarball.display()
-                        )
-                    });
-                    self.remove(&tarball);
-                }
-            }
-            Some(sha256)
-        } else if tarball.exists() {
-            self.unpack(&tarball, &bin_root, prefix);
-            return;
-        } else {
-            None
-        };
-
-        let mut help_on_error = "";
-        if destination == "ci-rustc" {
-            help_on_error = "ERROR: failed to download pre-built rustc from CI
-
-NOTE: old builds get deleted after a certain time
-HELP: if trying to compile an old commit of rustc, disable `download-rustc` in bootstrap.toml:
-
-[rust]
-download-rustc = false
-";
-        }
-        self.download_file(&format!("{base_url}/{url}"), &tarball, help_on_error);
-        if let Some(sha256) = checksum
-            && !self.verify(&tarball, sha256)
-        {
-            panic!("failed to verify {}", tarball.display());
-        }
-
-        self.unpack(&tarball, &bin_root, prefix);
+        let dwn_ctx: DownloadContext<'_> = self.into();
+        download_component(dwn_ctx, mode, filename, prefix, key, destination);
     }
 
     #[cfg(test)]
@@ -852,6 +396,39 @@ download-rustc = false
     }
 }
 
+/// Only should be used for pre config initialization downloads.
+pub(crate) struct DownloadContext<'a> {
+    host_target: TargetSelection,
+    out: &'a Path,
+    patch_binaries_for_nix: Option<bool>,
+    exec_ctx: &'a ExecutionContext,
+    stage0_metadata: &'a build_helper::stage0_parser::Stage0,
+    llvm_assertions: bool,
+    bootstrap_cache_path: &'a Option<PathBuf>,
+    is_running_on_ci: bool,
+}
+
+impl<'a> AsRef<DownloadContext<'a>> for DownloadContext<'a> {
+    fn as_ref(&self) -> &DownloadContext<'a> {
+        self
+    }
+}
+
+impl<'a> From<&'a Config> for DownloadContext<'a> {
+    fn from(value: &'a Config) -> Self {
+        DownloadContext {
+            host_target: value.host_target,
+            out: &value.out,
+            patch_binaries_for_nix: value.patch_binaries_for_nix,
+            exec_ctx: &value.exec_ctx,
+            stage0_metadata: &value.stage0_metadata,
+            llvm_assertions: value.llvm_assertions,
+            bootstrap_cache_path: &value.bootstrap_cache_path,
+            is_running_on_ci: value.is_running_on_ci,
+        }
+    }
+}
+
 fn path_is_dylib(path: &Path) -> bool {
     // The .so is not necessarily the extension, it might be libLLVM.so.18.1
     path.to_str().is_some_and(|path| path.contains(".so"))
@@ -897,3 +474,596 @@ pub(crate) fn is_download_ci_available(target_triple: &str, llvm_assertions: boo
         SUPPORTED_PLATFORMS.contains(&target_triple)
     }
 }
+
+#[cfg(test)]
+pub(crate) fn maybe_download_rustfmt<'a>(
+    dwn_ctx: impl AsRef<DownloadContext<'a>>,
+) -> Option<PathBuf> {
+    Some(PathBuf::new())
+}
+
+/// NOTE: rustfmt is a completely different toolchain than the bootstrap compiler, so it can't
+/// reuse target directories or artifacts
+#[cfg(not(test))]
+pub(crate) fn maybe_download_rustfmt<'a>(
+    dwn_ctx: impl AsRef<DownloadContext<'a>>,
+) -> Option<PathBuf> {
+    use build_helper::stage0_parser::VersionMetadata;
+
+    let dwn_ctx = dwn_ctx.as_ref();
+
+    if dwn_ctx.exec_ctx.dry_run() {
+        return Some(PathBuf::new());
+    }
+
+    let VersionMetadata { date, version } = dwn_ctx.stage0_metadata.rustfmt.as_ref()?;
+    let channel = format!("{version}-{date}");
+
+    let host = dwn_ctx.host_target;
+    let bin_root = dwn_ctx.out.join(host).join("rustfmt");
+    let rustfmt_path = bin_root.join("bin").join(exe("rustfmt", host));
+    let rustfmt_stamp = BuildStamp::new(&bin_root).with_prefix("rustfmt").add_stamp(channel);
+    if rustfmt_path.exists() && rustfmt_stamp.is_up_to_date() {
+        return Some(rustfmt_path);
+    }
+
+    download_component(
+        dwn_ctx,
+        DownloadSource::Dist,
+        format!("rustfmt-{version}-{build}.tar.xz", build = host.triple),
+        "rustfmt-preview",
+        date,
+        "rustfmt",
+    );
+
+    download_component(
+        dwn_ctx,
+        DownloadSource::Dist,
+        format!("rustc-{version}-{build}.tar.xz", build = host.triple),
+        "rustc",
+        date,
+        "rustfmt",
+    );
+
+    if should_fix_bins_and_dylibs(dwn_ctx.patch_binaries_for_nix, dwn_ctx.exec_ctx) {
+        fix_bin_or_dylib(dwn_ctx.out, &bin_root.join("bin").join("rustfmt"), dwn_ctx.exec_ctx);
+        fix_bin_or_dylib(dwn_ctx.out, &bin_root.join("bin").join("cargo-fmt"), dwn_ctx.exec_ctx);
+        let lib_dir = bin_root.join("lib");
+        for lib in t!(fs::read_dir(&lib_dir), lib_dir.display().to_string()) {
+            let lib = t!(lib);
+            if path_is_dylib(&lib.path()) {
+                fix_bin_or_dylib(dwn_ctx.out, &lib.path(), dwn_ctx.exec_ctx);
+            }
+        }
+    }
+
+    t!(rustfmt_stamp.write());
+    Some(rustfmt_path)
+}
+
+#[cfg(test)]
+pub(crate) fn download_beta_toolchain<'a>(dwn_ctx: impl AsRef<DownloadContext<'a>>) {}
+
+#[cfg(not(test))]
+pub(crate) fn download_beta_toolchain<'a>(dwn_ctx: impl AsRef<DownloadContext<'a>>) {
+    let dwn_ctx = dwn_ctx.as_ref();
+    dwn_ctx.exec_ctx.verbose(|| {
+        println!("downloading stage0 beta artifacts");
+    });
+
+    let date = dwn_ctx.stage0_metadata.compiler.date.clone();
+    let version = dwn_ctx.stage0_metadata.compiler.version.clone();
+    let extra_components = ["cargo"];
+    let sysroot = "stage0";
+    download_toolchain(
+        dwn_ctx,
+        &version,
+        sysroot,
+        &date,
+        &extra_components,
+        "stage0",
+        DownloadSource::Dist,
+    );
+}
+
+fn download_toolchain<'a>(
+    dwn_ctx: impl AsRef<DownloadContext<'a>>,
+    version: &str,
+    sysroot: &str,
+    stamp_key: &str,
+    extra_components: &[&str],
+    destination: &str,
+    mode: DownloadSource,
+) {
+    let dwn_ctx = dwn_ctx.as_ref();
+    let host = dwn_ctx.host_target.triple;
+    let bin_root = dwn_ctx.out.join(host).join(sysroot);
+    let rustc_stamp = BuildStamp::new(&bin_root).with_prefix("rustc").add_stamp(stamp_key);
+
+    if !bin_root.join("bin").join(exe("rustc", dwn_ctx.host_target)).exists()
+        || !rustc_stamp.is_up_to_date()
+    {
+        if bin_root.exists() {
+            t!(fs::remove_dir_all(&bin_root));
+        }
+        let filename = format!("rust-std-{version}-{host}.tar.xz");
+        let pattern = format!("rust-std-{host}");
+        download_component(dwn_ctx, mode.clone(), filename, &pattern, stamp_key, destination);
+        let filename = format!("rustc-{version}-{host}.tar.xz");
+        download_component(dwn_ctx, mode.clone(), filename, "rustc", stamp_key, destination);
+
+        for component in extra_components {
+            let filename = format!("{component}-{version}-{host}.tar.xz");
+            download_component(dwn_ctx, mode.clone(), filename, component, stamp_key, destination);
+        }
+
+        if should_fix_bins_and_dylibs(dwn_ctx.patch_binaries_for_nix, dwn_ctx.exec_ctx) {
+            fix_bin_or_dylib(dwn_ctx.out, &bin_root.join("bin").join("rustc"), dwn_ctx.exec_ctx);
+            fix_bin_or_dylib(dwn_ctx.out, &bin_root.join("bin").join("rustdoc"), dwn_ctx.exec_ctx);
+            fix_bin_or_dylib(
+                dwn_ctx.out,
+                &bin_root.join("libexec").join("rust-analyzer-proc-macro-srv"),
+                dwn_ctx.exec_ctx,
+            );
+            let lib_dir = bin_root.join("lib");
+            for lib in t!(fs::read_dir(&lib_dir), lib_dir.display().to_string()) {
+                let lib = t!(lib);
+                if path_is_dylib(&lib.path()) {
+                    fix_bin_or_dylib(dwn_ctx.out, &lib.path(), dwn_ctx.exec_ctx);
+                }
+            }
+        }
+
+        t!(rustc_stamp.write());
+    }
+}
+
+pub(crate) fn remove(exec_ctx: &ExecutionContext, f: &Path) {
+    if exec_ctx.dry_run() {
+        return;
+    }
+    fs::remove_file(f).unwrap_or_else(|_| panic!("failed to remove {f:?}"));
+}
+
+fn fix_bin_or_dylib(out: &Path, fname: &Path, exec_ctx: &ExecutionContext) {
+    assert_eq!(SHOULD_FIX_BINS_AND_DYLIBS.get(), Some(&true));
+    println!("attempting to patch {}", fname.display());
+
+    // Only build `.nix-deps` once.
+    static NIX_DEPS_DIR: OnceLock<PathBuf> = OnceLock::new();
+    let mut nix_build_succeeded = true;
+    let nix_deps_dir = NIX_DEPS_DIR.get_or_init(|| {
+        // Run `nix-build` to "build" each dependency (which will likely reuse
+        // the existing `/nix/store` copy, or at most download a pre-built copy).
+        //
+        // Importantly, we create a gc-root called `.nix-deps` in the `build/`
+        // directory, but still reference the actual `/nix/store` path in the rpath
+        // as it makes it significantly more robust against changes to the location of
+        // the `.nix-deps` location.
+        //
+        // bintools: Needed for the path of `ld-linux.so` (via `nix-support/dynamic-linker`).
+        // zlib: Needed as a system dependency of `libLLVM-*.so`.
+        // patchelf: Needed for patching ELF binaries (see doc comment above).
+        let nix_deps_dir = out.join(".nix-deps");
+        const NIX_EXPR: &str = "
+        with (import <nixpkgs> {});
+        symlinkJoin {
+            name = \"rust-stage0-dependencies\";
+            paths = [
+                zlib
+                patchelf
+                stdenv.cc.bintools
+            ];
+        }
+        ";
+        nix_build_succeeded = command("nix-build")
+            .allow_failure()
+            .args([Path::new("-E"), Path::new(NIX_EXPR), Path::new("-o"), &nix_deps_dir])
+            .run_capture_stdout(exec_ctx)
+            .is_success();
+        nix_deps_dir
+    });
+    if !nix_build_succeeded {
+        return;
+    }
+
+    let mut patchelf = command(nix_deps_dir.join("bin/patchelf"));
+    patchelf.args(&[
+        OsString::from("--add-rpath"),
+        OsString::from(t!(fs::canonicalize(nix_deps_dir)).join("lib")),
+    ]);
+    if !path_is_dylib(fname) {
+        // Finally, set the correct .interp for binaries
+        let dynamic_linker_path = nix_deps_dir.join("nix-support/dynamic-linker");
+        let dynamic_linker = t!(fs::read_to_string(dynamic_linker_path));
+        patchelf.args(["--set-interpreter", dynamic_linker.trim_end()]);
+    }
+    patchelf.arg(fname);
+    let _ = patchelf.allow_failure().run_capture_stdout(exec_ctx);
+}
+
+fn should_fix_bins_and_dylibs(
+    patch_binaries_for_nix: Option<bool>,
+    exec_ctx: &ExecutionContext,
+) -> bool {
+    let val = *SHOULD_FIX_BINS_AND_DYLIBS.get_or_init(|| {
+        let uname = command("uname").allow_failure().arg("-s").run_capture_stdout(exec_ctx);
+        if uname.is_failure() {
+            return false;
+        }
+        let output = uname.stdout();
+        if !output.starts_with("Linux") {
+            return false;
+        }
+        // If the user has asked binaries to be patched for Nix, then
+        // don't check for NixOS or `/lib`.
+        // NOTE: this intentionally comes after the Linux check:
+        // - patchelf only works with ELF files, so no need to run it on Mac or Windows
+        // - On other Unix systems, there is no stable syscall interface, so Nix doesn't manage the global libc.
+        if let Some(explicit_value) = patch_binaries_for_nix {
+            return explicit_value;
+        }
+
+        // Use `/etc/os-release` instead of `/etc/NIXOS`.
+        // The latter one does not exist on NixOS when using tmpfs as root.
+        let is_nixos = match File::open("/etc/os-release") {
+            Err(e) if e.kind() == ErrorKind::NotFound => false,
+            Err(e) => panic!("failed to access /etc/os-release: {e}"),
+            Ok(os_release) => BufReader::new(os_release).lines().any(|l| {
+                let l = l.expect("reading /etc/os-release");
+                matches!(l.trim(), "ID=nixos" | "ID='nixos'" | "ID=\"nixos\"")
+            }),
+        };
+        if !is_nixos {
+            let in_nix_shell = env::var("IN_NIX_SHELL");
+            if let Ok(in_nix_shell) = in_nix_shell {
+                eprintln!(
+                    "The IN_NIX_SHELL environment variable is `{in_nix_shell}`; \
+                     you may need to set `patch-binaries-for-nix=true` in bootstrap.toml"
+                );
+            }
+        }
+        is_nixos
+    });
+    if val {
+        eprintln!("INFO: You seem to be using Nix.");
+    }
+    val
+}
+
+fn download_component<'a>(
+    dwn_ctx: impl AsRef<DownloadContext<'a>>,
+    mode: DownloadSource,
+    filename: String,
+    prefix: &str,
+    key: &str,
+    destination: &str,
+) {
+    let dwn_ctx = dwn_ctx.as_ref();
+
+    if dwn_ctx.exec_ctx.dry_run() {
+        return;
+    }
+
+    let cache_dst =
+        dwn_ctx.bootstrap_cache_path.as_ref().cloned().unwrap_or_else(|| dwn_ctx.out.join("cache"));
+
+    let cache_dir = cache_dst.join(key);
+    if !cache_dir.exists() {
+        t!(fs::create_dir_all(&cache_dir));
+    }
+
+    let bin_root = dwn_ctx.out.join(dwn_ctx.host_target).join(destination);
+    let tarball = cache_dir.join(&filename);
+    let (base_url, url, should_verify) = match mode {
+        DownloadSource::CI => {
+            let dist_server = if dwn_ctx.llvm_assertions {
+                dwn_ctx.stage0_metadata.config.artifacts_with_llvm_assertions_server.clone()
+            } else {
+                dwn_ctx.stage0_metadata.config.artifacts_server.clone()
+            };
+            let url = format!(
+                "{}/{filename}",
+                key.strip_suffix(&format!("-{}", dwn_ctx.llvm_assertions)).unwrap()
+            );
+            (dist_server, url, false)
+        }
+        DownloadSource::Dist => {
+            let dist_server = env::var("RUSTUP_DIST_SERVER")
+                .unwrap_or(dwn_ctx.stage0_metadata.config.dist_server.to_string());
+            // NOTE: make `dist` part of the URL because that's how it's stored in src/stage0
+            (dist_server, format!("dist/{key}/{filename}"), true)
+        }
+    };
+
+    // For the stage0 compiler, put special effort into ensuring the checksums are valid.
+    let checksum = if should_verify {
+        let error = format!(
+            "src/stage0 doesn't contain a checksum for {url}. \
+            Pre-built artifacts might not be available for this \
+            target at this time, see https://doc.rust-lang.org/nightly\
+            /rustc/platform-support.html for more information."
+        );
+        let sha256 = dwn_ctx.stage0_metadata.checksums_sha256.get(&url).expect(&error);
+        if tarball.exists() {
+            if verify(dwn_ctx.exec_ctx, &tarball, sha256) {
+                unpack(dwn_ctx.exec_ctx, &tarball, &bin_root, prefix);
+                return;
+            } else {
+                dwn_ctx.exec_ctx.verbose(|| {
+                    println!(
+                        "ignoring cached file {} due to failed verification",
+                        tarball.display()
+                    )
+                });
+                remove(dwn_ctx.exec_ctx, &tarball);
+            }
+        }
+        Some(sha256)
+    } else if tarball.exists() {
+        unpack(dwn_ctx.exec_ctx, &tarball, &bin_root, prefix);
+        return;
+    } else {
+        None
+    };
+
+    let mut help_on_error = "";
+    if destination == "ci-rustc" {
+        help_on_error = "ERROR: failed to download pre-built rustc from CI
+
+NOTE: old builds get deleted after a certain time
+HELP: if trying to compile an old commit of rustc, disable `download-rustc` in bootstrap.toml:
+
+[rust]
+download-rustc = false
+";
+    }
+    download_file(dwn_ctx, &format!("{base_url}/{url}"), &tarball, help_on_error);
+    if let Some(sha256) = checksum
+        && !verify(dwn_ctx.exec_ctx, &tarball, sha256)
+    {
+        panic!("failed to verify {}", tarball.display());
+    }
+
+    unpack(dwn_ctx.exec_ctx, &tarball, &bin_root, prefix);
+}
+
+pub(crate) fn verify(exec_ctx: &ExecutionContext, path: &Path, expected: &str) -> bool {
+    use sha2::Digest;
+
+    exec_ctx.verbose(|| {
+        println!("verifying {}", path.display());
+    });
+
+    if exec_ctx.dry_run() {
+        return false;
+    }
+
+    let mut hasher = sha2::Sha256::new();
+
+    let file = t!(File::open(path));
+    let mut reader = BufReader::new(file);
+
+    loop {
+        let buffer = t!(reader.fill_buf());
+        let l = buffer.len();
+        // break if EOF
+        if l == 0 {
+            break;
+        }
+        hasher.update(buffer);
+        reader.consume(l);
+    }
+
+    let checksum = hex_encode(hasher.finalize().as_slice());
+    let verified = checksum == expected;
+
+    if !verified {
+        println!(
+            "invalid checksum: \n\
+            found:    {checksum}\n\
+            expected: {expected}",
+        );
+    }
+
+    verified
+}
+
+fn unpack(exec_ctx: &ExecutionContext, tarball: &Path, dst: &Path, pattern: &str) {
+    eprintln!("extracting {} to {}", tarball.display(), dst.display());
+    if !dst.exists() {
+        t!(fs::create_dir_all(dst));
+    }
+
+    // `tarball` ends with `.tar.xz`; strip that suffix
+    // example: `rust-dev-nightly-x86_64-unknown-linux-gnu`
+    let uncompressed_filename =
+        Path::new(tarball.file_name().expect("missing tarball filename")).file_stem().unwrap();
+    let directory_prefix = Path::new(Path::new(uncompressed_filename).file_stem().unwrap());
+
+    // decompress the file
+    let data = t!(File::open(tarball), format!("file {} not found", tarball.display()));
+    let decompressor = XzDecoder::new(BufReader::new(data));
+
+    let mut tar = tar::Archive::new(decompressor);
+
+    let is_ci_rustc = dst.ends_with("ci-rustc");
+    let is_ci_llvm = dst.ends_with("ci-llvm");
+
+    // `compile::Sysroot` needs to know the contents of the `rustc-dev` tarball to avoid adding
+    // it to the sysroot unless it was explicitly requested. But parsing the 100 MB tarball is slow.
+    // Cache the entries when we extract it so we only have to read it once.
+    let mut recorded_entries = if is_ci_rustc { recorded_entries(dst, pattern) } else { None };
+
+    for member in t!(tar.entries()) {
+        let mut member = t!(member);
+        let original_path = t!(member.path()).into_owned();
+        // skip the top-level directory
+        if original_path == directory_prefix {
+            continue;
+        }
+        let mut short_path = t!(original_path.strip_prefix(directory_prefix));
+        let is_builder_config = short_path.to_str() == Some(BUILDER_CONFIG_FILENAME);
+
+        if !(short_path.starts_with(pattern) || ((is_ci_rustc || is_ci_llvm) && is_builder_config))
+        {
+            continue;
+        }
+        short_path = short_path.strip_prefix(pattern).unwrap_or(short_path);
+        let dst_path = dst.join(short_path);
+
+        exec_ctx.verbose(|| {
+            println!("extracting {} to {}", original_path.display(), dst.display());
+        });
+
+        if !t!(member.unpack_in(dst)) {
+            panic!("path traversal attack ??");
+        }
+        if let Some(record) = &mut recorded_entries {
+            t!(writeln!(record, "{}", short_path.to_str().unwrap()));
+        }
+        let src_path = dst.join(original_path);
+        if src_path.is_dir() && dst_path.exists() {
+            continue;
+        }
+        t!(move_file(src_path, dst_path));
+    }
+    let dst_dir = dst.join(directory_prefix);
+    if dst_dir.exists() {
+        t!(fs::remove_dir_all(&dst_dir), format!("failed to remove {}", dst_dir.display()));
+    }
+}
+
+fn download_file<'a>(
+    dwn_ctx: impl AsRef<DownloadContext<'a>>,
+    url: &str,
+    dest_path: &Path,
+    help_on_error: &str,
+) {
+    let dwn_ctx = dwn_ctx.as_ref();
+
+    dwn_ctx.exec_ctx.verbose(|| {
+        println!("download {url}");
+    });
+    // Use a temporary file in case we crash while downloading, to avoid a corrupt download in cache/.
+    let tempfile = tempdir(dwn_ctx.out).join(dest_path.file_name().unwrap());
+    // While bootstrap itself only supports http and https downloads, downstream forks might
+    // need to download components from other protocols. The match allows them adding more
+    // protocols without worrying about merge conflicts if we change the HTTP implementation.
+    match url.split_once("://").map(|(proto, _)| proto) {
+        Some("http") | Some("https") => download_http_with_retries(
+            dwn_ctx.host_target,
+            dwn_ctx.is_running_on_ci,
+            dwn_ctx.exec_ctx,
+            &tempfile,
+            url,
+            help_on_error,
+        ),
+        Some(other) => panic!("unsupported protocol {other} in {url}"),
+        None => panic!("no protocol in {url}"),
+    }
+    t!(move_file(&tempfile, dest_path), format!("failed to rename {tempfile:?} to {dest_path:?}"));
+}
+
+/// Create a temporary directory in `out` and return its path.
+///
+/// NOTE: this temporary directory is shared between all steps;
+/// if you need an empty directory, create a new subdirectory inside it.
+pub(crate) fn tempdir(out: &Path) -> PathBuf {
+    let tmp = out.join("tmp");
+    t!(fs::create_dir_all(&tmp));
+    tmp
+}
+
+fn download_http_with_retries(
+    host_target: TargetSelection,
+    is_running_on_ci: bool,
+    exec_ctx: &ExecutionContext,
+    tempfile: &Path,
+    url: &str,
+    help_on_error: &str,
+) {
+    println!("downloading {url}");
+    // Try curl. If that fails and we are on windows, fallback to PowerShell.
+    // options should be kept in sync with
+    // src/bootstrap/src/core/download.rs
+    // for consistency
+    let mut curl = command("curl").allow_failure();
+    curl.args([
+        // follow redirect
+        "--location",
+        // timeout if speed is < 10 bytes/sec for > 30 seconds
+        "--speed-time",
+        "30",
+        "--speed-limit",
+        "10",
+        // timeout if cannot connect within 30 seconds
+        "--connect-timeout",
+        "30",
+        // output file
+        "--output",
+        tempfile.to_str().unwrap(),
+        // if there is an error, don't restart the download,
+        // instead continue where it left off.
+        "--continue-at",
+        "-",
+        // retry up to 3 times.  note that this means a maximum of 4
+        // attempts will be made, since the first attempt isn't a *re*try.
+        "--retry",
+        "3",
+        // show errors, even if --silent is specified
+        "--show-error",
+        // set timestamp of downloaded file to that of the server
+        "--remote-time",
+        // fail on non-ok http status
+        "--fail",
+    ]);
+    // Don't print progress in CI; the \r wrapping looks bad and downloads don't take long enough for progress to be useful.
+    if is_running_on_ci {
+        curl.arg("--silent");
+    } else {
+        curl.arg("--progress-bar");
+    }
+    // --retry-all-errors was added in 7.71.0, don't use it if curl is old.
+    if curl_version(exec_ctx) >= semver::Version::new(7, 71, 0) {
+        curl.arg("--retry-all-errors");
+    }
+    curl.arg(url);
+    if !curl.run(exec_ctx) {
+        if host_target.contains("windows-msvc") {
+            eprintln!("Fallback to PowerShell");
+            for _ in 0..3 {
+                let powershell = command("PowerShell.exe").allow_failure().args([
+                    "/nologo",
+                    "-Command",
+                    "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;",
+                    &format!(
+                        "(New-Object System.Net.WebClient).DownloadFile('{}', '{}')",
+                        url, tempfile.to_str().expect("invalid UTF-8 not supported with powershell downloads"),
+                    ),
+                ]).run_capture_stdout(exec_ctx);
+
+                if powershell.is_failure() {
+                    return;
+                }
+
+                eprintln!("\nspurious failure, trying again");
+            }
+        }
+        if !help_on_error.is_empty() {
+            eprintln!("{help_on_error}");
+        }
+        crate::exit!(1);
+    }
+}
+
+fn curl_version(exec_ctx: &ExecutionContext) -> semver::Version {
+    let mut curl = command("curl");
+    curl.arg("-V");
+    let curl = curl.run_capture_stdout(exec_ctx);
+    if curl.is_failure() {
+        return semver::Version::new(1, 0, 0);
+    }
+    let output = curl.stdout();
+    extract_curl_version(output)
+}
diff --git a/src/bootstrap/src/utils/proc_macro_deps.rs b/src/bootstrap/src/utils/proc_macro_deps.rs
index 21c7fc89d7d..777c8601aa1 100644
--- a/src/bootstrap/src/utils/proc_macro_deps.rs
+++ b/src/bootstrap/src/utils/proc_macro_deps.rs
@@ -3,6 +3,7 @@
 /// See <https://github.com/rust-lang/rust/issues/134863>
 pub static CRATES: &[&str] = &[
     // tidy-alphabetical-start
+    "allocator-api2",
     "annotate-snippets",
     "anstyle",
     "askama_parser",
@@ -16,13 +17,17 @@ pub static CRATES: &[&str] = &[
     "darling_core",
     "derive_builder_core",
     "digest",
+    "equivalent",
     "fluent-bundle",
     "fluent-langneg",
     "fluent-syntax",
     "fnv",
+    "foldhash",
     "generic-array",
+    "hashbrown",
     "heck",
     "ident_case",
+    "indexmap",
     "intl-memoizer",
     "intl_pluralrules",
     "libc",
diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs
index 57456e906de..fed4296fa22 100644
--- a/src/librustdoc/config.rs
+++ b/src/librustdoc/config.rs
@@ -380,7 +380,7 @@ impl Options {
         early_dcx: &mut EarlyDiagCtxt,
         matches: &getopts::Matches,
         args: Vec<String>,
-    ) -> Option<(InputMode, Options, RenderOptions)> {
+    ) -> Option<(InputMode, Options, RenderOptions, Vec<PathBuf>)> {
         // Check for unstable options.
         nightly_options::check_nightly_options(early_dcx, matches, &opts());
 
@@ -643,10 +643,13 @@ impl Options {
 
         let extension_css = matches.opt_str("e").map(|s| PathBuf::from(&s));
 
-        if let Some(ref p) = extension_css
-            && !p.is_file()
-        {
-            dcx.fatal("option --extend-css argument must be a file");
+        let mut loaded_paths = Vec::new();
+
+        if let Some(ref p) = extension_css {
+            loaded_paths.push(p.clone());
+            if !p.is_file() {
+                dcx.fatal("option --extend-css argument must be a file");
+            }
         }
 
         let mut themes = Vec::new();
@@ -690,6 +693,7 @@ impl Options {
                     ))
                     .emit();
                 }
+                loaded_paths.push(theme_file.clone());
                 themes.push(StylePath { path: theme_file });
             }
         }
@@ -708,6 +712,7 @@ impl Options {
             &mut id_map,
             edition,
             &None,
+            &mut loaded_paths,
         ) else {
             dcx.fatal("`ExternalHtml::load` failed");
         };
@@ -799,7 +804,8 @@ impl Options {
 
         let scrape_examples_options = ScrapeExamplesOptions::new(matches, dcx);
         let with_examples = matches.opt_strs("with-examples");
-        let call_locations = crate::scrape_examples::load_call_locations(with_examples, dcx);
+        let call_locations =
+            crate::scrape_examples::load_call_locations(with_examples, dcx, &mut loaded_paths);
         let doctest_build_args = matches.opt_strs("doctest-build-arg");
 
         let unstable_features =
@@ -885,7 +891,7 @@ impl Options {
             parts_out_dir,
             disable_minification,
         };
-        Some((input, options, render_options))
+        Some((input, options, render_options, loaded_paths))
     }
 }
 
diff --git a/src/librustdoc/externalfiles.rs b/src/librustdoc/externalfiles.rs
index ea2aa963edd..42ade5b9004 100644
--- a/src/librustdoc/externalfiles.rs
+++ b/src/librustdoc/externalfiles.rs
@@ -1,4 +1,4 @@
-use std::path::Path;
+use std::path::{Path, PathBuf};
 use std::{fs, str};
 
 use rustc_errors::DiagCtxtHandle;
@@ -32,12 +32,13 @@ impl ExternalHtml {
         id_map: &mut IdMap,
         edition: Edition,
         playground: &Option<Playground>,
+        loaded_paths: &mut Vec<PathBuf>,
     ) -> Option<ExternalHtml> {
         let codes = ErrorCodes::from(nightly_build);
-        let ih = load_external_files(in_header, dcx)?;
+        let ih = load_external_files(in_header, dcx, loaded_paths)?;
         let bc = {
-            let mut bc = load_external_files(before_content, dcx)?;
-            let m_bc = load_external_files(md_before_content, dcx)?;
+            let mut bc = load_external_files(before_content, dcx, loaded_paths)?;
+            let m_bc = load_external_files(md_before_content, dcx, loaded_paths)?;
             Markdown {
                 content: &m_bc,
                 links: &[],
@@ -52,8 +53,8 @@ impl ExternalHtml {
             bc
         };
         let ac = {
-            let mut ac = load_external_files(after_content, dcx)?;
-            let m_ac = load_external_files(md_after_content, dcx)?;
+            let mut ac = load_external_files(after_content, dcx, loaded_paths)?;
+            let m_ac = load_external_files(md_after_content, dcx, loaded_paths)?;
             Markdown {
                 content: &m_ac,
                 links: &[],
@@ -79,8 +80,10 @@ pub(crate) enum LoadStringError {
 pub(crate) fn load_string<P: AsRef<Path>>(
     file_path: P,
     dcx: DiagCtxtHandle<'_>,
+    loaded_paths: &mut Vec<PathBuf>,
 ) -> Result<String, LoadStringError> {
     let file_path = file_path.as_ref();
+    loaded_paths.push(file_path.to_owned());
     let contents = match fs::read(file_path) {
         Ok(bytes) => bytes,
         Err(e) => {
@@ -101,10 +104,14 @@ pub(crate) fn load_string<P: AsRef<Path>>(
     }
 }
 
-fn load_external_files(names: &[String], dcx: DiagCtxtHandle<'_>) -> Option<String> {
+fn load_external_files(
+    names: &[String],
+    dcx: DiagCtxtHandle<'_>,
+    loaded_paths: &mut Vec<PathBuf>,
+) -> Option<String> {
     let mut out = String::new();
     for name in names {
-        let Ok(s) = load_string(name, dcx) else { return None };
+        let Ok(s) = load_string(name, dcx, loaded_paths) else { return None };
         out.push_str(&s);
         out.push('\n');
     }
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index a3cdc4f687f..7431ff8e1ad 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -799,7 +799,7 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
 
     // Note that we discard any distinction between different non-zero exit
     // codes from `from_matches` here.
-    let (input, options, render_options) =
+    let (input, options, render_options, loaded_paths) =
         match config::Options::from_matches(early_dcx, &matches, args) {
             Some(opts) => opts,
             None => return,
@@ -870,6 +870,12 @@ fn main_args(early_dcx: &mut EarlyDiagCtxt, at_args: &[String]) {
     interface::run_compiler(config, |compiler| {
         let sess = &compiler.sess;
 
+        // Register the loaded external files in the source map so they show up in depinfo.
+        // We can't load them via the source map because it gets created after we process the options.
+        for external_path in &loaded_paths {
+            let _ = sess.source_map().load_file(external_path);
+        }
+
         if sess.opts.describe_lints {
             rustc_driver::describe_lints(sess, registered_lints);
             return;
diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs
index fceacb69fb5..4d29c74e1eb 100644
--- a/src/librustdoc/scrape_examples.rs
+++ b/src/librustdoc/scrape_examples.rs
@@ -333,9 +333,11 @@ pub(crate) fn run(
 pub(crate) fn load_call_locations(
     with_examples: Vec<String>,
     dcx: DiagCtxtHandle<'_>,
+    loaded_paths: &mut Vec<PathBuf>,
 ) -> AllCallLocations {
     let mut all_calls: AllCallLocations = FxIndexMap::default();
     for path in with_examples {
+        loaded_paths.push(path.clone().into());
         let bytes = match fs::read(&path) {
             Ok(bytes) => bytes,
             Err(e) => dcx.fatal(format!("failed to load examples: {e}")),
diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml
index 11c0f08debe..c47f9695624 100644
--- a/src/tools/miri/.github/workflows/ci.yml
+++ b/src/tools/miri/.github/workflows/ci.yml
@@ -45,11 +45,17 @@ jobs:
             os: macos-latest
           - host_target: i686-pc-windows-msvc
             os: windows-latest
+          - host_target: aarch64-pc-windows-msvc
+            os: windows-11-arm
     runs-on: ${{ matrix.os }}
     env:
       HOST_TARGET: ${{ matrix.host_target }}
     steps:
       - uses: actions/checkout@v4
+      - name: apt update
+        if: ${{ startsWith(matrix.os, 'ubuntu') }}
+        # The runners seem to have outdated apt repos sometimes
+        run: sudo apt update
       - name: install qemu
         if: ${{ matrix.qemu }}
         run: sudo apt install qemu-user qemu-user-binfmt
@@ -63,6 +69,12 @@ jobs:
           sudo apt update
           # Install needed packages
           sudo apt install $(echo "libatomic1: zlib1g-dev:" | sed 's/:/:${{ matrix.multiarch }}/g')
+      - name: Install rustup on Windows ARM
+        if: ${{ matrix.os == 'windows-11-arm' }}
+        run: |
+            curl -LOs https://static.rust-lang.org/rustup/dist/aarch64-pc-windows-msvc/rustup-init.exe
+            ./rustup-init.exe -y --no-modify-path
+            echo "$USERPROFILE/.cargo/bin" >> "$GITHUB_PATH"
       - uses: ./.github/workflows/setup
         with:
           toolchain_flags: "--host ${{ matrix.host_target }}"
@@ -147,35 +159,48 @@ jobs:
       - uses: actions/checkout@v4
         with:
           fetch-depth: 256 # get a bit more of the history
-      - name: install josh-proxy
-        run: cargo +stable install josh-proxy --git https://github.com/josh-project/josh --tag r24.10.04
+      - name: install josh-sync
+        run: cargo +stable install --locked --git https://github.com/rust-lang/josh-sync
       - name: setup bot git name and email
         run: |
           git config --global user.name 'The Miri Cronjob Bot'
           git config --global user.email 'miri@cron.bot'
       - name: Install nightly toolchain
         run: rustup toolchain install nightly --profile minimal
-      - name: get changes from rustc
-        run: ./miri rustc-pull
       - name: Install rustup-toolchain-install-master
         run: cargo install -f rustup-toolchain-install-master
-      - name: format changes (if any)
+      - name: Push changes to a branch and create PR
         run: |
+          # Make it easier to see what happens.
+          set -x
+          # Temporarily disable early exit to examine the status code of rustc-josh-sync
+          set +e
+          rustc-josh-sync pull
+          exitcode=$?
+          set -e
+
+          # If there were no changes to pull, rustc-josh-sync returns status code 2.
+          # In that case, skip the rest of the job.
+          if [ $exitcode -eq 2 ]; then
+            echo "Nothing changed in rustc, skipping PR"
+            exit 0
+          elif [ $exitcode -ne 0 ]; then
+            # If return code was not 0 or 2, rustc-josh-sync actually failed
+            echo "error: rustc-josh-sync failed"
+            exit ${exitcode}
+          fi
+
+          # Format changes
           ./miri toolchain
           ./miri fmt --check || (./miri fmt && git commit -am "fmt")
-      - name: Push changes to a branch and create PR
-        run: |
-          # `git diff --exit-code` "succeeds" if the diff is empty.
-          if git diff --exit-code HEAD^; then echo "Nothing changed in rustc, skipping PR"; exit 0; fi
-          # The diff is non-empty, create a PR.
+
+          # Create a PR
           BRANCH="rustup-$(date -u +%Y-%m-%d)"
           git switch -c $BRANCH
           git push -u origin $BRANCH
           gh pr create -B master --title 'Automatic Rustup' --body 'Please close and re-open this PR to trigger CI, then enable auto-merge.'
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          ZULIP_BOT_EMAIL: ${{ secrets.ZULIP_BOT_EMAIL }}
-          ZULIP_API_TOKEN: ${{ secrets.ZULIP_API_TOKEN }}
 
   cron-fail-notify:
     name: cronjob failure notification
@@ -198,7 +223,7 @@ jobs:
           It would appear that the [Miri cron job build]('"https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"') failed.
 
           This likely means that rustc changed the miri directory and
-          we now need to do a [`./miri rustc-pull`](https://github.com/rust-lang/miri/blob/master/CONTRIBUTING.md#importing-changes-from-the-rustc-repo).
+          we now need to do a [`rustc-josh-sync pull`](https://github.com/rust-lang/miri/blob/master/CONTRIBUTING.md#importing-changes-from-the-rustc-repo).
 
           Would you mind investigating this issue?
 
diff --git a/src/tools/miri/.gitignore b/src/tools/miri/.gitignore
index ed2d0ba7ba0..4a238dc0313 100644
--- a/src/tools/miri/.gitignore
+++ b/src/tools/miri/.gitignore
@@ -1,5 +1,4 @@
 target
-/doc
 tex/*/out
 *.dot
 *.out
diff --git a/src/tools/miri/CONTRIBUTING.md b/src/tools/miri/CONTRIBUTING.md
index 637c0dd2fdf..7d78fdddbad 100644
--- a/src/tools/miri/CONTRIBUTING.md
+++ b/src/tools/miri/CONTRIBUTING.md
@@ -297,14 +297,14 @@ You can also directly run Miri on a Rust source file:
 
 ## Advanced topic: Syncing with the rustc repo
 
-We use the [`josh` proxy](https://github.com/josh-project/josh) to transmit changes between the
+We use the [`josh-sync`](https://github.com/rust-lang/josh-sync) tool to transmit changes between the
 rustc and Miri repositories. You can install it as follows:
 
 ```sh
-cargo +stable install josh-proxy --git https://github.com/josh-project/josh --tag r24.10.04
+cargo install --locked --git https://github.com/rust-lang/josh-sync
 ```
 
-Josh will automatically be started and stopped by `./miri`.
+The commands below will automatically install and manage the [Josh](https://github.com/josh-project/josh) proxy that performs the actual work.
 
 ### Importing changes from the rustc repo
 
@@ -312,10 +312,12 @@ Josh will automatically be started and stopped by `./miri`.
 
 We assume we start on an up-to-date master branch in the Miri repo.
 
+1) First, create a branch for the pull, e.g. `git checkout -b rustup`
+2) Then run the following:
 ```sh
 # Fetch and merge rustc side of the history. Takes ca 5 min the first time.
 # This will also update the `rustc-version` file.
-./miri rustc-pull
+rustc-josh-sync pull
 # Update local toolchain and apply formatting.
 ./miri toolchain && ./miri fmt
 git commit -am "rustup"
@@ -328,12 +330,12 @@ needed.
 
 ### Exporting changes to the rustc repo
 
-We will use the josh proxy to push to your fork of rustc. Run the following in the Miri repo,
+We will use the `josh-sync` tool to push to your fork of rustc. Run the following in the Miri repo,
 assuming we are on an up-to-date master branch:
 
 ```sh
 # Push the Miri changes to your rustc fork (substitute your github handle for YOUR_NAME).
-./miri rustc-push YOUR_NAME miri
+rustc-josh-sync push miri YOUR_NAME
 ```
 
 This will create a new branch called `miri` in your fork, and the output should include a link that
diff --git a/src/tools/miri/Cargo.lock b/src/tools/miri/Cargo.lock
index 0af4181dc15..b46f0f83420 100644
--- a/src/tools/miri/Cargo.lock
+++ b/src/tools/miri/Cargo.lock
@@ -166,10 +166,12 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.2.30"
+version = "1.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7"
+checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
 dependencies = [
+ "jobserver",
+ "libc",
  "shlex",
 ]
 
@@ -215,6 +217,52 @@ dependencies = [
 ]
 
 [[package]]
+name = "clap"
+version = "4.5.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
+dependencies = [
+ "clap_builder",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
+dependencies = [
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
+
+[[package]]
+name = "cmake"
+version = "0.1.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "codespan-reporting"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81"
+dependencies = [
+ "serde",
+ "termcolor",
+ "unicode-width 0.2.1",
+]
+
+[[package]]
 name = "color-eyre"
 version = "0.6.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -314,6 +362,68 @@ dependencies = [
 ]
 
 [[package]]
+name = "cxx"
+version = "1.0.161"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3523cc02ad831111491dd64b27ad999f1ae189986728e477604e61b81f828df"
+dependencies = [
+ "cc",
+ "cxxbridge-cmd",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "foldhash",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.161"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "212b754247a6f07b10fa626628c157593f0abf640a3dd04cce2760eca970f909"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "indexmap",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn",
+]
+
+[[package]]
+name = "cxxbridge-cmd"
+version = "1.0.161"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f426a20413ec2e742520ba6837c9324b55ffac24ead47491a6e29f933c5b135a"
+dependencies = [
+ "clap",
+ "codespan-reporting",
+ "indexmap",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.161"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a258b6069020b4e5da6415df94a50ee4f586a6c38b037a180e940a43d06a070d"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.161"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8dec184b52be5008d6eaf7e62fc1802caf1ad1227d11b3b7df2c409c7ffc3f4"
+dependencies = [
+ "indexmap",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn",
+]
+
+[[package]]
 name = "directories"
 version = "6.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -335,12 +445,29 @@ dependencies = [
 ]
 
 [[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
 name = "encode_unicode"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
 
 [[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
 name = "errno"
 version = "0.3.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -373,6 +500,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
 
 [[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
 name = "generic-array"
 version = "0.14.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -383,6 +525,17 @@ dependencies = [
 ]
 
 [[package]]
+name = "genmc-sys"
+version = "0.1.0"
+dependencies = [
+ "cc",
+ "cmake",
+ "cxx",
+ "cxx-build",
+ "git2",
+]
+
+[[package]]
 name = "getrandom"
 version = "0.2.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -412,12 +565,150 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
 
 [[package]]
+name = "git2"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110"
+dependencies = [
+ "bitflags",
+ "libc",
+ "libgit2-sys",
+ "log",
+ "openssl-probe",
+ "openssl-sys",
+ "url",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.15.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
+
+[[package]]
+name = "icu_collections"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
+
+[[package]]
+name = "icu_properties"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "potential_utf",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
+
+[[package]]
+name = "icu_provider"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "idna"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
 name = "indenter"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
 
 [[package]]
+name = "indexmap"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
 name = "indicatif"
 version = "0.17.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -464,6 +755,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
 
 [[package]]
+name = "jobserver"
+version = "0.1.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
+dependencies = [
+ "getrandom 0.3.3",
+ "libc",
+]
+
+[[package]]
 name = "js-sys"
 version = "0.3.77"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -511,6 +812,19 @@ dependencies = [
 ]
 
 [[package]]
+name = "libgit2-sys"
+version = "0.18.2+1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c42fe03df2bd3c53a3a9c7317ad91d80c81cd1fb0caec8d7cc4cd2bfa10c222"
+dependencies = [
+ "cc",
+ "libc",
+ "libz-sys",
+ "openssl-sys",
+ "pkg-config",
+]
+
+[[package]]
 name = "libloading"
 version = "0.8.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -531,12 +845,39 @@ dependencies = [
 ]
 
 [[package]]
+name = "libz-sys"
+version = "1.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "link-cplusplus"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a6f6da007f968f9def0d65a05b187e2960183de70c160204ecfccf0ee330212"
+dependencies = [
+ "cc",
+]
+
+[[package]]
 name = "linux-raw-sys"
 version = "0.9.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
 
 [[package]]
+name = "litemap"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
+
+[[package]]
 name = "lock_api"
 version = "0.4.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -612,6 +953,7 @@ dependencies = [
  "chrono-tz",
  "colored 3.0.0",
  "directories",
+ "genmc-sys",
  "getrandom 0.3.3",
  "ipc-channel",
  "libc",
@@ -673,6 +1015,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
 
 [[package]]
+name = "openssl-probe"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
 name = "option-ext"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -723,6 +1083,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
 name = "perf-event-open-sys"
 version = "3.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -756,12 +1122,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
 
 [[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
 name = "portable-atomic"
 version = "1.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
 
 [[package]]
+name = "potential_utf"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
 name = "ppv-lite86"
 version = "0.2.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -947,6 +1328,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
 [[package]]
+name = "scratch"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f6280af86e5f559536da57a45ebc84948833b3bee313a7dd25232e09c878a52"
+
+[[package]]
 name = "semver"
 version = "1.0.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1026,6 +1413,18 @@ dependencies = [
 ]
 
 [[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
 name = "syn"
 version = "2.0.104"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1037,6 +1436,17 @@ dependencies = [
 ]
 
 [[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
 name = "tempfile"
 version = "3.20.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1050,6 +1460,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
 name = "thiserror"
 version = "1.0.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1109,6 +1528,16 @@ dependencies = [
 ]
 
 [[package]]
+name = "tinystr"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
 name = "tracing"
 version = "0.1.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1200,6 +1629,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
 
 [[package]]
+name = "url"
+version = "2.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
 name = "uuid"
 version = "1.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1217,6 +1663,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
 
 [[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
 name = "version_check"
 version = "0.9.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1306,6 +1758,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
 name = "windows"
 version = "0.58.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1525,6 +1986,36 @@ dependencies = [
 ]
 
 [[package]]
+name = "writeable"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
+
+[[package]]
+name = "yoke"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
 name = "zerocopy"
 version = "0.8.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1543,3 +2034,57 @@ dependencies = [
  "quote",
  "syn",
 ]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
+
+[[package]]
+name = "zerotrie"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/src/tools/miri/Cargo.toml b/src/tools/miri/Cargo.toml
index d293af5cea2..91dadf78a2f 100644
--- a/src/tools/miri/Cargo.toml
+++ b/src/tools/miri/Cargo.toml
@@ -48,6 +48,10 @@ nix = { version = "0.30.1", features = ["mman", "ptrace", "signal"], optional =
 ipc-channel = { version = "0.20.0", optional = true }
 capstone = { version = "0.13", optional = true }
 
+# FIXME(genmc,macos): Add `target_os = "macos"` once https://github.com/dtolnay/cxx/issues/1535 is fixed.
+[target.'cfg(all(target_os = "linux", target_pointer_width = "64", target_endian = "little"))'.dependencies]
+genmc-sys = { path = "./genmc-sys/", version = "0.1.0", optional = true }
+
 [dev-dependencies]
 ui_test = "0.30.2"
 colored = "3"
@@ -66,7 +70,7 @@ harness = false
 
 [features]
 default = ["stack-cache", "native-lib"]
-genmc = []
+genmc = ["dep:genmc-sys"] # this enables a GPL dependency!
 stack-cache = []
 stack-cache-consistency-check = ["stack-cache"]
 tracing = ["serde_json"]
diff --git a/src/tools/miri/cargo-miri/src/phases.rs b/src/tools/miri/cargo-miri/src/phases.rs
index b72b974bdbd..efb9053f69a 100644
--- a/src/tools/miri/cargo-miri/src/phases.rs
+++ b/src/tools/miri/cargo-miri/src/phases.rs
@@ -1,9 +1,9 @@
 //! Implements the various phases of `cargo miri run/test`.
 
 use std::env;
-use std::fs::{self, File};
+use std::fs::File;
 use std::io::BufReader;
-use std::path::{Path, PathBuf};
+use std::path::{self, Path, PathBuf};
 use std::process::Command;
 
 use rustc_version::VersionMeta;
@@ -222,12 +222,12 @@ pub fn phase_cargo_miri(mut args: impl Iterator<Item = String>) {
     // that to be the Miri driver, but acting as rustc, in host mode.
     //
     // In `main`, we need the value of `RUSTC` to distinguish RUSTC_WRAPPER invocations from rustdoc
-    // or TARGET_RUNNER invocations, so we canonicalize it here to make it exceedingly unlikely that
+    // or TARGET_RUNNER invocations, so we make it absolute to make it exceedingly unlikely that
     // there would be a collision with other invocations of cargo-miri (as rustdoc or as runner). We
     // explicitly do this even if RUSTC_STAGE is set, since for these builds we do *not* want the
     // bootstrap `rustc` thing in our way! Instead, we have MIRI_HOST_SYSROOT to use for host
     // builds.
-    cmd.env("RUSTC", fs::canonicalize(find_miri()).unwrap());
+    cmd.env("RUSTC", path::absolute(find_miri()).unwrap());
     // In case we get invoked as RUSTC without the wrapper, let's be a host rustc. This makes no
     // sense for cross-interpretation situations, but without the wrapper, this will use the host
     // sysroot, so asking it to behave like a target build makes even less sense.
diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh
index 5767d178279..b66530e77b8 100755
--- a/src/tools/miri/ci/ci.sh
+++ b/src/tools/miri/ci/ci.sh
@@ -142,7 +142,6 @@ case $HOST_TARGET in
     # Host
     GC_STRESS=1 MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests
     # Extra tier 1
-    MANY_SEEDS=64 TEST_TARGET=i686-unknown-linux-gnu run_tests
     MANY_SEEDS=64 TEST_TARGET=x86_64-apple-darwin run_tests
     MANY_SEEDS=64 TEST_TARGET=x86_64-pc-windows-gnu run_tests
     ;;
@@ -161,8 +160,6 @@ case $HOST_TARGET in
   aarch64-unknown-linux-gnu)
     # Host
     GC_STRESS=1 MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests
-    # Extra tier 1 candidate
-    MANY_SEEDS=64 TEST_TARGET=aarch64-pc-windows-msvc run_tests
     # Extra tier 2
     MANY_SEEDS=16 TEST_TARGET=arm-unknown-linux-gnueabi run_tests # 32bit ARM
     MANY_SEEDS=16 TEST_TARGET=aarch64-pc-windows-gnullvm run_tests # gnullvm ABI
@@ -189,13 +186,20 @@ case $HOST_TARGET in
     ;;
   i686-pc-windows-msvc)
     # Host
-    # Without GC_STRESS as this is the slowest runner.
+    # Without GC_STRESS as this is a very slow runner.
     MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 run_tests
     # Extra tier 1
     # We really want to ensure a Linux target works on a Windows host,
     # and a 64bit target works on a 32bit host.
     TEST_TARGET=x86_64-unknown-linux-gnu run_tests
     ;;
+  aarch64-pc-windows-msvc)
+    # Host
+    # Without GC_STRESS as this is a very slow runner.
+    MIR_OPT=1 MANY_SEEDS=64 TEST_BENCH=1 CARGO_MIRI_ENV=1 run_tests
+    # Extra tier 1
+    MANY_SEEDS=64 TEST_TARGET=i686-unknown-linux-gnu run_tests
+    ;;
   *)
     echo "FATAL: unknown host target: $HOST_TARGET"
     exit 1
diff --git a/src/tools/miri/doc/genmc.md b/src/tools/miri/doc/genmc.md
new file mode 100644
index 00000000000..5aabe90b5da
--- /dev/null
+++ b/src/tools/miri/doc/genmc.md
@@ -0,0 +1,62 @@
+# **(WIP)** Documentation for Miri-GenMC
+
+[GenMC](https://github.com/MPI-SWS/genmc) is a stateless model checker for exploring concurrent executions of a program.
+Miri-GenMC integrates that model checker into Miri.
+
+**NOTE: Currently, no actual GenMC functionality is part of Miri, this is still WIP.**
+
+<!-- FIXME(genmc): add explanation. -->
+
+## Usage
+
+**IMPORTANT: The license of GenMC and thus the `genmc-sys` crate in the Miri repo is currently "GPL-3.0-or-later", so a binary produced with the `genmc` feature is subject to the requirements of the GPL. As long as that remains the case, the `genmc` feature of Miri is OFF-BY-DEFAULT and must be OFF for all Miri releases.**
+
+For testing/developing Miri-GenMC (while keeping in mind the licensing issues):
+- clone the Miri repo.
+- build Miri-GenMC with `./miri build --features=genmc`.
+- OR: install Miri-GenMC in the current system with `./miri install --features=genmc`
+
+Basic usage:
+```shell
+MIRIFLAGS="-Zmiri-genmc" cargo miri run
+```
+
+<!-- FIXME(genmc): explain options. -->
+
+<!-- FIXME(genmc): explain Miri-GenMC specific functions. -->
+
+## Tips
+
+<!-- FIXME(genmc): add tips for using Miri-GenMC more efficiently. -->
+
+## Limitations
+
+Some or all of these limitations might get removed in the future:
+
+- Borrow tracking is currently incompatible (stacked/tree borrows).
+- Only Linux is supported for now.
+- No support for 32-bit or big-endian targets.
+- No cross-target interpretation.
+
+<!-- FIXME(genmc): document remaining limitations -->
+
+## Development
+
+GenMC is written in C++, which complicates development a bit.
+The prerequisites for building Miri-GenMC are:
+- A compiler with C++23 support.
+- LLVM developments headers and clang.
+  <!-- FIXME(genmc,llvm): remove once LLVM dependency is no longer required. -->
+
+The actual code for GenMC is not contained in the Miri repo itself, but in a [separate GenMC repo](https://github.com/MPI-SWS/genmc) (with its own maintainers).
+These sources need to be available to build Miri-GenMC.
+The process for obtaining them is as follows:
+- By default, a fixed commit of GenMC is downloaded to `genmc-sys/genmc-src` and built automatically.
+  (The commit is determined by `GENMC_COMMIT` in `genmc-sys/build.rs`.)
+- If you want to overwrite that, set the `GENMC_SRC_PATH` environment variable to a path that contains the GenMC sources.
+  If you place this directory inside the Miri folder, it is recommended to call it `genmc-src` as that tells `./miri fmt` to avoid
+  formatting the Rust files inside that folder.
+
+<!-- FIXME(genmc): explain how submitting code to GenMC should be handled. -->
+
+<!-- FIXME(genmc): explain development. -->
diff --git a/src/tools/miri/etc/rust_analyzer_helix.toml b/src/tools/miri/etc/rust_analyzer_helix.toml
index 91e4070478c..c46b246049f 100644
--- a/src/tools/miri/etc/rust_analyzer_helix.toml
+++ b/src/tools/miri/etc/rust_analyzer_helix.toml
@@ -5,6 +5,7 @@ source = "discover"
 linkedProjects = [
     "Cargo.toml",
     "cargo-miri/Cargo.toml",
+    "genmc-sys/Cargo.toml",
     "miri-script/Cargo.toml",
 ]
 
diff --git a/src/tools/miri/etc/rust_analyzer_vscode.json b/src/tools/miri/etc/rust_analyzer_vscode.json
index 6917c6a1fd8..8e647f5331f 100644
--- a/src/tools/miri/etc/rust_analyzer_vscode.json
+++ b/src/tools/miri/etc/rust_analyzer_vscode.json
@@ -3,6 +3,7 @@
     "rust-analyzer.linkedProjects": [
         "Cargo.toml",
         "cargo-miri/Cargo.toml",
+        "genmc-sys/Cargo.toml",
         "miri-script/Cargo.toml",
     ],
     "rust-analyzer.check.invocationStrategy": "once",
diff --git a/src/tools/miri/genmc-sys/.gitignore b/src/tools/miri/genmc-sys/.gitignore
new file mode 100644
index 00000000000..276a053cd05
--- /dev/null
+++ b/src/tools/miri/genmc-sys/.gitignore
@@ -0,0 +1 @@
+genmc-src*/
diff --git a/src/tools/miri/genmc-sys/Cargo.toml b/src/tools/miri/genmc-sys/Cargo.toml
new file mode 100644
index 00000000000..737ab9073bf
--- /dev/null
+++ b/src/tools/miri/genmc-sys/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+authors = ["Miri Team"]
+# The parts in this repo are MIT OR Apache-2.0, but we are linking in
+# code from https://github.com/MPI-SWS/genmc which is GPL-3.0-or-later.
+license = "(MIT OR Apache-2.0) AND GPL-3.0-or-later"
+name = "genmc-sys"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+cxx = { version = "1.0.160", features = ["c++20"] }
+
+[build-dependencies]
+cc = "1.2.16"
+cmake = "0.1.54"
+git2 = { version = "0.20.2", default-features = false, features = ["https"] }
+cxx-build = { version = "1.0.160", features = ["parallel"] }
diff --git a/src/tools/miri/genmc-sys/build.rs b/src/tools/miri/genmc-sys/build.rs
new file mode 100644
index 00000000000..479a3bd7186
--- /dev/null
+++ b/src/tools/miri/genmc-sys/build.rs
@@ -0,0 +1,269 @@
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
+
+// Build script for running Miri with GenMC.
+// Check out doc/genmc.md for more info.
+
+/// Path where the downloaded GenMC repository will be stored (relative to the `genmc-sys` directory).
+/// Note that this directory is *not* cleaned up automatically by `cargo clean`.
+const GENMC_DOWNLOAD_PATH: &str = "./genmc-src/";
+
+/// Name of the library of the GenMC model checker.
+const GENMC_MODEL_CHECKER: &str = "genmc_lib";
+
+/// Path where the `cxx_bridge!` macro is used to define the Rust-C++ interface.
+const RUST_CXX_BRIDGE_FILE_PATH: &str = "src/lib.rs";
+
+/// The profile with which to build GenMC.
+const GENMC_CMAKE_PROFILE: &str = "RelWithDebInfo";
+
+mod downloading {
+    use std::path::PathBuf;
+    use std::str::FromStr;
+
+    use git2::{Commit, Oid, Remote, Repository, StatusOptions};
+
+    use super::GENMC_DOWNLOAD_PATH;
+
+    /// The GenMC repository the we get our commit from.
+    pub(crate) const GENMC_GITHUB_URL: &str = "https://github.com/MPI-SWS/genmc.git";
+    /// The GenMC commit we depend on. It must be available on the specified GenMC repository.
+    pub(crate) const GENMC_COMMIT: &str = "3438dd2c1202cd4a47ed7881d099abf23e4167ab";
+
+    pub(crate) fn download_genmc() -> PathBuf {
+        let Ok(genmc_download_path) = PathBuf::from_str(GENMC_DOWNLOAD_PATH);
+        let commit_oid = Oid::from_str(GENMC_COMMIT).expect("Commit should be valid.");
+
+        match Repository::open(&genmc_download_path) {
+            Ok(repo) => {
+                assert_repo_unmodified(&repo);
+                let commit = update_local_repo(&repo, commit_oid);
+                checkout_commit(&repo, &commit);
+            }
+            Err(_) => {
+                let repo = clone_remote_repo(&genmc_download_path);
+                let Ok(commit) = repo.find_commit(commit_oid) else {
+                    panic!(
+                        "Cloned GenMC repository does not contain required commit '{GENMC_COMMIT}'"
+                    );
+                };
+                checkout_commit(&repo, &commit);
+            }
+        };
+
+        genmc_download_path
+    }
+
+    fn get_remote(repo: &Repository) -> Remote<'_> {
+        let remote = repo.find_remote("origin").unwrap_or_else(|e| {
+                panic!(
+                    "Could not load commit ({GENMC_COMMIT}) from remote repository '{GENMC_GITHUB_URL}'. Error: {e}"
+                );
+            });
+
+        // Ensure that the correct remote URL is set.
+        let remote_url = remote.url();
+        if let Some(remote_url) = remote_url
+            && remote_url == GENMC_GITHUB_URL
+        {
+            return remote;
+        }
+
+        // Update remote URL.
+        println!(
+            "cargo::warning=GenMC repository remote URL has changed from '{remote_url:?}' to '{GENMC_GITHUB_URL}'"
+        );
+        repo.remote_set_url("origin", GENMC_GITHUB_URL)
+            .expect("cannot rename url of remote 'origin'");
+
+        // Reacquire the `Remote`, since `remote_set_url` doesn't update Remote objects already in memory.
+        repo.find_remote("origin").unwrap()
+    }
+
+    // Check if the required commit exists already, otherwise try fetching it.
+    fn update_local_repo(repo: &Repository, commit_oid: Oid) -> Commit<'_> {
+        repo.find_commit(commit_oid).unwrap_or_else(|_find_error| {
+            println!("GenMC repository at path '{GENMC_DOWNLOAD_PATH}' does not contain commit '{GENMC_COMMIT}'.");
+            // The commit is not in the checkout. Try `git fetch` and hope that we find the commit then.
+            let mut remote = get_remote(repo);
+            remote.fetch(&[GENMC_COMMIT], None, None).expect("Failed to fetch from remote.");
+
+            repo.find_commit(commit_oid)
+                .expect("Remote repository should contain expected commit")
+        })
+    }
+
+    fn clone_remote_repo(genmc_download_path: &PathBuf) -> Repository {
+        Repository::clone(GENMC_GITHUB_URL, &genmc_download_path).unwrap_or_else(|e| {
+            panic!("Cannot clone GenMC repo from '{GENMC_GITHUB_URL}': {e:?}");
+        })
+    }
+
+    /// Set the state of the repo to a specific commit
+    fn checkout_commit(repo: &Repository, commit: &Commit<'_>) {
+        repo.checkout_tree(commit.as_object(), None).expect("Failed to checkout");
+        repo.set_head_detached(commit.id()).expect("Failed to set HEAD");
+        println!("Successfully set checked out commit {commit:?}");
+    }
+
+    /// Check that the downloaded repository is unmodified.
+    /// If it is modified, explain that it shouldn't be, and hint at how to do local development with GenMC.
+    /// We don't overwrite any changes made to the directory, to prevent data loss.
+    fn assert_repo_unmodified(repo: &Repository) {
+        let statuses = repo
+            .statuses(Some(
+                StatusOptions::new()
+                    .include_untracked(true)
+                    .include_ignored(false)
+                    .include_unmodified(false),
+            ))
+            .expect("should be able to get repository status");
+        if statuses.is_empty() {
+            return;
+        }
+
+        panic!(
+            "Downloaded GenMC repository at path '{GENMC_DOWNLOAD_PATH}' has been modified. Please undo any changes made, or delete the '{GENMC_DOWNLOAD_PATH}' directory to have it downloaded again.\n\
+            HINT: For local development, set the environment variable 'GENMC_SRC_PATH' to the path of a GenMC repository."
+        );
+    }
+}
+
+// FIXME(genmc,llvm): Remove once the LLVM dependency of the GenMC model checker is removed.
+/// The linked LLVM version is in the generated `config.h`` file, which we parse and use to link to LLVM.
+/// Returns c++ compiler definitions required for building with/including LLVM, and the include path for LLVM headers.
+fn link_to_llvm(config_file: &Path) -> (String, String) {
+    /// Search a string for a line matching `//@VARIABLE_NAME: VARIABLE CONTENT`
+    fn extract_value<'a>(input: &'a str, name: &str) -> Option<&'a str> {
+        input
+            .lines()
+            .find_map(|line| line.strip_prefix("//@")?.strip_prefix(name)?.strip_prefix(": "))
+    }
+
+    let file_content = std::fs::read_to_string(&config_file).unwrap_or_else(|err| {
+        panic!("GenMC config file ({}) should exist, but got errror {err:?}", config_file.display())
+    });
+
+    let llvm_definitions = extract_value(&file_content, "LLVM_DEFINITIONS")
+        .expect("Config file should contain LLVM_DEFINITIONS");
+    let llvm_include_dirs = extract_value(&file_content, "LLVM_INCLUDE_DIRS")
+        .expect("Config file should contain LLVM_INCLUDE_DIRS");
+    let llvm_library_dir = extract_value(&file_content, "LLVM_LIBRARY_DIR")
+        .expect("Config file should contain LLVM_LIBRARY_DIR");
+    let llvm_config_path = extract_value(&file_content, "LLVM_CONFIG_PATH")
+        .expect("Config file should contain LLVM_CONFIG_PATH");
+
+    // Add linker search path.
+    let lib_dir = PathBuf::from_str(llvm_library_dir).unwrap();
+    println!("cargo::rustc-link-search=native={}", lib_dir.display());
+
+    // Add libraries to link.
+    let output = std::process::Command::new(llvm_config_path)
+        .arg("--libs") // Print the libraries to link to (space-separated list)
+        .output()
+        .expect("failed to execute llvm-config");
+    let llvm_link_libs =
+        String::try_from(output.stdout).expect("llvm-config output should be a valid string");
+
+    for link_lib in llvm_link_libs.trim().split(" ") {
+        let link_lib =
+            link_lib.strip_prefix("-l").expect("Linker parameter should start with \"-l\"");
+        println!("cargo::rustc-link-lib=dylib={link_lib}");
+    }
+
+    (llvm_definitions.to_string(), llvm_include_dirs.to_string())
+}
+
+/// Build the GenMC model checker library and the Rust-C++ interop library with cxx.rs
+fn compile_cpp_dependencies(genmc_path: &Path) {
+    // Part 1:
+    // Compile the GenMC library using cmake.
+
+    let cmakelists_path = genmc_path.join("CMakeLists.txt");
+
+    // FIXME(genmc,cargo): Switch to using `CARGO_CFG_DEBUG_ASSERTIONS` once https://github.com/rust-lang/cargo/issues/15760 is completed.
+    // Enable/disable additional debug checks, prints and options for GenMC, based on the Rust profile (debug/release)
+    let enable_genmc_debug = matches!(std::env::var("PROFILE").as_deref().unwrap(), "debug");
+
+    let mut config = cmake::Config::new(cmakelists_path);
+    config.profile(GENMC_CMAKE_PROFILE);
+    config.define("GENMC_DEBUG", if enable_genmc_debug { "ON" } else { "OFF" });
+
+    // The actual compilation happens here:
+    let genmc_install_dir = config.build();
+
+    // Add the model checker library to be linked and tell rustc where to find it:
+    let cmake_lib_dir = genmc_install_dir.join("lib").join("genmc");
+    println!("cargo::rustc-link-search=native={}", cmake_lib_dir.display());
+    println!("cargo::rustc-link-lib=static={GENMC_MODEL_CHECKER}");
+
+    // FIXME(genmc,llvm): Remove once the LLVM dependency of the GenMC model checker is removed.
+    let config_file = genmc_install_dir.join("include").join("genmc").join("config.h");
+    let (llvm_definitions, llvm_include_dirs) = link_to_llvm(&config_file);
+
+    // Part 2:
+    // Compile the cxx_bridge (the link between the Rust and C++ code).
+
+    let genmc_include_dir = genmc_install_dir.join("include").join("genmc");
+
+    // FIXME(genmc,llvm): remove once LLVM dependency is removed.
+    // These definitions are parsed into a cmake list and then printed to the config.h file, so they are ';' separated.
+    let definitions = llvm_definitions.split(";");
+
+    let mut bridge = cxx_build::bridge("src/lib.rs");
+    // FIXME(genmc,cmake): Remove once the GenMC debug setting is available in the config.h file.
+    if enable_genmc_debug {
+        bridge.define("ENABLE_GENMC_DEBUG", None);
+    }
+    for definition in definitions {
+        bridge.flag(definition);
+    }
+    bridge
+        .opt_level(2)
+        .debug(true) // Same settings that GenMC uses (default for cmake `RelWithDebInfo`)
+        .warnings(false) // NOTE: enabling this produces a lot of warnings.
+        .std("c++23")
+        .include(genmc_include_dir)
+        .include(llvm_include_dirs)
+        .include("./src_cpp")
+        .file("./src_cpp/MiriInterface.hpp")
+        .file("./src_cpp/MiriInterface.cpp")
+        .compile("genmc_interop");
+
+    // Link the Rust-C++ interface library generated by cxx_build:
+    println!("cargo::rustc-link-lib=static=genmc_interop");
+}
+
+fn main() {
+    // Make sure we don't accidentally distribute a binary with GPL code.
+    if option_env!("RUSTC_STAGE").is_some() {
+        panic!(
+            "genmc should not be enabled in the rustc workspace since it includes a GPL dependency"
+        );
+    }
+
+    // Select which path to use for the GenMC repo:
+    let genmc_path = if let Ok(genmc_src_path) = std::env::var("GENMC_SRC_PATH") {
+        let genmc_src_path =
+            PathBuf::from_str(&genmc_src_path).expect("GENMC_SRC_PATH should contain a valid path");
+        assert!(
+            genmc_src_path.exists(),
+            "GENMC_SRC_PATH={} does not exist!",
+            genmc_src_path.display()
+        );
+        genmc_src_path
+    } else {
+        downloading::download_genmc()
+    };
+
+    // Build all required components:
+    compile_cpp_dependencies(&genmc_path);
+
+    // Only rebuild if anything changes:
+    // Note that we don't add the downloaded GenMC repo, since that should never be modified
+    // manually. Adding that path here would also trigger an unnecessary rebuild after the repo is
+    // cloned (since cargo detects that as a file modification).
+    println!("cargo::rerun-if-changed={RUST_CXX_BRIDGE_FILE_PATH}");
+    println!("cargo::rerun-if-changed=./src");
+    println!("cargo::rerun-if-changed=./src_cpp");
+}
diff --git a/src/tools/miri/genmc-sys/src/lib.rs b/src/tools/miri/genmc-sys/src/lib.rs
new file mode 100644
index 00000000000..ab46d729ea1
--- /dev/null
+++ b/src/tools/miri/genmc-sys/src/lib.rs
@@ -0,0 +1,30 @@
+pub use self::ffi::*;
+
+impl Default for GenmcParams {
+    fn default() -> Self {
+        Self {
+            print_random_schedule_seed: false,
+            do_symmetry_reduction: false,
+            // FIXME(GenMC): Add defaults for remaining parameters
+        }
+    }
+}
+
+#[cxx::bridge]
+mod ffi {
+    /// Parameters that will be given to GenMC for setting up the model checker.
+    /// (The fields of this struct are visible to both Rust and C++)
+    #[derive(Clone, Debug)]
+    struct GenmcParams {
+        pub print_random_schedule_seed: bool,
+        pub do_symmetry_reduction: bool,
+        // FIXME(GenMC): Add remaining parameters.
+    }
+    unsafe extern "C++" {
+        include!("MiriInterface.hpp");
+
+        type MiriGenMCShim;
+
+        fn createGenmcHandle(config: &GenmcParams) -> UniquePtr<MiriGenMCShim>;
+    }
+}
diff --git a/src/tools/miri/genmc-sys/src_cpp/MiriInterface.cpp b/src/tools/miri/genmc-sys/src_cpp/MiriInterface.cpp
new file mode 100644
index 00000000000..0827bb3d407
--- /dev/null
+++ b/src/tools/miri/genmc-sys/src_cpp/MiriInterface.cpp
@@ -0,0 +1,50 @@
+#include "MiriInterface.hpp"
+
+#include "genmc-sys/src/lib.rs.h"
+
+auto MiriGenMCShim::createHandle(const GenmcParams &config)
+	-> std::unique_ptr<MiriGenMCShim>
+{
+	auto conf = std::make_shared<Config>();
+
+	// Miri needs all threads to be replayed, even fully completed ones.
+	conf->replayCompletedThreads = true;
+
+	// We only support the RC11 memory model for Rust.
+	conf->model = ModelType::RC11;
+
+	conf->printRandomScheduleSeed = config.print_random_schedule_seed;
+
+	// FIXME(genmc): disable any options we don't support currently:
+	conf->ipr = false;
+	conf->disableBAM = true;
+	conf->instructionCaching = false;
+
+	ERROR_ON(config.do_symmetry_reduction, "Symmetry reduction is currently unsupported in GenMC mode.");
+	conf->symmetryReduction = config.do_symmetry_reduction;
+
+	// FIXME(genmc): Should there be a way to change this option from Miri?
+	conf->schedulePolicy = SchedulePolicy::WF;
+
+	// FIXME(genmc): implement estimation mode:
+	conf->estimate = false;
+	conf->estimationMax = 1000;
+	const auto mode = conf->estimate ? GenMCDriver::Mode(GenMCDriver::EstimationMode{})
+									  : GenMCDriver::Mode(GenMCDriver::VerificationMode{});
+
+	// Running Miri-GenMC without race detection is not supported.
+	// Disabling this option also changes the behavior of the replay scheduler to only schedule at atomic operations, which is required with Miri.
+	// This happens because Miri can generate multiple GenMC events for a single MIR terminator. Without this option,
+	// the scheduler might incorrectly schedule an atomic MIR terminator because the first event it creates is a non-atomic (e.g., `StorageLive`).
+	conf->disableRaceDetection = false;
+
+	// Miri can already check for unfreed memory. Also, GenMC cannot distinguish between memory
+	// that is allowed to leak and memory that is not.
+	conf->warnUnfreedMemory = false;
+
+	// FIXME(genmc): check config:
+	// checkConfigOptions(*conf);
+
+	auto driver = std::make_unique<MiriGenMCShim>(std::move(conf), mode);
+	return driver;
+}
diff --git a/src/tools/miri/genmc-sys/src_cpp/MiriInterface.hpp b/src/tools/miri/genmc-sys/src_cpp/MiriInterface.hpp
new file mode 100644
index 00000000000..e55522ef418
--- /dev/null
+++ b/src/tools/miri/genmc-sys/src_cpp/MiriInterface.hpp
@@ -0,0 +1,44 @@
+#ifndef GENMC_MIRI_INTERFACE_HPP
+#define GENMC_MIRI_INTERFACE_HPP
+
+#include "rust/cxx.h"
+
+#include "config.h"
+
+#include "Config/Config.hpp"
+#include "Verification/GenMCDriver.hpp"
+
+#include <iostream>
+
+/**** Types available to Miri ****/
+
+// Config struct defined on the Rust side and translated to C++ by cxx.rs:
+struct GenmcParams;
+
+struct MiriGenMCShim : private GenMCDriver
+{
+
+public:
+	MiriGenMCShim(std::shared_ptr<const Config> conf, Mode mode /* = VerificationMode{} */)
+		: GenMCDriver(std::move(conf), nullptr, mode)
+	{
+		std::cerr << "C++: GenMC handle created!" << std::endl;
+	}
+
+	virtual ~MiriGenMCShim()
+	{
+		std::cerr << "C++: GenMC handle destroyed!" << std::endl;
+	}
+
+	static std::unique_ptr<MiriGenMCShim> createHandle(const GenmcParams &config);
+};
+
+/**** Functions available to Miri ****/
+
+// NOTE: CXX doesn't support exposing static methods to Rust currently, so we expose this function instead.
+static inline auto createGenmcHandle(const GenmcParams &config) -> std::unique_ptr<MiriGenMCShim>
+{
+	return MiriGenMCShim::createHandle(config);
+}
+
+#endif /* GENMC_MIRI_INTERFACE_HPP */
diff --git a/src/tools/miri/josh-sync.toml b/src/tools/miri/josh-sync.toml
new file mode 100644
index 00000000000..86208b3742d
--- /dev/null
+++ b/src/tools/miri/josh-sync.toml
@@ -0,0 +1,2 @@
+repo = "miri"
+filter = ":rev(75dd959a3a40eb5b4574f8d2e23aa6efbeb33573:prefix=src/tools/miri):/src/tools/miri"
diff --git a/src/tools/miri/miri-script/Cargo.lock b/src/tools/miri/miri-script/Cargo.lock
index a049bfcbccd..044a678869e 100644
--- a/src/tools/miri/miri-script/Cargo.lock
+++ b/src/tools/miri/miri-script/Cargo.lock
@@ -117,27 +117,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
 
 [[package]]
-name = "directories"
-version = "6.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d"
-dependencies = [
- "dirs-sys",
-]
-
-[[package]]
-name = "dirs-sys"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
-dependencies = [
- "libc",
- "option-ext",
- "redox_users",
- "windows-sys 0.60.2",
-]
-
-[[package]]
 name = "dunce"
 version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -167,17 +146,6 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
 
 [[package]]
 name = "getrandom"
-version = "0.2.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi 0.11.1+wasi-snapshot-preview1",
-]
-
-[[package]]
-name = "getrandom"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
@@ -185,7 +153,7 @@ dependencies = [
  "cfg-if",
  "libc",
  "r-efi",
- "wasi 0.14.2+wasi-0.2.4",
+ "wasi",
 ]
 
 [[package]]
@@ -222,16 +190,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
 
 [[package]]
-name = "libredox"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638"
-dependencies = [
- "bitflags",
- "libc",
-]
-
-[[package]]
 name = "linux-raw-sys"
 version = "0.9.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -249,7 +207,6 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "clap",
- "directories",
  "dunce",
  "itertools",
  "path_macro",
@@ -276,12 +233,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
 
 [[package]]
-name = "option-ext"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
-
-[[package]]
 name = "path_macro"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -312,17 +263,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
 
 [[package]]
-name = "redox_users"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
-dependencies = [
- "getrandom 0.2.16",
- "libredox",
- "thiserror",
-]
-
-[[package]]
 name = "rustc_version"
 version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -427,33 +367,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
 dependencies = [
  "fastrand",
- "getrandom 0.3.3",
+ "getrandom",
  "once_cell",
  "rustix",
  "windows-sys 0.59.0",
 ]
 
 [[package]]
-name = "thiserror"
-version = "2.0.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
-dependencies = [
- "thiserror-impl",
-]
-
-[[package]]
-name = "thiserror-impl"
-version = "2.0.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
 name = "unicode-ident"
 version = "1.0.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -477,12 +397,6 @@ dependencies = [
 
 [[package]]
 name = "wasi"
-version = "0.11.1+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
-
-[[package]]
-name = "wasi"
 version = "0.14.2+wasi-0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
diff --git a/src/tools/miri/miri-script/Cargo.toml b/src/tools/miri/miri-script/Cargo.toml
index b3f82cd1d50..39858880e8c 100644
--- a/src/tools/miri/miri-script/Cargo.toml
+++ b/src/tools/miri/miri-script/Cargo.toml
@@ -22,7 +22,6 @@ anyhow = "1.0"
 xshell = "0.2.6"
 rustc_version = "0.4"
 dunce = "1.0.4"
-directories = "6"
 serde = "1"
 serde_json = "1"
 serde_derive = "1"
diff --git a/src/tools/miri/miri-script/src/commands.rs b/src/tools/miri/miri-script/src/commands.rs
index 9aaad9ca04a..ee09b9b4b73 100644
--- a/src/tools/miri/miri-script/src/commands.rs
+++ b/src/tools/miri/miri-script/src/commands.rs
@@ -2,11 +2,9 @@ use std::collections::BTreeMap;
 use std::ffi::{OsStr, OsString};
 use std::fmt::Write as _;
 use std::fs::{self, File};
-use std::io::{self, BufRead, BufReader, BufWriter, Write as _};
-use std::ops::Not;
+use std::io::{self, BufRead, BufReader, BufWriter};
 use std::path::PathBuf;
-use std::time::Duration;
-use std::{env, net, process};
+use std::{env, process};
 
 use anyhow::{Context, Result, anyhow, bail};
 use path_macro::path;
@@ -18,11 +16,6 @@ use xshell::{Shell, cmd};
 use crate::Command;
 use crate::util::*;
 
-/// Used for rustc syncs.
-const JOSH_FILTER: &str =
-    ":rev(75dd959a3a40eb5b4574f8d2e23aa6efbeb33573:prefix=src/tools/miri):/src/tools/miri";
-const JOSH_PORT: u16 = 42042;
-
 impl MiriEnv {
     /// Prepares the environment: builds miri and cargo-miri and a sysroot.
     /// Returns the location of the sysroot.
@@ -99,66 +92,6 @@ impl Command {
         Ok(())
     }
 
-    fn start_josh() -> Result<impl Drop> {
-        // Determine cache directory.
-        let local_dir = {
-            let user_dirs =
-                directories::ProjectDirs::from("org", "rust-lang", "miri-josh").unwrap();
-            user_dirs.cache_dir().to_owned()
-        };
-
-        // Start josh, silencing its output.
-        let mut cmd = process::Command::new("josh-proxy");
-        cmd.arg("--local").arg(local_dir);
-        cmd.arg("--remote").arg("https://github.com");
-        cmd.arg("--port").arg(JOSH_PORT.to_string());
-        cmd.arg("--no-background");
-        cmd.stdout(process::Stdio::null());
-        cmd.stderr(process::Stdio::null());
-        let josh = cmd.spawn().context("failed to start josh-proxy, make sure it is installed")?;
-
-        // Create a wrapper that stops it on drop.
-        struct Josh(process::Child);
-        impl Drop for Josh {
-            fn drop(&mut self) {
-                #[cfg(unix)]
-                {
-                    // Try to gracefully shut it down.
-                    process::Command::new("kill")
-                        .args(["-s", "INT", &self.0.id().to_string()])
-                        .output()
-                        .expect("failed to SIGINT josh-proxy");
-                    // Sadly there is no "wait with timeout"... so we just give it some time to finish.
-                    std::thread::sleep(Duration::from_millis(100));
-                    // Now hopefully it is gone.
-                    if self.0.try_wait().expect("failed to wait for josh-proxy").is_some() {
-                        return;
-                    }
-                }
-                // If that didn't work (or we're not on Unix), kill it hard.
-                eprintln!(
-                    "I have to kill josh-proxy the hard way, let's hope this does not break anything."
-                );
-                self.0.kill().expect("failed to SIGKILL josh-proxy");
-            }
-        }
-
-        // Wait until the port is open. We try every 10ms until 1s passed.
-        for _ in 0..100 {
-            // This will generally fail immediately when the port is still closed.
-            let josh_ready = net::TcpStream::connect_timeout(
-                &net::SocketAddr::from(([127, 0, 0, 1], JOSH_PORT)),
-                Duration::from_millis(1),
-            );
-            if josh_ready.is_ok() {
-                return Ok(Josh(josh));
-            }
-            // Not ready yet.
-            std::thread::sleep(Duration::from_millis(10));
-        }
-        bail!("Even after waiting for 1s, josh-proxy is still not available.")
-    }
-
     pub fn exec(self) -> Result<()> {
         // First, and crucially only once, run the auto-actions -- but not for all commands.
         match &self {
@@ -170,11 +103,7 @@ impl Command {
             | Command::Fmt { .. }
             | Command::Doc { .. }
             | Command::Clippy { .. } => Self::auto_actions()?,
-            | Command::Toolchain { .. }
-            | Command::Bench { .. }
-            | Command::RustcPull { .. }
-            | Command::RustcPush { .. }
-            | Command::Squash => {}
+            | Command::Toolchain { .. } | Command::Bench { .. } | Command::Squash => {}
         }
         // Then run the actual command.
         match self {
@@ -191,8 +120,6 @@ impl Command {
             Command::Bench { target, no_install, save_baseline, load_baseline, benches } =>
                 Self::bench(target, no_install, save_baseline, load_baseline, benches),
             Command::Toolchain { flags } => Self::toolchain(flags),
-            Command::RustcPull { commit } => Self::rustc_pull(commit.clone()),
-            Command::RustcPush { github_user, branch } => Self::rustc_push(github_user, branch),
             Command::Squash => Self::squash(),
         }
     }
@@ -233,156 +160,6 @@ impl Command {
         Ok(())
     }
 
-    fn rustc_pull(commit: Option<String>) -> Result<()> {
-        let sh = Shell::new()?;
-        sh.change_dir(miri_dir()?);
-        let commit = commit.map(Result::Ok).unwrap_or_else(|| {
-            let rust_repo_head =
-                cmd!(sh, "git ls-remote https://github.com/rust-lang/rust/ HEAD").read()?;
-            rust_repo_head
-                .split_whitespace()
-                .next()
-                .map(|front| front.trim().to_owned())
-                .ok_or_else(|| anyhow!("Could not obtain Rust repo HEAD from remote."))
-        })?;
-        // Make sure the repo is clean.
-        if cmd!(sh, "git status --untracked-files=no --porcelain").read()?.is_empty().not() {
-            bail!("working directory must be clean before running `./miri rustc-pull`");
-        }
-        // Make sure josh is running.
-        let josh = Self::start_josh()?;
-        let josh_url =
-            format!("http://localhost:{JOSH_PORT}/rust-lang/rust.git@{commit}{JOSH_FILTER}.git");
-
-        // Update rust-version file. As a separate commit, since making it part of
-        // the merge has confused the heck out of josh in the past.
-        // We pass `--no-verify` to avoid running git hooks like `./miri fmt` that could in turn
-        // trigger auto-actions.
-        // We do this before the merge so that if there are merge conflicts, we have
-        // the right rust-version file while resolving them.
-        sh.write_file("rust-version", format!("{commit}\n"))?;
-        const PREPARING_COMMIT_MESSAGE: &str = "Preparing for merge from rustc";
-        cmd!(sh, "git commit rust-version --no-verify -m {PREPARING_COMMIT_MESSAGE}")
-            .run()
-            .context("FAILED to commit rust-version file, something went wrong")?;
-
-        // Fetch given rustc commit.
-        cmd!(sh, "git fetch {josh_url}")
-            .run()
-            .inspect_err(|_| {
-                // Try to un-do the previous `git commit`, to leave the repo in the state we found it.
-                cmd!(sh, "git reset --hard HEAD^")
-                    .run()
-                    .expect("FAILED to clean up again after failed `git fetch`, sorry for that");
-            })
-            .context("FAILED to fetch new commits, something went wrong (committing the rust-version file has been undone)")?;
-
-        // This should not add any new root commits. So count those before and after merging.
-        let num_roots = || -> Result<u32> {
-            Ok(cmd!(sh, "git rev-list HEAD --max-parents=0 --count")
-                .read()
-                .context("failed to determine the number of root commits")?
-                .parse::<u32>()?)
-        };
-        let num_roots_before = num_roots()?;
-
-        // Merge the fetched commit.
-        const MERGE_COMMIT_MESSAGE: &str = "Merge from rustc";
-        cmd!(sh, "git merge FETCH_HEAD --no-verify --no-ff -m {MERGE_COMMIT_MESSAGE}")
-            .run()
-            .context("FAILED to merge new commits, something went wrong")?;
-
-        // Check that the number of roots did not increase.
-        if num_roots()? != num_roots_before {
-            bail!("Josh created a new root commit. This is probably not the history you want.");
-        }
-
-        drop(josh);
-        Ok(())
-    }
-
-    fn rustc_push(github_user: String, branch: String) -> Result<()> {
-        let sh = Shell::new()?;
-        sh.change_dir(miri_dir()?);
-        let base = sh.read_file("rust-version")?.trim().to_owned();
-        // Make sure the repo is clean.
-        if cmd!(sh, "git status --untracked-files=no --porcelain").read()?.is_empty().not() {
-            bail!("working directory must be clean before running `./miri rustc-push`");
-        }
-        // Make sure josh is running.
-        let josh = Self::start_josh()?;
-        let josh_url =
-            format!("http://localhost:{JOSH_PORT}/{github_user}/rust.git{JOSH_FILTER}.git");
-
-        // Find a repo we can do our preparation in.
-        if let Ok(rustc_git) = env::var("RUSTC_GIT") {
-            // If rustc_git is `Some`, we'll use an existing fork for the branch updates.
-            sh.change_dir(rustc_git);
-        } else {
-            // Otherwise, do this in the local Miri repo.
-            println!(
-                "This will pull a copy of the rust-lang/rust history into this Miri checkout, growing it by about 1GB."
-            );
-            print!(
-                "To avoid that, abort now and set the `RUSTC_GIT` environment variable to an existing rustc checkout. Proceed? [y/N] "
-            );
-            std::io::stdout().flush()?;
-            let mut answer = String::new();
-            std::io::stdin().read_line(&mut answer)?;
-            if answer.trim().to_lowercase() != "y" {
-                std::process::exit(1);
-            }
-        };
-        // Prepare the branch. Pushing works much better if we use as base exactly
-        // the commit that we pulled from last time, so we use the `rust-version`
-        // file to find out which commit that would be.
-        println!("Preparing {github_user}/rust (base: {base})...");
-        if cmd!(sh, "git fetch https://github.com/{github_user}/rust {branch}")
-            .ignore_stderr()
-            .read()
-            .is_ok()
-        {
-            println!(
-                "The branch '{branch}' seems to already exist in 'https://github.com/{github_user}/rust'. Please delete it and try again."
-            );
-            std::process::exit(1);
-        }
-        cmd!(sh, "git fetch https://github.com/rust-lang/rust {base}").run()?;
-        cmd!(sh, "git push https://github.com/{github_user}/rust {base}:refs/heads/{branch}")
-            .ignore_stdout()
-            .ignore_stderr() // silence the "create GitHub PR" message
-            .run()?;
-        println!();
-
-        // Do the actual push.
-        sh.change_dir(miri_dir()?);
-        println!("Pushing miri changes...");
-        cmd!(sh, "git push {josh_url} HEAD:{branch}").run()?;
-        println!();
-
-        // Do a round-trip check to make sure the push worked as expected.
-        cmd!(sh, "git fetch {josh_url} {branch}").ignore_stderr().read()?;
-        let head = cmd!(sh, "git rev-parse HEAD").read()?;
-        let fetch_head = cmd!(sh, "git rev-parse FETCH_HEAD").read()?;
-        if head != fetch_head {
-            bail!(
-                "Josh created a non-roundtrip push! Do NOT merge this into rustc!\n\
-                Expected {head}, got {fetch_head}."
-            );
-        }
-        println!(
-            "Confirmed that the push round-trips back to Miri properly. Please create a rustc PR:"
-        );
-        println!(
-            // Open PR with `subtree update` title to silence the `no-merges` triagebot check
-            // See https://github.com/rust-lang/rust/pull/114157
-            "    https://github.com/rust-lang/rust/compare/{github_user}:{branch}?quick_pull=1&title=Miri+subtree+update&body=r?+@ghost"
-        );
-
-        drop(josh);
-        Ok(())
-    }
-
     fn squash() -> Result<()> {
         let sh = Shell::new()?;
         sh.change_dir(miri_dir()?);
@@ -757,8 +534,8 @@ impl Command {
                 if ty.is_file() {
                     name.ends_with(".rs")
                 } else {
-                    // dir or symlink. skip `target` and `.git`.
-                    &name != "target" && &name != ".git"
+                    // dir or symlink. skip `target`, `.git` and `genmc-src*`
+                    &name != "target" && &name != ".git" && !name.starts_with("genmc-src")
                 }
             })
             .filter_ok(|item| item.file_type().is_file())
diff --git a/src/tools/miri/miri-script/src/main.rs b/src/tools/miri/miri-script/src/main.rs
index e41df662ca2..761ec5979fa 100644
--- a/src/tools/miri/miri-script/src/main.rs
+++ b/src/tools/miri/miri-script/src/main.rs
@@ -142,25 +142,6 @@ pub enum Command {
         #[arg(trailing_var_arg = true, allow_hyphen_values = true)]
         flags: Vec<String>,
     },
-    /// Pull and merge Miri changes from the rustc repo.
-    ///
-    /// The fetched commit is stored in the `rust-version` file, so the next `./miri toolchain` will
-    /// install the rustc that just got pulled.
-    RustcPull {
-        /// The commit to fetch (default: latest rustc commit).
-        commit: Option<String>,
-    },
-    /// Push Miri changes back to the rustc repo.
-    ///
-    /// This will pull a copy of the rustc history into the Miri repo, unless you set the RUSTC_GIT
-    /// env var to an existing clone of the rustc repo.
-    RustcPush {
-        /// The Github user that owns the rustc fork to which we should push.
-        github_user: String,
-        /// The branch to push to.
-        #[arg(default_value = "miri-sync")]
-        branch: String,
-    },
     /// Squash the commits of the current feature branch into one.
     Squash,
 }
@@ -184,8 +165,7 @@ impl Command {
                 flags.extend(remainder);
                 Ok(())
             }
-            Self::Bench { .. } | Self::RustcPull { .. } | Self::RustcPush { .. } | Self::Squash =>
-                bail!("unexpected \"--\" found in arguments"),
+            Self::Bench { .. } | Self::Squash => bail!("unexpected \"--\" found in arguments"),
         }
     }
 }
diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version
index d734ec333a5..2178caf6396 100644
--- a/src/tools/miri/rust-version
+++ b/src/tools/miri/rust-version
@@ -1 +1 @@
-6707bf0f59485cf054ac1095725df43220e4be20
+733dab558992d902d6d17576de1da768094e2cf3
diff --git a/src/tools/miri/src/bin/log/setup.rs b/src/tools/miri/src/bin/log/setup.rs
index da0ba528b2c..a9392d010f8 100644
--- a/src/tools/miri/src/bin/log/setup.rs
+++ b/src/tools/miri/src/bin/log/setup.rs
@@ -60,7 +60,7 @@ fn init_logger_once(early_dcx: &EarlyDiagCtxt) {
             #[cfg(not(feature = "tracing"))]
             {
                 crate::fatal_error!(
-                    "fatal error: cannot enable MIRI_TRACING since Miri was not built with the \"tracing\" feature"
+                    "Cannot enable MIRI_TRACING since Miri was not built with the \"tracing\" feature"
                 );
             }
 
diff --git a/src/tools/miri/src/bin/log/tracing_chrome.rs b/src/tools/miri/src/bin/log/tracing_chrome.rs
index 5a96633c99e..3379816550c 100644
--- a/src/tools/miri/src/bin/log/tracing_chrome.rs
+++ b/src/tools/miri/src/bin/log/tracing_chrome.rs
@@ -12,6 +12,7 @@
 //!   ```rust
 //!   tracing::info_span!("my_span", tracing_separate_thread = tracing::field::Empty, /* ... */)
 //!   ```
+//! - use i64 instead of u64 for the "id" in [ChromeLayer::get_root_id] to be compatible with Perfetto
 //!
 //! Depending on the tracing-chrome crate from crates.io is unfortunately not possible, since it
 //! depends on `tracing_core` which conflicts with rustc_private's `tracing_core` (meaning it would
@@ -285,9 +286,9 @@ struct Callsite {
 }
 
 enum Message {
-    Enter(f64, Callsite, Option<u64>),
+    Enter(f64, Callsite, Option<i64>),
     Event(f64, Callsite),
-    Exit(f64, Callsite, Option<u64>),
+    Exit(f64, Callsite, Option<i64>),
     NewThread(usize, String),
     Flush,
     Drop,
@@ -519,14 +520,17 @@ where
         }
     }
 
-    fn get_root_id(&self, span: SpanRef<S>) -> Option<u64> {
+    fn get_root_id(&self, span: SpanRef<S>) -> Option<i64> {
+        // Returns `Option<i64>` instead of `Option<u64>` because apparently Perfetto gives an
+        // error if an id does not fit in a 64-bit signed integer in 2's complement. We cast the
+        // span id from `u64` to `i64` with wraparound, since negative values are fine.
         match self.trace_style {
             TraceStyle::Threaded => {
                 if span.fields().field("tracing_separate_thread").is_some() {
                     // assign an independent "id" to spans with argument "tracing_separate_thread",
                     // so they appear a separate trace line in trace visualization tools, see
                     // https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview#heading=h.jh64i9l3vwa1
-                    Some(span.id().into_u64())
+                    Some(span.id().into_u64().cast_signed()) // the comment above explains the cast
                 } else {
                     None
                 }
@@ -539,6 +543,7 @@ where
                     .unwrap_or(span)
                     .id()
                     .into_u64()
+                    .cast_signed() // the comment above explains the cast
             ),
         }
     }
diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs
index 89fa980ff64..ae1b25f8857 100644
--- a/src/tools/miri/src/bin/miri.rs
+++ b/src/tools/miri/src/bin/miri.rs
@@ -67,8 +67,6 @@ use crate::log::setup::{deinit_loggers, init_early_loggers, init_late_loggers};
 struct MiriCompilerCalls {
     miri_config: Option<MiriConfig>,
     many_seeds: Option<ManySeedsConfig>,
-    /// Settings for using GenMC with Miri.
-    genmc_config: Option<GenmcConfig>,
 }
 
 struct ManySeedsConfig {
@@ -77,12 +75,8 @@ struct ManySeedsConfig {
 }
 
 impl MiriCompilerCalls {
-    fn new(
-        miri_config: MiriConfig,
-        many_seeds: Option<ManySeedsConfig>,
-        genmc_config: Option<GenmcConfig>,
-    ) -> Self {
-        Self { miri_config: Some(miri_config), many_seeds, genmc_config }
+    fn new(miri_config: MiriConfig, many_seeds: Option<ManySeedsConfig>) -> Self {
+        Self { miri_config: Some(miri_config), many_seeds }
     }
 }
 
@@ -192,8 +186,8 @@ impl rustc_driver::Callbacks for MiriCompilerCalls {
                     optimizations is usually marginal at best.");
         }
 
-        if let Some(genmc_config) = &self.genmc_config {
-            let _genmc_ctx = Rc::new(GenmcCtx::new(&config, genmc_config));
+        if let Some(_genmc_config) = &config.genmc_config {
+            let _genmc_ctx = Rc::new(GenmcCtx::new(&config));
 
             todo!("GenMC mode not yet implemented");
         };
@@ -487,7 +481,6 @@ fn main() {
     let mut many_seeds_keep_going = false;
     let mut miri_config = MiriConfig::default();
     miri_config.env = env_snapshot;
-    let mut genmc_config = None;
 
     let mut rustc_args = vec![];
     let mut after_dashdash = false;
@@ -603,9 +596,9 @@ fn main() {
         } else if arg == "-Zmiri-many-seeds-keep-going" {
             many_seeds_keep_going = true;
         } else if let Some(trimmed_arg) = arg.strip_prefix("-Zmiri-genmc") {
-            // FIXME(GenMC): Currently, GenMC mode is incompatible with aliasing model checking.
-            miri_config.borrow_tracker = None;
-            GenmcConfig::parse_arg(&mut genmc_config, trimmed_arg);
+            if let Err(msg) = GenmcConfig::parse_arg(&mut miri_config.genmc_config, trimmed_arg) {
+                fatal_error!("{msg}");
+            }
         } else if let Some(param) = arg.strip_prefix("-Zmiri-env-forward=") {
             miri_config.forwarded_env_vars.push(param.to_owned());
         } else if let Some(param) = arg.strip_prefix("-Zmiri-env-set=") {
@@ -740,13 +733,18 @@ fn main() {
         many_seeds.map(|seeds| ManySeedsConfig { seeds, keep_going: many_seeds_keep_going });
 
     // Validate settings for data race detection and GenMC mode.
-    assert_eq!(genmc_config.is_some(), miri_config.genmc_mode);
-    if genmc_config.is_some() {
+    if miri_config.genmc_config.is_some() {
         if !miri_config.data_race_detector {
             fatal_error!("Cannot disable data race detection in GenMC mode (currently)");
         } else if !miri_config.weak_memory_emulation {
             fatal_error!("Cannot disable weak memory emulation in GenMC mode");
         }
+        if miri_config.borrow_tracker.is_some() {
+            eprintln!(
+                "warning: borrow tracking has been disabled, it is not (yet) supported in GenMC mode."
+            );
+            miri_config.borrow_tracker = None;
+        }
     } else if miri_config.weak_memory_emulation && !miri_config.data_race_detector {
         fatal_error!(
             "Weak memory emulation cannot be enabled when the data race detector is disabled"
@@ -765,8 +763,5 @@ fn main() {
             );
         }
     }
-    run_compiler_and_exit(
-        &rustc_args,
-        &mut MiriCompilerCalls::new(miri_config, many_seeds, genmc_config),
-    )
+    run_compiler_and_exit(&rustc_args, &mut MiriCompilerCalls::new(miri_config, many_seeds))
 }
diff --git a/src/tools/miri/src/concurrency/genmc/config.rs b/src/tools/miri/src/concurrency/genmc/config.rs
index f91211a670f..c56adab90fe 100644
--- a/src/tools/miri/src/concurrency/genmc/config.rs
+++ b/src/tools/miri/src/concurrency/genmc/config.rs
@@ -1,19 +1,35 @@
-use crate::MiriConfig;
+use super::GenmcParams;
 
+/// Configuration for GenMC mode.
+/// The `params` field is shared with the C++ side.
+/// The remaining options are kept on the Rust side.
 #[derive(Debug, Default, Clone)]
 pub struct GenmcConfig {
-    // TODO: add fields
+    pub(super) params: GenmcParams,
+    do_estimation: bool,
+    // FIXME(GenMC): add remaining options.
 }
 
 impl GenmcConfig {
     /// Function for parsing command line options for GenMC mode.
+    ///
     /// All GenMC arguments start with the string "-Zmiri-genmc".
+    /// Passing any GenMC argument will enable GenMC mode.
     ///
-    /// `trimmed_arg` should be the argument to be parsed, with the suffix "-Zmiri-genmc" removed
-    pub fn parse_arg(genmc_config: &mut Option<GenmcConfig>, trimmed_arg: &str) {
+    /// `trimmed_arg` should be the argument to be parsed, with the suffix "-Zmiri-genmc" removed.
+    pub fn parse_arg(
+        genmc_config: &mut Option<GenmcConfig>,
+        trimmed_arg: &str,
+    ) -> Result<(), String> {
+        // FIXME(genmc): Ensure host == target somewhere.
+
         if genmc_config.is_none() {
             *genmc_config = Some(Default::default());
         }
-        todo!("implement parsing of GenMC options")
+        if trimmed_arg.is_empty() {
+            return Ok(()); // this corresponds to "-Zmiri-genmc"
+        }
+        // FIXME(GenMC): implement remaining parameters.
+        todo!();
     }
 }
diff --git a/src/tools/miri/src/concurrency/genmc/dummy.rs b/src/tools/miri/src/concurrency/genmc/dummy.rs
index 3d0558fb685..79d27c4be15 100644
--- a/src/tools/miri/src/concurrency/genmc/dummy.rs
+++ b/src/tools/miri/src/concurrency/genmc/dummy.rs
@@ -16,7 +16,7 @@ pub struct GenmcCtx {}
 pub struct GenmcConfig {}
 
 impl GenmcCtx {
-    pub fn new(_miri_config: &MiriConfig, _genmc_config: &GenmcConfig) -> Self {
+    pub fn new(_miri_config: &MiriConfig) -> Self {
         unreachable!()
     }
 
@@ -227,10 +227,15 @@ impl VisitProvenance for GenmcCtx {
 }
 
 impl GenmcConfig {
-    pub fn parse_arg(_genmc_config: &mut Option<GenmcConfig>, trimmed_arg: &str) {
-        unimplemented!(
-            "GenMC feature im Miri is disabled, cannot handle argument: \"-Zmiri-genmc{trimmed_arg}\""
-        );
+    pub fn parse_arg(
+        _genmc_config: &mut Option<GenmcConfig>,
+        trimmed_arg: &str,
+    ) -> Result<(), String> {
+        if cfg!(feature = "genmc") {
+            Err(format!("GenMC is disabled in this build of Miri"))
+        } else {
+            Err(format!("GenMC is not supported on this target"))
+        }
     }
 
     pub fn should_print_graph(&self, _rep: usize) -> bool {
diff --git a/src/tools/miri/src/concurrency/genmc/mod.rs b/src/tools/miri/src/concurrency/genmc/mod.rs
index 0dfd4b9b80f..3617775e27e 100644
--- a/src/tools/miri/src/concurrency/genmc/mod.rs
+++ b/src/tools/miri/src/concurrency/genmc/mod.rs
@@ -2,6 +2,7 @@
 
 use std::cell::Cell;
 
+use genmc_sys::{GenmcParams, createGenmcHandle};
 use rustc_abi::{Align, Size};
 use rustc_const_eval::interpret::{InterpCx, InterpResult, interp_ok};
 use rustc_middle::mir;
@@ -24,9 +25,19 @@ pub struct GenmcCtx {
 
 impl GenmcCtx {
     /// Create a new `GenmcCtx` from a given config.
-    pub fn new(miri_config: &MiriConfig, genmc_config: &GenmcConfig) -> Self {
-        assert!(miri_config.genmc_mode);
-        todo!()
+    pub fn new(miri_config: &MiriConfig) -> Self {
+        let genmc_config = miri_config.genmc_config.as_ref().unwrap();
+
+        let handle = createGenmcHandle(&genmc_config.params);
+        assert!(!handle.is_null());
+
+        eprintln!("Miri: GenMC handle creation successful!");
+
+        drop(handle);
+        eprintln!("Miri: Dropping GenMC handle successful!");
+
+        // FIXME(GenMC): implement
+        std::process::exit(0);
     }
 
     pub fn get_stuck_execution_count(&self) -> usize {
diff --git a/src/tools/miri/src/concurrency/mod.rs b/src/tools/miri/src/concurrency/mod.rs
index c2ea8a00dec..435615efd9f 100644
--- a/src/tools/miri/src/concurrency/mod.rs
+++ b/src/tools/miri/src/concurrency/mod.rs
@@ -8,7 +8,17 @@ mod vector_clock;
 pub mod weak_memory;
 
 // Import either the real genmc adapter or a dummy module.
-#[cfg_attr(not(feature = "genmc"), path = "genmc/dummy.rs")]
+// On unsupported platforms, we always include the dummy module, even if the `genmc` feature is enabled.
+// FIXME(genmc,macos): Add `target_os = "macos"` once `https://github.com/dtolnay/cxx/issues/1535` is fixed.
+#[cfg_attr(
+    not(all(
+        feature = "genmc",
+        target_os = "linux",
+        target_pointer_width = "64",
+        target_endian = "little"
+    )),
+    path = "genmc/dummy.rs"
+)]
 mod genmc;
 
 pub use self::data_race_handler::{AllocDataRaceHandler, GlobalDataRaceHandler};
diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs
index 878afdf2517..fe1ef86ccd3 100644
--- a/src/tools/miri/src/concurrency/thread.rs
+++ b/src/tools/miri/src/concurrency/thread.rs
@@ -375,7 +375,7 @@ impl Timeout {
 }
 
 /// The clock to use for the timeout you are asking for.
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, PartialEq)]
 pub enum TimeoutClock {
     Monotonic,
     RealTime,
diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs
index 8fdf8d643ea..9ecbd31c5b9 100644
--- a/src/tools/miri/src/diagnostics.rs
+++ b/src/tools/miri/src/diagnostics.rs
@@ -382,7 +382,7 @@ pub fn report_error<'tcx>(
                             helps.push(note_span!(span, "{:?} was deallocated here:", alloc_id));
                         }
                     }
-                    AbiMismatchArgument { .. } => {
+                    AbiMismatchArgument { .. } | AbiMismatchReturn { .. } => {
                         helps.push(note!("this means these two types are not *guaranteed* to be ABI-compatible across all targets"));
                         helps.push(note!("if you think this code should be accepted anyway, please report an issue with Miri"));
                     }
diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs
index 3c80e60b772..4c531a8d1f5 100644
--- a/src/tools/miri/src/eval.rs
+++ b/src/tools/miri/src/eval.rs
@@ -125,8 +125,8 @@ pub struct MiriConfig {
     pub data_race_detector: bool,
     /// Determine if weak memory emulation should be enabled. Requires data race detection to be enabled.
     pub weak_memory_emulation: bool,
-    /// Determine if we are running in GenMC mode. In this mode, Miri will explore multiple concurrent executions of the given program.
-    pub genmc_mode: bool,
+    /// Determine if we are running in GenMC mode and with which settings. In GenMC mode, Miri will explore multiple concurrent executions of the given program.
+    pub genmc_config: Option<GenmcConfig>,
     /// Track when an outdated (weak memory) load happens.
     pub track_outdated_loads: bool,
     /// Rate of spurious failures for compare_exchange_weak atomic operations,
@@ -192,7 +192,7 @@ impl Default for MiriConfig {
             track_alloc_accesses: false,
             data_race_detector: true,
             weak_memory_emulation: true,
-            genmc_mode: false,
+            genmc_config: None,
             track_outdated_loads: false,
             cmpxchg_weak_failure_rate: 0.8, // 80%
             measureme_out: None,
@@ -334,8 +334,8 @@ pub fn create_ecx<'tcx>(
         helpers::try_resolve_path(tcx, &["core", "ascii", "escape_default"], Namespace::ValueNS);
     if !matches!(sentinel, Some(s) if tcx.is_mir_available(s.def.def_id())) {
         tcx.dcx().fatal(
-            "the current sysroot was built without `-Zalways-encode-mir`, or libcore seems missing. \
-            Use `cargo miri setup` to prepare a sysroot that is suitable for Miri."
+            "the current sysroot was built without `-Zalways-encode-mir`, or libcore seems missing.\n\
+            Note that directly invoking the `miri` binary is not supported; please use `cargo miri` instead."
         );
     }
 
diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs
index 15699828e00..ab7e35710d3 100644
--- a/src/tools/miri/src/helpers.rs
+++ b/src/tools/miri/src/helpers.rs
@@ -3,7 +3,7 @@ use std::time::Duration;
 use std::{cmp, iter};
 
 use rand::RngCore;
-use rustc_abi::{Align, CanonAbi, ExternAbi, FieldIdx, FieldsShape, Size, Variants};
+use rustc_abi::{Align, ExternAbi, FieldIdx, FieldsShape, Size, Variants};
 use rustc_apfloat::Float;
 use rustc_apfloat::ieee::{Double, Half, Quad, Single};
 use rustc_hir::Safety;
@@ -14,11 +14,10 @@ use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
 use rustc_middle::middle::dependency_format::Linkage;
 use rustc_middle::middle::exported_symbols::ExportedSymbol;
 use rustc_middle::ty::layout::{LayoutOf, MaybeResult, TyAndLayout};
-use rustc_middle::ty::{self, Binder, FloatTy, FnSig, IntTy, Ty, TyCtxt, UintTy};
+use rustc_middle::ty::{self, FloatTy, IntTy, Ty, TyCtxt, UintTy};
 use rustc_session::config::CrateType;
 use rustc_span::{Span, Symbol};
 use rustc_symbol_mangling::mangle_internal_symbol;
-use rustc_target::callconv::FnAbi;
 
 use crate::*;
 
@@ -437,7 +436,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     /// For now, arguments must be scalars (so that the caller does not have to know the layout).
     ///
     /// If you do not provide a return place, a dangling zero-sized place will be created
-    /// for your convenience.
+    /// for your convenience. This is only valid if the return type is `()`.
     fn call_function(
         &mut self,
         f: ty::Instance<'tcx>,
@@ -452,7 +451,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         let mir = this.load_mir(f.def, None)?;
         let dest = match dest {
             Some(dest) => dest.clone(),
-            None => MPlaceTy::fake_alloc_zst(this.layout_of(mir.return_ty())?),
+            None => MPlaceTy::fake_alloc_zst(this.machine.layouts.unit),
         };
 
         // Construct a function pointer type representing the caller perspective.
@@ -465,6 +464,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         );
         let caller_fn_abi = this.fn_abi_of_fn_ptr(ty::Binder::dummy(sig), ty::List::empty())?;
 
+        // This will also show proper errors if there is any ABI mismatch.
         this.init_stack_frame(
             f,
             mir,
@@ -929,21 +929,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         self.read_c_str_with_char_size(ptr, wchar_t.size, wchar_t.align.abi)
     }
 
-    /// Check that the calling convention is what we expect.
-    fn check_callconv<'a>(
-        &self,
-        fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
-        exp_abi: CanonAbi,
-    ) -> InterpResult<'a, ()> {
-        if fn_abi.conv != exp_abi {
-            throw_ub_format!(
-                r#"calling a function with calling convention "{exp_abi}" using caller calling convention "{}""#,
-                fn_abi.conv
-            );
-        }
-        interp_ok(())
-    }
-
     fn frame_in_std(&self) -> bool {
         let this = self.eval_context_ref();
         let frame = this.frame();
@@ -967,162 +952,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         crate_name == "std" || crate_name == "std_miri_test"
     }
 
-    fn check_abi_and_shim_symbol_clash(
-        &mut self,
-        abi: &FnAbi<'tcx, Ty<'tcx>>,
-        exp_abi: CanonAbi,
-        link_name: Symbol,
-    ) -> InterpResult<'tcx, ()> {
-        self.check_callconv(abi, exp_abi)?;
-        if let Some((body, instance)) = self.eval_context_mut().lookup_exported_symbol(link_name)? {
-            // If compiler-builtins is providing the symbol, then don't treat it as a clash.
-            // We'll use our built-in implementation in `emulate_foreign_item_inner` for increased
-            // performance. Note that this means we won't catch any undefined behavior in
-            // compiler-builtins when running other crates, but Miri can still be run on
-            // compiler-builtins itself (or any crate that uses it as a normal dependency)
-            if self.eval_context_ref().tcx.is_compiler_builtins(instance.def_id().krate) {
-                return interp_ok(());
-            }
-
-            throw_machine_stop!(TerminationInfo::SymbolShimClashing {
-                link_name,
-                span: body.span.data(),
-            })
-        }
-        interp_ok(())
-    }
-
-    fn check_shim<'a, const N: usize>(
-        &mut self,
-        abi: &FnAbi<'tcx, Ty<'tcx>>,
-        exp_abi: CanonAbi,
-        link_name: Symbol,
-        args: &'a [OpTy<'tcx>],
-    ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> {
-        self.check_abi_and_shim_symbol_clash(abi, exp_abi, link_name)?;
-
-        if abi.c_variadic {
-            throw_ub_format!(
-                "calling a non-variadic function with a variadic caller-side signature"
-            );
-        }
-        if let Ok(ops) = args.try_into() {
-            return interp_ok(ops);
-        }
-        throw_ub_format!(
-            "incorrect number of arguments for `{link_name}`: got {}, expected {}",
-            args.len(),
-            N
-        )
-    }
-
-    /// Check that the given `caller_fn_abi` matches the expected ABI described by
-    /// `callee_abi`, `callee_input_tys`, `callee_output_ty`, and then returns the list of
-    /// arguments.
-    fn check_shim_abi<'a, const N: usize>(
-        &mut self,
-        link_name: Symbol,
-        caller_fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
-        callee_abi: ExternAbi,
-        callee_input_tys: [Ty<'tcx>; N],
-        callee_output_ty: Ty<'tcx>,
-        caller_args: &'a [OpTy<'tcx>],
-    ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> {
-        let this = self.eval_context_mut();
-        let mut inputs_and_output = callee_input_tys.to_vec();
-        inputs_and_output.push(callee_output_ty);
-        let fn_sig_binder = Binder::dummy(FnSig {
-            inputs_and_output: this.machine.tcx.mk_type_list(&inputs_and_output),
-            c_variadic: false,
-            // This does not matter for the ABI.
-            safety: Safety::Safe,
-            abi: callee_abi,
-        });
-        let callee_fn_abi = this.fn_abi_of_fn_ptr(fn_sig_binder, Default::default())?;
-
-        this.check_abi_and_shim_symbol_clash(caller_fn_abi, callee_fn_abi.conv, link_name)?;
-
-        if caller_fn_abi.c_variadic {
-            throw_ub_format!(
-                "ABI mismatch: calling a non-variadic function with a variadic caller-side signature"
-            );
-        }
-
-        if callee_fn_abi.fixed_count != caller_fn_abi.fixed_count {
-            throw_ub_format!(
-                "ABI mismatch: expected {} arguments, found {} arguments ",
-                callee_fn_abi.fixed_count,
-                caller_fn_abi.fixed_count
-            );
-        }
-
-        if callee_fn_abi.can_unwind && !caller_fn_abi.can_unwind {
-            throw_ub_format!(
-                "ABI mismatch: callee may unwind, but caller-side signature prohibits unwinding",
-            );
-        }
-
-        if !this.check_argument_compat(&caller_fn_abi.ret, &callee_fn_abi.ret)? {
-            throw_ub!(AbiMismatchReturn {
-                caller_ty: caller_fn_abi.ret.layout.ty,
-                callee_ty: callee_fn_abi.ret.layout.ty
-            });
-        }
-
-        if let Some(index) = caller_fn_abi
-            .args
-            .iter()
-            .zip(callee_fn_abi.args.iter())
-            .map(|(caller_arg, callee_arg)| this.check_argument_compat(caller_arg, callee_arg))
-            .collect::<InterpResult<'tcx, Vec<bool>>>()?
-            .into_iter()
-            .position(|b| !b)
-        {
-            throw_ub!(AbiMismatchArgument {
-                arg_idx: index,
-                caller_ty: caller_fn_abi.args[index].layout.ty,
-                callee_ty: callee_fn_abi.args[index].layout.ty
-            });
-        }
-
-        if let Ok(ops) = caller_args.try_into() {
-            return interp_ok(ops);
-        }
-        unreachable!()
-    }
-
-    /// Check shim for variadic function.
-    /// Returns a tuple that consisting of an array of fixed args, and a slice of varargs.
-    fn check_shim_variadic<'a, const N: usize>(
-        &mut self,
-        abi: &FnAbi<'tcx, Ty<'tcx>>,
-        exp_abi: CanonAbi,
-        link_name: Symbol,
-        args: &'a [OpTy<'tcx>],
-    ) -> InterpResult<'tcx, (&'a [OpTy<'tcx>; N], &'a [OpTy<'tcx>])>
-    where
-        &'a [OpTy<'tcx>; N]: TryFrom<&'a [OpTy<'tcx>]>,
-    {
-        self.check_abi_and_shim_symbol_clash(abi, exp_abi, link_name)?;
-
-        if !abi.c_variadic {
-            throw_ub_format!(
-                "calling a variadic function with a non-variadic caller-side signature"
-            );
-        }
-        if abi.fixed_count != u32::try_from(N).unwrap() {
-            throw_ub_format!(
-                "incorrect number of fixed arguments for variadic function `{}`: got {}, expected {N}",
-                link_name.as_str(),
-                abi.fixed_count
-            )
-        }
-        if let Some(args) = args.split_first_chunk() {
-            return interp_ok(args);
-        }
-        panic!("mismatch between signature and `args` slice");
-    }
-
     /// Mark a machine allocation that was just created as immutable.
     fn mark_immutable(&mut self, mplace: &MPlaceTy<'tcx>) {
         let this = self.eval_context_mut();
@@ -1258,8 +1087,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                         "failed to evaluate static in required link_section: {def_id:?}\n{err:?}"
                     )
                 });
-                let val = this.read_immediate(&const_val)?;
-                array.push(val);
+                match const_val.layout.ty.kind() {
+                    ty::FnPtr(..) => {
+                        array.push(this.read_immediate(&const_val)?);
+                    }
+                    ty::Array(elem_ty, _) if matches!(elem_ty.kind(), ty::FnPtr(..)) => {
+                        let mut elems = this.project_array_fields(&const_val)?;
+                        while let Some((_idx, elem)) = elems.next(this)? {
+                            array.push(this.read_immediate(&elem)?);
+                        }
+                    }
+                    _ =>
+                        throw_unsup_format!(
+                            "only function pointers and arrays of function pointers are supported in well-known linker sections"
+                        ),
+                }
             }
             interp_ok(())
         })?;
@@ -1318,39 +1160,6 @@ impl<'tcx> MiriMachine<'tcx> {
     }
 }
 
-/// Check that the number of args is what we expect.
-pub fn check_intrinsic_arg_count<'a, 'tcx, const N: usize>(
-    args: &'a [OpTy<'tcx>],
-) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]>
-where
-    &'a [OpTy<'tcx>; N]: TryFrom<&'a [OpTy<'tcx>]>,
-{
-    if let Ok(ops) = args.try_into() {
-        return interp_ok(ops);
-    }
-    throw_ub_format!(
-        "incorrect number of arguments for intrinsic: got {}, expected {}",
-        args.len(),
-        N
-    )
-}
-
-/// Check that the number of varargs is at least the minimum what we expect.
-/// Fixed args should not be included.
-pub fn check_min_vararg_count<'a, 'tcx, const N: usize>(
-    name: &'a str,
-    args: &'a [OpTy<'tcx>],
-) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> {
-    if let Some((ops, _)) = args.split_first_chunk() {
-        return interp_ok(ops);
-    }
-    throw_ub_format!(
-        "not enough variadic arguments for `{name}`: got {}, expected at least {}",
-        args.len(),
-        N
-    )
-}
-
 pub fn isolation_abort_error<'tcx>(name: &str) -> InterpResult<'tcx> {
     throw_machine_stop!(TerminationInfo::UnsupportedInIsolation(format!(
         "{name} not available when isolation is enabled",
@@ -1466,7 +1275,7 @@ pub struct MaybeEnteredTraceSpan {
 #[macro_export]
 macro_rules! enter_trace_span {
     ($name:ident :: $subname:ident $($tt:tt)*) => {{
-        enter_trace_span!(stringify!($name), $name = %stringify!(subname) $($tt)*)
+        enter_trace_span!(stringify!($name), $name = %stringify!($subname) $($tt)*)
     }};
 
     ($($tt:tt)*) => {
diff --git a/src/tools/miri/src/intrinsics/atomic.rs b/src/tools/miri/src/intrinsics/atomic.rs
index 0a59a707a10..bcc3e9ec885 100644
--- a/src/tools/miri/src/intrinsics/atomic.rs
+++ b/src/tools/miri/src/intrinsics/atomic.rs
@@ -2,7 +2,7 @@ use rustc_middle::mir::BinOp;
 use rustc_middle::ty::AtomicOrdering;
 use rustc_middle::{mir, ty};
 
-use self::helpers::check_intrinsic_arg_count;
+use super::check_intrinsic_arg_count;
 use crate::*;
 
 pub enum AtomicOp {
diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs
index 4efa7dd4dcf..b5e81460773 100644
--- a/src/tools/miri/src/intrinsics/mod.rs
+++ b/src/tools/miri/src/intrinsics/mod.rs
@@ -14,11 +14,28 @@ use rustc_middle::ty::{self, FloatTy, ScalarInt};
 use rustc_span::{Symbol, sym};
 
 use self::atomic::EvalContextExt as _;
-use self::helpers::{ToHost, ToSoft, check_intrinsic_arg_count};
+use self::helpers::{ToHost, ToSoft};
 use self::simd::EvalContextExt as _;
 use crate::math::{IeeeExt, apply_random_float_error_ulp};
 use crate::*;
 
+/// Check that the number of args is what we expect.
+fn check_intrinsic_arg_count<'a, 'tcx, const N: usize>(
+    args: &'a [OpTy<'tcx>],
+) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]>
+where
+    &'a [OpTy<'tcx>; N]: TryFrom<&'a [OpTy<'tcx>]>,
+{
+    if let Ok(ops) = args.try_into() {
+        return interp_ok(ops);
+    }
+    throw_ub_format!(
+        "incorrect number of arguments for intrinsic: got {}, expected {}",
+        args.len(),
+        N
+    )
+}
+
 impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
 pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     fn call_intrinsic(
@@ -114,7 +131,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 ));
             }
             "catch_unwind" => {
-                this.handle_catch_unwind(args, dest, ret)?;
+                let [try_fn, data, catch_fn] = check_intrinsic_arg_count(args)?;
+                this.handle_catch_unwind(try_fn, data, catch_fn, dest, ret)?;
                 // This pushed a stack frame, don't jump to `ret`.
                 return interp_ok(EmulateItemResult::AlreadyJumped);
             }
diff --git a/src/tools/miri/src/intrinsics/simd.rs b/src/tools/miri/src/intrinsics/simd.rs
index e63992aa95f..b26516c0ff0 100644
--- a/src/tools/miri/src/intrinsics/simd.rs
+++ b/src/tools/miri/src/intrinsics/simd.rs
@@ -6,9 +6,8 @@ use rustc_middle::ty::FloatTy;
 use rustc_middle::{mir, ty};
 use rustc_span::{Symbol, sym};
 
-use crate::helpers::{
-    ToHost, ToSoft, bool_to_simd_element, check_intrinsic_arg_count, simd_element_to_bool,
-};
+use super::check_intrinsic_arg_count;
+use crate::helpers::{ToHost, ToSoft, bool_to_simd_element, simd_element_to_bool};
 use crate::*;
 
 #[derive(Copy, Clone)]
diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs
index ae70257653c..507d4f7b428 100644
--- a/src/tools/miri/src/lib.rs
+++ b/src/tools/miri/src/lib.rs
@@ -7,6 +7,7 @@
 #![feature(never_type)]
 #![feature(try_blocks)]
 #![feature(io_error_more)]
+#![feature(if_let_guard)]
 #![feature(variant_count)]
 #![feature(yeet_expr)]
 #![feature(nonzero_ops)]
@@ -158,6 +159,7 @@ pub use crate::shims::foreign_items::{DynSym, EvalContextExt as _};
 pub use crate::shims::io_error::{EvalContextExt as _, IoError, LibcError};
 pub use crate::shims::os_str::EvalContextExt as _;
 pub use crate::shims::panic::EvalContextExt as _;
+pub use crate::shims::sig::EvalContextExt as _;
 pub use crate::shims::time::EvalContextExt as _;
 pub use crate::shims::tls::TlsData;
 pub use crate::shims::unwind::{CatchUnwindData, EvalContextExt as _};
diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs
index 7271d3f619c..8f0814a070c 100644
--- a/src/tools/miri/src/machine.rs
+++ b/src/tools/miri/src/machine.rs
@@ -76,13 +76,8 @@ pub struct FrameExtra<'tcx> {
 impl<'tcx> std::fmt::Debug for FrameExtra<'tcx> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         // Omitting `timing`, it does not support `Debug`.
-        let FrameExtra {
-            borrow_tracker,
-            catch_unwind,
-            timing: _,
-            is_user_relevant,
-            data_race,
-        } = self;
+        let FrameExtra { borrow_tracker, catch_unwind, timing: _, is_user_relevant, data_race } =
+            self;
         f.debug_struct("FrameData")
             .field("borrow_tracker", borrow_tracker)
             .field("catch_unwind", catch_unwind)
@@ -607,6 +602,9 @@ pub struct MiriMachine<'tcx> {
 }
 
 impl<'tcx> MiriMachine<'tcx> {
+    /// Create a new MiriMachine.
+    ///
+    /// Invariant: `genmc_ctx.is_some() == config.genmc_config.is_some()`
     pub(crate) fn new(
         config: &MiriConfig,
         layout_cx: LayoutCx<'tcx>,
@@ -630,7 +628,7 @@ impl<'tcx> MiriMachine<'tcx> {
         });
         let rng = StdRng::seed_from_u64(config.seed.unwrap_or(0));
         let borrow_tracker = config.borrow_tracker.map(|bt| bt.instantiate_global_state(config));
-        let data_race = if config.genmc_mode {
+        let data_race = if config.genmc_config.is_some() {
             // `genmc_ctx` persists across executions, so we don't create a new one here.
             GlobalDataRaceHandler::Genmc(genmc_ctx.unwrap())
         } else if config.data_race_detector {
diff --git a/src/tools/miri/src/shims/aarch64.rs b/src/tools/miri/src/shims/aarch64.rs
index 44ad5081ad5..6e422b4ab71 100644
--- a/src/tools/miri/src/shims/aarch64.rs
+++ b/src/tools/miri/src/shims/aarch64.rs
@@ -20,7 +20,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         let unprefixed_name = link_name.as_str().strip_prefix("llvm.aarch64.").unwrap();
         match unprefixed_name {
             "isb" => {
-                let [arg] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [arg] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let arg = this.read_scalar(arg)?.to_i32()?;
                 match arg {
                     // SY ("full system scope")
@@ -38,7 +38,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // `left` input, the second half of the output from the `right` input.
             // https://developer.arm.com/architectures/instruction-sets/intrinsics/vpmaxq_u8
             "neon.umaxp.v16i8" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (left, left_len) = this.project_to_simd(left)?;
                 let (right, right_len) = this.project_to_simd(right)?;
diff --git a/src/tools/miri/src/shims/backtrace.rs b/src/tools/miri/src/shims/backtrace.rs
index 18d60915d20..bd3914b652a 100644
--- a/src/tools/miri/src/shims/backtrace.rs
+++ b/src/tools/miri/src/shims/backtrace.rs
@@ -15,7 +15,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         dest: &MPlaceTy<'tcx>,
     ) -> InterpResult<'tcx> {
         let this = self.eval_context_mut();
-        let [flags] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?;
+        let [flags] = this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
 
         let flags = this.read_scalar(flags)?.to_u64()?;
         if flags != 0 {
@@ -37,7 +37,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         let ptr_ty = this.machine.layouts.mut_raw_ptr.ty;
         let ptr_layout = this.layout_of(ptr_ty)?;
 
-        let [flags, buf] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?;
+        let [flags, buf] = this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
 
         let flags = this.read_scalar(flags)?.to_u64()?;
         let buf_place = this.deref_pointer_as(buf, ptr_layout)?;
@@ -117,7 +117,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         dest: &MPlaceTy<'tcx>,
     ) -> InterpResult<'tcx> {
         let this = self.eval_context_mut();
-        let [ptr, flags] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?;
+        let [ptr, flags] = this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
 
         let flags = this.read_scalar(flags)?.to_u64()?;
 
@@ -195,7 +195,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         let this = self.eval_context_mut();
 
         let [ptr, flags, name_ptr, filename_ptr] =
-            this.check_shim(abi, CanonAbi::Rust, link_name, args)?;
+            this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
 
         let flags = this.read_scalar(flags)?.to_u64()?;
         if flags != 0 {
diff --git a/src/tools/miri/src/shims/files.rs b/src/tools/miri/src/shims/files.rs
index 606d1ffbea6..0d4642c6ad0 100644
--- a/src/tools/miri/src/shims/files.rs
+++ b/src/tools/miri/src/shims/files.rs
@@ -1,7 +1,7 @@
 use std::any::Any;
 use std::collections::BTreeMap;
 use std::fs::{File, Metadata};
-use std::io::{IsTerminal, Seek, SeekFrom, Write};
+use std::io::{ErrorKind, IsTerminal, Seek, SeekFrom, Write};
 use std::marker::CoercePointee;
 use std::ops::Deref;
 use std::rc::{Rc, Weak};
@@ -167,6 +167,11 @@ pub trait FileDescription: std::fmt::Debug + FileDescriptionExt {
         throw_unsup_format!("cannot write to {}", self.name());
     }
 
+    /// Determines whether this FD non-deterministically has its reads and writes shortened.
+    fn nondet_short_accesses(&self) -> bool {
+        true
+    }
+
     /// Seeks to the given offset (which can be relative to the beginning, end, or current position).
     /// Returns the new position from the start of the stream.
     fn seek<'tcx>(
@@ -334,6 +339,15 @@ impl FileDescription for FileHandle {
     ) -> InterpResult<'tcx> {
         assert!(communicate_allowed, "isolation should have prevented even opening a file");
 
+        if !self.writable {
+            // Linux hosts return EBADF here which we can't translate via the platform-independent
+            // code since it does not map to any `io::ErrorKind` -- so if we don't do anything
+            // special, we'd throw an "unsupported error code" here. Windows returns something that
+            // gets translated to `PermissionDenied`. That seems like a good value so let's just use
+            // this everywhere, even if it means behavior on Unix targets does not match the real
+            // thing.
+            return finish.call(ecx, Err(ErrorKind::PermissionDenied.into()));
+        }
         let result = ecx.write_to_host(&self.file, len, ptr)?;
         finish.call(ecx, result)
     }
diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs
index 94cda57658a..21545b68029 100644
--- a/src/tools/miri/src/shims/foreign_items.rs
+++ b/src/tools/miri/src/shims/foreign_items.rs
@@ -288,16 +288,17 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
         match link_name.as_str() {
             // Miri-specific extern functions
             "miri_start_unwind" => {
-                let [payload] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?;
+                let [payload] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
                 this.handle_miri_start_unwind(payload)?;
                 return interp_ok(EmulateItemResult::NeedsUnwind);
             }
             "miri_run_provenance_gc" => {
-                let [] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
                 this.run_provenance_gc();
             }
             "miri_get_alloc_id" => {
-                let [ptr] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?;
+                let [ptr] = this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
                 let ptr = this.read_pointer(ptr)?;
                 let (alloc_id, _, _) = this.ptr_get_alloc_id(ptr, 0).map_err_kind(|_e| {
                     err_machine_stop!(TerminationInfo::Abort(format!(
@@ -307,7 +308,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(Scalar::from_u64(alloc_id.0.get()), dest)?;
             }
             "miri_print_borrow_state" => {
-                let [id, show_unnamed] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?;
+                let [id, show_unnamed] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
                 let id = this.read_scalar(id)?.to_u64()?;
                 let show_unnamed = this.read_scalar(show_unnamed)?.to_bool()?;
                 if let Some(id) = std::num::NonZero::new(id).map(AllocId)
@@ -322,7 +324,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 // This associates a name to a tag. Very useful for debugging, and also makes
                 // tests more strict.
                 let [ptr, nth_parent, name] =
-                    this.check_shim(abi, CanonAbi::Rust, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
                 let ptr = this.read_pointer(ptr)?;
                 let nth_parent = this.read_scalar(nth_parent)?.to_u8()?;
                 let name = this.read_immediate(name)?;
@@ -335,7 +337,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.give_pointer_debug_name(ptr, nth_parent, &name)?;
             }
             "miri_static_root" => {
-                let [ptr] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?;
+                let [ptr] = this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
                 let ptr = this.read_pointer(ptr)?;
                 let (alloc_id, offset, _) = this.ptr_get_alloc_id(ptr, 0)?;
                 if offset != Size::ZERO {
@@ -346,7 +348,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.machine.static_roots.push(alloc_id);
             }
             "miri_host_to_target_path" => {
-                let [ptr, out, out_size] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?;
+                let [ptr, out, out_size] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
                 let ptr = this.read_pointer(ptr)?;
                 let out = this.read_pointer(out)?;
                 let out_size = this.read_scalar(out_size)?.to_target_usize(this)?;
@@ -382,7 +385,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Writes some bytes to the interpreter's stdout/stderr. See the
             // README for details.
             "miri_write_to_stdout" | "miri_write_to_stderr" => {
-                let [msg] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?;
+                let [msg] = this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
                 let msg = this.read_immediate(msg)?;
                 let msg = this.read_byte_slice(&msg)?;
                 // Note: we're ignoring errors writing to host stdout/stderr.
@@ -396,7 +399,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "miri_promise_symbolic_alignment" => {
                 use rustc_abi::AlignFromBytesError;
 
-                let [ptr, align] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?;
+                let [ptr, align] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
                 let ptr = this.read_pointer(ptr)?;
                 let align = this.read_target_usize(align)?;
                 if !align.is_power_of_two() {
@@ -437,12 +441,12 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // Aborting the process.
             "exit" => {
-                let [code] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [code] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let code = this.read_scalar(code)?.to_i32()?;
                 throw_machine_stop!(TerminationInfo::Exit { code, leak_check: false });
             }
             "abort" => {
-                let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 throw_machine_stop!(TerminationInfo::Abort(
                     "the program aborted execution".to_owned()
                 ))
@@ -450,7 +454,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // Standard C allocation
             "malloc" => {
-                let [size] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [size] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let size = this.read_target_usize(size)?;
                 if size <= this.max_size_of_val().bytes() {
                     let res = this.malloc(size, AllocInit::Uninit)?;
@@ -464,7 +468,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 }
             }
             "calloc" => {
-                let [items, elem_size] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [items, elem_size] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let items = this.read_target_usize(items)?;
                 let elem_size = this.read_target_usize(elem_size)?;
                 if let Some(size) = this.compute_size_in_bytes(Size::from_bytes(elem_size), items) {
@@ -479,12 +484,13 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 }
             }
             "free" => {
-                let [ptr] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [ptr] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let ptr = this.read_pointer(ptr)?;
                 this.free(ptr)?;
             }
             "realloc" => {
-                let [old_ptr, new_size] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [old_ptr, new_size] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let old_ptr = this.read_pointer(old_ptr)?;
                 let new_size = this.read_target_usize(new_size)?;
                 if new_size <= this.max_size_of_val().bytes() {
@@ -504,7 +510,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let default = |ecx: &mut MiriInterpCx<'tcx>| {
                     // Only call `check_shim` when `#[global_allocator]` isn't used. When that
                     // macro is used, we act like no shim exists, so that the exported function can run.
-                    let [size, align] = ecx.check_shim(abi, CanonAbi::Rust, link_name, args)?;
+                    let [size, align] =
+                        ecx.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
                     let size = ecx.read_target_usize(size)?;
                     let align = ecx.read_target_usize(align)?;
 
@@ -537,7 +544,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 return this.emulate_allocator(|this| {
                     // See the comment for `__rust_alloc` why `check_shim` is only called in the
                     // default case.
-                    let [size, align] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?;
+                    let [size, align] =
+                        this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
                     let size = this.read_target_usize(size)?;
                     let align = this.read_target_usize(align)?;
 
@@ -559,7 +567,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     // See the comment for `__rust_alloc` why `check_shim` is only called in the
                     // default case.
                     let [ptr, old_size, align] =
-                        ecx.check_shim(abi, CanonAbi::Rust, link_name, args)?;
+                        ecx.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
                     let ptr = ecx.read_pointer(ptr)?;
                     let old_size = ecx.read_target_usize(old_size)?;
                     let align = ecx.read_target_usize(align)?;
@@ -590,7 +598,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     // See the comment for `__rust_alloc` why `check_shim` is only called in the
                     // default case.
                     let [ptr, old_size, align, new_size] =
-                        this.check_shim(abi, CanonAbi::Rust, link_name, args)?;
+                        this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
                     let ptr = this.read_pointer(ptr)?;
                     let old_size = this.read_target_usize(old_size)?;
                     let align = this.read_target_usize(align)?;
@@ -613,20 +621,21 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             name if name == this.mangle_internal_symbol("__rust_no_alloc_shim_is_unstable_v2") => {
                 // This is a no-op shim that only exists to prevent making the allocator shims instantly stable.
-                let [] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
             }
             name if name
                 == this.mangle_internal_symbol("__rust_alloc_error_handler_should_panic_v2") =>
             {
                 // Gets the value of the `oom` option.
-                let [] = this.check_shim(abi, CanonAbi::Rust, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
                 let val = this.tcx.sess.opts.unstable_opts.oom.should_panic();
                 this.write_int(val, dest)?;
             }
 
             // C memory handling functions
             "memcmp" => {
-                let [left, right, n] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right, n] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let left = this.read_pointer(left)?;
                 let right = this.read_pointer(right)?;
                 let n = Size::from_bytes(this.read_target_usize(n)?);
@@ -650,7 +659,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(Scalar::from_i32(result), dest)?;
             }
             "memrchr" => {
-                let [ptr, val, num] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [ptr, val, num] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let ptr = this.read_pointer(ptr)?;
                 let val = this.read_scalar(val)?.to_i32()?;
                 let num = this.read_target_usize(num)?;
@@ -676,7 +686,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 }
             }
             "memchr" => {
-                let [ptr, val, num] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [ptr, val, num] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let ptr = this.read_pointer(ptr)?;
                 let val = this.read_scalar(val)?.to_i32()?;
                 let num = this.read_target_usize(num)?;
@@ -699,7 +710,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 }
             }
             "strlen" => {
-                let [ptr] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [ptr] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let ptr = this.read_pointer(ptr)?;
                 // This reads at least 1 byte, so we are already enforcing that this is a valid pointer.
                 let n = this.read_c_str(ptr)?.len();
@@ -709,7 +720,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 )?;
             }
             "wcslen" => {
-                let [ptr] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [ptr] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let ptr = this.read_pointer(ptr)?;
                 // This reads at least 1 byte, so we are already enforcing that this is a valid pointer.
                 let n = this.read_wchar_t_str(ptr)?.len();
@@ -719,7 +730,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 )?;
             }
             "memcpy" => {
-                let [ptr_dest, ptr_src, n] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [ptr_dest, ptr_src, n] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let ptr_dest = this.read_pointer(ptr_dest)?;
                 let ptr_src = this.read_pointer(ptr_src)?;
                 let n = this.read_target_usize(n)?;
@@ -733,7 +745,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_pointer(ptr_dest, dest)?;
             }
             "strcpy" => {
-                let [ptr_dest, ptr_src] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [ptr_dest, ptr_src] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let ptr_dest = this.read_pointer(ptr_dest)?;
                 let ptr_src = this.read_pointer(ptr_src)?;
 
@@ -764,7 +777,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
             | "erff"
             | "erfcf"
             => {
-                let [f] = this.check_shim(abi, CanonAbi::C , link_name, args)?;
+                let [f] = this.check_shim_sig_lenient(abi, CanonAbi::C , link_name, args)?;
                 let f = this.read_scalar(f)?.to_f32()?;
                 // Using host floats (but it's fine, these operations do not have guaranteed precision).
                 let f_host = f.to_host();
@@ -802,7 +815,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
             | "atan2f"
             | "fdimf"
             => {
-                let [f1, f2] = this.check_shim(abi, CanonAbi::C , link_name, args)?;
+                let [f1, f2] = this.check_shim_sig_lenient(abi, CanonAbi::C , link_name, args)?;
                 let f1 = this.read_scalar(f1)?.to_f32()?;
                 let f2 = this.read_scalar(f2)?.to_f32()?;
                 // underscore case for windows, here and below
@@ -841,7 +854,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
             | "erf"
             | "erfc"
             => {
-                let [f] = this.check_shim(abi, CanonAbi::C , link_name, args)?;
+                let [f] = this.check_shim_sig_lenient(abi, CanonAbi::C , link_name, args)?;
                 let f = this.read_scalar(f)?.to_f64()?;
                 // Using host floats (but it's fine, these operations do not have guaranteed precision).
                 let f_host = f.to_host();
@@ -879,7 +892,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
             | "atan2"
             | "fdim"
             => {
-                let [f1, f2] = this.check_shim(abi, CanonAbi::C , link_name, args)?;
+                let [f1, f2] = this.check_shim_sig_lenient(abi, CanonAbi::C , link_name, args)?;
                 let f1 = this.read_scalar(f1)?.to_f64()?;
                 let f2 = this.read_scalar(f2)?.to_f64()?;
                 // underscore case for windows, here and below
@@ -908,7 +921,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
             | "ldexp"
             | "scalbn"
             => {
-                let [x, exp] = this.check_shim(abi, CanonAbi::C , link_name, args)?;
+                let [x, exp] = this.check_shim_sig_lenient(abi, CanonAbi::C , link_name, args)?;
                 // For radix-2 (binary) systems, `ldexp` and `scalbn` are the same.
                 let x = this.read_scalar(x)?.to_f64()?;
                 let exp = this.read_scalar(exp)?.to_i32()?;
@@ -918,7 +931,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(res, dest)?;
             }
             "lgammaf_r" => {
-                let [x, signp] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [x, signp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let x = this.read_scalar(x)?.to_f32()?;
                 let signp = this.deref_pointer_as(signp, this.machine.layouts.i32)?;
 
@@ -934,7 +947,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(res, dest)?;
             }
             "lgamma_r" => {
-                let [x, signp] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [x, signp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let x = this.read_scalar(x)?.to_f64()?;
                 let signp = this.deref_pointer_as(signp, this.machine.layouts.i32)?;
 
@@ -952,7 +965,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // LLVM intrinsics
             "llvm.prefetch" => {
-                let [p, rw, loc, ty] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [p, rw, loc, ty] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let _ = this.read_pointer(p)?;
                 let rw = this.read_scalar(rw)?.to_i32()?;
@@ -979,7 +993,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Used to implement the x86 `_mm{,256,512}_popcnt_epi{8,16,32,64}` and wasm
             // `{i,u}8x16_popcnt` functions.
             name if name.starts_with("llvm.ctpop.v") => {
-                let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (op, op_len) = this.project_to_simd(op)?;
                 let (dest, dest_len) = this.project_to_simd(dest)?;
@@ -1015,7 +1029,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             // FIXME: Move this to an `arm` submodule.
             "llvm.arm.hint" if this.tcx.sess.target.arch == "arm" => {
-                let [arg] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [arg] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let arg = this.read_scalar(arg)?.to_i32()?;
                 // Note that different arguments might have different target feature requirements.
                 match arg {
diff --git a/src/tools/miri/src/shims/mod.rs b/src/tools/miri/src/shims/mod.rs
index 2a7709829ee..7f594d4fdd6 100644
--- a/src/tools/miri/src/shims/mod.rs
+++ b/src/tools/miri/src/shims/mod.rs
@@ -18,6 +18,7 @@ pub mod global_ctor;
 pub mod io_error;
 pub mod os_str;
 pub mod panic;
+pub mod sig;
 pub mod time;
 pub mod tls;
 pub mod unwind;
diff --git a/src/tools/miri/src/shims/sig.rs b/src/tools/miri/src/shims/sig.rs
new file mode 100644
index 00000000000..bc5e7f584f5
--- /dev/null
+++ b/src/tools/miri/src/shims/sig.rs
@@ -0,0 +1,266 @@
+//! Everything related to checking the signature of shim invocations.
+
+use rustc_abi::{CanonAbi, ExternAbi};
+use rustc_hir::Safety;
+use rustc_middle::ty::{Binder, FnSig, Ty};
+use rustc_span::Symbol;
+use rustc_target::callconv::FnAbi;
+
+use crate::*;
+
+/// Describes the expected signature of a shim.
+pub struct ShimSig<'tcx, const ARGS: usize> {
+    pub abi: ExternAbi,
+    pub args: [Ty<'tcx>; ARGS],
+    pub ret: Ty<'tcx>,
+}
+
+/// Construct a `ShimSig` with convenient syntax:
+/// ```rust,ignore
+/// shim_sig!(this, extern "C" fn (*const T, i32) -> usize)
+/// ```
+#[macro_export]
+macro_rules! shim_sig {
+    (extern $abi:literal fn($($arg:ty),*) -> $ret:ty) => {
+        |this| $crate::shims::sig::ShimSig {
+            abi: std::str::FromStr::from_str($abi).expect("incorrect abi specified"),
+            args: [$(shim_sig_arg!(this, $arg)),*],
+            ret: shim_sig_arg!(this, $ret),
+        }
+    };
+}
+
+/// Helper for `shim_sig!`.
+#[macro_export]
+macro_rules! shim_sig_arg {
+    // Unfortuantely we cannot take apart a `ty`-typed token at compile time,
+    // so we have to stringify it and match at runtime.
+    ($this:ident, $x:ty) => {{
+        match stringify!($x) {
+            "i8" => $this.tcx.types.i8,
+            "i16" => $this.tcx.types.i16,
+            "i32" => $this.tcx.types.i32,
+            "i64" => $this.tcx.types.i64,
+            "i128" => $this.tcx.types.i128,
+            "isize" => $this.tcx.types.isize,
+            "u8" => $this.tcx.types.u8,
+            "u16" => $this.tcx.types.u16,
+            "u32" => $this.tcx.types.u32,
+            "u64" => $this.tcx.types.u64,
+            "u128" => $this.tcx.types.u128,
+            "usize" => $this.tcx.types.usize,
+            "()" => $this.tcx.types.unit,
+            "*const _" => $this.machine.layouts.const_raw_ptr.ty,
+            "*mut _" => $this.machine.layouts.mut_raw_ptr.ty,
+            ty if let Some(libc_ty) = ty.strip_prefix("libc::") => $this.libc_ty_layout(libc_ty).ty,
+            ty => panic!("unsupported signature type {ty:?}"),
+        }
+    }};
+}
+
+/// Helper function to compare two ABIs.
+fn check_shim_abi<'tcx>(
+    this: &MiriInterpCx<'tcx>,
+    callee_abi: &FnAbi<'tcx, Ty<'tcx>>,
+    caller_abi: &FnAbi<'tcx, Ty<'tcx>>,
+) -> InterpResult<'tcx> {
+    if callee_abi.conv != caller_abi.conv {
+        throw_ub_format!(
+            r#"calling a function with calling convention "{callee}" using caller calling convention "{caller}""#,
+            callee = callee_abi.conv,
+            caller = caller_abi.conv,
+        );
+    }
+    if callee_abi.can_unwind && !caller_abi.can_unwind {
+        throw_ub_format!(
+            "ABI mismatch: callee may unwind, but caller-side signature prohibits unwinding",
+        );
+    }
+    if caller_abi.c_variadic && !callee_abi.c_variadic {
+        throw_ub_format!(
+            "ABI mismatch: calling a non-variadic function with a variadic caller-side signature"
+        );
+    }
+    if !caller_abi.c_variadic && callee_abi.c_variadic {
+        throw_ub_format!(
+            "ABI mismatch: calling a variadic function with a non-variadic caller-side signature"
+        );
+    }
+
+    if callee_abi.fixed_count != caller_abi.fixed_count {
+        throw_ub_format!(
+            "ABI mismatch: expected {} arguments, found {} arguments ",
+            callee_abi.fixed_count,
+            caller_abi.fixed_count
+        );
+    }
+
+    if !this.check_argument_compat(&caller_abi.ret, &callee_abi.ret)? {
+        throw_ub!(AbiMismatchReturn {
+            caller_ty: caller_abi.ret.layout.ty,
+            callee_ty: callee_abi.ret.layout.ty
+        });
+    }
+
+    for (idx, (caller_arg, callee_arg)) in
+        caller_abi.args.iter().zip(callee_abi.args.iter()).enumerate()
+    {
+        if !this.check_argument_compat(caller_arg, callee_arg)? {
+            throw_ub!(AbiMismatchArgument {
+                arg_idx: idx,
+                caller_ty: caller_abi.args[idx].layout.ty,
+                callee_ty: callee_abi.args[idx].layout.ty
+            });
+        }
+    }
+
+    interp_ok(())
+}
+
+fn check_shim_symbol_clash<'tcx>(
+    this: &mut MiriInterpCx<'tcx>,
+    link_name: Symbol,
+) -> InterpResult<'tcx, ()> {
+    if let Some((body, instance)) = this.lookup_exported_symbol(link_name)? {
+        // If compiler-builtins is providing the symbol, then don't treat it as a clash.
+        // We'll use our built-in implementation in `emulate_foreign_item_inner` for increased
+        // performance. Note that this means we won't catch any undefined behavior in
+        // compiler-builtins when running other crates, but Miri can still be run on
+        // compiler-builtins itself (or any crate that uses it as a normal dependency)
+        if this.tcx.is_compiler_builtins(instance.def_id().krate) {
+            return interp_ok(());
+        }
+
+        throw_machine_stop!(TerminationInfo::SymbolShimClashing {
+            link_name,
+            span: body.span.data(),
+        })
+    }
+    interp_ok(())
+}
+
+impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
+pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
+    fn check_shim_sig_lenient<'a, const N: usize>(
+        &mut self,
+        abi: &FnAbi<'tcx, Ty<'tcx>>,
+        exp_abi: CanonAbi,
+        link_name: Symbol,
+        args: &'a [OpTy<'tcx>],
+    ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> {
+        let this = self.eval_context_mut();
+        check_shim_symbol_clash(this, link_name)?;
+
+        if abi.conv != exp_abi {
+            throw_ub_format!(
+                r#"calling a function with calling convention "{exp_abi}" using caller calling convention "{}""#,
+                abi.conv
+            );
+        }
+        if abi.c_variadic {
+            throw_ub_format!(
+                "calling a non-variadic function with a variadic caller-side signature"
+            );
+        }
+
+        if let Ok(ops) = args.try_into() {
+            return interp_ok(ops);
+        }
+        throw_ub_format!(
+            "incorrect number of arguments for `{link_name}`: got {}, expected {}",
+            args.len(),
+            N
+        )
+    }
+
+    /// Check that the given `caller_fn_abi` matches the expected ABI described by `shim_sig`, and
+    /// then returns the list of arguments.
+    fn check_shim_sig<'a, const N: usize>(
+        &mut self,
+        shim_sig: fn(&MiriInterpCx<'tcx>) -> ShimSig<'tcx, N>,
+        link_name: Symbol,
+        caller_fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
+        caller_args: &'a [OpTy<'tcx>],
+    ) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> {
+        let this = self.eval_context_mut();
+        let shim_sig = shim_sig(this);
+
+        // Compute full callee ABI.
+        let mut inputs_and_output = Vec::with_capacity(N.strict_add(1));
+        inputs_and_output.extend(&shim_sig.args);
+        inputs_and_output.push(shim_sig.ret);
+        let fn_sig_binder = Binder::dummy(FnSig {
+            inputs_and_output: this.machine.tcx.mk_type_list(&inputs_and_output),
+            c_variadic: false,
+            // This does not matter for the ABI.
+            safety: Safety::Safe,
+            abi: shim_sig.abi,
+        });
+        let callee_fn_abi = this.fn_abi_of_fn_ptr(fn_sig_binder, Default::default())?;
+
+        // Check everything.
+        check_shim_abi(this, callee_fn_abi, caller_fn_abi)?;
+        check_shim_symbol_clash(this, link_name)?;
+
+        // Return arguments.
+        if let Ok(ops) = caller_args.try_into() {
+            return interp_ok(ops);
+        }
+        unreachable!()
+    }
+
+    /// Check shim for variadic function.
+    /// Returns a tuple that consisting of an array of fixed args, and a slice of varargs.
+    fn check_shim_sig_variadic_lenient<'a, const N: usize>(
+        &mut self,
+        abi: &FnAbi<'tcx, Ty<'tcx>>,
+        exp_abi: CanonAbi,
+        link_name: Symbol,
+        args: &'a [OpTy<'tcx>],
+    ) -> InterpResult<'tcx, (&'a [OpTy<'tcx>; N], &'a [OpTy<'tcx>])>
+    where
+        &'a [OpTy<'tcx>; N]: TryFrom<&'a [OpTy<'tcx>]>,
+    {
+        let this = self.eval_context_mut();
+        check_shim_symbol_clash(this, link_name)?;
+
+        if abi.conv != exp_abi {
+            throw_ub_format!(
+                r#"calling a function with calling convention "{exp_abi}" using caller calling convention "{}""#,
+                abi.conv
+            );
+        }
+        if !abi.c_variadic {
+            throw_ub_format!(
+                "calling a variadic function with a non-variadic caller-side signature"
+            );
+        }
+        if abi.fixed_count != u32::try_from(N).unwrap() {
+            throw_ub_format!(
+                "incorrect number of fixed arguments for variadic function `{}`: got {}, expected {N}",
+                link_name.as_str(),
+                abi.fixed_count
+            )
+        }
+        if let Some(args) = args.split_first_chunk() {
+            return interp_ok(args);
+        }
+        panic!("mismatch between signature and `args` slice");
+    }
+}
+
+/// Check that the number of varargs is at least the minimum what we expect.
+/// Fixed args should not be included.
+pub fn check_min_vararg_count<'a, 'tcx, const N: usize>(
+    name: &'a str,
+    args: &'a [OpTy<'tcx>],
+) -> InterpResult<'tcx, &'a [OpTy<'tcx>; N]> {
+    if let Some((ops, _)) = args.split_first_chunk() {
+        return interp_ok(ops);
+    }
+    throw_ub_format!(
+        "not enough variadic arguments for `{name}`: got {}, expected at least {}",
+        args.len(),
+        N
+    )
+}
diff --git a/src/tools/miri/src/shims/time.rs b/src/tools/miri/src/shims/time.rs
index eb21abc2a45..b5b35797fec 100644
--- a/src/tools/miri/src/shims/time.rs
+++ b/src/tools/miri/src/shims/time.rs
@@ -17,73 +17,71 @@ pub fn system_time_to_duration<'tcx>(time: &SystemTime) -> InterpResult<'tcx, Du
 
 impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
 pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
-    fn clock_gettime(
-        &mut self,
-        clk_id_op: &OpTy<'tcx>,
-        tp_op: &OpTy<'tcx>,
-        dest: &MPlaceTy<'tcx>,
-    ) -> InterpResult<'tcx> {
+    fn parse_clockid(&self, clk_id: Scalar) -> Option<TimeoutClock> {
         // This clock support is deliberately minimal because a lot of clock types have fiddly
         // properties (is it possible for Miri to be suspended independently of the host?). If you
         // have a use for another clock type, please open an issue.
+        let this = self.eval_context_ref();
 
-        let this = self.eval_context_mut();
-
-        this.assert_target_os_is_unix("clock_gettime");
-        let clockid_t_size = this.libc_ty_layout("clockid_t").size;
-
-        let clk_id = this.read_scalar(clk_id_op)?.to_int(clockid_t_size)?;
-        let tp = this.deref_pointer_as(tp_op, this.libc_ty_layout("timespec"))?;
-
-        let absolute_clocks;
-        let mut relative_clocks;
+        // Portable names that exist everywhere.
+        if clk_id == this.eval_libc("CLOCK_REALTIME") {
+            return Some(TimeoutClock::RealTime);
+        } else if clk_id == this.eval_libc("CLOCK_MONOTONIC") {
+            return Some(TimeoutClock::Monotonic);
+        }
 
+        // Some further platform-specific names we support.
         match this.tcx.sess.target.os.as_ref() {
             "linux" | "freebsd" | "android" => {
-                // Linux, Android, and FreeBSD have two main kinds of clocks. REALTIME clocks return the actual time since the
-                // Unix epoch, including effects which may cause time to move backwards such as NTP.
                 // Linux further distinguishes regular and "coarse" clocks, but the "coarse" version
-                // is just specified to be "faster and less precise", so we implement both the same way.
-                absolute_clocks = vec![
-                    this.eval_libc("CLOCK_REALTIME").to_int(clockid_t_size)?,
-                    this.eval_libc("CLOCK_REALTIME_COARSE").to_int(clockid_t_size)?,
-                ];
-                // The second kind is MONOTONIC clocks for which 0 is an arbitrary time point, but they are
-                // never allowed to go backwards. We don't need to do any additional monotonicity
-                // enforcement because std::time::Instant already guarantees that it is monotonic.
-                relative_clocks = vec![
-                    this.eval_libc("CLOCK_MONOTONIC").to_int(clockid_t_size)?,
-                    this.eval_libc("CLOCK_MONOTONIC_COARSE").to_int(clockid_t_size)?,
-                ];
+                // is just specified to be "faster and less precise", so we treat it like normal
+                // clocks.
+                if clk_id == this.eval_libc("CLOCK_REALTIME_COARSE") {
+                    return Some(TimeoutClock::RealTime);
+                } else if clk_id == this.eval_libc("CLOCK_MONOTONIC_COARSE") {
+                    return Some(TimeoutClock::Monotonic);
+                }
             }
             "macos" => {
-                absolute_clocks = vec![this.eval_libc("CLOCK_REALTIME").to_int(clockid_t_size)?];
-                relative_clocks = vec![this.eval_libc("CLOCK_MONOTONIC").to_int(clockid_t_size)?];
                 // `CLOCK_UPTIME_RAW` supposed to not increment while the system is asleep... but
                 // that's not really something a program running inside Miri can tell, anyway.
                 // We need to support it because std uses it.
-                relative_clocks.push(this.eval_libc("CLOCK_UPTIME_RAW").to_int(clockid_t_size)?);
-            }
-            "solaris" | "illumos" => {
-                // The REALTIME clock returns the actual time since the Unix epoch.
-                absolute_clocks = vec![this.eval_libc("CLOCK_REALTIME").to_int(clockid_t_size)?];
-                // MONOTONIC, in the other hand, is the high resolution, non-adjustable
-                // clock from an arbitrary time in the past.
-                // Note that the man page mentions HIGHRES but it is just
-                // an alias of MONOTONIC and the libc crate does not expose it anyway.
-                // https://docs.oracle.com/cd/E23824_01/html/821-1465/clock-gettime-3c.html
-                relative_clocks = vec![this.eval_libc("CLOCK_MONOTONIC").to_int(clockid_t_size)?];
+                if clk_id == this.eval_libc("CLOCK_UPTIME_RAW") {
+                    return Some(TimeoutClock::Monotonic);
+                }
             }
-            target => throw_unsup_format!("`clock_gettime` is not supported on target OS {target}"),
+            _ => {}
         }
 
-        let duration = if absolute_clocks.contains(&clk_id) {
-            this.check_no_isolation("`clock_gettime` with `REALTIME` clocks")?;
-            system_time_to_duration(&SystemTime::now())?
-        } else if relative_clocks.contains(&clk_id) {
-            this.machine.monotonic_clock.now().duration_since(this.machine.monotonic_clock.epoch())
-        } else {
-            return this.set_last_error_and_return(LibcError("EINVAL"), dest);
+        None
+    }
+
+    fn clock_gettime(
+        &mut self,
+        clk_id_op: &OpTy<'tcx>,
+        tp_op: &OpTy<'tcx>,
+        dest: &MPlaceTy<'tcx>,
+    ) -> InterpResult<'tcx> {
+        let this = self.eval_context_mut();
+
+        this.assert_target_os_is_unix("clock_gettime");
+
+        let clk_id = this.read_scalar(clk_id_op)?;
+        let tp = this.deref_pointer_as(tp_op, this.libc_ty_layout("timespec"))?;
+
+        let duration = match this.parse_clockid(clk_id) {
+            Some(TimeoutClock::RealTime) => {
+                this.check_no_isolation("`clock_gettime` with `REALTIME` clocks")?;
+                system_time_to_duration(&SystemTime::now())?
+            }
+            Some(TimeoutClock::Monotonic) =>
+                this.machine
+                    .monotonic_clock
+                    .now()
+                    .duration_since(this.machine.monotonic_clock.epoch()),
+            None => {
+                return this.set_last_error_and_return(LibcError("EINVAL"), dest);
+            }
         };
 
         let tv_sec = duration.as_secs();
diff --git a/src/tools/miri/src/shims/unix/android/foreign_items.rs b/src/tools/miri/src/shims/unix/android/foreign_items.rs
index 690b5295681..04c5d28838b 100644
--- a/src/tools/miri/src/shims/unix/android/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/android/foreign_items.rs
@@ -26,29 +26,30 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         match link_name.as_str() {
             // epoll, eventfd
             "epoll_create1" => {
-                let [flag] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [flag] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.epoll_create1(flag)?;
                 this.write_scalar(result, dest)?;
             }
             "epoll_ctl" => {
-                let [epfd, op, fd, event] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [epfd, op, fd, event] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.epoll_ctl(epfd, op, fd, event)?;
                 this.write_scalar(result, dest)?;
             }
             "epoll_wait" => {
                 let [epfd, events, maxevents, timeout] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.epoll_wait(epfd, events, maxevents, timeout, dest)?;
             }
             "eventfd" => {
-                let [val, flag] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [val, flag] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.eventfd(val, flag)?;
                 this.write_scalar(result, dest)?;
             }
 
             // Miscellaneous
             "__errno" => {
-                let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let errno_place = this.last_error_place()?;
                 this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?;
             }
diff --git a/src/tools/miri/src/shims/unix/android/thread.rs b/src/tools/miri/src/shims/unix/android/thread.rs
index 5d17d6c8517..4e7b21d7d94 100644
--- a/src/tools/miri/src/shims/unix/android/thread.rs
+++ b/src/tools/miri/src/shims/unix/android/thread.rs
@@ -3,7 +3,7 @@ use rustc_middle::ty::Ty;
 use rustc_span::Symbol;
 use rustc_target::callconv::FnAbi;
 
-use crate::helpers::check_min_vararg_count;
+use crate::shims::sig::check_min_vararg_count;
 use crate::shims::unix::thread::{EvalContextExt as _, ThreadNameResult};
 use crate::*;
 
@@ -16,7 +16,7 @@ pub fn prctl<'tcx>(
     args: &[OpTy<'tcx>],
     dest: &MPlaceTy<'tcx>,
 ) -> InterpResult<'tcx> {
-    let ([op], varargs) = ecx.check_shim_variadic(abi, CanonAbi::C, link_name, args)?;
+    let ([op], varargs) = ecx.check_shim_sig_variadic_lenient(abi, CanonAbi::C, link_name, args)?;
 
     // FIXME: Use constants once https://github.com/rust-lang/libc/pull/3941 backported to the 0.2 branch.
     let pr_set_name = 15;
diff --git a/src/tools/miri/src/shims/unix/fd.rs b/src/tools/miri/src/shims/unix/fd.rs
index 71102d9f2f3..e226a55d8b1 100644
--- a/src/tools/miri/src/shims/unix/fd.rs
+++ b/src/tools/miri/src/shims/unix/fd.rs
@@ -4,10 +4,11 @@
 use std::io;
 use std::io::ErrorKind;
 
+use rand::Rng;
 use rustc_abi::Size;
 
-use crate::helpers::check_min_vararg_count;
 use crate::shims::files::FileDescription;
+use crate::shims::sig::check_min_vararg_count;
 use crate::shims::unix::linux_like::epoll::EpollReadyEvents;
 use crate::shims::unix::*;
 use crate::*;
@@ -263,9 +264,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             return this.set_last_error_and_return(LibcError("EBADF"), dest);
         };
 
+        // Non-deterministically decide to further reduce the count, simulating a partial read (but
+        // never to 0, that has different behavior).
+        let count =
+            if fd.nondet_short_accesses() && count >= 2 && this.machine.rng.get_mut().random() {
+                count / 2
+            } else {
+                count
+            };
+
         trace!("read: FD mapped to {fd:?}");
         // We want to read at most `count` bytes. We are sure that `count` is not negative
-        // because it was a target's `usize`. Also we are sure that its smaller than
+        // because it was a target's `usize`. Also we are sure that it's smaller than
         // `usize::MAX` because it is bounded by the host's `isize`.
 
         let finish = {
@@ -328,6 +338,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             return this.set_last_error_and_return(LibcError("EBADF"), dest);
         };
 
+        // Non-deterministically decide to further reduce the count, simulating a partial write (but
+        // never to 0, that has different behavior).
+        let count =
+            if fd.nondet_short_accesses() && count >= 2 && this.machine.rng.get_mut().random() {
+                count / 2
+            } else {
+                count
+            };
+
         let finish = {
             let dest = dest.clone();
             callback!(
diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs
index 548eabb1b9f..55906f4eb95 100644
--- a/src/tools/miri/src/shims/unix/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/foreign_items.rs
@@ -1,7 +1,7 @@
 use std::ffi::OsStr;
 use std::str;
 
-use rustc_abi::{CanonAbi, ExternAbi, Size};
+use rustc_abi::{CanonAbi, Size};
 use rustc_middle::ty::Ty;
 use rustc_span::Symbol;
 use rustc_target::callconv::FnAbi;
@@ -14,7 +14,7 @@ use self::shims::unix::solarish::foreign_items as solarish;
 use crate::concurrency::cpu_affinity::CpuAffinityMask;
 use crate::shims::alloc::EvalContextExt as _;
 use crate::shims::unix::*;
-use crate::*;
+use crate::{shim_sig, *};
 
 pub fn is_dyn_sym(name: &str, target_os: &str) -> bool {
     match name {
@@ -111,40 +111,30 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         match link_name.as_str() {
             // Environment related shims
             "getenv" => {
-                let [name] = this.check_shim_abi(
+                let [name] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(*const _) -> *mut _),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.machine.layouts.const_raw_ptr.ty],
-                    this.machine.layouts.mut_raw_ptr.ty,
                     args,
                 )?;
                 let result = this.getenv(name)?;
                 this.write_pointer(result, dest)?;
             }
             "unsetenv" => {
-                let [name] = this.check_shim_abi(
+                let [name] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(*const _) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.machine.layouts.const_raw_ptr.ty],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let result = this.unsetenv(name)?;
                 this.write_scalar(result, dest)?;
             }
             "setenv" => {
-                let [name, value, overwrite] = this.check_shim_abi(
+                let [name, value, overwrite] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(*const _, *const _, i32) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [
-                        this.machine.layouts.const_raw_ptr.ty,
-                        this.machine.layouts.const_raw_ptr.ty,
-                        this.tcx.types.i32,
-                    ],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 this.read_scalar(overwrite)?.to_i32()?;
@@ -152,48 +142,40 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(result, dest)?;
             }
             "getcwd" => {
-                let [buf, size] = this.check_shim_abi(
+                let [buf, size] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(*mut _, usize) -> *mut _),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.machine.layouts.mut_raw_ptr.ty, this.tcx.types.usize],
-                    this.machine.layouts.mut_raw_ptr.ty,
                     args,
                 )?;
                 let result = this.getcwd(buf, size)?;
                 this.write_pointer(result, dest)?;
             }
             "chdir" => {
-                let [path] = this.check_shim_abi(
+                let [path] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(*const _) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.machine.layouts.const_raw_ptr.ty],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let result = this.chdir(path)?;
                 this.write_scalar(result, dest)?;
             }
             "getpid" => {
-                let [] = this.check_shim_abi(
+                let [] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn() -> libc::pid_t),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [],
-                    this.libc_ty_layout("pid_t").ty,
                     args,
                 )?;
                 let result = this.getpid()?;
                 this.write_scalar(result, dest)?;
             }
             "sysconf" => {
-                let [val] = this.check_shim_abi(
+                let [val] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(i32) -> isize),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.tcx.types.i32],
-                    this.tcx.types.isize,
                     args,
                 )?;
                 let result = this.sysconf(val)?;
@@ -201,12 +183,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             // File descriptors
             "read" => {
-                let [fd, buf, count] = this.check_shim_abi(
+                let [fd, buf, count] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(i32, *mut _, usize) -> isize),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.tcx.types.i32, this.machine.layouts.mut_raw_ptr.ty, this.tcx.types.usize],
-                    this.tcx.types.isize,
                     args,
                 )?;
                 let fd = this.read_scalar(fd)?.to_i32()?;
@@ -215,16 +195,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.read(fd, buf, count, None, dest)?;
             }
             "write" => {
-                let [fd, buf, n] = this.check_shim_abi(
+                let [fd, buf, n] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(i32, *const _, usize) -> isize),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [
-                        this.tcx.types.i32,
-                        this.machine.layouts.const_raw_ptr.ty,
-                        this.tcx.types.usize,
-                    ],
-                    this.tcx.types.isize,
                     args,
                 )?;
                 let fd = this.read_scalar(fd)?.to_i32()?;
@@ -234,98 +208,64 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write(fd, buf, count, None, dest)?;
             }
             "pread" => {
-                let off_t = this.libc_ty_layout("off_t");
-                let [fd, buf, count, offset] = this.check_shim_abi(
+                let [fd, buf, count, offset] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(i32, *mut _, usize, libc::off_t) -> isize),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [
-                        this.tcx.types.i32,
-                        this.machine.layouts.mut_raw_ptr.ty,
-                        this.tcx.types.usize,
-                        off_t.ty,
-                    ],
-                    this.tcx.types.isize,
                     args,
                 )?;
                 let fd = this.read_scalar(fd)?.to_i32()?;
                 let buf = this.read_pointer(buf)?;
                 let count = this.read_target_usize(count)?;
-                let offset = this.read_scalar(offset)?.to_int(off_t.size)?;
+                let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?;
                 this.read(fd, buf, count, Some(offset), dest)?;
             }
             "pwrite" => {
-                let off_t = this.libc_ty_layout("off_t");
-                let [fd, buf, n, offset] = this.check_shim_abi(
+                let [fd, buf, n, offset] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(i32, *const _, usize, libc::off_t) -> isize),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [
-                        this.tcx.types.i32,
-                        this.machine.layouts.const_raw_ptr.ty,
-                        this.tcx.types.usize,
-                        off_t.ty,
-                    ],
-                    this.tcx.types.isize,
                     args,
                 )?;
                 let fd = this.read_scalar(fd)?.to_i32()?;
                 let buf = this.read_pointer(buf)?;
                 let count = this.read_target_usize(n)?;
-                let offset = this.read_scalar(offset)?.to_int(off_t.size)?;
+                let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?;
                 trace!("Called pwrite({:?}, {:?}, {:?}, {:?})", fd, buf, count, offset);
                 this.write(fd, buf, count, Some(offset), dest)?;
             }
             "pread64" => {
-                let off64_t = this.libc_ty_layout("off64_t");
-                let [fd, buf, count, offset] = this.check_shim_abi(
+                let [fd, buf, count, offset] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(i32, *mut _, usize, libc::off64_t) -> isize),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [
-                        this.tcx.types.i32,
-                        this.machine.layouts.mut_raw_ptr.ty,
-                        this.tcx.types.usize,
-                        off64_t.ty,
-                    ],
-                    this.tcx.types.isize,
                     args,
                 )?;
                 let fd = this.read_scalar(fd)?.to_i32()?;
                 let buf = this.read_pointer(buf)?;
                 let count = this.read_target_usize(count)?;
-                let offset = this.read_scalar(offset)?.to_int(off64_t.size)?;
+                let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?;
                 this.read(fd, buf, count, Some(offset), dest)?;
             }
             "pwrite64" => {
-                let off64_t = this.libc_ty_layout("off64_t");
-                let [fd, buf, n, offset] = this.check_shim_abi(
+                let [fd, buf, n, offset] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(i32, *const _, usize, libc::off64_t) -> isize),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [
-                        this.tcx.types.i32,
-                        this.machine.layouts.const_raw_ptr.ty,
-                        this.tcx.types.usize,
-                        off64_t.ty,
-                    ],
-                    this.tcx.types.isize,
                     args,
                 )?;
                 let fd = this.read_scalar(fd)?.to_i32()?;
                 let buf = this.read_pointer(buf)?;
                 let count = this.read_target_usize(n)?;
-                let offset = this.read_scalar(offset)?.to_int(off64_t.size)?;
+                let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?;
                 trace!("Called pwrite64({:?}, {:?}, {:?}, {:?})", fd, buf, count, offset);
                 this.write(fd, buf, count, Some(offset), dest)?;
             }
             "close" => {
-                let [fd] = this.check_shim_abi(
+                let [fd] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(i32) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.tcx.types.i32],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let result = this.close(fd)?;
@@ -333,17 +273,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             "fcntl" => {
                 let ([fd_num, cmd], varargs) =
-                    this.check_shim_variadic(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_variadic_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.fcntl(fd_num, cmd, varargs)?;
                 this.write_scalar(result, dest)?;
             }
             "dup" => {
-                let [old_fd] = this.check_shim_abi(
+                let [old_fd] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(i32) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.tcx.types.i32],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let old_fd = this.read_scalar(old_fd)?.to_i32()?;
@@ -351,12 +289,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(new_fd, dest)?;
             }
             "dup2" => {
-                let [old_fd, new_fd] = this.check_shim_abi(
+                let [old_fd, new_fd] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(i32, i32) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.tcx.types.i32, this.tcx.types.i32],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let old_fd = this.read_scalar(old_fd)?.to_i32()?;
@@ -367,12 +303,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "flock" => {
                 // Currently this function does not exist on all Unixes, e.g. on Solaris.
                 this.check_target_os(&["linux", "freebsd", "macos", "illumos"], link_name)?;
-                let [fd, op] = this.check_shim_abi(
+                let [fd, op] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(i32, i32) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.tcx.types.i32, this.tcx.types.i32],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let fd = this.read_scalar(fd)?.to_i32()?;
@@ -386,230 +320,187 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 // `open` is variadic, the third argument is only present when the second argument
                 // has O_CREAT (or on linux O_TMPFILE, but miri doesn't support that) set
                 let ([path_raw, flag], varargs) =
-                    this.check_shim_variadic(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_variadic_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.open(path_raw, flag, varargs)?;
                 this.write_scalar(result, dest)?;
             }
             "unlink" => {
-                let [path] = this.check_shim_abi(
+                let [path] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(*const _) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.machine.layouts.const_raw_ptr.ty],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let result = this.unlink(path)?;
                 this.write_scalar(result, dest)?;
             }
             "symlink" => {
-                let [target, linkpath] = this.check_shim_abi(
+                let [target, linkpath] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(*const _, *const _) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.machine.layouts.const_raw_ptr.ty, this.machine.layouts.const_raw_ptr.ty],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let result = this.symlink(target, linkpath)?;
                 this.write_scalar(result, dest)?;
             }
             "rename" => {
-                let [oldpath, newpath] = this.check_shim_abi(
+                let [oldpath, newpath] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(*const _, *const _) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.machine.layouts.const_raw_ptr.ty, this.machine.layouts.const_raw_ptr.ty],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let result = this.rename(oldpath, newpath)?;
                 this.write_scalar(result, dest)?;
             }
             "mkdir" => {
-                let [path, mode] = this.check_shim_abi(
+                let [path, mode] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(*const _, libc::mode_t) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.machine.layouts.const_raw_ptr.ty, this.libc_ty_layout("mode_t").ty],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let result = this.mkdir(path, mode)?;
                 this.write_scalar(result, dest)?;
             }
             "rmdir" => {
-                let [path] = this.check_shim_abi(
+                let [path] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(*const _) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.machine.layouts.const_raw_ptr.ty],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let result = this.rmdir(path)?;
                 this.write_scalar(result, dest)?;
             }
             "opendir" => {
-                let [name] = this.check_shim_abi(
+                let [name] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(*const _) -> *mut _),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.machine.layouts.const_raw_ptr.ty],
-                    this.machine.layouts.mut_raw_ptr.ty,
                     args,
                 )?;
                 let result = this.opendir(name)?;
                 this.write_scalar(result, dest)?;
             }
             "closedir" => {
-                let [dirp] = this.check_shim_abi(
+                let [dirp] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(*mut _) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.machine.layouts.mut_raw_ptr.ty],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let result = this.closedir(dirp)?;
                 this.write_scalar(result, dest)?;
             }
             "lseek64" => {
-                let off64_t = this.libc_ty_layout("off64_t");
-                let [fd, offset, whence] = this.check_shim_abi(
+                let [fd, offset, whence] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(i32, libc::off64_t, i32) -> libc::off64_t),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.tcx.types.i32, off64_t.ty, this.tcx.types.i32],
-                    off64_t.ty,
                     args,
                 )?;
                 let fd = this.read_scalar(fd)?.to_i32()?;
-                let offset = this.read_scalar(offset)?.to_int(off64_t.size)?;
+                let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?;
                 let whence = this.read_scalar(whence)?.to_i32()?;
                 this.lseek64(fd, offset, whence, dest)?;
             }
             "lseek" => {
-                let off_t = this.libc_ty_layout("off_t");
-                let [fd, offset, whence] = this.check_shim_abi(
+                let [fd, offset, whence] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(i32, libc::off_t, i32) -> libc::off_t),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.tcx.types.i32, off_t.ty, this.tcx.types.i32],
-                    off_t.ty,
                     args,
                 )?;
                 let fd = this.read_scalar(fd)?.to_i32()?;
-                let offset = this.read_scalar(offset)?.to_int(off_t.size)?;
+                let offset = this.read_scalar(offset)?.to_int(offset.layout.size)?;
                 let whence = this.read_scalar(whence)?.to_i32()?;
                 this.lseek64(fd, offset, whence, dest)?;
             }
             "ftruncate64" => {
-                let off64_t = this.libc_ty_layout("off64_t");
-                let [fd, length] = this.check_shim_abi(
+                let [fd, length] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(i32, libc::off64_t) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.tcx.types.i32, off64_t.ty],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let fd = this.read_scalar(fd)?.to_i32()?;
-                let length = this.read_scalar(length)?.to_int(off64_t.size)?;
+                let length = this.read_scalar(length)?.to_int(length.layout.size)?;
                 let result = this.ftruncate64(fd, length)?;
                 this.write_scalar(result, dest)?;
             }
             "ftruncate" => {
-                let off_t = this.libc_ty_layout("off_t");
-                let [fd, length] = this.check_shim_abi(
+                let [fd, length] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(i32, libc::off_t) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.tcx.types.i32, off_t.ty],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let fd = this.read_scalar(fd)?.to_i32()?;
-                let length = this.read_scalar(length)?.to_int(off_t.size)?;
+                let length = this.read_scalar(length)?.to_int(length.layout.size)?;
                 let result = this.ftruncate64(fd, length)?;
                 this.write_scalar(result, dest)?;
             }
             "fsync" => {
-                let [fd] = this.check_shim_abi(
+                let [fd] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(i32) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.tcx.types.i32],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let result = this.fsync(fd)?;
                 this.write_scalar(result, dest)?;
             }
             "fdatasync" => {
-                let [fd] = this.check_shim_abi(
+                let [fd] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(i32) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.tcx.types.i32],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let result = this.fdatasync(fd)?;
                 this.write_scalar(result, dest)?;
             }
             "readlink" => {
-                let [pathname, buf, bufsize] = this.check_shim_abi(
+                let [pathname, buf, bufsize] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(*const _, *mut _, usize) -> isize),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [
-                        this.machine.layouts.const_raw_ptr.ty,
-                        this.machine.layouts.mut_raw_ptr.ty,
-                        this.tcx.types.usize,
-                    ],
-                    this.tcx.types.isize,
                     args,
                 )?;
                 let result = this.readlink(pathname, buf, bufsize)?;
                 this.write_scalar(Scalar::from_target_isize(result, this), dest)?;
             }
             "posix_fadvise" => {
-                let off_t = this.libc_ty_layout("off_t");
-                let [fd, offset, len, advice] = this.check_shim_abi(
+                let [fd, offset, len, advice] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(i32, libc::off_t, libc::off_t, i32) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.tcx.types.i32, off_t.ty, off_t.ty, this.tcx.types.i32],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 this.read_scalar(fd)?.to_i32()?;
-                this.read_scalar(offset)?.to_int(off_t.size)?;
-                this.read_scalar(len)?.to_int(off_t.size)?;
+                this.read_scalar(offset)?.to_int(offset.layout.size)?;
+                this.read_scalar(len)?.to_int(len.layout.size)?;
                 this.read_scalar(advice)?.to_i32()?;
                 // fadvise is only informational, we can ignore it.
                 this.write_null(dest)?;
             }
             "realpath" => {
-                let [path, resolved_path] = this.check_shim_abi(
+                let [path, resolved_path] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(*const _, *mut _) -> *mut _),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.machine.layouts.const_raw_ptr.ty, this.machine.layouts.mut_raw_ptr.ty],
-                    this.machine.layouts.mut_raw_ptr.ty,
                     args,
                 )?;
                 let result = this.realpath(path, resolved_path)?;
                 this.write_scalar(result, dest)?;
             }
             "mkstemp" => {
-                let [template] = this.check_shim_abi(
+                let [template] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(*mut _) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.machine.layouts.mut_raw_ptr.ty],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let result = this.mkstemp(template)?;
@@ -618,29 +509,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // Unnamed sockets and pipes
             "socketpair" => {
-                let [domain, type_, protocol, sv] = this.check_shim_abi(
+                let [domain, type_, protocol, sv] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(i32, i32, i32, *mut _) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [
-                        this.tcx.types.i32,
-                        this.tcx.types.i32,
-                        this.tcx.types.i32,
-                        this.machine.layouts.mut_raw_ptr.ty,
-                    ],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let result = this.socketpair(domain, type_, protocol, sv)?;
                 this.write_scalar(result, dest)?;
             }
             "pipe" => {
-                let [pipefd] = this.check_shim_abi(
+                let [pipefd] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(*mut _) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.machine.layouts.mut_raw_ptr.ty],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let result = this.pipe2(pipefd, /*flags*/ None)?;
@@ -649,12 +531,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "pipe2" => {
                 // Currently this function does not exist on all Unixes, e.g. on macOS.
                 this.check_target_os(&["linux", "freebsd", "solaris", "illumos"], link_name)?;
-                let [pipefd, flags] = this.check_shim_abi(
+                let [pipefd, flags] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(*mut _, i32) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.machine.layouts.mut_raw_ptr.ty, this.tcx.types.i32],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let result = this.pipe2(pipefd, Some(flags))?;
@@ -663,36 +543,30 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // Time
             "gettimeofday" => {
-                let [tv, tz] = this.check_shim_abi(
+                let [tv, tz] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(*mut _, *mut _) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.machine.layouts.mut_raw_ptr.ty, this.machine.layouts.mut_raw_ptr.ty],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 let result = this.gettimeofday(tv, tz)?;
                 this.write_scalar(result, dest)?;
             }
             "localtime_r" => {
-                let [timep, result_op] = this.check_shim_abi(
+                let [timep, result_op] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(*const _, *mut _) -> *mut _),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.machine.layouts.const_raw_ptr.ty, this.machine.layouts.mut_raw_ptr.ty],
-                    this.machine.layouts.mut_raw_ptr.ty,
                     args,
                 )?;
                 let result = this.localtime_r(timep, result_op)?;
                 this.write_pointer(result, dest)?;
             }
             "clock_gettime" => {
-                let [clk_id, tp] = this.check_shim_abi(
+                let [clk_id, tp] = this.check_shim_sig(
+                    shim_sig!(extern "C" fn(libc::clockid_t, *mut _) -> i32),
                     link_name,
                     abi,
-                    ExternAbi::C { unwind: false },
-                    [this.libc_ty_layout("clockid_t").ty, this.machine.layouts.mut_raw_ptr.ty],
-                    this.tcx.types.i32,
                     args,
                 )?;
                 this.clock_gettime(clk_id, tp, dest)?;
@@ -700,20 +574,22 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // Allocation
             "posix_memalign" => {
-                let [memptr, align, size] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [memptr, align, size] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.posix_memalign(memptr, align, size)?;
                 this.write_scalar(result, dest)?;
             }
 
             "mmap" => {
                 let [addr, length, prot, flags, fd, offset] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let offset = this.read_scalar(offset)?.to_int(this.libc_ty_layout("off_t").size)?;
                 let ptr = this.mmap(addr, length, prot, flags, fd, offset)?;
                 this.write_scalar(ptr, dest)?;
             }
             "munmap" => {
-                let [addr, length] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [addr, length] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.munmap(addr, length)?;
                 this.write_scalar(result, dest)?;
             }
@@ -721,7 +597,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "reallocarray" => {
                 // Currently this function does not exist on all Unixes, e.g. on macOS.
                 this.check_target_os(&["linux", "freebsd", "android"], link_name)?;
-                let [ptr, nmemb, size] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [ptr, nmemb, size] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let ptr = this.read_pointer(ptr)?;
                 let nmemb = this.read_target_usize(nmemb)?;
                 let size = this.read_target_usize(size)?;
@@ -744,14 +621,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "aligned_alloc" => {
                 // This is a C11 function, we assume all Unixes have it.
                 // (MSVC explicitly does not support this.)
-                let [align, size] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [align, size] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let res = this.aligned_alloc(align, size)?;
                 this.write_pointer(res, dest)?;
             }
 
             // Dynamic symbol loading
             "dlsym" => {
-                let [handle, symbol] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [handle, symbol] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.read_target_usize(handle)?;
                 let symbol = this.read_pointer(symbol)?;
                 let name = this.read_c_str(symbol)?;
@@ -767,7 +646,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // Thread-local storage
             "pthread_key_create" => {
-                let [key, dtor] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [key, dtor] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let key_place = this.deref_pointer_as(key, this.libc_ty_layout("pthread_key_t"))?;
                 let dtor = this.read_pointer(dtor)?;
 
@@ -795,21 +674,22 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_null(dest)?;
             }
             "pthread_key_delete" => {
-                let [key] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [key] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let key = this.read_scalar(key)?.to_bits(key.layout.size)?;
                 this.machine.tls.delete_tls_key(key)?;
                 // Return success (0)
                 this.write_null(dest)?;
             }
             "pthread_getspecific" => {
-                let [key] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [key] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let key = this.read_scalar(key)?.to_bits(key.layout.size)?;
                 let active_thread = this.active_thread();
                 let ptr = this.machine.tls.load_tls(key, active_thread, this)?;
                 this.write_scalar(ptr, dest)?;
             }
             "pthread_setspecific" => {
-                let [key, new_ptr] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [key, new_ptr] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let key = this.read_scalar(key)?.to_bits(key.layout.size)?;
                 let active_thread = this.active_thread();
                 let new_data = this.read_scalar(new_ptr)?;
@@ -821,117 +701,124 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // Synchronization primitives
             "pthread_mutexattr_init" => {
-                let [attr] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [attr] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_mutexattr_init(attr)?;
                 this.write_null(dest)?;
             }
             "pthread_mutexattr_settype" => {
-                let [attr, kind] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [attr, kind] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.pthread_mutexattr_settype(attr, kind)?;
                 this.write_scalar(result, dest)?;
             }
             "pthread_mutexattr_destroy" => {
-                let [attr] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [attr] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_mutexattr_destroy(attr)?;
                 this.write_null(dest)?;
             }
             "pthread_mutex_init" => {
-                let [mutex, attr] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [mutex, attr] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_mutex_init(mutex, attr)?;
                 this.write_null(dest)?;
             }
             "pthread_mutex_lock" => {
-                let [mutex] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [mutex] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_mutex_lock(mutex, dest)?;
             }
             "pthread_mutex_trylock" => {
-                let [mutex] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [mutex] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.pthread_mutex_trylock(mutex)?;
                 this.write_scalar(result, dest)?;
             }
             "pthread_mutex_unlock" => {
-                let [mutex] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [mutex] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.pthread_mutex_unlock(mutex)?;
                 this.write_scalar(result, dest)?;
             }
             "pthread_mutex_destroy" => {
-                let [mutex] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [mutex] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_mutex_destroy(mutex)?;
                 this.write_int(0, dest)?;
             }
             "pthread_rwlock_rdlock" => {
-                let [rwlock] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [rwlock] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_rwlock_rdlock(rwlock, dest)?;
             }
             "pthread_rwlock_tryrdlock" => {
-                let [rwlock] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [rwlock] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.pthread_rwlock_tryrdlock(rwlock)?;
                 this.write_scalar(result, dest)?;
             }
             "pthread_rwlock_wrlock" => {
-                let [rwlock] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [rwlock] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_rwlock_wrlock(rwlock, dest)?;
             }
             "pthread_rwlock_trywrlock" => {
-                let [rwlock] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [rwlock] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.pthread_rwlock_trywrlock(rwlock)?;
                 this.write_scalar(result, dest)?;
             }
             "pthread_rwlock_unlock" => {
-                let [rwlock] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [rwlock] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_rwlock_unlock(rwlock)?;
                 this.write_null(dest)?;
             }
             "pthread_rwlock_destroy" => {
-                let [rwlock] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [rwlock] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_rwlock_destroy(rwlock)?;
                 this.write_null(dest)?;
             }
             "pthread_condattr_init" => {
-                let [attr] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [attr] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_condattr_init(attr)?;
                 this.write_null(dest)?;
             }
             "pthread_condattr_setclock" => {
-                let [attr, clock_id] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [attr, clock_id] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.pthread_condattr_setclock(attr, clock_id)?;
                 this.write_scalar(result, dest)?;
             }
             "pthread_condattr_getclock" => {
-                let [attr, clock_id] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [attr, clock_id] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_condattr_getclock(attr, clock_id)?;
                 this.write_null(dest)?;
             }
             "pthread_condattr_destroy" => {
-                let [attr] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [attr] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_condattr_destroy(attr)?;
                 this.write_null(dest)?;
             }
             "pthread_cond_init" => {
-                let [cond, attr] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [cond, attr] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_cond_init(cond, attr)?;
                 this.write_null(dest)?;
             }
             "pthread_cond_signal" => {
-                let [cond] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [cond] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_cond_signal(cond)?;
                 this.write_null(dest)?;
             }
             "pthread_cond_broadcast" => {
-                let [cond] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [cond] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_cond_broadcast(cond)?;
                 this.write_null(dest)?;
             }
             "pthread_cond_wait" => {
-                let [cond, mutex] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [cond, mutex] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_cond_wait(cond, mutex, dest)?;
             }
             "pthread_cond_timedwait" => {
-                let [cond, mutex, abstime] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [cond, mutex, abstime] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_cond_timedwait(cond, mutex, abstime, dest)?;
             }
             "pthread_cond_destroy" => {
-                let [cond] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [cond] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_cond_destroy(cond)?;
                 this.write_null(dest)?;
             }
@@ -939,31 +826,33 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Threading
             "pthread_create" => {
                 let [thread, attr, start, arg] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_create(thread, attr, start, arg)?;
                 this.write_null(dest)?;
             }
             "pthread_join" => {
-                let [thread, retval] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [thread, retval] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.pthread_join(thread, retval, dest)?;
             }
             "pthread_detach" => {
-                let [thread] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [thread] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let res = this.pthread_detach(thread)?;
                 this.write_scalar(res, dest)?;
             }
             "pthread_self" => {
-                let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let res = this.pthread_self()?;
                 this.write_scalar(res, dest)?;
             }
             "sched_yield" => {
-                let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.sched_yield()?;
                 this.write_null(dest)?;
             }
             "nanosleep" => {
-                let [duration, rem] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [duration, rem] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.nanosleep(duration, rem)?;
                 this.write_scalar(result, dest)?;
             }
@@ -974,14 +863,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     link_name,
                 )?;
                 let [clock_id, flags, req, rem] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.clock_nanosleep(clock_id, flags, req, rem)?;
                 this.write_scalar(result, dest)?;
             }
             "sched_getaffinity" => {
                 // Currently this function does not exist on all Unixes, e.g. on macOS.
                 this.check_target_os(&["linux", "freebsd", "android"], link_name)?;
-                let [pid, cpusetsize, mask] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [pid, cpusetsize, mask] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let pid = this.read_scalar(pid)?.to_u32()?;
                 let cpusetsize = this.read_target_usize(cpusetsize)?;
                 let mask = this.read_pointer(mask)?;
@@ -1018,7 +908,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "sched_setaffinity" => {
                 // Currently this function does not exist on all Unixes, e.g. on macOS.
                 this.check_target_os(&["linux", "freebsd", "android"], link_name)?;
-                let [pid, cpusetsize, mask] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [pid, cpusetsize, mask] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let pid = this.read_scalar(pid)?.to_u32()?;
                 let cpusetsize = this.read_target_usize(cpusetsize)?;
                 let mask = this.read_pointer(mask)?;
@@ -1058,13 +949,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // Miscellaneous
             "isatty" => {
-                let [fd] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [fd] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.isatty(fd)?;
                 this.write_scalar(result, dest)?;
             }
             "pthread_atfork" => {
                 let [prepare, parent, child] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.read_pointer(prepare)?;
                 this.read_pointer(parent)?;
                 this.read_pointer(child)?;
@@ -1078,7 +969,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     &["linux", "macos", "freebsd", "illumos", "solaris", "android"],
                     link_name,
                 )?;
-                let [buf, bufsize] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [buf, bufsize] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let buf = this.read_pointer(buf)?;
                 let bufsize = this.read_target_usize(bufsize)?;
 
@@ -1096,7 +988,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
 
             "strerror_r" => {
-                let [errnum, buf, buflen] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [errnum, buf, buflen] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.strerror_r(errnum, buf, buflen)?;
                 this.write_scalar(result, dest)?;
             }
@@ -1108,7 +1001,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     &["linux", "freebsd", "illumos", "solaris", "android"],
                     link_name,
                 )?;
-                let [ptr, len, flags] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [ptr, len, flags] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let ptr = this.read_pointer(ptr)?;
                 let len = this.read_target_usize(len)?;
                 let _flags = this.read_scalar(flags)?.to_i32()?;
@@ -1120,7 +1014,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 // This function is non-standard but exists with the same signature and
                 // same behavior (eg never fails) on FreeBSD and Solaris/Illumos.
                 this.check_target_os(&["freebsd", "illumos", "solaris"], link_name)?;
-                let [ptr, len] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [ptr, len] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let ptr = this.read_pointer(ptr)?;
                 let len = this.read_target_usize(len)?;
                 this.gen_random(ptr, len)?;
@@ -1144,12 +1038,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     link_name,
                 )?;
                 // This function looks and behaves excatly like miri_start_unwind.
-                let [payload] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [payload] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.handle_miri_start_unwind(payload)?;
                 return interp_ok(EmulateItemResult::NeedsUnwind);
             }
             "getuid" | "geteuid" => {
-                let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 // For now, just pretend we always have this fixed UID.
                 this.write_int(UID, dest)?;
             }
@@ -1157,7 +1051,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Incomplete shims that we "stub out" just to get pre-main initialization code to work.
             // These shims are enabled only when the caller is in the standard library.
             "pthread_attr_getguardsize" if this.frame_in_std() => {
-                let [_attr, guard_size] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [_attr, guard_size] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let guard_size_layout = this.machine.layouts.usize;
                 let guard_size = this.deref_pointer_as(guard_size, guard_size_layout)?;
                 this.write_scalar(
@@ -1170,11 +1065,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
 
             "pthread_attr_init" | "pthread_attr_destroy" if this.frame_in_std() => {
-                let [_] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [_] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.write_null(dest)?;
             }
             "pthread_attr_setstacksize" if this.frame_in_std() => {
-                let [_, _] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [_, _] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.write_null(dest)?;
             }
 
@@ -1182,7 +1077,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 // We don't support "pthread_attr_setstack", so we just pretend all stacks have the same values here.
                 // Hence we can mostly ignore the input `attr_place`.
                 let [attr_place, addr_place, size_place] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let _attr_place =
                     this.deref_pointer_as(attr_place, this.libc_ty_layout("pthread_attr_t"))?;
                 let addr_place = this.deref_pointer_as(addr_place, this.machine.layouts.usize)?;
@@ -1202,18 +1097,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
 
             "signal" | "sigaltstack" if this.frame_in_std() => {
-                let [_, _] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [_, _] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.write_null(dest)?;
             }
             "sigaction" | "mprotect" if this.frame_in_std() => {
-                let [_, _, _] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [_, _, _] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.write_null(dest)?;
             }
 
             "getpwuid_r" | "__posix_getpwuid_r" if this.frame_in_std() => {
                 // getpwuid_r is the standard name, __posix_getpwuid_r is used on solarish
                 let [uid, pwd, buf, buflen, result] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.check_no_isolation("`getpwuid_r`")?;
 
                 let uid = this.read_scalar(uid)?.to_u32()?;
diff --git a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
index 33564a2f84c..9e247053fbc 100644
--- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
@@ -24,7 +24,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         match link_name.as_str() {
             // Threading
             "pthread_setname_np" => {
-                let [thread, name] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [thread, name] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let max_len = u64::MAX; // FreeBSD does not seem to have a limit.
                 let res = match this.pthread_setname_np(
                     this.read_scalar(thread)?,
@@ -39,7 +40,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(res, dest)?;
             }
             "pthread_getname_np" => {
-                let [thread, name, len] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [thread, name, len] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 // FreeBSD's pthread_getname_np uses strlcpy, which truncates the resulting value,
                 // but always adds a null terminator (except for zero-sized buffers).
                 // https://github.com/freebsd/freebsd-src/blob/c2d93a803acef634bd0eede6673aeea59e90c277/lib/libthr/thread/thr_info.c#L119-L144
@@ -57,7 +59,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(res, dest)?;
             }
             "pthread_getthreadid_np" => {
-                let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.unix_gettid(link_name.as_str())?;
                 this.write_scalar(result, dest)?;
             }
@@ -65,7 +67,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "cpuset_getaffinity" => {
                 // The "same" kind of api as `sched_getaffinity` but more fine grained control for FreeBSD specifically.
                 let [level, which, id, set_size, mask] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let level = this.read_scalar(level)?.to_i32()?;
                 let which = this.read_scalar(which)?.to_i32()?;
@@ -129,7 +131,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Synchronization primitives
             "_umtx_op" => {
                 let [obj, op, val, uaddr, uaddr2] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this._umtx_op(obj, op, val, uaddr, uaddr2, dest)?;
             }
 
@@ -137,29 +139,30 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // For those, we both intercept `func` and `call@FBSD_1.0` symbols cases
             // since freebsd 12 the former form can be expected.
             "stat" | "stat@FBSD_1.0" => {
-                let [path, buf] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.macos_fbsd_solarish_stat(path, buf)?;
                 this.write_scalar(result, dest)?;
             }
             "lstat" | "lstat@FBSD_1.0" => {
-                let [path, buf] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.macos_fbsd_solarish_lstat(path, buf)?;
                 this.write_scalar(result, dest)?;
             }
             "fstat" | "fstat@FBSD_1.0" => {
-                let [fd, buf] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [fd, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.macos_fbsd_solarish_fstat(fd, buf)?;
                 this.write_scalar(result, dest)?;
             }
             "readdir_r" | "readdir_r@FBSD_1.0" => {
-                let [dirp, entry, result] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [dirp, entry, result] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.macos_fbsd_readdir_r(dirp, entry, result)?;
                 this.write_scalar(result, dest)?;
             }
 
             // Miscellaneous
             "__error" => {
-                let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let errno_place = this.last_error_place()?;
                 this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?;
             }
@@ -167,7 +170,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Incomplete shims that we "stub out" just to get pre-main initialization code to work.
             // These shims are enabled only when the caller is in the standard library.
             "pthread_attr_get_np" if this.frame_in_std() => {
-                let [_thread, _attr] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [_thread, _attr] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.write_null(dest)?;
             }
 
diff --git a/src/tools/miri/src/shims/unix/freebsd/sync.rs b/src/tools/miri/src/shims/unix/freebsd/sync.rs
index f4e7d9e58f9..13d30e05573 100644
--- a/src/tools/miri/src/shims/unix/freebsd/sync.rs
+++ b/src/tools/miri/src/shims/unix/freebsd/sync.rs
@@ -228,26 +228,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         let abs_time_flag = flags == abs_time;
 
         let clock_id_place = this.project_field(ut, FieldIdx::from_u32(2))?;
-        let clock_id = this.read_scalar(&clock_id_place)?.to_i32()?;
-        let timeout_clock = this.translate_umtx_time_clock_id(clock_id)?;
+        let clock_id = this.read_scalar(&clock_id_place)?;
+        let Some(timeout_clock) = this.parse_clockid(clock_id) else {
+            throw_unsup_format!("unsupported clock")
+        };
+        if timeout_clock == TimeoutClock::RealTime {
+            this.check_no_isolation("`_umtx_op` with `CLOCK_REALTIME`")?;
+        }
 
         interp_ok(Some(UmtxTime { timeout: duration, abs_time: abs_time_flag, timeout_clock }))
     }
-
-    /// Translate raw FreeBSD clockid to a Miri TimeoutClock.
-    /// FIXME: share this code with the pthread and clock_gettime shims.
-    fn translate_umtx_time_clock_id(&mut self, raw_id: i32) -> InterpResult<'tcx, TimeoutClock> {
-        let this = self.eval_context_mut();
-
-        let timeout = if raw_id == this.eval_libc_i32("CLOCK_REALTIME") {
-            // RealTime clock can't be used in isolation mode.
-            this.check_no_isolation("`_umtx_op` with `CLOCK_REALTIME` timeout")?;
-            TimeoutClock::RealTime
-        } else if raw_id == this.eval_libc_i32("CLOCK_MONOTONIC") {
-            TimeoutClock::Monotonic
-        } else {
-            throw_unsup_format!("unsupported clock id {raw_id}");
-        };
-        interp_ok(timeout)
-    }
 }
diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs
index 0f2878ad26c..f9bcacf64c4 100644
--- a/src/tools/miri/src/shims/unix/fs.rs
+++ b/src/tools/miri/src/shims/unix/fs.rs
@@ -13,9 +13,9 @@ use rustc_abi::Size;
 use rustc_data_structures::fx::FxHashMap;
 
 use self::shims::time::system_time_to_duration;
-use crate::helpers::check_min_vararg_count;
 use crate::shims::files::FileHandle;
 use crate::shims::os_str::bytes_to_os_str;
+use crate::shims::sig::check_min_vararg_count;
 use crate::shims::unix::fd::{FlockOp, UnixFileDescription};
 use crate::*;
 
diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs
index b3e99e6cc68..e7e0c3b6ecd 100644
--- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs
@@ -37,48 +37,50 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         match link_name.as_str() {
             // File related shims
             "readdir64" => {
-                let [dirp] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [dirp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.linux_solarish_readdir64("dirent64", dirp)?;
                 this.write_scalar(result, dest)?;
             }
             "sync_file_range" => {
                 let [fd, offset, nbytes, flags] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.sync_file_range(fd, offset, nbytes, flags)?;
                 this.write_scalar(result, dest)?;
             }
             "statx" => {
                 let [dirfd, pathname, flags, mask, statxbuf] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.linux_statx(dirfd, pathname, flags, mask, statxbuf)?;
                 this.write_scalar(result, dest)?;
             }
 
             // epoll, eventfd
             "epoll_create1" => {
-                let [flag] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [flag] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.epoll_create1(flag)?;
                 this.write_scalar(result, dest)?;
             }
             "epoll_ctl" => {
-                let [epfd, op, fd, event] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [epfd, op, fd, event] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.epoll_ctl(epfd, op, fd, event)?;
                 this.write_scalar(result, dest)?;
             }
             "epoll_wait" => {
                 let [epfd, events, maxevents, timeout] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.epoll_wait(epfd, events, maxevents, timeout, dest)?;
             }
             "eventfd" => {
-                let [val, flag] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [val, flag] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.eventfd(val, flag)?;
                 this.write_scalar(result, dest)?;
             }
 
             // Threading
             "pthread_setname_np" => {
-                let [thread, name] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [thread, name] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let res = match this.pthread_setname_np(
                     this.read_scalar(thread)?,
                     this.read_scalar(name)?,
@@ -93,7 +95,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(res, dest)?;
             }
             "pthread_getname_np" => {
-                let [thread, name, len] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [thread, name, len] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 // The function's behavior isn't portable between platforms.
                 // In case of glibc, the length of the output buffer must
                 // be not shorter than TASK_COMM_LEN.
@@ -116,7 +119,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(res, dest)?;
             }
             "gettid" => {
-                let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.unix_gettid(link_name.as_str())?;
                 this.write_scalar(result, dest)?;
             }
@@ -129,34 +132,35 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Miscellaneous
             "mmap64" => {
                 let [addr, length, prot, flags, fd, offset] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let offset = this.read_scalar(offset)?.to_i64()?;
                 let ptr = this.mmap(addr, length, prot, flags, fd, offset.into())?;
                 this.write_scalar(ptr, dest)?;
             }
             "mremap" => {
                 let ([old_address, old_size, new_size, flags], _) =
-                    this.check_shim_variadic(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_variadic_lenient(abi, CanonAbi::C, link_name, args)?;
                 let ptr = this.mremap(old_address, old_size, new_size, flags)?;
                 this.write_scalar(ptr, dest)?;
             }
             "__xpg_strerror_r" => {
-                let [errnum, buf, buflen] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [errnum, buf, buflen] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.strerror_r(errnum, buf, buflen)?;
                 this.write_scalar(result, dest)?;
             }
             "__errno_location" => {
-                let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let errno_place = this.last_error_place()?;
                 this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?;
             }
             "__libc_current_sigrtmin" => {
-                let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 this.write_int(SIGRTMIN, dest)?;
             }
             "__libc_current_sigrtmax" => {
-                let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 this.write_int(SIGRTMAX, dest)?;
             }
@@ -164,7 +168,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Incomplete shims that we "stub out" just to get pre-main initialization code to work.
             // These shims are enabled only when the caller is in the standard library.
             "pthread_getattr_np" if this.frame_in_std() => {
-                let [_thread, _attr] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [_thread, _attr] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.write_null(dest)?;
             }
 
diff --git a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs
index ee7deb8d383..2d35ef064db 100644
--- a/src/tools/miri/src/shims/unix/linux_like/eventfd.rs
+++ b/src/tools/miri/src/shims/unix/linux_like/eventfd.rs
@@ -37,6 +37,11 @@ impl FileDescription for EventFd {
         "event"
     }
 
+    fn nondet_short_accesses(&self) -> bool {
+        // We always read and write exactly one `u64`.
+        false
+    }
+
     fn close<'tcx>(
         self,
         _communicate_allowed: bool,
diff --git a/src/tools/miri/src/shims/unix/linux_like/sync.rs b/src/tools/miri/src/shims/unix/linux_like/sync.rs
index 9fad74c0241..5f032c52dee 100644
--- a/src/tools/miri/src/shims/unix/linux_like/sync.rs
+++ b/src/tools/miri/src/shims/unix/linux_like/sync.rs
@@ -1,5 +1,5 @@
 use crate::concurrency::sync::FutexRef;
-use crate::helpers::check_min_vararg_count;
+use crate::shims::sig::check_min_vararg_count;
 use crate::*;
 
 struct LinuxFutex {
diff --git a/src/tools/miri/src/shims/unix/linux_like/syscall.rs b/src/tools/miri/src/shims/unix/linux_like/syscall.rs
index d3534e6e1bc..106e6c448d0 100644
--- a/src/tools/miri/src/shims/unix/linux_like/syscall.rs
+++ b/src/tools/miri/src/shims/unix/linux_like/syscall.rs
@@ -3,7 +3,7 @@ use rustc_middle::ty::Ty;
 use rustc_span::Symbol;
 use rustc_target::callconv::FnAbi;
 
-use crate::helpers::check_min_vararg_count;
+use crate::shims::sig::check_min_vararg_count;
 use crate::shims::unix::env::EvalContextExt;
 use crate::shims::unix::linux_like::eventfd::EvalContextExt as _;
 use crate::shims::unix::linux_like::sync::futex;
@@ -16,7 +16,7 @@ pub fn syscall<'tcx>(
     args: &[OpTy<'tcx>],
     dest: &MPlaceTy<'tcx>,
 ) -> InterpResult<'tcx> {
-    let ([op], varargs) = ecx.check_shim_variadic(abi, CanonAbi::C, link_name, args)?;
+    let ([op], varargs) = ecx.check_shim_sig_variadic_lenient(abi, CanonAbi::C, link_name, args)?;
     // The syscall variadic function is legal to call with more arguments than needed,
     // extra arguments are simply ignored. The important check is that when we use an
     // argument, we have to also check all arguments *before* it to ensure that they
diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs
index 23303718091..297d903c6ba 100644
--- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs
@@ -35,64 +35,67 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         match link_name.as_str() {
             // errno
             "__error" => {
-                let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let errno_place = this.last_error_place()?;
                 this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?;
             }
 
             // File related shims
             "close$NOCANCEL" => {
-                let [result] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [result] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.close(result)?;
                 this.write_scalar(result, dest)?;
             }
             "stat" | "stat64" | "stat$INODE64" => {
-                let [path, buf] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.macos_fbsd_solarish_stat(path, buf)?;
                 this.write_scalar(result, dest)?;
             }
             "lstat" | "lstat64" | "lstat$INODE64" => {
-                let [path, buf] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.macos_fbsd_solarish_lstat(path, buf)?;
                 this.write_scalar(result, dest)?;
             }
             "fstat" | "fstat64" | "fstat$INODE64" => {
-                let [fd, buf] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [fd, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.macos_fbsd_solarish_fstat(fd, buf)?;
                 this.write_scalar(result, dest)?;
             }
             "opendir$INODE64" => {
-                let [name] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [name] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.opendir(name)?;
                 this.write_scalar(result, dest)?;
             }
             "readdir_r" | "readdir_r$INODE64" => {
-                let [dirp, entry, result] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [dirp, entry, result] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.macos_fbsd_readdir_r(dirp, entry, result)?;
                 this.write_scalar(result, dest)?;
             }
             "realpath$DARWIN_EXTSN" => {
-                let [path, resolved_path] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [path, resolved_path] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.realpath(path, resolved_path)?;
                 this.write_scalar(result, dest)?;
             }
             "ioctl" => {
                 let ([fd_num, cmd], varargs) =
-                    this.check_shim_variadic(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_variadic_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.ioctl(fd_num, cmd, varargs)?;
                 this.write_scalar(result, dest)?;
             }
 
             // Environment related shims
             "_NSGetEnviron" => {
-                let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let environ = this.machine.env_vars.unix().environ();
                 this.write_pointer(environ, dest)?;
             }
 
             // Random data generation
             "CCRandomGenerateBytes" => {
-                let [bytes, count] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [bytes, count] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let bytes = this.read_pointer(bytes)?;
                 let count = this.read_target_usize(count)?;
                 let success = this.eval_libc_i32("kCCSuccess");
@@ -102,28 +105,29 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // Time related shims
             "mach_absolute_time" => {
-                let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.mach_absolute_time()?;
                 this.write_scalar(result, dest)?;
             }
 
             "mach_timebase_info" => {
-                let [info] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [info] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.mach_timebase_info(info)?;
                 this.write_scalar(result, dest)?;
             }
 
             // Access to command-line arguments
             "_NSGetArgc" => {
-                let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.write_pointer(this.machine.argc.expect("machine must be initialized"), dest)?;
             }
             "_NSGetArgv" => {
-                let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.write_pointer(this.machine.argv.expect("machine must be initialized"), dest)?;
             }
             "_NSGetExecutablePath" => {
-                let [buf, bufsize] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [buf, bufsize] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.check_no_isolation("`_NSGetExecutablePath`")?;
 
                 let buf_ptr = this.read_pointer(buf)?;
@@ -148,7 +152,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // Thread-local storage
             "_tlv_atexit" => {
-                let [dtor, data] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [dtor, data] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let dtor = this.read_pointer(dtor)?;
                 let dtor = this.get_ptr_fn(dtor)?.as_instance()?;
                 let data = this.read_scalar(data)?;
@@ -158,13 +163,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // Querying system information
             "pthread_get_stackaddr_np" => {
-                let [thread] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [thread] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.read_target_usize(thread)?;
                 let stack_addr = Scalar::from_uint(this.machine.stack_addr, this.pointer_size());
                 this.write_scalar(stack_addr, dest)?;
             }
             "pthread_get_stacksize_np" => {
-                let [thread] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [thread] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.read_target_usize(thread)?;
                 let stack_size = Scalar::from_uint(this.machine.stack_size, this.pointer_size());
                 this.write_scalar(stack_size, dest)?;
@@ -172,7 +177,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // Threading
             "pthread_setname_np" => {
-                let [name] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [name] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 // The real implementation has logic in two places:
                 // * in userland at https://github.com/apple-oss-distributions/libpthread/blob/c032e0b076700a0a47db75528a282b8d3a06531a/src/pthread.c#L1178-L1200,
@@ -199,7 +204,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(res, dest)?;
             }
             "pthread_getname_np" => {
-                let [thread, name, len] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [thread, name, len] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 // The function's behavior isn't portable between platforms.
                 // In case of macOS, a truncated name (due to a too small buffer)
@@ -223,7 +229,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(res, dest)?;
             }
             "pthread_threadid_np" => {
-                let [thread, tid_ptr] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [thread, tid_ptr] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let res = this.apple_pthread_threadip_np(thread, tid_ptr)?;
                 this.write_scalar(res, dest)?;
             }
@@ -231,7 +238,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Synchronization primitives
             "os_sync_wait_on_address" => {
                 let [addr_op, value_op, size_op, flags_op] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.os_sync_wait_on_address(
                     addr_op,
                     value_op,
@@ -243,7 +250,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             "os_sync_wait_on_address_with_deadline" => {
                 let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.os_sync_wait_on_address(
                     addr_op,
                     value_op,
@@ -255,7 +262,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             "os_sync_wait_on_address_with_timeout" => {
                 let [addr_op, value_op, size_op, flags_op, clock_op, timeout_op] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.os_sync_wait_on_address(
                     addr_op,
                     value_op,
@@ -267,36 +274,36 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             "os_sync_wake_by_address_any" => {
                 let [addr_op, size_op, flags_op] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.os_sync_wake_by_address(
                     addr_op, size_op, flags_op, /* all */ false, dest,
                 )?;
             }
             "os_sync_wake_by_address_all" => {
                 let [addr_op, size_op, flags_op] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.os_sync_wake_by_address(
                     addr_op, size_op, flags_op, /* all */ true, dest,
                 )?;
             }
             "os_unfair_lock_lock" => {
-                let [lock_op] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [lock_op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.os_unfair_lock_lock(lock_op)?;
             }
             "os_unfair_lock_trylock" => {
-                let [lock_op] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [lock_op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.os_unfair_lock_trylock(lock_op, dest)?;
             }
             "os_unfair_lock_unlock" => {
-                let [lock_op] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [lock_op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.os_unfair_lock_unlock(lock_op)?;
             }
             "os_unfair_lock_assert_owner" => {
-                let [lock_op] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [lock_op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.os_unfair_lock_assert_owner(lock_op)?;
             }
             "os_unfair_lock_assert_not_owner" => {
-                let [lock_op] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [lock_op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.os_unfair_lock_assert_not_owner(lock_op)?;
             }
 
diff --git a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs
index e3d15b89be6..d7033a65fe2 100644
--- a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs
@@ -27,32 +27,34 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // epoll, eventfd (NOT available on Solaris!)
             "epoll_create1" => {
                 this.assert_target_os("illumos", "epoll_create1");
-                let [flag] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [flag] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.epoll_create1(flag)?;
                 this.write_scalar(result, dest)?;
             }
             "epoll_ctl" => {
                 this.assert_target_os("illumos", "epoll_ctl");
-                let [epfd, op, fd, event] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [epfd, op, fd, event] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.epoll_ctl(epfd, op, fd, event)?;
                 this.write_scalar(result, dest)?;
             }
             "epoll_wait" => {
                 this.assert_target_os("illumos", "epoll_wait");
                 let [epfd, events, maxevents, timeout] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.epoll_wait(epfd, events, maxevents, timeout, dest)?;
             }
             "eventfd" => {
                 this.assert_target_os("illumos", "eventfd");
-                let [val, flag] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [val, flag] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.eventfd(val, flag)?;
                 this.write_scalar(result, dest)?;
             }
 
             // Threading
             "pthread_setname_np" => {
-                let [thread, name] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [thread, name] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 // THREAD_NAME_MAX allows a thread name of 31+1 length
                 // https://github.com/illumos/illumos-gate/blob/7671517e13b8123748eda4ef1ee165c6d9dba7fe/usr/src/uts/common/sys/thread.h#L613
                 let max_len = 32;
@@ -70,7 +72,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(res, dest)?;
             }
             "pthread_getname_np" => {
-                let [thread, name, len] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [thread, name, len] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 // See https://illumos.org/man/3C/pthread_getname_np for the error codes.
                 let res = match this.pthread_getname_np(
                     this.read_scalar(thread)?,
@@ -87,22 +90,22 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // File related shims
             "stat" | "stat64" => {
-                let [path, buf] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.macos_fbsd_solarish_stat(path, buf)?;
                 this.write_scalar(result, dest)?;
             }
             "lstat" | "lstat64" => {
-                let [path, buf] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.macos_fbsd_solarish_lstat(path, buf)?;
                 this.write_scalar(result, dest)?;
             }
             "fstat" | "fstat64" => {
-                let [fd, buf] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [fd, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.macos_fbsd_solarish_fstat(fd, buf)?;
                 this.write_scalar(result, dest)?;
             }
             "readdir" => {
-                let [dirp] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [dirp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.linux_solarish_readdir64("dirent", dirp)?;
                 this.write_scalar(result, dest)?;
             }
@@ -110,20 +113,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Sockets and pipes
             "__xnet_socketpair" => {
                 let [domain, type_, protocol, sv] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.socketpair(domain, type_, protocol, sv)?;
                 this.write_scalar(result, dest)?;
             }
 
             // Miscellaneous
             "___errno" => {
-                let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let errno_place = this.last_error_place()?;
                 this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?;
             }
 
             "stack_getbounds" => {
-                let [stack] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [stack] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let stack = this.deref_pointer_as(stack, this.libc_ty_layout("stack_t"))?;
 
                 this.write_int_fields_named(
@@ -141,7 +144,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
 
             "pset_info" => {
-                let [pset, tpe, cpus, list] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [pset, tpe, cpus, list] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 // We do not need to handle the current process cpu mask, available_parallelism
                 // implementation pass null anyway. We only care for the number of
                 // cpus.
@@ -170,7 +174,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
 
             "__sysconf_xpg7" => {
-                let [val] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [val] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.sysconf(val)?;
                 this.write_scalar(result, dest)?;
             }
diff --git a/src/tools/miri/src/shims/unix/sync.rs b/src/tools/miri/src/shims/unix/sync.rs
index e20e3b79c3b..5ad4fd501a6 100644
--- a/src/tools/miri/src/shims/unix/sync.rs
+++ b/src/tools/miri/src/shims/unix/sync.rs
@@ -297,14 +297,13 @@ fn condattr_clock_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, u
 fn condattr_get_clock_id<'tcx>(
     ecx: &MiriInterpCx<'tcx>,
     attr_ptr: &OpTy<'tcx>,
-) -> InterpResult<'tcx, i32> {
+) -> InterpResult<'tcx, Scalar> {
     ecx.deref_pointer_and_read(
         attr_ptr,
         condattr_clock_offset(ecx)?,
         ecx.libc_ty_layout("pthread_condattr_t"),
         ecx.machine.layouts.i32,
-    )?
-    .to_i32()
+    )
 }
 
 fn condattr_set_clock_id<'tcx>(
@@ -321,20 +320,6 @@ fn condattr_set_clock_id<'tcx>(
     )
 }
 
-/// Translates the clock from what is stored in pthread_condattr_t to our enum.
-fn condattr_translate_clock_id<'tcx>(
-    ecx: &MiriInterpCx<'tcx>,
-    raw_id: i32,
-) -> InterpResult<'tcx, ClockId> {
-    interp_ok(if raw_id == ecx.eval_libc_i32("CLOCK_REALTIME") {
-        ClockId::Realtime
-    } else if raw_id == ecx.eval_libc_i32("CLOCK_MONOTONIC") {
-        ClockId::Monotonic
-    } else {
-        throw_unsup_format!("unsupported clock id: {raw_id}");
-    })
-}
-
 // # pthread_cond_t
 // We store some data directly inside the type, ignoring the platform layout:
 // - init: u32
@@ -363,22 +348,16 @@ fn cond_init_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, Size>
     interp_ok(offset)
 }
 
-#[derive(Debug, Clone, Copy)]
-enum ClockId {
-    Realtime,
-    Monotonic,
-}
-
 #[derive(Debug, Clone)]
 struct PthreadCondvar {
     condvar_ref: CondvarRef,
-    clock: ClockId,
+    clock: TimeoutClock,
 }
 
 fn cond_create<'tcx>(
     ecx: &mut MiriInterpCx<'tcx>,
     cond_ptr: &OpTy<'tcx>,
-    clock: ClockId,
+    clock: TimeoutClock,
 ) -> InterpResult<'tcx, PthreadCondvar> {
     let cond = ecx.deref_pointer_as(cond_ptr, ecx.libc_ty_layout("pthread_cond_t"))?;
     let data = PthreadCondvar { condvar_ref: CondvarRef::new(), clock };
@@ -407,7 +386,10 @@ where
                 throw_unsup_format!("unsupported static initializer used for `pthread_cond_t`");
             }
             // This used the static initializer. The clock there is always CLOCK_REALTIME.
-            interp_ok(PthreadCondvar { condvar_ref: CondvarRef::new(), clock: ClockId::Realtime })
+            interp_ok(PthreadCondvar {
+                condvar_ref: CondvarRef::new(),
+                clock: TimeoutClock::RealTime,
+            })
         },
     )
 }
@@ -742,11 +724,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     ) -> InterpResult<'tcx, Scalar> {
         let this = self.eval_context_mut();
 
-        let clock_id = this.read_scalar(clock_id_op)?.to_i32()?;
-        if clock_id == this.eval_libc_i32("CLOCK_REALTIME")
-            || clock_id == this.eval_libc_i32("CLOCK_MONOTONIC")
-        {
-            condattr_set_clock_id(this, attr_op, clock_id)?;
+        let clock_id = this.read_scalar(clock_id_op)?;
+        if this.parse_clockid(clock_id).is_some() {
+            condattr_set_clock_id(this, attr_op, clock_id.to_i32()?)?;
         } else {
             let einval = this.eval_libc_i32("EINVAL");
             return interp_ok(Scalar::from_i32(einval));
@@ -764,7 +744,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
         let clock_id = condattr_get_clock_id(this, attr_op)?;
         this.write_scalar(
-            Scalar::from_i32(clock_id),
+            clock_id,
             &this.deref_pointer_as(clk_id_op, this.libc_ty_layout("clockid_t"))?,
         )?;
 
@@ -799,13 +779,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         let attr = this.read_pointer(attr_op)?;
         // Default clock if `attr` is null, and on macOS where there is no clock attribute.
         let clock_id = if this.ptr_is_null(attr)? || this.tcx.sess.target.os == "macos" {
-            this.eval_libc_i32("CLOCK_REALTIME")
+            this.eval_libc("CLOCK_REALTIME")
         } else {
             condattr_get_clock_id(this, attr_op)?
         };
-        let clock_id = condattr_translate_clock_id(this, clock_id)?;
+        let Some(clock) = this.parse_clockid(clock_id) else {
+            // This is UB since this situation cannot arise when using pthread_condattr_setclock.
+            throw_ub_format!("pthread_cond_init: invalid attributes (unsupported clock)")
+        };
 
-        cond_create(this, cond_op, clock_id)?;
+        cond_create(this, cond_op, clock)?;
 
         interp_ok(())
     }
@@ -870,18 +853,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 return interp_ok(());
             }
         };
-        let timeout_clock = match data.clock {
-            ClockId::Realtime => {
-                this.check_no_isolation("`pthread_cond_timedwait` with `CLOCK_REALTIME`")?;
-                TimeoutClock::RealTime
-            }
-            ClockId::Monotonic => TimeoutClock::Monotonic,
-        };
+        if data.clock == TimeoutClock::RealTime {
+            this.check_no_isolation("`pthread_cond_timedwait` with `CLOCK_REALTIME`")?;
+        }
 
         this.condvar_wait(
             data.condvar_ref,
             mutex_ref,
-            Some((timeout_clock, TimeoutAnchor::Absolute, duration)),
+            Some((data.clock, TimeoutAnchor::Absolute, duration)),
             Scalar::from_i32(0),
             this.eval_libc("ETIMEDOUT"), // retval_timeout
             dest.clone(),
diff --git a/src/tools/miri/src/shims/unwind.rs b/src/tools/miri/src/shims/unwind.rs
index ba0c50b54b4..0dd2b20487d 100644
--- a/src/tools/miri/src/shims/unwind.rs
+++ b/src/tools/miri/src/shims/unwind.rs
@@ -16,7 +16,6 @@ use rustc_abi::ExternAbi;
 use rustc_middle::mir;
 use rustc_target::spec::PanicStrategy;
 
-use self::helpers::check_intrinsic_arg_count;
 use crate::*;
 
 /// Holds all of the relevant data for when unwinding hits a `try` frame.
@@ -60,7 +59,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     /// Handles the `catch_unwind` intrinsic.
     fn handle_catch_unwind(
         &mut self,
-        args: &[OpTy<'tcx>],
+        try_fn: &OpTy<'tcx>,
+        data: &OpTy<'tcx>,
+        catch_fn: &OpTy<'tcx>,
         dest: &MPlaceTy<'tcx>,
         ret: Option<mir::BasicBlock>,
     ) -> InterpResult<'tcx> {
@@ -78,7 +79,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         // a pointer to `Box<dyn Any + Send + 'static>`.
 
         // Get all the arguments.
-        let [try_fn, data, catch_fn] = check_intrinsic_arg_count(args)?;
         let try_fn = this.read_pointer(try_fn)?;
         let data = this.read_immediate(data)?;
         let catch_fn = this.read_pointer(catch_fn)?;
diff --git a/src/tools/miri/src/shims/wasi/foreign_items.rs b/src/tools/miri/src/shims/wasi/foreign_items.rs
index 8d92d0f3381..bfcdbd8130d 100644
--- a/src/tools/miri/src/shims/wasi/foreign_items.rs
+++ b/src/tools/miri/src/shims/wasi/foreign_items.rs
@@ -23,12 +23,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         match link_name.as_str() {
             // Allocation
             "posix_memalign" => {
-                let [memptr, align, size] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [memptr, align, size] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let result = this.posix_memalign(memptr, align, size)?;
                 this.write_scalar(result, dest)?;
             }
             "aligned_alloc" => {
-                let [align, size] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [align, size] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let res = this.aligned_alloc(align, size)?;
                 this.write_pointer(res, dest)?;
             }
diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs
index 959abc0baca..7b13f1d9080 100644
--- a/src/tools/miri/src/shims/windows/foreign_items.rs
+++ b/src/tools/miri/src/shims/windows/foreign_items.rs
@@ -157,42 +157,44 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         match link_name.as_str() {
             // Environment related shims
             "GetEnvironmentVariableW" => {
-                let [name, buf, size] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [name, buf, size] =
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let result = this.GetEnvironmentVariableW(name, buf, size)?;
                 this.write_scalar(result, dest)?;
             }
             "SetEnvironmentVariableW" => {
-                let [name, value] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [name, value] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let result = this.SetEnvironmentVariableW(name, value)?;
                 this.write_scalar(result, dest)?;
             }
             "GetEnvironmentStringsW" => {
-                let [] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let result = this.GetEnvironmentStringsW()?;
                 this.write_pointer(result, dest)?;
             }
             "FreeEnvironmentStringsW" => {
-                let [env_block] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [env_block] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let result = this.FreeEnvironmentStringsW(env_block)?;
                 this.write_scalar(result, dest)?;
             }
             "GetCurrentDirectoryW" => {
-                let [size, buf] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [size, buf] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let result = this.GetCurrentDirectoryW(size, buf)?;
                 this.write_scalar(result, dest)?;
             }
             "SetCurrentDirectoryW" => {
-                let [path] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [path] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let result = this.SetCurrentDirectoryW(path)?;
                 this.write_scalar(result, dest)?;
             }
             "GetUserProfileDirectoryW" => {
-                let [token, buf, size] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [token, buf, size] =
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let result = this.GetUserProfileDirectoryW(token, buf, size)?;
                 this.write_scalar(result, dest)?;
             }
             "GetCurrentProcessId" => {
-                let [] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let result = this.GetCurrentProcessId()?;
                 this.write_scalar(result, dest)?;
             }
@@ -209,7 +211,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     n,
                     byte_offset,
                     key,
-                ] = this.check_shim(abi, sys_conv, link_name, args)?;
+                ] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 this.NtWriteFile(
                     handle,
                     event,
@@ -234,7 +236,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     n,
                     byte_offset,
                     key,
-                ] = this.check_shim(abi, sys_conv, link_name, args)?;
+                ] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 this.NtReadFile(
                     handle,
                     event,
@@ -250,7 +252,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             "GetFullPathNameW" => {
                 let [filename, size, buffer, filepart] =
-                    this.check_shim(abi, sys_conv, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 this.check_no_isolation("`GetFullPathNameW`")?;
 
                 let filename = this.read_pointer(filename)?;
@@ -287,7 +289,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     creation_disposition,
                     flags_and_attributes,
                     template_file,
-                ] = this.check_shim(abi, sys_conv, link_name, args)?;
+                ] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let handle = this.CreateFileW(
                     file_name,
                     desired_access,
@@ -300,18 +302,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(handle.to_scalar(this), dest)?;
             }
             "GetFileInformationByHandle" => {
-                let [handle, info] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [handle, info] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let res = this.GetFileInformationByHandle(handle, info)?;
                 this.write_scalar(res, dest)?;
             }
             "DeleteFileW" => {
-                let [file_name] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [file_name] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let res = this.DeleteFileW(file_name)?;
                 this.write_scalar(res, dest)?;
             }
             "SetFilePointerEx" => {
                 let [file, distance_to_move, new_file_pointer, move_method] =
-                    this.check_shim(abi, sys_conv, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let res =
                     this.SetFilePointerEx(file, distance_to_move, new_file_pointer, move_method)?;
                 this.write_scalar(res, dest)?;
@@ -319,7 +321,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // Allocation
             "HeapAlloc" => {
-                let [handle, flags, size] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [handle, flags, size] =
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 this.read_target_isize(handle)?;
                 let flags = this.read_scalar(flags)?.to_u32()?;
                 let size = this.read_target_usize(size)?;
@@ -341,7 +344,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_pointer(ptr, dest)?;
             }
             "HeapFree" => {
-                let [handle, flags, ptr] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [handle, flags, ptr] =
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 this.read_target_isize(handle)?;
                 this.read_scalar(flags)?.to_u32()?;
                 let ptr = this.read_pointer(ptr)?;
@@ -354,7 +358,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             "HeapReAlloc" => {
                 let [handle, flags, old_ptr, size] =
-                    this.check_shim(abi, sys_conv, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 this.read_target_isize(handle)?;
                 this.read_scalar(flags)?.to_u32()?;
                 let old_ptr = this.read_pointer(old_ptr)?;
@@ -374,7 +378,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_pointer(new_ptr, dest)?;
             }
             "LocalFree" => {
-                let [ptr] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [ptr] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let ptr = this.read_pointer(ptr)?;
                 // "If the hMem parameter is NULL, LocalFree ignores the parameter and returns NULL."
                 // (https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-localfree)
@@ -386,17 +390,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // errno
             "SetLastError" => {
-                let [error] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [error] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let error = this.read_scalar(error)?;
                 this.set_last_error(error)?;
             }
             "GetLastError" => {
-                let [] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let last_error = this.get_last_error()?;
                 this.write_scalar(last_error, dest)?;
             }
             "RtlNtStatusToDosError" => {
-                let [status] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [status] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let status = this.read_scalar(status)?.to_u32()?;
                 let err = match status {
                     // STATUS_MEDIA_WRITE_PROTECTED => ERROR_WRITE_PROTECT
@@ -418,7 +422,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Querying system information
             "GetSystemInfo" => {
                 // Also called from `page_size` crate.
-                let [system_info] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [system_info] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let system_info =
                     this.deref_pointer_as(system_info, this.windows_ty_layout("SYSTEM_INFO"))?;
                 // Initialize with `0`.
@@ -441,19 +445,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 // This just creates a key; Windows does not natively support TLS destructors.
 
                 // Create key and return it.
-                let [] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let key = this.machine.tls.create_tls_key(None, dest.layout.size)?;
                 this.write_scalar(Scalar::from_uint(key, dest.layout.size), dest)?;
             }
             "TlsGetValue" => {
-                let [key] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [key] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let key = u128::from(this.read_scalar(key)?.to_u32()?);
                 let active_thread = this.active_thread();
                 let ptr = this.machine.tls.load_tls(key, active_thread, this)?;
                 this.write_scalar(ptr, dest)?;
             }
             "TlsSetValue" => {
-                let [key, new_ptr] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [key, new_ptr] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let key = u128::from(this.read_scalar(key)?.to_u32()?);
                 let active_thread = this.active_thread();
                 let new_data = this.read_scalar(new_ptr)?;
@@ -463,7 +467,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_int(1, dest)?;
             }
             "TlsFree" => {
-                let [key] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [key] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let key = u128::from(this.read_scalar(key)?.to_u32()?);
                 this.machine.tls.delete_tls_key(key)?;
 
@@ -473,7 +477,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // Access to command-line arguments
             "GetCommandLineW" => {
-                let [] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 this.write_pointer(
                     this.machine.cmd_line.expect("machine must be initialized"),
                     dest,
@@ -483,29 +487,30 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Time related shims
             "GetSystemTimeAsFileTime" | "GetSystemTimePreciseAsFileTime" => {
                 #[allow(non_snake_case)]
-                let [LPFILETIME] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [LPFILETIME] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 this.GetSystemTimeAsFileTime(link_name.as_str(), LPFILETIME)?;
             }
             "QueryPerformanceCounter" => {
                 #[allow(non_snake_case)]
-                let [lpPerformanceCount] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [lpPerformanceCount] =
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let result = this.QueryPerformanceCounter(lpPerformanceCount)?;
                 this.write_scalar(result, dest)?;
             }
             "QueryPerformanceFrequency" => {
                 #[allow(non_snake_case)]
-                let [lpFrequency] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [lpFrequency] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let result = this.QueryPerformanceFrequency(lpFrequency)?;
                 this.write_scalar(result, dest)?;
             }
             "Sleep" => {
-                let [timeout] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [timeout] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
 
                 this.Sleep(timeout)?;
             }
             "CreateWaitableTimerExW" => {
                 let [attributes, name, flags, access] =
-                    this.check_shim(abi, sys_conv, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 this.read_pointer(attributes)?;
                 this.read_pointer(name)?;
                 this.read_scalar(flags)?.to_u32()?;
@@ -519,27 +524,28 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Synchronization primitives
             "InitOnceBeginInitialize" => {
                 let [ptr, flags, pending, context] =
-                    this.check_shim(abi, sys_conv, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 this.InitOnceBeginInitialize(ptr, flags, pending, context, dest)?;
             }
             "InitOnceComplete" => {
-                let [ptr, flags, context] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [ptr, flags, context] =
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let result = this.InitOnceComplete(ptr, flags, context)?;
                 this.write_scalar(result, dest)?;
             }
             "WaitOnAddress" => {
                 let [ptr_op, compare_op, size_op, timeout_op] =
-                    this.check_shim(abi, sys_conv, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
 
                 this.WaitOnAddress(ptr_op, compare_op, size_op, timeout_op, dest)?;
             }
             "WakeByAddressSingle" => {
-                let [ptr_op] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [ptr_op] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
 
                 this.WakeByAddressSingle(ptr_op)?;
             }
             "WakeByAddressAll" => {
-                let [ptr_op] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [ptr_op] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
 
                 this.WakeByAddressAll(ptr_op)?;
             }
@@ -547,7 +553,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Dynamic symbol loading
             "GetProcAddress" => {
                 #[allow(non_snake_case)]
-                let [hModule, lpProcName] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [hModule, lpProcName] =
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 this.read_target_isize(hModule)?;
                 let name = this.read_c_str(this.read_pointer(lpProcName)?)?;
                 if let Ok(name) = str::from_utf8(name)
@@ -563,7 +570,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Threading
             "CreateThread" => {
                 let [security, stacksize, start, arg, flags, thread] =
-                    this.check_shim(abi, sys_conv, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
 
                 let thread_id =
                     this.CreateThread(security, stacksize, start, arg, flags, thread)?;
@@ -571,12 +578,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(Handle::Thread(thread_id).to_scalar(this), dest)?;
             }
             "WaitForSingleObject" => {
-                let [handle, timeout] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [handle, timeout] =
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
 
                 this.WaitForSingleObject(handle, timeout, dest)?;
             }
             "GetCurrentProcess" => {
-                let [] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
 
                 this.write_scalar(
                     Handle::Pseudo(PseudoHandle::CurrentProcess).to_scalar(this),
@@ -584,7 +592,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 )?;
             }
             "GetCurrentThread" => {
-                let [] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
 
                 this.write_scalar(
                     Handle::Pseudo(PseudoHandle::CurrentThread).to_scalar(this),
@@ -592,7 +600,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 )?;
             }
             "SetThreadDescription" => {
-                let [handle, name] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [handle, name] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
 
                 let handle = this.read_handle(handle, "SetThreadDescription")?;
                 let name = this.read_wide_str(this.read_pointer(name)?)?;
@@ -607,7 +615,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(Scalar::from_u32(0), dest)?;
             }
             "GetThreadDescription" => {
-                let [handle, name_ptr] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [handle, name_ptr] =
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
 
                 let handle = this.read_handle(handle, "GetThreadDescription")?;
                 let name_ptr = this.deref_pointer_as(name_ptr, this.machine.layouts.mut_raw_ptr)?; // the pointer where we should store the ptr to the name
@@ -630,7 +639,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(res, dest)?;
             }
             "GetThreadId" => {
-                let [handle] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [handle] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let handle = this.read_handle(handle, "GetThreadId")?;
                 let thread = match handle {
                     Handle::Thread(thread) => thread,
@@ -641,7 +650,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(Scalar::from_u32(tid), dest)?;
             }
             "GetCurrentThreadId" => {
-                let [] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let thread = this.active_thread();
                 let tid = this.get_tid(thread);
                 this.write_scalar(Scalar::from_u32(tid), dest)?;
@@ -649,7 +658,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // Miscellaneous
             "ExitProcess" => {
-                let [code] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [code] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 // Windows technically uses u32, but we unify everything to a Unix-style i32.
                 let code = this.read_scalar(code)?.to_i32()?;
                 throw_machine_stop!(TerminationInfo::Exit { code, leak_check: false });
@@ -657,7 +666,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "SystemFunction036" => {
                 // used by getrandom 0.1
                 // This is really 'RtlGenRandom'.
-                let [ptr, len] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [ptr, len] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let ptr = this.read_pointer(ptr)?;
                 let len = this.read_scalar(len)?.to_u32()?;
                 this.gen_random(ptr, len.into())?;
@@ -665,7 +674,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             "ProcessPrng" => {
                 // used by `std`
-                let [ptr, len] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [ptr, len] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let ptr = this.read_pointer(ptr)?;
                 let len = this.read_target_usize(len)?;
                 this.gen_random(ptr, len)?;
@@ -674,7 +683,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "BCryptGenRandom" => {
                 // used by getrandom 0.2
                 let [algorithm, ptr, len, flags] =
-                    this.check_shim(abi, sys_conv, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let algorithm = this.read_scalar(algorithm)?;
                 let algorithm = algorithm.to_target_usize(this)?;
                 let ptr = this.read_pointer(ptr)?;
@@ -708,7 +717,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             "GetConsoleScreenBufferInfo" => {
                 // `term` needs this, so we fake it.
-                let [console, buffer_info] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [console, buffer_info] =
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 this.read_target_isize(console)?;
                 // FIXME: this should use deref_pointer_as, but CONSOLE_SCREEN_BUFFER_INFO is not in std
                 this.deref_pointer(buffer_info)?;
@@ -717,13 +727,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_null(dest)?;
             }
             "GetStdHandle" => {
-                let [which] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [which] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let res = this.GetStdHandle(which)?;
                 this.write_scalar(res, dest)?;
             }
             "DuplicateHandle" => {
                 let [src_proc, src_handle, target_proc, target_handle, access, inherit, options] =
-                    this.check_shim(abi, sys_conv, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 let res = this.DuplicateHandle(
                     src_proc,
                     src_handle,
@@ -736,14 +746,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(res, dest)?;
             }
             "CloseHandle" => {
-                let [handle] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [handle] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
 
                 let ret = this.CloseHandle(handle)?;
 
                 this.write_scalar(ret, dest)?;
             }
             "GetModuleFileNameW" => {
-                let [handle, filename, size] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [handle, filename, size] =
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 this.check_no_isolation("`GetModuleFileNameW`")?;
 
                 let handle = this.read_handle(handle, "GetModuleFileNameW")?;
@@ -777,7 +788,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             "FormatMessageW" => {
                 let [flags, module, message_id, language_id, buffer, size, arguments] =
-                    this.check_shim(abi, sys_conv, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
 
                 let flags = this.read_scalar(flags)?.to_u32()?;
                 let _module = this.read_pointer(module)?; // seems to contain a module name
@@ -812,26 +823,28 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Incomplete shims that we "stub out" just to get pre-main initialization code to work.
             // These shims are enabled only when the caller is in the standard library.
             "GetProcessHeap" if this.frame_in_std() => {
-                let [] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 // Just fake a HANDLE
                 // It's fine to not use the Handle type here because its a stub
                 this.write_int(1, dest)?;
             }
             "GetModuleHandleA" if this.frame_in_std() => {
                 #[allow(non_snake_case)]
-                let [_lpModuleName] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [_lpModuleName] =
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 // We need to return something non-null here to make `compat_fn!` work.
                 this.write_int(1, dest)?;
             }
             "SetConsoleTextAttribute" if this.frame_in_std() => {
                 #[allow(non_snake_case)]
                 let [_hConsoleOutput, _wAttribute] =
-                    this.check_shim(abi, sys_conv, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 // Pretend these does not exist / nothing happened, by returning zero.
                 this.write_null(dest)?;
             }
             "GetConsoleMode" if this.frame_in_std() => {
-                let [console, mode] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [console, mode] =
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 this.read_target_isize(console)?;
                 this.deref_pointer_as(mode, this.machine.layouts.u32)?;
                 // Indicate an error.
@@ -839,25 +852,27 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             "GetFileType" if this.frame_in_std() => {
                 #[allow(non_snake_case)]
-                let [_hFile] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [_hFile] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 // Return unknown file type.
                 this.write_null(dest)?;
             }
             "AddVectoredExceptionHandler" if this.frame_in_std() => {
                 #[allow(non_snake_case)]
-                let [_First, _Handler] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [_First, _Handler] =
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 // Any non zero value works for the stdlib. This is just used for stack overflows anyway.
                 this.write_int(1, dest)?;
             }
             "SetThreadStackGuarantee" if this.frame_in_std() => {
                 #[allow(non_snake_case)]
-                let [_StackSizeInBytes] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [_StackSizeInBytes] =
+                    this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
                 // Any non zero value works for the stdlib. This is just used for stack overflows anyway.
                 this.write_int(1, dest)?;
             }
             // this is only callable from std because we know that std ignores the return value
             "SwitchToThread" if this.frame_in_std() => {
-                let [] = this.check_shim(abi, sys_conv, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, sys_conv, link_name, args)?;
 
                 this.yield_active_thread();
 
@@ -876,7 +891,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     );
                 }
                 // This function looks and behaves excatly like miri_start_unwind.
-                let [payload] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [payload] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 this.handle_miri_start_unwind(payload)?;
                 return interp_ok(EmulateItemResult::NeedsUnwind);
             }
diff --git a/src/tools/miri/src/shims/windows/fs.rs b/src/tools/miri/src/shims/windows/fs.rs
index 72e016c12e9..e4ec1b0130c 100644
--- a/src/tools/miri/src/shims/windows/fs.rs
+++ b/src/tools/miri/src/shims/windows/fs.rs
@@ -462,6 +462,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         };
         let io_status_info = this.project_field_named(&io_status_block, "Information")?;
 
+        // It seems like short writes are not a thing on Windows, so we don't truncate `count` here.
+        // FIXME: if we are on a Unix host, short host writes are still visible to the program!
+
         let finish = {
             let io_status = io_status.clone();
             let io_status_info = io_status_info.clone();
@@ -491,7 +494,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 }}
             )
         };
-
         desc.write(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
 
         // Return status is written to `dest` and `io_status_block` on callback completion.
@@ -556,6 +558,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         };
         let io_status_info = this.project_field_named(&io_status_block, "Information")?;
 
+        let fd = match handle {
+            Handle::File(fd) => fd,
+            _ => this.invalid_handle("NtWriteFile")?,
+        };
+
+        let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtReadFile")? };
+
+        // It seems like short reads are not a thing on Windows, so we don't truncate `count` here.
+        // FIXME: if we are on a Unix host, short host reads are still visible to the program!
+
         let finish = {
             let io_status = io_status.clone();
             let io_status_info = io_status_info.clone();
@@ -585,14 +597,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 }}
             )
         };
-
-        let fd = match handle {
-            Handle::File(fd) => fd,
-            _ => this.invalid_handle("NtWriteFile")?,
-        };
-
-        let Some(desc) = this.machine.fds.get(fd) else { this.invalid_handle("NtReadFile")? };
-
         desc.read(this.machine.communicate(), buf, count.try_into().unwrap(), this, finish)?;
 
         // See NtWriteFile for commentary on this
diff --git a/src/tools/miri/src/shims/x86/aesni.rs b/src/tools/miri/src/shims/x86/aesni.rs
index 058ca24e730..fdd3e78c610 100644
--- a/src/tools/miri/src/shims/x86/aesni.rs
+++ b/src/tools/miri/src/shims/x86/aesni.rs
@@ -26,7 +26,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // `state` with the corresponding 128-bit key of `key`.
             // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesdec_si128
             "aesdec" | "aesdec.256" | "aesdec.512" => {
-                let [state, key] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [state, key] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 aes_round(this, state, key, dest, |state, key| {
                     let key = aes::Block::from(key.to_le_bytes());
                     let mut state = aes::Block::from(state.to_le_bytes());
@@ -42,7 +43,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // `state` with the corresponding 128-bit key of `key`.
             // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesdeclast_si128
             "aesdeclast" | "aesdeclast.256" | "aesdeclast.512" => {
-                let [state, key] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [state, key] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 aes_round(this, state, key, dest, |state, key| {
                     let mut state = aes::Block::from(state.to_le_bytes());
@@ -66,7 +68,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // `state` with the corresponding 128-bit key of `key`.
             // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesenc_si128
             "aesenc" | "aesenc.256" | "aesenc.512" => {
-                let [state, key] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [state, key] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 aes_round(this, state, key, dest, |state, key| {
                     let key = aes::Block::from(key.to_le_bytes());
                     let mut state = aes::Block::from(state.to_le_bytes());
@@ -82,7 +85,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // `state` with the corresponding 128-bit key of `key`.
             // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesenclast_si128
             "aesenclast" | "aesenclast.256" | "aesenclast.512" => {
-                let [state, key] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [state, key] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 aes_round(this, state, key, dest, |state, key| {
                     let mut state = aes::Block::from(state.to_le_bytes());
                     // `aes::hazmat::cipher_round` does the following operations:
@@ -102,7 +106,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Used to implement the _mm_aesimc_si128 function.
             // Performs the AES InvMixColumns operation on `op`
             "aesimc" => {
-                let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 // Transmute to `u128`
                 let op = op.transmute(this.machine.layouts.u128, this)?;
                 let dest = dest.transmute(this.machine.layouts.u128, this)?;
diff --git a/src/tools/miri/src/shims/x86/avx.rs b/src/tools/miri/src/shims/x86/avx.rs
index 83d23d6ad36..269ce3b51b9 100644
--- a/src/tools/miri/src/shims/x86/avx.rs
+++ b/src/tools/miri/src/shims/x86/avx.rs
@@ -33,7 +33,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // matches the IEEE min/max operations, while x86 has different
             // semantics.
             "min.ps.256" | "max.ps.256" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which = match unprefixed_name {
                     "min.ps.256" => FloatBinOp::Min,
@@ -45,7 +46,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             // Used to implement _mm256_min_pd and _mm256_max_pd functions.
             "min.pd.256" | "max.pd.256" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which = match unprefixed_name {
                     "min.pd.256" => FloatBinOp::Min,
@@ -58,21 +60,23 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Used to implement the _mm256_round_ps function.
             // Rounds the elements of `op` according to `rounding`.
             "round.ps.256" => {
-                let [op, rounding] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [op, rounding] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 round_all::<rustc_apfloat::ieee::Single>(this, op, rounding, dest)?;
             }
             // Used to implement the _mm256_round_pd function.
             // Rounds the elements of `op` according to `rounding`.
             "round.pd.256" => {
-                let [op, rounding] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [op, rounding] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 round_all::<rustc_apfloat::ieee::Double>(this, op, rounding, dest)?;
             }
             // Used to implement _mm256_{rcp,rsqrt}_ps functions.
             // Performs the operations on all components of `op`.
             "rcp.ps.256" | "rsqrt.ps.256" => {
-                let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which = match unprefixed_name {
                     "rcp.ps.256" => FloatUnaryOp::Rcp,
@@ -84,7 +88,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             // Used to implement the _mm256_dp_ps function.
             "dp.ps.256" => {
-                let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right, imm] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 conditional_dot_product(this, left, right, imm, dest)?;
             }
@@ -92,7 +97,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Horizontally add/subtract adjacent floating point values
             // in `left` and `right`.
             "hadd.ps.256" | "hadd.pd.256" | "hsub.ps.256" | "hsub.pd.256" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which = match unprefixed_name {
                     "hadd.ps.256" | "hadd.pd.256" => mir::BinOp::Add,
@@ -107,7 +113,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // and `right`. For each component, returns 0 if false or u32::MAX
             // if true.
             "cmp.ps.256" => {
-                let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right, imm] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which =
                     FloatBinOp::cmp_from_imm(this, this.read_scalar(imm)?.to_i8()?, link_name)?;
@@ -119,7 +126,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // and `right`. For each component, returns 0 if false or u64::MAX
             // if true.
             "cmp.pd.256" => {
-                let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right, imm] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which =
                     FloatBinOp::cmp_from_imm(this, this.read_scalar(imm)?.to_i8()?, link_name)?;
@@ -130,7 +138,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // and _mm256_cvttpd_epi32 functions.
             // Converts packed f32/f64 to packed i32.
             "cvt.ps2dq.256" | "cvtt.ps2dq.256" | "cvt.pd2dq.256" | "cvtt.pd2dq.256" => {
-                let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let rnd = match unprefixed_name {
                     // "current SSE rounding mode", assume nearest
@@ -148,7 +156,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // sequence of 4-element arrays, and we shuffle each of these arrays, where
             // `control` determines which element of the current `data` array is written.
             "vpermilvar.ps" | "vpermilvar.ps.256" => {
-                let [data, control] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [data, control] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (data, data_len) = this.project_to_simd(data)?;
                 let (control, control_len) = this.project_to_simd(control)?;
@@ -181,7 +190,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // where `right` determines which element of the current `left` array is
             // written.
             "vpermilvar.pd" | "vpermilvar.pd.256" => {
-                let [data, control] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [data, control] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (data, data_len) = this.project_to_simd(data)?;
                 let (control, control_len) = this.project_to_simd(control)?;
@@ -213,7 +223,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // For each 128-bit element of `dest`, copies one from `left`, `right` or
             // zero, according to `imm`.
             "vperm2f128.ps.256" | "vperm2f128.pd.256" | "vperm2f128.si.256" => {
-                let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right, imm] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 assert_eq!(dest.layout, left.layout);
                 assert_eq!(dest.layout, right.layout);
@@ -256,7 +267,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // is one, it is loaded from `ptr.wrapping_add(i)`, otherwise zero is
             // loaded.
             "maskload.ps" | "maskload.pd" | "maskload.ps.256" | "maskload.pd.256" => {
-                let [ptr, mask] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [ptr, mask] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 mask_load(this, ptr, mask, dest)?;
             }
@@ -266,7 +277,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // is one, it is stored into `ptr.wapping_add(i)`.
             // Unlike SSE2's _mm_maskmoveu_si128, these are not non-temporal stores.
             "maskstore.ps" | "maskstore.pd" | "maskstore.ps.256" | "maskstore.pd.256" => {
-                let [ptr, mask, value] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [ptr, mask, value] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 mask_store(this, ptr, mask, value)?;
             }
@@ -276,7 +288,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // the data crosses a cache line, but for Miri this is just a regular
             // unaligned read.
             "ldu.dq.256" => {
-                let [src_ptr] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [src_ptr] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let src_ptr = this.read_pointer(src_ptr)?;
                 let dest = dest.force_mplace(this)?;
 
@@ -288,7 +300,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Tests `op & mask == 0`, `op & mask == mask` or
             // `op & mask != 0 && op & mask != mask`
             "ptestz.256" | "ptestc.256" | "ptestnzc.256" => {
-                let [op, mask] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [op, mask] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (all_zero, masked_set) = test_bits_masked(this, op, mask)?;
                 let res = match unprefixed_name {
@@ -311,7 +323,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "vtestz.pd.256" | "vtestc.pd.256" | "vtestnzc.pd.256" | "vtestz.pd" | "vtestc.pd"
             | "vtestnzc.pd" | "vtestz.ps.256" | "vtestc.ps.256" | "vtestnzc.ps.256"
             | "vtestz.ps" | "vtestc.ps" | "vtestnzc.ps" => {
-                let [op, mask] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [op, mask] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (direct, negated) = test_high_bits_masked(this, op, mask)?;
                 let res = match unprefixed_name {
@@ -333,7 +345,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 // compiler, making these functions no-ops.
 
                 // The only thing that needs to be ensured is the correct calling convention.
-                let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
             }
             _ => return interp_ok(EmulateItemResult::NotSupported),
         }
diff --git a/src/tools/miri/src/shims/x86/avx2.rs b/src/tools/miri/src/shims/x86/avx2.rs
index 49d5977078b..ca80c0eba1e 100644
--- a/src/tools/miri/src/shims/x86/avx2.rs
+++ b/src/tools/miri/src/shims/x86/avx2.rs
@@ -28,7 +28,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Used to implement the _mm256_abs_epi{8,16,32} functions.
             // Calculates the absolute value of packed 8/16/32-bit integers.
             "pabs.b" | "pabs.w" | "pabs.d" => {
-                let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 int_abs(this, op, dest)?;
             }
@@ -36,7 +36,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Horizontally add / add with saturation / subtract adjacent 16/32-bit
             // integer values in `left` and `right`.
             "phadd.w" | "phadd.sw" | "phadd.d" | "phsub.w" | "phsub.sw" | "phsub.d" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (which, saturating) = match unprefixed_name {
                     "phadd.w" | "phadd.d" => (mir::BinOp::Add, false),
@@ -57,7 +58,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             | "gather.d.pd.256" | "gather.q.pd" | "gather.q.pd.256" | "gather.d.ps"
             | "gather.d.ps.256" | "gather.q.ps" | "gather.q.ps.256" => {
                 let [src, slice, offsets, mask, scale] =
-                    this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 assert_eq!(dest.layout, src.layout);
 
@@ -114,7 +115,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // intermediate signed 32-bit integers. Horizontally add adjacent pairs of
             // intermediate 32-bit integers, and pack the results in `dest`.
             "pmadd.wd" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (left, left_len) = this.project_to_simd(left)?;
                 let (right, right_len) = this.project_to_simd(right)?;
@@ -150,7 +152,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // the saturating sum of the products with indices `2*i` and `2*i+1`
             // produces the output at index `i`.
             "pmadd.ub.sw" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (left, left_len) = this.project_to_simd(left)?;
                 let (right, right_len) = this.project_to_simd(right)?;
@@ -184,7 +187,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // is one, it is loaded from `ptr.wrapping_add(i)`, otherwise zero is
             // loaded.
             "maskload.d" | "maskload.q" | "maskload.d.256" | "maskload.q.256" => {
-                let [ptr, mask] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [ptr, mask] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 mask_load(this, ptr, mask, dest)?;
             }
@@ -194,7 +197,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // is one, it is stored into `ptr.wapping_add(i)`.
             // Unlike SSE2's _mm_maskmoveu_si128, these are not non-temporal stores.
             "maskstore.d" | "maskstore.q" | "maskstore.d.256" | "maskstore.q.256" => {
-                let [ptr, mask, value] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [ptr, mask, value] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 mask_store(this, ptr, mask, value)?;
             }
@@ -205,7 +209,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // offsets specified in `imm`.
             // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_mpsadbw_epu8
             "mpsadbw" => {
-                let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right, imm] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 mpsadbw(this, left, right, imm, dest)?;
             }
@@ -216,7 +221,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // 1 and then taking the bits `1..=16`.
             // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_mulhrs_epi16
             "pmul.hr.sw" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 pmulhrsw(this, left, right, dest)?;
             }
@@ -224,7 +230,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Converts two 16-bit integer vectors to a single 8-bit integer
             // vector with signed saturation.
             "packsswb" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 packsswb(this, left, right, dest)?;
             }
@@ -232,7 +239,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Converts two 32-bit integer vectors to a single 16-bit integer
             // vector with signed saturation.
             "packssdw" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 packssdw(this, left, right, dest)?;
             }
@@ -240,7 +248,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Converts two 16-bit signed integer vectors to a single 8-bit
             // unsigned integer vector with saturation.
             "packuswb" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 packuswb(this, left, right, dest)?;
             }
@@ -248,7 +257,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Concatenates two 32-bit signed integer vectors and converts
             // the result to a 16-bit unsigned integer vector with saturation.
             "packusdw" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 packusdw(this, left, right, dest)?;
             }
@@ -257,7 +267,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Shuffles `left` using the three low bits of each element of `right`
             // as indices.
             "permd" | "permps" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (left, left_len) = this.project_to_simd(left)?;
                 let (right, right_len) = this.project_to_simd(right)?;
@@ -277,7 +288,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Used to implement the _mm256_permute2x128_si256 function.
             // Shuffles 128-bit blocks of `a` and `b` using `imm` as pattern.
             "vperm2i128" => {
-                let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right, imm] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 assert_eq!(left.layout.size.bits(), 256);
                 assert_eq!(right.layout.size.bits(), 256);
@@ -314,7 +326,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // in `dest`.
             // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_sad_epu8
             "psad.bw" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (left, left_len) = this.project_to_simd(left)?;
                 let (right, right_len) = this.project_to_simd(right)?;
@@ -346,7 +359,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Shuffles bytes from `left` using `right` as pattern.
             // Each 128-bit block is shuffled independently.
             "pshuf.b" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (left, left_len) = this.project_to_simd(left)?;
                 let (right, right_len) = this.project_to_simd(right)?;
@@ -377,7 +391,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // is writen to the corresponding output element.
             // Basically, we multiply `left` with `right.signum()`.
             "psign.b" | "psign.w" | "psign.d" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 psign(this, left, right, dest)?;
             }
@@ -391,7 +406,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // is copied to remaining bits.
             "psll.w" | "psrl.w" | "psra.w" | "psll.d" | "psrl.d" | "psra.d" | "psll.q"
             | "psrl.q" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which = match unprefixed_name {
                     "psll.w" | "psll.d" | "psll.q" => ShiftOp::Left,
@@ -406,7 +422,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // (except _mm{,256}_srav_epi64, which are not available in AVX2).
             "psllv.d" | "psllv.d.256" | "psllv.q" | "psllv.q.256" | "psrlv.d" | "psrlv.d.256"
             | "psrlv.q" | "psrlv.q.256" | "psrav.d" | "psrav.d.256" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which = match unprefixed_name {
                     "psllv.d" | "psllv.d.256" | "psllv.q" | "psllv.q.256" => ShiftOp::Left,
diff --git a/src/tools/miri/src/shims/x86/bmi.rs b/src/tools/miri/src/shims/x86/bmi.rs
index 80b1b2e16e6..140e31cc513 100644
--- a/src/tools/miri/src/shims/x86/bmi.rs
+++ b/src/tools/miri/src/shims/x86/bmi.rs
@@ -35,7 +35,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             return interp_ok(EmulateItemResult::NotSupported);
         }
 
-        let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+        let [left, right] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
         let left = this.read_scalar(left)?;
         let right = this.read_scalar(right)?;
 
diff --git a/src/tools/miri/src/shims/x86/gfni.rs b/src/tools/miri/src/shims/x86/gfni.rs
index f83ce560c84..9a98a80d6dc 100644
--- a/src/tools/miri/src/shims/x86/gfni.rs
+++ b/src/tools/miri/src/shims/x86/gfni.rs
@@ -31,14 +31,16 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // See `affine_transform` for details.
             // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=gf2p8affine_
             "vgf2p8affineqb.128" | "vgf2p8affineqb.256" | "vgf2p8affineqb.512" => {
-                let [left, right, imm8] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right, imm8] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 affine_transform(this, left, right, imm8, dest, /* inverse */ false)?;
             }
             // Used to implement the `_mm{, 256, 512}_gf2p8affineinv_epi64_epi8` functions.
             // See `affine_transform` for details.
             // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=gf2p8affineinv
             "vgf2p8affineinvqb.128" | "vgf2p8affineinvqb.256" | "vgf2p8affineinvqb.512" => {
-                let [left, right, imm8] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right, imm8] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 affine_transform(this, left, right, imm8, dest, /* inverse */ true)?;
             }
             // Used to implement the `_mm{, 256, 512}_gf2p8mul_epi8` functions.
@@ -47,7 +49,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // polynomial representation with the reduction polynomial x^8 + x^4 + x^3 + x + 1.
             // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=gf2p8mul
             "vgf2p8mulb.128" | "vgf2p8mulb.256" | "vgf2p8mulb.512" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let (left, left_len) = this.project_to_simd(left)?;
                 let (right, right_len) = this.project_to_simd(right)?;
                 let (dest, dest_len) = this.project_to_simd(dest)?;
diff --git a/src/tools/miri/src/shims/x86/mod.rs b/src/tools/miri/src/shims/x86/mod.rs
index fbfe459711e..3324b7b024a 100644
--- a/src/tools/miri/src/shims/x86/mod.rs
+++ b/src/tools/miri/src/shims/x86/mod.rs
@@ -45,7 +45,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     return interp_ok(EmulateItemResult::NotSupported);
                 }
 
-                let [cb_in, a, b] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [cb_in, a, b] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let op = if unprefixed_name.starts_with("add") {
                     mir::BinOp::AddWithOverflow
                 } else {
@@ -67,7 +68,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 if is_u64 && this.tcx.sess.target.arch != "x86_64" {
                     return interp_ok(EmulateItemResult::NotSupported);
                 }
-                let [c_in, a, b, out] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [c_in, a, b, out] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let out = this.deref_pointer_as(
                     out,
                     if is_u64 { this.machine.layouts.u64 } else { this.machine.layouts.u32 },
@@ -84,7 +86,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // the instruction behaves like a no-op, so it is always safe to call the
             // intrinsic.
             "sse2.pause" => {
-                let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 // Only exhibit the spin-loop hint behavior when SSE2 is enabled.
                 if this.tcx.sess.unstable_target_features.contains(&Symbol::intern("sse2")) {
                     this.yield_active_thread();
@@ -103,7 +105,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     len = 8;
                 }
 
-                let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right, imm] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 pclmulqdq(this, left, right, imm, dest, len)?;
             }
diff --git a/src/tools/miri/src/shims/x86/sha.rs b/src/tools/miri/src/shims/x86/sha.rs
index d37fad3e6c7..00fe58119e4 100644
--- a/src/tools/miri/src/shims/x86/sha.rs
+++ b/src/tools/miri/src/shims/x86/sha.rs
@@ -53,7 +53,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         match unprefixed_name {
             // Used to implement the _mm_sha256rnds2_epu32 function.
             "256rnds2" => {
-                let [a, b, k] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [a, b, k] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (a_reg, a_len) = this.project_to_simd(a)?;
                 let (b_reg, b_len) = this.project_to_simd(b)?;
@@ -74,7 +74,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             // Used to implement the _mm_sha256msg1_epu32 function.
             "256msg1" => {
-                let [a, b] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [a, b] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (a_reg, a_len) = this.project_to_simd(a)?;
                 let (b_reg, b_len) = this.project_to_simd(b)?;
@@ -92,7 +92,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             // Used to implement the _mm_sha256msg2_epu32 function.
             "256msg2" => {
-                let [a, b] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [a, b] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (a_reg, a_len) = this.project_to_simd(a)?;
                 let (b_reg, b_len) = this.project_to_simd(b)?;
diff --git a/src/tools/miri/src/shims/x86/sse.rs b/src/tools/miri/src/shims/x86/sse.rs
index 1ec15d609c6..6d8def5b53f 100644
--- a/src/tools/miri/src/shims/x86/sse.rs
+++ b/src/tools/miri/src/shims/x86/sse.rs
@@ -34,7 +34,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Performs the operations on the first component of `left` and
             // `right` and copies the remaining components from `left`.
             "min.ss" | "max.ss" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which = match unprefixed_name {
                     "min.ss" => FloatBinOp::Min,
@@ -50,7 +51,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // matches the IEEE min/max operations, while x86 has different
             // semantics.
             "min.ps" | "max.ps" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which = match unprefixed_name {
                     "min.ps" => FloatBinOp::Min,
@@ -64,7 +66,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Performs the operations on the first component of `op` and
             // copies the remaining components from `op`.
             "rcp.ss" | "rsqrt.ss" => {
-                let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which = match unprefixed_name {
                     "rcp.ss" => FloatUnaryOp::Rcp,
@@ -77,7 +79,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Used to implement _mm_{sqrt,rcp,rsqrt}_ps functions.
             // Performs the operations on all components of `op`.
             "rcp.ps" | "rsqrt.ps" => {
-                let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which = match unprefixed_name {
                     "rcp.ps" => FloatUnaryOp::Rcp,
@@ -96,7 +98,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // _mm_cmp{eq,lt,le,gt,ge,neq,nlt,nle,ngt,nge,ord,unord}_ss are SSE functions
             // with hard-coded operations.
             "cmp.ss" => {
-                let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right, imm] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which =
                     FloatBinOp::cmp_from_imm(this, this.read_scalar(imm)?.to_i8()?, link_name)?;
@@ -112,7 +115,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // _mm_cmp{eq,lt,le,gt,ge,neq,nlt,nle,ngt,nge,ord,unord}_ps are SSE functions
             // with hard-coded operations.
             "cmp.ps" => {
-                let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right, imm] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which =
                     FloatBinOp::cmp_from_imm(this, this.read_scalar(imm)?.to_i8()?, link_name)?;
@@ -125,7 +129,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "comieq.ss" | "comilt.ss" | "comile.ss" | "comigt.ss" | "comige.ss" | "comineq.ss"
             | "ucomieq.ss" | "ucomilt.ss" | "ucomile.ss" | "ucomigt.ss" | "ucomige.ss"
             | "ucomineq.ss" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (left, left_len) = this.project_to_simd(left)?;
                 let (right, right_len) = this.project_to_simd(right)?;
@@ -153,7 +158,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // _mm_cvtss_si64 and _mm_cvttss_si64 functions.
             // Converts the first component of `op` from f32 to i32/i64.
             "cvtss2si" | "cvttss2si" | "cvtss2si64" | "cvttss2si64" => {
-                let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let (op, _) = this.project_to_simd(op)?;
 
                 let op = this.read_immediate(&this.project_index(&op, 0)?)?;
@@ -181,7 +186,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // are copied from `left`.
             // https://www.felixcloutier.com/x86/cvtsi2ss
             "cvtsi2ss" | "cvtsi642ss" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (left, left_len) = this.project_to_simd(left)?;
                 let (dest, dest_len) = this.project_to_simd(dest)?;
diff --git a/src/tools/miri/src/shims/x86/sse2.rs b/src/tools/miri/src/shims/x86/sse2.rs
index d6052f83077..8f53adfb5ec 100644
--- a/src/tools/miri/src/shims/x86/sse2.rs
+++ b/src/tools/miri/src/shims/x86/sse2.rs
@@ -41,7 +41,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // intermediate signed 32-bit integers. Horizontally add adjacent pairs of
             // intermediate 32-bit integers, and pack the results in `dest`.
             "pmadd.wd" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (left, left_len) = this.project_to_simd(left)?;
                 let (right, right_len) = this.project_to_simd(right)?;
@@ -79,7 +80,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             //
             // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_sad_epu8
             "psad.bw" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (left, left_len) = this.project_to_simd(left)?;
                 let (right, right_len) = this.project_to_simd(right)?;
@@ -117,7 +119,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // is copied to remaining bits.
             "psll.w" | "psrl.w" | "psra.w" | "psll.d" | "psrl.d" | "psra.d" | "psll.q"
             | "psrl.q" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which = match unprefixed_name {
                     "psll.w" | "psll.d" | "psll.q" => ShiftOp::Left,
@@ -132,7 +135,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // and _mm_cvttpd_epi32 functions.
             // Converts packed f32/f64 to packed i32.
             "cvtps2dq" | "cvttps2dq" | "cvtpd2dq" | "cvttpd2dq" => {
-                let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (op_len, _) = op.layout.ty.simd_size_and_type(*this.tcx);
                 let (dest_len, _) = dest.layout.ty.simd_size_and_type(*this.tcx);
@@ -169,7 +172,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Converts two 16-bit integer vectors to a single 8-bit integer
             // vector with signed saturation.
             "packsswb.128" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 packsswb(this, left, right, dest)?;
             }
@@ -177,7 +181,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Converts two 16-bit signed integer vectors to a single 8-bit
             // unsigned integer vector with saturation.
             "packuswb.128" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 packuswb(this, left, right, dest)?;
             }
@@ -185,7 +190,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Converts two 32-bit integer vectors to a single 16-bit integer
             // vector with signed saturation.
             "packssdw.128" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 packssdw(this, left, right, dest)?;
             }
@@ -195,7 +201,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // matches the IEEE min/max operations, while x86 has different
             // semantics.
             "min.sd" | "max.sd" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which = match unprefixed_name {
                     "min.sd" => FloatBinOp::Min,
@@ -211,7 +218,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // matches the IEEE min/max operations, while x86 has different
             // semantics.
             "min.pd" | "max.pd" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which = match unprefixed_name {
                     "min.pd" => FloatBinOp::Min,
@@ -230,7 +238,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // _mm_cmp{eq,lt,le,gt,ge,neq,nlt,nle,ngt,nge,ord,unord}_sd are SSE2 functions
             // with hard-coded operations.
             "cmp.sd" => {
-                let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right, imm] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which =
                     FloatBinOp::cmp_from_imm(this, this.read_scalar(imm)?.to_i8()?, link_name)?;
@@ -246,7 +255,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // _mm_cmp{eq,lt,le,gt,ge,neq,nlt,nle,ngt,nge,ord,unord}_pd are SSE2 functions
             // with hard-coded operations.
             "cmp.pd" => {
-                let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right, imm] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which =
                     FloatBinOp::cmp_from_imm(this, this.read_scalar(imm)?.to_i8()?, link_name)?;
@@ -259,7 +269,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "comieq.sd" | "comilt.sd" | "comile.sd" | "comigt.sd" | "comige.sd" | "comineq.sd"
             | "ucomieq.sd" | "ucomilt.sd" | "ucomile.sd" | "ucomigt.sd" | "ucomige.sd"
             | "ucomineq.sd" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (left, left_len) = this.project_to_simd(left)?;
                 let (right, right_len) = this.project_to_simd(right)?;
@@ -287,7 +298,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // _mm_cvtsd_si64 and _mm_cvttsd_si64 functions.
             // Converts the first component of `op` from f64 to i32/i64.
             "cvtsd2si" | "cvttsd2si" | "cvtsd2si64" | "cvttsd2si64" => {
-                let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let (op, _) = this.project_to_simd(op)?;
 
                 let op = this.read_immediate(&this.project_index(&op, 0)?)?;
@@ -313,7 +324,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Converts the first f64/f32 from `right` to f32/f64 and copies
             // the remaining elements from `left`
             "cvtsd2ss" | "cvtss2sd" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (left, left_len) = this.project_to_simd(left)?;
                 let (right, _) = this.project_to_simd(right)?;
diff --git a/src/tools/miri/src/shims/x86/sse3.rs b/src/tools/miri/src/shims/x86/sse3.rs
index ebf3cb5c3ee..0fd8c3bc389 100644
--- a/src/tools/miri/src/shims/x86/sse3.rs
+++ b/src/tools/miri/src/shims/x86/sse3.rs
@@ -26,7 +26,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Horizontally add/subtract adjacent floating point values
             // in `left` and `right`.
             "hadd.ps" | "hadd.pd" | "hsub.ps" | "hsub.pd" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let which = match unprefixed_name {
                     "hadd.ps" | "hadd.pd" => mir::BinOp::Add,
@@ -42,7 +43,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // the data crosses a cache line, but for Miri this is just a regular
             // unaligned read.
             "ldu.dq" => {
-                let [src_ptr] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [src_ptr] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let src_ptr = this.read_pointer(src_ptr)?;
                 let dest = dest.force_mplace(this)?;
 
diff --git a/src/tools/miri/src/shims/x86/sse41.rs b/src/tools/miri/src/shims/x86/sse41.rs
index 6797039cf56..7736b5e443d 100644
--- a/src/tools/miri/src/shims/x86/sse41.rs
+++ b/src/tools/miri/src/shims/x86/sse41.rs
@@ -28,7 +28,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // bits `4..=5` if `imm`, and `i`th bit specifies whether element
             // `i` is zeroed.
             "insertps" => {
-                let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right, imm] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (left, left_len) = this.project_to_simd(left)?;
                 let (right, right_len) = this.project_to_simd(right)?;
@@ -63,7 +64,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Concatenates two 32-bit signed integer vectors and converts
             // the result to a 16-bit unsigned integer vector with saturation.
             "packusdw" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 packusdw(this, left, right, dest)?;
             }
@@ -73,7 +75,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // products, and conditionally stores the sum in `dest` using the low
             // 4 bits of `imm`.
             "dpps" | "dppd" => {
-                let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right, imm] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 conditional_dot_product(this, left, right, imm, dest)?;
             }
@@ -81,14 +84,16 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // functions. Rounds the first element of `right` according to `rounding`
             // and copies the remaining elements from `left`.
             "round.ss" => {
-                let [left, right, rounding] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right, rounding] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 round_first::<rustc_apfloat::ieee::Single>(this, left, right, rounding, dest)?;
             }
             // Used to implement the _mm_floor_ps, _mm_ceil_ps and _mm_round_ps
             // functions. Rounds the elements of `op` according to `rounding`.
             "round.ps" => {
-                let [op, rounding] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [op, rounding] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 round_all::<rustc_apfloat::ieee::Single>(this, op, rounding, dest)?;
             }
@@ -96,14 +101,16 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // functions. Rounds the first element of `right` according to `rounding`
             // and copies the remaining elements from `left`.
             "round.sd" => {
-                let [left, right, rounding] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right, rounding] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 round_first::<rustc_apfloat::ieee::Double>(this, left, right, rounding, dest)?;
             }
             // Used to implement the _mm_floor_pd, _mm_ceil_pd and _mm_round_pd
             // functions. Rounds the elements of `op` according to `rounding`.
             "round.pd" => {
-                let [op, rounding] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [op, rounding] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 round_all::<rustc_apfloat::ieee::Double>(this, op, rounding, dest)?;
             }
@@ -111,7 +118,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Find the minimum unsinged 16-bit integer in `op` and
             // returns its value and position.
             "phminposuw" => {
-                let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (op, op_len) = this.project_to_simd(op)?;
                 let (dest, dest_len) = this.project_to_simd(dest)?;
@@ -145,7 +152,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // offsets specified in `imm`.
             // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mpsadbw_epu8
             "mpsadbw" => {
-                let [left, right, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right, imm] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 mpsadbw(this, left, right, imm, dest)?;
             }
@@ -154,7 +162,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Tests `(op & mask) == 0`, `(op & mask) == mask` or
             // `(op & mask) != 0 && (op & mask) != mask`
             "ptestz" | "ptestc" | "ptestnzc" => {
-                let [op, mask] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [op, mask] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (all_zero, masked_set) = test_bits_masked(this, op, mask)?;
                 let res = match unprefixed_name {
diff --git a/src/tools/miri/src/shims/x86/sse42.rs b/src/tools/miri/src/shims/x86/sse42.rs
index 7e1e1482ef4..72c5039a12d 100644
--- a/src/tools/miri/src/shims/x86/sse42.rs
+++ b/src/tools/miri/src/shims/x86/sse42.rs
@@ -222,7 +222,8 @@ fn deconstruct_args<'tcx>(
     };
 
     if is_explicit {
-        let [str1, len1, str2, len2, imm] = ecx.check_shim(abi, CanonAbi::C, link_name, args)?;
+        let [str1, len1, str2, len2, imm] =
+            ecx.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
         let imm = ecx.read_scalar(imm)?.to_u8()?;
 
         let default_len = default_len::<u32>(imm);
@@ -235,7 +236,7 @@ fn deconstruct_args<'tcx>(
 
         interp_ok((str1, str2, Some((len1, len2)), imm))
     } else {
-        let [str1, str2, imm] = ecx.check_shim(abi, CanonAbi::C, link_name, args)?;
+        let [str1, str2, imm] = ecx.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
         let imm = ecx.read_scalar(imm)?.to_u8()?;
 
         let array_layout = array_layout_fn(ecx, imm)?;
@@ -385,7 +386,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // search for a null terminator (see `deconstruct_args` for more details).
             // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=924,925
             "pcmpistriz128" | "pcmpistris128" => {
-                let [str1, str2, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [str1, str2, imm] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let imm = this.read_scalar(imm)?.to_u8()?;
 
                 let str = if unprefixed_name == "pcmpistris128" { str1 } else { str2 };
@@ -405,7 +407,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // than 16 for byte-sized operands or 8 for word-sized operands.
             // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=1046,1047
             "pcmpestriz128" | "pcmpestris128" => {
-                let [_, len1, _, len2, imm] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [_, len1, _, len2, imm] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let len = if unprefixed_name == "pcmpestris128" { len1 } else { len2 };
                 let len = this.read_scalar(len)?.to_i32()?;
                 let imm = this.read_scalar(imm)?.to_u8()?;
@@ -432,7 +435,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     return interp_ok(EmulateItemResult::NotSupported);
                 }
 
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
                 let left = this.read_scalar(left)?;
                 let right = this.read_scalar(right)?;
 
diff --git a/src/tools/miri/src/shims/x86/ssse3.rs b/src/tools/miri/src/shims/x86/ssse3.rs
index 310d6b8f765..52ad6bd4419 100644
--- a/src/tools/miri/src/shims/x86/ssse3.rs
+++ b/src/tools/miri/src/shims/x86/ssse3.rs
@@ -25,7 +25,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Used to implement the _mm_abs_epi{8,16,32} functions.
             // Calculates the absolute value of packed 8/16/32-bit integers.
             "pabs.b.128" | "pabs.w.128" | "pabs.d.128" => {
-                let [op] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [op] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 int_abs(this, op, dest)?;
             }
@@ -33,7 +33,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // Shuffles bytes from `left` using `right` as pattern.
             // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_shuffle_epi8
             "pshuf.b.128" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (left, left_len) = this.project_to_simd(left)?;
                 let (right, right_len) = this.project_to_simd(right)?;
@@ -62,7 +63,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // integer values in `left` and `right`.
             "phadd.w.128" | "phadd.sw.128" | "phadd.d.128" | "phsub.w.128" | "phsub.sw.128"
             | "phsub.d.128" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (which, saturating) = match unprefixed_name {
                     "phadd.w.128" | "phadd.d.128" => (mir::BinOp::Add, false),
@@ -81,7 +83,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // produces the output at index `i`.
             // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_maddubs_epi16
             "pmadd.ub.sw.128" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 let (left, left_len) = this.project_to_simd(left)?;
                 let (right, right_len) = this.project_to_simd(right)?;
@@ -116,7 +119,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // 1 and then taking the bits `1..=16`.
             // https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_mulhrs_epi16
             "pmul.hr.sw.128" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 pmulhrsw(this, left, right, dest)?;
             }
@@ -126,7 +130,8 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             // is writen to the corresponding output element.
             // Basically, we multiply `left` with `right.signum()`.
             "psign.b.128" | "psign.w.128" | "psign.d.128" => {
-                let [left, right] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
+                let [left, right] =
+                    this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?;
 
                 psign(this, left, right, dest)?;
             }
diff --git a/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs b/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs
index 314ce90cfb5..f6ec5be61bb 100644
--- a/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs
+++ b/src/tools/miri/tests/fail-dep/libc/libc-epoll-data-race.rs
@@ -10,6 +10,9 @@ use std::convert::TryInto;
 use std::thread;
 use std::thread::spawn;
 
+#[path = "../../utils/libc.rs"]
+mod libc_utils;
+
 #[track_caller]
 fn check_epoll_wait<const N: usize>(epfd: i32, expected_notifications: &[(u32, u64)]) {
     let epoll_event = libc::epoll_event { events: 0, u64: 0 };
@@ -69,12 +72,12 @@ fn main() {
         unsafe { VAL_ONE = 41 };
 
         let data = "abcde".as_bytes().as_ptr();
-        let res = unsafe { libc::write(fds_a[0], data as *const libc::c_void, 5) };
+        let res = unsafe { libc_utils::write_all(fds_a[0], data as *const libc::c_void, 5) };
         assert_eq!(res, 5);
 
         unsafe { VAL_TWO = 51 };
 
-        let res = unsafe { libc::write(fds_b[0], data as *const libc::c_void, 5) };
+        let res = unsafe { libc_utils::write_all(fds_b[0], data as *const libc::c_void, 5) };
         assert_eq!(res, 5);
     });
     thread::yield_now();
diff --git a/src/tools/miri/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs b/src/tools/miri/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs
index 1dc334486c3..e2fd6463a11 100644
--- a/src/tools/miri/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs
+++ b/src/tools/miri/tests/fail-dep/libc/libc-read-and-uninit-premature-eof.rs
@@ -10,6 +10,9 @@ use std::mem::MaybeUninit;
 #[path = "../../utils/mod.rs"]
 mod utils;
 
+#[path = "../../utils/libc.rs"]
+mod libc_utils;
+
 fn main() {
     let path =
         utils::prepare_with_content("fail-libc-read-and-uninit-premature-eof.txt", &[1u8, 2, 3]);
@@ -18,8 +21,9 @@ fn main() {
         let fd = libc::open(cpath.as_ptr(), libc::O_RDONLY);
         assert_ne!(fd, -1);
         let mut buf: MaybeUninit<[u8; 4]> = std::mem::MaybeUninit::uninit();
-        // Read 4 bytes from a 3-byte file.
-        assert_eq!(libc::read(fd, buf.as_mut_ptr().cast::<std::ffi::c_void>(), 4), 3);
+        // Read as much as we can from a 3-byte file.
+        let res = libc_utils::read_all(fd, buf.as_mut_ptr().cast::<std::ffi::c_void>(), 4);
+        assert!(res == 3);
         buf.assume_init(); //~ERROR: encountered uninitialized memory, but expected an integer
         assert_eq!(libc::close(fd), 0);
     }
diff --git a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs
index f6f2e2b9312..054cb812d9e 100644
--- a/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs
+++ b/src/tools/miri/tests/fail-dep/libc/libc_epoll_block_two_thread.rs
@@ -75,9 +75,10 @@ fn main() {
     });
 
     let thread3 = spawn(move || {
+        // Just a single write, so we only wake up one of them.
         let data = "abcde".as_bytes().as_ptr();
         let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
-        assert_eq!(res, 5);
+        assert!(res > 0 && res <= 5);
     });
 
     thread1.join().unwrap();
diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.rs b/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.rs
index b3839859500..0fecfb8f663 100644
--- a/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.rs
+++ b/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.rs
@@ -4,6 +4,7 @@
 // test_race depends on a deterministic schedule.
 //@compile-flags: -Zmiri-deterministic-concurrency
 //@error-in-other-file: deadlock
+//@require-annotations-for-level: error
 
 use std::thread;
 
@@ -22,24 +23,26 @@ fn main() {
     assert_eq!(res, 0);
     let thread1 = thread::spawn(move || {
         // Let this thread block on read.
-        let mut buf: [u8; 3] = [0; 3];
+        let mut buf: [u8; 1] = [0; 1];
         let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
-        assert_eq!(res, 3);
-        assert_eq!(&buf, "abc".as_bytes());
+        assert_eq!(res, buf.len().cast_signed());
+        assert_eq!(&buf, "a".as_bytes());
     });
     let thread2 = thread::spawn(move || {
         // Let this thread block on read.
-        let mut buf: [u8; 3] = [0; 3];
-        let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
-        //~^ERROR: deadlocked
-        assert_eq!(res, 3);
-        assert_eq!(&buf, "abc".as_bytes());
+        let mut buf: [u8; 1] = [0; 1];
+        let res = unsafe {
+            libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
+            //~^ERROR: deadlock
+        };
+        assert_eq!(res, buf.len().cast_signed());
+        assert_eq!(&buf, "a".as_bytes());
     });
     let thread3 = thread::spawn(move || {
         // Unblock thread1 by writing something.
-        let data = "abc".as_bytes().as_ptr();
-        let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
-        assert_eq!(res, 3);
+        let data = "a".as_bytes();
+        let res = unsafe { libc::write(fds[0], data.as_ptr() as *const libc::c_void, data.len()) };
+        assert_eq!(res, data.len().cast_signed());
     });
     thread1.join().unwrap();
     thread2.join().unwrap();
diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.stderr b/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.stderr
index 9f19a60e6ae..99d242ec7da 100644
--- a/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.stderr
+++ b/src/tools/miri/tests/fail-dep/libc/socketpair_block_read_twice.stderr
@@ -23,8 +23,8 @@ error: the evaluated program deadlocked
 error: the evaluated program deadlocked
   --> tests/fail-dep/libc/socketpair_block_read_twice.rs:LL:CC
    |
-LL |         let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
-   |                                                                                                 ^ this thread got stuck here
+LL |             libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
+   |                                                                                  ^ this thread got stuck here
    |
    = note: BACKTRACE on thread `unnamed-ID`:
    = note: inside closure at tests/fail-dep/libc/socketpair_block_read_twice.rs:LL:CC
diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs b/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs
index 7d84d87ebbb..048938c091e 100644
--- a/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs
+++ b/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.rs
@@ -4,16 +4,20 @@
 // test_race depends on a deterministic schedule.
 //@compile-flags: -Zmiri-deterministic-concurrency
 //@error-in-other-file: deadlock
+//@require-annotations-for-level: error
 
 use std::thread;
 
+#[path = "../../utils/libc.rs"]
+mod libc_utils;
+
 // Test the behaviour of a thread being blocked on write, get unblocked, then blocked again.
 
 // The expected execution is
 // 1. Thread 1 blocks.
 // 2. Thread 2 blocks.
 // 3. Thread 3 unblocks both thread 1 and thread 2.
-// 4. Thread 1 reads.
+// 4. Thread 1 writes.
 // 5. Thread 2's `write` can never complete -> deadlocked.
 fn main() {
     let mut fds = [-1, -1];
@@ -21,27 +25,28 @@ fn main() {
     assert_eq!(res, 0);
     let arr1: [u8; 212992] = [1; 212992];
     // Exhaust the space in the buffer so the subsequent write will block.
-    let res = unsafe { libc::write(fds[0], arr1.as_ptr() as *const libc::c_void, 212992) };
+    let res =
+        unsafe { libc_utils::write_all(fds[0], arr1.as_ptr() as *const libc::c_void, 212992) };
     assert_eq!(res, 212992);
     let thread1 = thread::spawn(move || {
-        let data = "abc".as_bytes().as_ptr();
+        let data = "a".as_bytes();
         // The write below will be blocked because the buffer is already full.
-        let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
-        assert_eq!(res, 3);
+        let res = unsafe { libc::write(fds[0], data.as_ptr() as *const libc::c_void, data.len()) };
+        assert_eq!(res, data.len().cast_signed());
     });
     let thread2 = thread::spawn(move || {
-        let data = "abc".as_bytes().as_ptr();
+        let data = "a".as_bytes();
         // The write below will be blocked because the buffer is already full.
-        let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
-        //~^ERROR: deadlocked
-        assert_eq!(res, 3);
+        let res = unsafe { libc::write(fds[0], data.as_ptr() as *const libc::c_void, data.len()) };
+        //~^ERROR: deadlock
+        assert_eq!(res, data.len().cast_signed());
     });
     let thread3 = thread::spawn(move || {
         // Unblock thread1 by freeing up some space.
-        let mut buf: [u8; 3] = [0; 3];
+        let mut buf: [u8; 1] = [0; 1];
         let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
-        assert_eq!(res, 3);
-        assert_eq!(buf, [1, 1, 1]);
+        assert_eq!(res, buf.len().cast_signed());
+        assert_eq!(buf, [1]);
     });
     thread1.join().unwrap();
     thread2.join().unwrap();
diff --git a/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.stderr b/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.stderr
index b29cd70f35e..f766500d331 100644
--- a/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.stderr
+++ b/src/tools/miri/tests/fail-dep/libc/socketpair_block_write_twice.stderr
@@ -23,8 +23,8 @@ error: the evaluated program deadlocked
 error: the evaluated program deadlocked
   --> tests/fail-dep/libc/socketpair_block_write_twice.rs:LL:CC
    |
-LL |         let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
-   |                                                                              ^ this thread got stuck here
+LL |         let res = unsafe { libc::write(fds[0], data.as_ptr() as *const libc::c_void, data.len()) };
+   |                                                                                                ^ this thread got stuck here
    |
    = note: BACKTRACE on thread `unnamed-ID`:
    = note: inside closure at tests/fail-dep/libc/socketpair_block_write_twice.rs:LL:CC
diff --git a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_return_type.stderr b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_return_type.stderr
index 07e6561b53e..28c676ad482 100644
--- a/src/tools/miri/tests/fail/function_pointers/abi_mismatch_return_type.stderr
+++ b/src/tools/miri/tests/fail/function_pointers/abi_mismatch_return_type.stderr
@@ -6,6 +6,8 @@ LL |     g()
    |
    = 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
+   = help: this means these two types are not *guaranteed* to be ABI-compatible across all targets
+   = help: if you think this code should be accepted anyway, please report an issue with Miri
    = note: BACKTRACE:
    = note: inside `main` at tests/fail/function_pointers/abi_mismatch_return_type.rs:LL:CC
 
diff --git a/src/tools/miri/tests/fail/shims/ctor_wrong_ret_type.rs b/src/tools/miri/tests/fail/shims/ctor_wrong_ret_type.rs
new file mode 100644
index 00000000000..1e10f682e71
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/ctor_wrong_ret_type.rs
@@ -0,0 +1,39 @@
+unsafe extern "C" fn ctor() -> i32 {
+    //~^ERROR: calling a function with return type i32 passing return place of type ()
+    0
+}
+
+#[rustfmt::skip]
+macro_rules! ctor {
+    ($ident:ident = $ctor:ident) => {
+        #[cfg_attr(
+            all(any(
+                target_os = "linux",
+                target_os = "android",
+                target_os = "dragonfly",
+                target_os = "freebsd",
+                target_os = "haiku",
+                target_os = "illumos",
+                target_os = "netbsd",
+                target_os = "openbsd",
+                target_os = "solaris",
+                target_os = "none",
+                target_family = "wasm",
+            )),
+            link_section = ".init_array"
+        )]
+        #[cfg_attr(windows, link_section = ".CRT$XCU")]
+        #[cfg_attr(
+            any(target_os = "macos", target_os = "ios"),
+            // We do not set the `mod_init_funcs` flag here since ctor/inventory also do not do
+            // that. See <https://github.com/rust-lang/miri/pull/4459#discussion_r2200115629>.
+            link_section = "__DATA,__mod_init_func"
+        )]
+        #[used]
+        static $ident: unsafe extern "C" fn() -> i32 = $ctor;
+    };
+}
+
+ctor! { CTOR = ctor }
+
+fn main() {}
diff --git a/src/tools/miri/tests/fail/shims/ctor_wrong_ret_type.stderr b/src/tools/miri/tests/fail/shims/ctor_wrong_ret_type.stderr
new file mode 100644
index 00000000000..664bfbd32db
--- /dev/null
+++ b/src/tools/miri/tests/fail/shims/ctor_wrong_ret_type.stderr
@@ -0,0 +1,12 @@
+error: Undefined Behavior: calling a function with return type i32 passing return place of type ()
+   |
+   = note: Undefined Behavior occurred here
+   = note: (no span available)
+   = 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
+   = help: this means these two types are not *guaranteed* to be ABI-compatible across all targets
+   = help: if you think this code should be accepted anyway, please report an issue with Miri
+   = note: BACKTRACE:
+
+error: aborting due to 1 previous error
+
diff --git a/src/tools/miri/tests/fail/shims/return_type_mismatch.stderr b/src/tools/miri/tests/fail/shims/return_type_mismatch.stderr
index 080ee16a2eb..18ff3067fa0 100644
--- a/src/tools/miri/tests/fail/shims/return_type_mismatch.stderr
+++ b/src/tools/miri/tests/fail/shims/return_type_mismatch.stderr
@@ -6,6 +6,8 @@ LL |         close(fd);
    |
    = 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
+   = help: this means these two types are not *guaranteed* to be ABI-compatible across all targets
+   = help: if you think this code should be accepted anyway, please report an issue with Miri
    = note: BACKTRACE:
    = note: inside `main` at tests/fail/shims/return_type_mismatch.rs:LL:CC
 
diff --git a/src/tools/miri/tests/genmc/pass/test_cxx_build.rs b/src/tools/miri/tests/genmc/pass/test_cxx_build.rs
new file mode 100644
index 00000000000..f621bd9114f
--- /dev/null
+++ b/src/tools/miri/tests/genmc/pass/test_cxx_build.rs
@@ -0,0 +1,8 @@
+//@compile-flags: -Zmiri-genmc
+
+#![no_main]
+
+#[unsafe(no_mangle)]
+fn miri_start(_argc: isize, _argv: *const *const u8) -> isize {
+    0
+}
diff --git a/src/tools/miri/tests/genmc/pass/test_cxx_build.stderr b/src/tools/miri/tests/genmc/pass/test_cxx_build.stderr
new file mode 100644
index 00000000000..4b7aa824bd1
--- /dev/null
+++ b/src/tools/miri/tests/genmc/pass/test_cxx_build.stderr
@@ -0,0 +1,5 @@
+warning: borrow tracking has been disabled, it is not (yet) supported in GenMC mode.
+C++: GenMC handle created!
+Miri: GenMC handle creation successful!
+C++: GenMC handle destroyed!
+Miri: Dropping GenMC handle successful!
diff --git a/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs
index 54ebfa9d198..c97206487a1 100644
--- a/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs
+++ b/src/tools/miri/tests/pass-dep/libc/libc-epoll-blocking.rs
@@ -6,6 +6,9 @@ use std::convert::TryInto;
 use std::thread;
 use std::thread::spawn;
 
+#[path = "../../utils/libc.rs"]
+mod libc_utils;
+
 // This is a set of testcases for blocking epoll.
 
 fn main() {
@@ -97,7 +100,7 @@ fn test_epoll_block_then_unblock() {
     let thread1 = spawn(move || {
         thread::yield_now();
         let data = "abcde".as_bytes().as_ptr();
-        let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+        let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
         assert_eq!(res, 5);
     });
     check_epoll_wait::<1>(epfd, &[(expected_event, expected_value)], 10);
@@ -130,7 +133,7 @@ fn test_notification_after_timeout() {
 
     // Trigger epoll notification after timeout.
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
 
     // Check the result of the notification.
diff --git a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs
index dc3ab2828fa..7130790b86d 100644
--- a/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs
+++ b/src/tools/miri/tests/pass-dep/libc/libc-epoll-no-blocking.rs
@@ -2,6 +2,9 @@
 
 use std::convert::TryInto;
 
+#[path = "../../utils/libc.rs"]
+mod libc_utils;
+
 fn main() {
     test_epoll_socketpair();
     test_epoll_socketpair_both_sides();
@@ -64,7 +67,7 @@ fn test_epoll_socketpair() {
 
     // Write to fd[0]
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
 
     // Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP
@@ -85,7 +88,7 @@ fn test_epoll_socketpair() {
 
     // Write some more to fd[0].
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
 
     // This did not change the readiness of fd[1]. And yet, we're seeing the event reported
@@ -153,7 +156,7 @@ fn test_epoll_ctl_del() {
 
     // Write to fd[0]
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
 
     // Register fd[1] with EPOLLIN|EPOLLOUT|EPOLLET
@@ -182,7 +185,7 @@ fn test_two_epoll_instance() {
 
     // Write to the socketpair.
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
 
     // Register one side of the socketpair with EPOLLIN | EPOLLOUT | EPOLLET.
@@ -224,7 +227,7 @@ fn test_two_same_fd_in_same_epoll_instance() {
 
     // Write to the socketpair.
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
 
     //Two notification should be received.
@@ -243,7 +246,7 @@ fn test_epoll_eventfd() {
 
     // Write to the eventfd instance.
     let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
-    let res = unsafe { libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
+    let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
     assert_eq!(res, 8);
 
     // Create an epoll instance.
@@ -282,7 +285,7 @@ fn test_epoll_socketpair_both_sides() {
 
     // Write to fds[1].
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
 
     //Two notification should be received.
@@ -297,7 +300,8 @@ fn test_epoll_socketpair_both_sides() {
 
     // Read from fds[0].
     let mut buf: [u8; 5] = [0; 5];
-    let res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+    let res =
+        unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
     assert_eq!(res, 5);
     assert_eq!(buf, "abcde".as_bytes());
 
@@ -325,7 +329,7 @@ fn test_closed_fd() {
 
     // Write to the eventfd instance.
     let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
-    let res = unsafe { libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
+    let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
     assert_eq!(res, 8);
 
     // Close the eventfd.
@@ -371,7 +375,8 @@ fn test_not_fully_closed_fd() {
 
     // Write to the eventfd instance to produce notification.
     let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
-    let res = unsafe { libc::write(newfd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
+    let res =
+        unsafe { libc_utils::write_all(newfd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
     assert_eq!(res, 8);
 
     // Close the dupped fd.
@@ -391,7 +396,7 @@ fn test_event_overwrite() {
 
     // Write to the eventfd instance.
     let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
-    let res = unsafe { libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
+    let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
     assert_eq!(res, 8);
 
     // Create an epoll instance.
@@ -445,7 +450,7 @@ fn test_socketpair_read() {
 
     // Write 5 bytes to fds[1].
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
 
     //Two notification should be received.
@@ -460,7 +465,8 @@ fn test_socketpair_read() {
 
     // Read 3 bytes from fds[0].
     let mut buf: [u8; 3] = [0; 3];
-    let res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+    let res =
+        unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
     assert_eq!(res, 3);
     assert_eq!(buf, "abc".as_bytes());
 
@@ -478,7 +484,8 @@ fn test_socketpair_read() {
 
     // Read until the buffer is empty.
     let mut buf: [u8; 2] = [0; 2];
-    let res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+    let res =
+        unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
     assert_eq!(res, 2);
     assert_eq!(buf, "de".as_bytes());
 
@@ -510,8 +517,9 @@ fn test_no_notification_for_unregister_flag() {
 
     // Write to fd[1].
     let data = "abcde".as_bytes().as_ptr();
-    let res: i32 =
-        unsafe { libc::write(fds[1], data as *const libc::c_void, 5).try_into().unwrap() };
+    let res: i32 = unsafe {
+        libc_utils::write_all(fds[1], data as *const libc::c_void, 5).try_into().unwrap()
+    };
     assert_eq!(res, 5);
 
     // Check result from epoll_wait. Since we didn't register EPOLLIN flag, the notification won't
@@ -546,7 +554,7 @@ fn test_socketpair_epollerr() {
 
     // Write to fd[0]
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
 
     // Close fds[1].
@@ -717,6 +725,6 @@ fn test_issue_3858() {
 
     // Write to the eventfd instance.
     let sized_8_data: [u8; 8] = 1_u64.to_ne_bytes();
-    let res = unsafe { libc::write(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
+    let res = unsafe { libc_utils::write_all(fd, sized_8_data.as_ptr() as *const libc::c_void, 8) };
     assert_eq!(res, 8);
 }
diff --git a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs
index 0ff48c389e8..86cf2a041f0 100644
--- a/src/tools/miri/tests/pass-dep/libc/libc-fs.rs
+++ b/src/tools/miri/tests/pass-dep/libc/libc-fs.rs
@@ -14,6 +14,9 @@ use std::path::PathBuf;
 #[path = "../../utils/mod.rs"]
 mod utils;
 
+#[path = "../../utils/libc.rs"]
+mod libc_utils;
+
 fn main() {
     test_dup();
     test_dup_stdout_stderr();
@@ -74,8 +77,8 @@ fn test_dup_stdout_stderr() {
     unsafe {
         let new_stdout = libc::fcntl(1, libc::F_DUPFD, 0);
         let new_stderr = libc::fcntl(2, libc::F_DUPFD, 0);
-        libc::write(new_stdout, bytes.as_ptr() as *const libc::c_void, bytes.len());
-        libc::write(new_stderr, bytes.as_ptr() as *const libc::c_void, bytes.len());
+        libc_utils::write_all(new_stdout, bytes.as_ptr() as *const libc::c_void, bytes.len());
+        libc_utils::write_all(new_stderr, bytes.as_ptr() as *const libc::c_void, bytes.len());
     }
 }
 
@@ -92,16 +95,24 @@ fn test_dup() {
         let new_fd2 = libc::dup2(fd, 8);
 
         let mut first_buf = [0u8; 4];
-        libc::read(fd, first_buf.as_mut_ptr() as *mut libc::c_void, 4);
-        assert_eq!(&first_buf, b"dup ");
+        let first_len = libc::read(fd, first_buf.as_mut_ptr() as *mut libc::c_void, 4);
+        assert!(first_len > 0);
+        let first_len = first_len as usize;
+        assert_eq!(first_buf[..first_len], bytes[..first_len]);
+        let remaining_bytes = &bytes[first_len..];
 
         let mut second_buf = [0u8; 4];
-        libc::read(new_fd, second_buf.as_mut_ptr() as *mut libc::c_void, 4);
-        assert_eq!(&second_buf, b"and ");
+        let second_len = libc::read(new_fd, second_buf.as_mut_ptr() as *mut libc::c_void, 4);
+        assert!(second_len > 0);
+        let second_len = second_len as usize;
+        assert_eq!(second_buf[..second_len], remaining_bytes[..second_len]);
+        let remaining_bytes = &remaining_bytes[second_len..];
 
         let mut third_buf = [0u8; 4];
-        libc::read(new_fd2, third_buf.as_mut_ptr() as *mut libc::c_void, 4);
-        assert_eq!(&third_buf, b"dup2");
+        let third_len = libc::read(new_fd2, third_buf.as_mut_ptr() as *mut libc::c_void, 4);
+        assert!(third_len > 0);
+        let third_len = third_len as usize;
+        assert_eq!(third_buf[..third_len], remaining_bytes[..third_len]);
     }
 }
 
@@ -145,7 +156,7 @@ fn test_ftruncate<T: From<i32>>(
     let bytes = b"hello";
     let path = utils::prepare("miri_test_libc_fs_ftruncate.txt");
     let mut file = File::create(&path).unwrap();
-    file.write(bytes).unwrap();
+    file.write_all(bytes).unwrap();
     file.sync_all().unwrap();
     assert_eq!(file.metadata().unwrap().len(), 5);
 
@@ -402,10 +413,10 @@ fn test_read_and_uninit() {
         unsafe {
             let fd = libc::open(cpath.as_ptr(), libc::O_RDONLY);
             assert_ne!(fd, -1);
-            let mut buf: MaybeUninit<[u8; 2]> = std::mem::MaybeUninit::uninit();
-            assert_eq!(libc::read(fd, buf.as_mut_ptr().cast::<std::ffi::c_void>(), 2), 2);
+            let mut buf: MaybeUninit<u8> = std::mem::MaybeUninit::uninit();
+            assert_eq!(libc::read(fd, buf.as_mut_ptr().cast::<std::ffi::c_void>(), 1), 1);
             let buf = buf.assume_init();
-            assert_eq!(buf, [1, 2]);
+            assert_eq!(buf, 1);
             assert_eq!(libc::close(fd), 0);
         }
         remove_file(&path).unwrap();
@@ -413,14 +424,22 @@ fn test_read_and_uninit() {
     {
         // We test that if we requested to read 4 bytes, but actually read 3 bytes, then
         // 3 bytes (not 4) will be overwritten, and remaining byte will be left as-is.
-        let path = utils::prepare_with_content("pass-libc-read-and-uninit-2.txt", &[1u8, 2, 3]);
+        let data = [1u8, 2, 3];
+        let path = utils::prepare_with_content("pass-libc-read-and-uninit-2.txt", &data);
         let cpath = CString::new(path.clone().into_os_string().into_encoded_bytes()).unwrap();
         unsafe {
             let fd = libc::open(cpath.as_ptr(), libc::O_RDONLY);
             assert_ne!(fd, -1);
             let mut buf = [42u8; 5];
-            assert_eq!(libc::read(fd, buf.as_mut_ptr().cast::<std::ffi::c_void>(), 4), 3);
-            assert_eq!(buf, [1, 2, 3, 42, 42]);
+            let res = libc::read(fd, buf.as_mut_ptr().cast::<std::ffi::c_void>(), 4);
+            assert!(res > 0 && res < 4);
+            for i in 0..buf.len() {
+                assert_eq!(
+                    buf[i],
+                    if i < res as usize { data[i] } else { 42 },
+                    "wrong result at pos {i}"
+                );
+            }
             assert_eq!(libc::close(fd), 0);
         }
         remove_file(&path).unwrap();
diff --git a/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs b/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs
index bc755af864c..ffbcf633b98 100644
--- a/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs
+++ b/src/tools/miri/tests/pass-dep/libc/libc-pipe.rs
@@ -2,6 +2,10 @@
 // test_race depends on a deterministic schedule.
 //@compile-flags: -Zmiri-deterministic-concurrency
 use std::thread;
+
+#[path = "../../utils/libc.rs"]
+mod libc_utils;
+
 fn main() {
     test_pipe();
     test_pipe_threaded();
@@ -26,21 +30,29 @@ fn test_pipe() {
 
     // Read size == data available in buffer.
     let data = "12345".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
     let mut buf3: [u8; 5] = [0; 5];
-    let res = unsafe { libc::read(fds[0], buf3.as_mut_ptr().cast(), buf3.len() as libc::size_t) };
+    let res = unsafe {
+        libc_utils::read_all(fds[0], buf3.as_mut_ptr().cast(), buf3.len() as libc::size_t)
+    };
     assert_eq!(res, 5);
     assert_eq!(buf3, "12345".as_bytes());
 
     // Read size > data available in buffer.
-    let data = "123".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 3) };
+    let data = "123".as_bytes();
+    let res = unsafe { libc_utils::write_all(fds[1], data.as_ptr() as *const libc::c_void, 3) };
     assert_eq!(res, 3);
     let mut buf4: [u8; 5] = [0; 5];
     let res = unsafe { libc::read(fds[0], buf4.as_mut_ptr().cast(), buf4.len() as libc::size_t) };
-    assert_eq!(res, 3);
-    assert_eq!(&buf4[0..3], "123".as_bytes());
+    assert!(res > 0 && res <= 3);
+    let res = res as usize;
+    assert_eq!(buf4[..res], data[..res]);
+    if res < 3 {
+        // Drain the rest from the read end.
+        let res = unsafe { libc_utils::read_all(fds[0], buf4[res..].as_mut_ptr().cast(), 3 - res) };
+        assert!(res > 0);
+    }
 }
 
 fn test_pipe_threaded() {
@@ -51,7 +63,7 @@ fn test_pipe_threaded() {
     let thread1 = thread::spawn(move || {
         let mut buf: [u8; 5] = [0; 5];
         let res: i64 = unsafe {
-            libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
+            libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
                 .try_into()
                 .unwrap()
         };
@@ -60,7 +72,7 @@ fn test_pipe_threaded() {
     });
     thread::yield_now();
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
     thread1.join().unwrap();
 
@@ -68,11 +80,12 @@ fn test_pipe_threaded() {
     let thread2 = thread::spawn(move || {
         thread::yield_now();
         let data = "12345".as_bytes().as_ptr();
-        let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+        let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
         assert_eq!(res, 5);
     });
     let mut buf: [u8; 5] = [0; 5];
-    let res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+    let res =
+        unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
     assert_eq!(res, 5);
     assert_eq!(buf, "12345".as_bytes());
     thread2.join().unwrap();
@@ -90,7 +103,7 @@ fn test_race() {
         // write() from the main thread will occur before the read() here
         // because preemption is disabled and the main thread yields after write().
         let res: i32 = unsafe {
-            libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
+            libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
                 .try_into()
                 .unwrap()
         };
@@ -101,7 +114,7 @@ fn test_race() {
     });
     unsafe { VAL = 1 };
     let data = "a".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 1) };
+    let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 1) };
     assert_eq!(res, 1);
     thread::yield_now();
     thread1.join().unwrap();
@@ -186,11 +199,12 @@ fn test_pipe_fcntl_threaded() {
         // the socket is now "non-blocking", the shim needs to deal correctly
         // with threads that were blocked before the socket was made non-blocking.
         let data = "abcde".as_bytes().as_ptr();
-        let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+        let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
         assert_eq!(res, 5);
     });
     // The `read` below will block.
-    let res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+    let res =
+        unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
     thread1.join().unwrap();
     assert_eq!(res, 5);
 }
diff --git a/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs b/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs
index c36f6b11224..9c211ffbdbe 100644
--- a/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs
+++ b/src/tools/miri/tests/pass-dep/libc/libc-socketpair.rs
@@ -6,6 +6,10 @@
 #![allow(static_mut_refs)]
 
 use std::thread;
+
+#[path = "../../utils/libc.rs"]
+mod libc_utils;
+
 fn main() {
     test_socketpair();
     test_socketpair_threaded();
@@ -22,54 +26,71 @@ fn test_socketpair() {
 
     // Read size == data available in buffer.
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
     let mut buf: [u8; 5] = [0; 5];
-    let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+    let res =
+        unsafe { libc_utils::read_all(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
     assert_eq!(res, 5);
     assert_eq!(buf, "abcde".as_bytes());
 
     // Read size > data available in buffer.
-    let data = "abc".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
+    let data = "abc".as_bytes();
+    let res = unsafe { libc_utils::write_all(fds[0], data.as_ptr() as *const libc::c_void, 3) };
     assert_eq!(res, 3);
     let mut buf2: [u8; 5] = [0; 5];
     let res = unsafe { libc::read(fds[1], buf2.as_mut_ptr().cast(), buf2.len() as libc::size_t) };
-    assert_eq!(res, 3);
-    assert_eq!(&buf2[0..3], "abc".as_bytes());
+    assert!(res > 0 && res <= 3);
+    let res = res as usize;
+    assert_eq!(buf2[..res], data[..res]);
+    if res < 3 {
+        // Drain the rest from the read end.
+        let res = unsafe { libc_utils::read_all(fds[1], buf2[res..].as_mut_ptr().cast(), 3 - res) };
+        assert!(res > 0);
+    }
 
     // Test read and write from another direction.
     // Read size == data available in buffer.
     let data = "12345".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
     let mut buf3: [u8; 5] = [0; 5];
-    let res = unsafe { libc::read(fds[0], buf3.as_mut_ptr().cast(), buf3.len() as libc::size_t) };
+    let res = unsafe {
+        libc_utils::read_all(fds[0], buf3.as_mut_ptr().cast(), buf3.len() as libc::size_t)
+    };
     assert_eq!(res, 5);
     assert_eq!(buf3, "12345".as_bytes());
 
     // Read size > data available in buffer.
-    let data = "123".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 3) };
+    let data = "123".as_bytes();
+    let res = unsafe { libc_utils::write_all(fds[1], data.as_ptr() as *const libc::c_void, 3) };
     assert_eq!(res, 3);
     let mut buf4: [u8; 5] = [0; 5];
     let res = unsafe { libc::read(fds[0], buf4.as_mut_ptr().cast(), buf4.len() as libc::size_t) };
-    assert_eq!(res, 3);
-    assert_eq!(&buf4[0..3], "123".as_bytes());
+    assert!(res > 0 && res <= 3);
+    let res = res as usize;
+    assert_eq!(buf4[..res], data[..res]);
+    if res < 3 {
+        // Drain the rest from the read end.
+        let res = unsafe { libc_utils::read_all(fds[0], buf4[res..].as_mut_ptr().cast(), 3 - res) };
+        assert!(res > 0);
+    }
 
     // Test when happens when we close one end, with some data in the buffer.
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
+    let res = unsafe { libc_utils::write_all(fds[0], data.as_ptr() as *const libc::c_void, 3) };
     assert_eq!(res, 3);
     unsafe { libc::close(fds[0]) };
     // Reading the other end should return that data, then EOF.
     let mut buf: [u8; 5] = [0; 5];
-    let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+    let res =
+        unsafe { libc_utils::read_all(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
     assert_eq!(res, 3);
     assert_eq!(&buf[0..3], "123".as_bytes());
-    let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+    let res =
+        unsafe { libc_utils::read_all(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
     assert_eq!(res, 0); // 0-sized read: EOF.
     // Writing the other end should emit EPIPE.
-    let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 1) };
+    let res = unsafe { libc_utils::write_all(fds[1], data.as_ptr() as *const libc::c_void, 1) };
     assert_eq!(res, -1);
     assert_eq!(std::io::Error::last_os_error().raw_os_error(), Some(libc::EPIPE));
 }
@@ -82,7 +103,7 @@ fn test_socketpair_threaded() {
     let thread1 = thread::spawn(move || {
         let mut buf: [u8; 5] = [0; 5];
         let res: i64 = unsafe {
-            libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
+            libc_utils::read_all(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
                 .try_into()
                 .unwrap()
         };
@@ -91,7 +112,7 @@ fn test_socketpair_threaded() {
     });
     thread::yield_now();
     let data = "abcde".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 5) };
+    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 5) };
     assert_eq!(res, 5);
     thread1.join().unwrap();
 
@@ -99,11 +120,12 @@ fn test_socketpair_threaded() {
     let thread2 = thread::spawn(move || {
         thread::yield_now();
         let data = "12345".as_bytes().as_ptr();
-        let res = unsafe { libc::write(fds[1], data as *const libc::c_void, 5) };
+        let res = unsafe { libc_utils::write_all(fds[1], data as *const libc::c_void, 5) };
         assert_eq!(res, 5);
     });
     let mut buf: [u8; 5] = [0; 5];
-    let res = unsafe { libc::read(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+    let res =
+        unsafe { libc_utils::read_all(fds[0], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
     assert_eq!(res, 5);
     assert_eq!(buf, "12345".as_bytes());
     thread2.join().unwrap();
@@ -119,7 +141,7 @@ fn test_race() {
         // write() from the main thread will occur before the read() here
         // because preemption is disabled and the main thread yields after write().
         let res: i32 = unsafe {
-            libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
+            libc_utils::read_all(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
                 .try_into()
                 .unwrap()
         };
@@ -130,7 +152,7 @@ fn test_race() {
     });
     unsafe { VAL = 1 };
     let data = "a".as_bytes().as_ptr();
-    let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 1) };
+    let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 1) };
     assert_eq!(res, 1);
     thread::yield_now();
     thread1.join().unwrap();
@@ -144,14 +166,16 @@ fn test_blocking_read() {
     let thread1 = thread::spawn(move || {
         // Let this thread block on read.
         let mut buf: [u8; 3] = [0; 3];
-        let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+        let res = unsafe {
+            libc_utils::read_all(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
+        };
         assert_eq!(res, 3);
         assert_eq!(&buf, "abc".as_bytes());
     });
     let thread2 = thread::spawn(move || {
         // Unblock thread1 by doing writing something.
         let data = "abc".as_bytes().as_ptr();
-        let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
+        let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 3) };
         assert_eq!(res, 3);
     });
     thread1.join().unwrap();
@@ -165,18 +189,21 @@ fn test_blocking_write() {
     assert_eq!(res, 0);
     let arr1: [u8; 212992] = [1; 212992];
     // Exhaust the space in the buffer so the subsequent write will block.
-    let res = unsafe { libc::write(fds[0], arr1.as_ptr() as *const libc::c_void, 212992) };
+    let res =
+        unsafe { libc_utils::write_all(fds[0], arr1.as_ptr() as *const libc::c_void, 212992) };
     assert_eq!(res, 212992);
     let thread1 = thread::spawn(move || {
         let data = "abc".as_bytes().as_ptr();
         // The write below will be blocked because the buffer is already full.
-        let res = unsafe { libc::write(fds[0], data as *const libc::c_void, 3) };
+        let res = unsafe { libc_utils::write_all(fds[0], data as *const libc::c_void, 3) };
         assert_eq!(res, 3);
     });
     let thread2 = thread::spawn(move || {
         // Unblock thread1 by freeing up some space.
         let mut buf: [u8; 3] = [0; 3];
-        let res = unsafe { libc::read(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t) };
+        let res = unsafe {
+            libc_utils::read_all(fds[1], buf.as_mut_ptr().cast(), buf.len() as libc::size_t)
+        };
         assert_eq!(res, 3);
         assert_eq!(buf, [1, 1, 1]);
     });
diff --git a/src/tools/miri/tests/pass/shims/ctor.rs b/src/tools/miri/tests/pass/shims/ctor.rs
index b997d2386b8..a0fcdb1081e 100644
--- a/src/tools/miri/tests/pass/shims/ctor.rs
+++ b/src/tools/miri/tests/pass/shims/ctor.rs
@@ -2,13 +2,13 @@ use std::sync::atomic::{AtomicUsize, Ordering};
 
 static COUNT: AtomicUsize = AtomicUsize::new(0);
 
-unsafe extern "C" fn ctor() {
-    COUNT.fetch_add(1, Ordering::Relaxed);
+unsafe extern "C" fn ctor<const N: usize>() {
+    COUNT.fetch_add(N, Ordering::Relaxed);
 }
 
 #[rustfmt::skip]
 macro_rules! ctor {
-    ($ident:ident = $ctor:ident) => {
+    ($ident:ident: $ty:ty = $ctor:expr) => {
         #[cfg_attr(
             all(any(
                 target_os = "linux",
@@ -33,14 +33,13 @@ macro_rules! ctor {
             link_section = "__DATA,__mod_init_func"
         )]
         #[used]
-        static $ident: unsafe extern "C" fn() = $ctor;
+        static $ident: $ty = $ctor;
     };
 }
 
-ctor! { CTOR1 = ctor }
-ctor! { CTOR2 = ctor }
-ctor! { CTOR3 = ctor }
+ctor! { CTOR1: unsafe extern "C" fn() = ctor::<1> }
+ctor! { CTOR2: [unsafe extern "C" fn(); 2] = [ctor::<2>, ctor::<3>] }
 
 fn main() {
-    assert_eq!(COUNT.load(Ordering::Relaxed), 3, "ctors did not run");
+    assert_eq!(COUNT.load(Ordering::Relaxed), 6, "ctors did not run");
 }
diff --git a/src/tools/miri/tests/pass/shims/fs.rs b/src/tools/miri/tests/pass/shims/fs.rs
index 9d5725773e6..e7f11c54704 100644
--- a/src/tools/miri/tests/pass/shims/fs.rs
+++ b/src/tools/miri/tests/pass/shims/fs.rs
@@ -17,6 +17,10 @@ mod utils;
 fn main() {
     test_path_conversion();
     test_file();
+    // Partial reads/writes are apparently not a thing on Windows.
+    if cfg!(not(windows)) {
+        test_file_partial_reads_writes();
+    }
     test_file_create_new();
     test_metadata();
     test_seek();
@@ -53,7 +57,7 @@ fn test_file() {
     file.write(&mut []).unwrap();
     assert_eq!(file.metadata().unwrap().len(), 0);
 
-    file.write(bytes).unwrap();
+    file.write_all(bytes).unwrap();
     assert_eq!(file.metadata().unwrap().len(), bytes.len() as u64);
     // Test opening, reading and closing a file.
     let mut file = File::open(&path).unwrap();
@@ -66,10 +70,36 @@ fn test_file() {
 
     assert!(!file.is_terminal());
 
+    // Writing to a file opened for reading should error (and not stop interpretation). std does not
+    // categorize the error so we don't check for details.
+    file.write(&[]).unwrap_err();
+
     // Removing file should succeed.
     remove_file(&path).unwrap();
 }
 
+fn test_file_partial_reads_writes() {
+    let path = utils::prepare_with_content("miri_test_fs_file.txt", b"abcdefg");
+
+    // Ensure we sometimes do incomplete writes.
+    let got_short_write = (0..16).any(|_| {
+        let _ = remove_file(&path); // FIXME(win, issue #4483): errors if the file already exists
+        let mut file = File::create(&path).unwrap();
+        file.write(&[0; 4]).unwrap() != 4
+    });
+    assert!(got_short_write);
+    // Ensure we sometimes do incomplete reads.
+    let got_short_read = (0..16).any(|_| {
+        let mut file = File::open(&path).unwrap();
+        let mut buf = [0; 4];
+        file.read(&mut buf).unwrap() != 4
+    });
+    assert!(got_short_read);
+
+    // Clean up
+    remove_file(&path).unwrap();
+}
+
 fn test_file_clone() {
     let bytes = b"Hello, World!\n";
     let path = utils::prepare_with_content("miri_test_fs_file_clone.txt", bytes);
diff --git a/src/tools/miri/tests/pass/shims/pipe.rs b/src/tools/miri/tests/pass/shims/pipe.rs
index c47feb8774a..4915e54c533 100644
--- a/src/tools/miri/tests/pass/shims/pipe.rs
+++ b/src/tools/miri/tests/pass/shims/pipe.rs
@@ -4,8 +4,8 @@ use std::io::{Read, Write, pipe};
 
 fn main() {
     let (mut ping_rx, mut ping_tx) = pipe().unwrap();
-    ping_tx.write(b"hello").unwrap();
+    ping_tx.write_all(b"hello").unwrap();
     let mut buf: [u8; 5] = [0; 5];
-    ping_rx.read(&mut buf).unwrap();
+    ping_rx.read_exact(&mut buf).unwrap();
     assert_eq!(&buf, "hello".as_bytes());
 }
diff --git a/src/tools/miri/tests/ui.rs b/src/tools/miri/tests/ui.rs
index cb915b11b67..73fbe2cc020 100644
--- a/src/tools/miri/tests/ui.rs
+++ b/src/tools/miri/tests/ui.rs
@@ -338,6 +338,20 @@ fn main() -> Result<()> {
         ui(Mode::Fail, "tests/native-lib/fail", &target, WithoutDependencies, tmpdir.path())?;
     }
 
+    // We only enable GenMC tests when the `genmc` feature is enabled, but also only on platforms we support:
+    // FIXME(genmc,macos): Add `target_os = "macos"` once `https://github.com/dtolnay/cxx/issues/1535` is fixed.
+    // FIXME(genmc,cross-platform): remove `host == target` check once cross-platform support with GenMC is possible.
+    if cfg!(all(
+        feature = "genmc",
+        target_os = "linux",
+        target_pointer_width = "64",
+        target_endian = "little"
+    )) && host == target
+    {
+        ui(Mode::Pass, "tests/genmc/pass", &target, WithDependencies, tmpdir.path())?;
+        ui(Mode::Fail, "tests/genmc/fail", &target, WithDependencies, tmpdir.path())?;
+    }
+
     Ok(())
 }
 
diff --git a/src/tools/miri/tests/utils/fs.rs b/src/tools/miri/tests/utils/fs.rs
index 7340908626f..7d75b3fced3 100644
--- a/src/tools/miri/tests/utils/fs.rs
+++ b/src/tools/miri/tests/utils/fs.rs
@@ -1,6 +1,6 @@
 use std::ffi::OsString;
-use std::fs;
 use std::path::PathBuf;
+use std::{fs, io};
 
 use super::miri_extern;
 
diff --git a/src/tools/miri/tests/utils/libc.rs b/src/tools/miri/tests/utils/libc.rs
new file mode 100644
index 00000000000..1a3cd067c04
--- /dev/null
+++ b/src/tools/miri/tests/utils/libc.rs
@@ -0,0 +1,44 @@
+//! Utils that need libc.
+#![allow(dead_code)]
+
+pub unsafe fn read_all(
+    fd: libc::c_int,
+    buf: *mut libc::c_void,
+    count: libc::size_t,
+) -> libc::ssize_t {
+    assert!(count > 0);
+    let mut read_so_far = 0;
+    while read_so_far < count {
+        let res = libc::read(fd, buf.add(read_so_far), count - read_so_far);
+        if res < 0 {
+            return res;
+        }
+        if res == 0 {
+            // EOF
+            break;
+        }
+        read_so_far += res as libc::size_t;
+    }
+    return read_so_far as libc::ssize_t;
+}
+
+pub unsafe fn write_all(
+    fd: libc::c_int,
+    buf: *const libc::c_void,
+    count: libc::size_t,
+) -> libc::ssize_t {
+    assert!(count > 0);
+    let mut written_so_far = 0;
+    while written_so_far < count {
+        let res = libc::write(fd, buf.add(written_so_far), count - written_so_far);
+        if res < 0 {
+            return res;
+        }
+        if res == 0 {
+            // EOF?
+            break;
+        }
+        written_so_far += res as libc::size_t;
+    }
+    return written_so_far as libc::ssize_t;
+}
diff --git a/src/tools/miri/tests/x86_64-unknown-kernel.json b/src/tools/miri/tests/x86_64-unknown-kernel.json
index 8da67d3a1c6..a5eaceb4f68 100644
--- a/src/tools/miri/tests/x86_64-unknown-kernel.json
+++ b/src/tools/miri/tests/x86_64-unknown-kernel.json
@@ -2,7 +2,7 @@
   "llvm-target": "x86_64-unknown-none",
   "target-endian": "little",
   "target-pointer-width": "64",
-  "target-c-int-width": "32",
+  "target-c-int-width": 32,
   "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
   "arch": "x86_64",
   "os": "none",
diff --git a/src/tools/tidy/src/extra_checks/mod.rs b/src/tools/tidy/src/extra_checks/mod.rs
index 8121eb057db..f90f716cd95 100644
--- a/src/tools/tidy/src/extra_checks/mod.rs
+++ b/src/tools/tidy/src/extra_checks/mod.rs
@@ -86,7 +86,7 @@ fn check_impl(
         std::env::var("TIDY_PRINT_DIFF").is_ok_and(|v| v.eq_ignore_ascii_case("true") || v == "1");
 
     // Split comma-separated args up
-    let lint_args = match extra_checks {
+    let mut lint_args = match extra_checks {
         Some(s) => s
             .strip_prefix("--extra-checks=")
             .unwrap()
@@ -99,11 +99,7 @@ fn check_impl(
             })
             .filter_map(|(res, src)| match res {
                 Ok(arg) => {
-                    if arg.is_inactive_auto(ci_info) {
-                        None
-                    } else {
-                        Some(arg)
-                    }
+                    Some(arg)
                 }
                 Err(err) => {
                     // only warn because before bad extra checks would be silently ignored.
@@ -114,6 +110,11 @@ fn check_impl(
             .collect(),
         None => vec![],
     };
+    if lint_args.iter().any(|ck| ck.auto) {
+        crate::files_modified_batch_filter(ci_info, &mut lint_args, |ck, path| {
+            ck.is_non_auto_or_matches(path)
+        });
+    }
 
     macro_rules! extra_check {
         ($lang:ident, $kind:ident) => {
@@ -721,10 +722,10 @@ impl ExtraCheckArg {
         self.lang == lang && self.kind.map(|k| k == kind).unwrap_or(true)
     }
 
-    /// Returns `true` if this is an auto arg and the relevant files are not modified.
-    fn is_inactive_auto(&self, ci_info: &CiInfo) -> bool {
+    /// Returns `false` if this is an auto arg and the passed filename does not trigger the auto rule
+    fn is_non_auto_or_matches(&self, filepath: &str) -> bool {
         if !self.auto {
-            return false;
+            return true;
         }
         let ext = match self.lang {
             ExtraCheckLang::Py => ".py",
@@ -732,12 +733,15 @@ impl ExtraCheckArg {
             ExtraCheckLang::Shell => ".sh",
             ExtraCheckLang::Js => ".js",
             ExtraCheckLang::Spellcheck => {
-                return !crate::files_modified(ci_info, |s| {
-                    SPELLCHECK_DIRS.iter().any(|dir| Path::new(s).starts_with(dir))
-                });
+                for dir in SPELLCHECK_DIRS {
+                    if Path::new(filepath).starts_with(dir) {
+                        return true;
+                    }
+                }
+                return false;
             }
         };
-        !crate::files_modified(ci_info, |s| s.ends_with(ext))
+        filepath.ends_with(ext)
     }
 
     fn has_supported_kind(&self) -> bool {
diff --git a/src/tools/tidy/src/lib.rs b/src/tools/tidy/src/lib.rs
index 4f9fb308a86..4ea9d051ddb 100644
--- a/src/tools/tidy/src/lib.rs
+++ b/src/tools/tidy/src/lib.rs
@@ -125,40 +125,61 @@ pub fn git_diff<S: AsRef<OsStr>>(base_commit: &str, extra_arg: S) -> Option<Stri
     Some(String::from_utf8_lossy(&output.stdout).into())
 }
 
-/// Returns true if any modified file matches the predicate, if we are in CI, or if unable to list modified files.
-pub fn files_modified(ci_info: &CiInfo, pred: impl Fn(&str) -> bool) -> bool {
+/// Similar to `files_modified`, but only involves a single call to `git`.
+///
+/// removes all elements from `items` that do not cause any match when `pred` is called with the list of modifed files.
+///
+/// if in CI, no elements will be removed.
+pub fn files_modified_batch_filter<T>(
+    ci_info: &CiInfo,
+    items: &mut Vec<T>,
+    pred: impl Fn(&T, &str) -> bool,
+) {
     if CiEnv::is_ci() {
         // assume everything is modified on CI because we really don't want false positives there.
-        return true;
+        return;
     }
     let Some(base_commit) = &ci_info.base_commit else {
         eprintln!("No base commit, assuming all files are modified");
-        return true;
+        return;
     };
     match crate::git_diff(base_commit, "--name-status") {
         Some(output) => {
-            let modified_files = output.lines().filter_map(|ln| {
-                let (status, name) = ln
-                    .trim_end()
-                    .split_once('\t')
-                    .expect("bad format from `git diff --name-status`");
-                if status == "M" { Some(name) } else { None }
-            });
-            for modified_file in modified_files {
-                if pred(modified_file) {
-                    return true;
+            let modified_files: Vec<_> = output
+                .lines()
+                .filter_map(|ln| {
+                    let (status, name) = ln
+                        .trim_end()
+                        .split_once('\t')
+                        .expect("bad format from `git diff --name-status`");
+                    if status == "M" { Some(name) } else { None }
+                })
+                .collect();
+            items.retain(|item| {
+                for modified_file in &modified_files {
+                    if pred(item, modified_file) {
+                        // at least one predicate matches, keep this item.
+                        return true;
+                    }
                 }
-            }
-            false
+                // no predicates matched, remove this item.
+                false
+            });
         }
         None => {
             eprintln!("warning: failed to run `git diff` to check for changes");
             eprintln!("warning: assuming all files are modified");
-            true
         }
     }
 }
 
+/// Returns true if any modified file matches the predicate, if we are in CI, or if unable to list modified files.
+pub fn files_modified(ci_info: &CiInfo, pred: impl Fn(&str) -> bool) -> bool {
+    let mut v = vec![()];
+    files_modified_batch_filter(ci_info, &mut v, |_, p| pred(p));
+    !v.is_empty()
+}
+
 pub mod alphabetical;
 pub mod bins;
 pub mod debug_artifacts;
diff --git a/tests/codegen-llvm/debuginfo-cyclic-structure.rs b/tests/codegen-llvm/debuginfo-cyclic-structure.rs
new file mode 100644
index 00000000000..b8cc5447741
--- /dev/null
+++ b/tests/codegen-llvm/debuginfo-cyclic-structure.rs
@@ -0,0 +1,32 @@
+//@ compile-flags:-g -Copt-level=0 -C panic=abort
+
+// Check that debug information exists for structures containing loops (cyclic references).
+// Previously it may incorrectly prune member information during recursive type inference check.
+
+// CHECK: !DICompositeType(tag: DW_TAG_structure_type, name: "Arc<debuginfo_cyclic_structure::Inner<alloc::sync::Arc<debuginfo_cyclic_structure::Handle{{.*}}elements: ![[FIELDS:[0-9]+]]
+// CHECK: ![[FIELDS]] = !{!{{.*}}}
+// CHECK-NOT: ![[FIELDS]] = !{}
+
+#![crate_type = "lib"]
+
+use std::mem::MaybeUninit;
+use std::sync::Arc;
+
+struct Inner<T> {
+    buffer: Box<MaybeUninit<T>>,
+}
+struct Shared {
+    shared: Arc<Inner<Arc<Handle>>>,
+}
+struct Handle {
+    shared: Shared,
+}
+struct Core {
+    inner: Arc<Inner<Arc<Handle>>>,
+}
+
+#[no_mangle]
+extern "C" fn test() {
+    let с = Core { inner: Arc::new(Inner { buffer: Box::new(MaybeUninit::uninit()) }) };
+    std::hint::black_box(с);
+}
diff --git a/tests/run-make/crate-loading-crate-depends-on-itself/foo.stderr b/tests/run-make/crate-loading-crate-depends-on-itself/foo.stderr
index 7f131153540..9433a0ba00e 100644
--- a/tests/run-make/crate-loading-crate-depends-on-itself/foo.stderr
+++ b/tests/run-make/crate-loading-crate-depends-on-itself/foo.stderr
@@ -7,19 +7,19 @@ error[E0277]: the trait bound `foo::Struct: Trait` is not satisfied
 note: there are multiple different versions of crate `foo` in the dependency graph
   --> foo-current.rs:7:1
    |
-4  | extern crate foo;
+ 4 | extern crate foo;
    | ----------------- one version of crate `foo` used here, as a direct dependency of the current crate
-5  |
-6  | pub struct Struct;
+ 5 |
+ 6 | pub struct Struct;
    | ----------------- this type implements the required trait
-7  | pub trait Trait {}
+ 7 | pub trait Trait {}
    | ^^^^^^^^^^^^^^^ this is the required trait
    |
   ::: foo-prev.rs:X:Y
    |
-4  | pub struct Struct;
+ 4 | pub struct Struct;
    | ----------------- this type doesn't implement the required trait
-5  | pub trait Trait {}
+ 5 | pub trait Trait {}
    | --------------- this is the found trait
    = note: two types coming from two different versions of the same crate are different types even if they look the same
    = help: you can use `cargo tree` to explore your dependency tree
diff --git a/tests/run-make/rustdoc-dep-info/after.md b/tests/run-make/rustdoc-dep-info/after.md
new file mode 100644
index 00000000000..10d4b4c4116
--- /dev/null
+++ b/tests/run-make/rustdoc-dep-info/after.md
@@ -0,0 +1 @@
+meow! :3
diff --git a/tests/run-make/rustdoc-dep-info/before.html b/tests/run-make/rustdoc-dep-info/before.html
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/tests/run-make/rustdoc-dep-info/before.html
diff --git a/tests/run-make/rustdoc-dep-info/extend.css b/tests/run-make/rustdoc-dep-info/extend.css
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/tests/run-make/rustdoc-dep-info/extend.css
diff --git a/tests/run-make/rustdoc-dep-info/rmake.rs b/tests/run-make/rustdoc-dep-info/rmake.rs
index db7a00a5ce2..625f81fd428 100644
--- a/tests/run-make/rustdoc-dep-info/rmake.rs
+++ b/tests/run-make/rustdoc-dep-info/rmake.rs
@@ -9,13 +9,26 @@ use run_make_support::{path, rfs, rustdoc};
 fn main() {
     // We're only emitting dep info, so we shouldn't be running static analysis to
     // figure out that this program is erroneous.
-    rustdoc().input("lib.rs").arg("-Zunstable-options").emit("dep-info").run();
+    // Ensure that all kinds of input reading flags end up in dep-info.
+    rustdoc()
+        .input("lib.rs")
+        .arg("-Zunstable-options")
+        .arg("--html-before-content=before.html")
+        .arg("--markdown-after-content=after.md")
+        .arg("--extend-css=extend.css")
+        .arg("--theme=theme.css")
+        .emit("dep-info")
+        .run();
 
     let content = rfs::read_to_string("foo.d");
     assert_contains(&content, "lib.rs:");
     assert_contains(&content, "foo.rs:");
     assert_contains(&content, "bar.rs:");
     assert_contains(&content, "doc.md:");
+    assert_contains(&content, "after.md:");
+    assert_contains(&content, "before.html:");
+    assert_contains(&content, "extend.css:");
+    assert_contains(&content, "theme.css:");
 
     // Now we check that we can provide a file name to the `dep-info` argument.
     rustdoc().input("lib.rs").arg("-Zunstable-options").emit("dep-info=bla.d").run();
diff --git a/tests/run-make/rustdoc-dep-info/theme.css b/tests/run-make/rustdoc-dep-info/theme.css
new file mode 100644
index 00000000000..459daffa9a1
--- /dev/null
+++ b/tests/run-make/rustdoc-dep-info/theme.css
@@ -0,0 +1 @@
+/* This is not a valid theme but that doesn't really matter */
diff --git a/tests/rustdoc-ui/doctest/standalone-warning-2024.stderr b/tests/rustdoc-ui/doctest/standalone-warning-2024.stderr
index bfc1e919404..ce65557c2c4 100644
--- a/tests/rustdoc-ui/doctest/standalone-warning-2024.stderr
+++ b/tests/rustdoc-ui/doctest/standalone-warning-2024.stderr
@@ -15,7 +15,7 @@ error: unknown attribute `standalone`
 note: the lint level is defined here
   --> $DIR/standalone-warning-2024.rs:9:9
    |
-9  | #![deny(warnings)]
+ 9 | #![deny(warnings)]
    |         ^^^^^^^^
    = note: `#[deny(rustdoc::invalid_codeblock_attributes)]` implied by `#[deny(warnings)]`
 
diff --git a/tests/ui/async-await/issue-64130-non-send-future-diags.stderr b/tests/ui/async-await/issue-64130-non-send-future-diags.stderr
index d28807e223b..beaf8e9c96d 100644
--- a/tests/ui/async-await/issue-64130-non-send-future-diags.stderr
+++ b/tests/ui/async-await/issue-64130-non-send-future-diags.stderr
@@ -4,12 +4,12 @@ error: future cannot be sent between threads safely
 LL |     is_send(foo());
    |             ^^^^^ future returned by `foo` is not `Send`
    |
-   = help: within `impl Future<Output = ()>`, the trait `Send` is not implemented for `MutexGuard<'_, u32>`
+   = help: within `impl Future<Output = ()>`, the trait `Send` is not implemented for `std::sync::MutexGuard<'_, u32>`
 note: future is not `Send` as this value is used across an await
   --> $DIR/issue-64130-non-send-future-diags.rs:17:11
    |
 LL |     let g = x.lock().unwrap();
-   |         - has type `MutexGuard<'_, u32>` which is not `Send`
+   |         - has type `std::sync::MutexGuard<'_, u32>` which is not `Send`
 LL |     baz().await;
    |           ^^^^^ await occurs here, with `g` maybe used later
 note: required by a bound in `is_send`
diff --git a/tests/ui/async-await/issue-71137.stderr b/tests/ui/async-await/issue-71137.stderr
index 8739c22a310..d567e3f2063 100644
--- a/tests/ui/async-await/issue-71137.stderr
+++ b/tests/ui/async-await/issue-71137.stderr
@@ -4,12 +4,12 @@ error: future cannot be sent between threads safely
 LL |   fake_spawn(wrong_mutex());
    |              ^^^^^^^^^^^^^ future returned by `wrong_mutex` is not `Send`
    |
-   = help: within `impl Future<Output = ()>`, the trait `Send` is not implemented for `MutexGuard<'_, i32>`
+   = help: within `impl Future<Output = ()>`, the trait `Send` is not implemented for `std::sync::MutexGuard<'_, i32>`
 note: future is not `Send` as this value is used across an await
   --> $DIR/issue-71137.rs:14:26
    |
 LL |     let mut guard = m.lock().unwrap();
-   |         --------- has type `MutexGuard<'_, i32>` which is not `Send`
+   |         --------- has type `std::sync::MutexGuard<'_, i32>` which is not `Send`
 LL |     (async { "right"; }).await;
    |                          ^^^^^ await occurs here, with `mut guard` maybe used later
 note: required by a bound in `fake_spawn`
diff --git a/tests/ui/async-await/issues/issue-67893.rs b/tests/ui/async-await/issues/issue-67893.rs
index 73cce38c94a..2020abe7a5a 100644
--- a/tests/ui/async-await/issues/issue-67893.rs
+++ b/tests/ui/async-await/issues/issue-67893.rs
@@ -7,5 +7,5 @@ fn g(_: impl Send) {}
 
 fn main() {
     g(issue_67893::run())
-    //~^ ERROR `MutexGuard<'_, ()>` cannot be sent between threads safely
+    //~^ ERROR `std::sync::MutexGuard<'_, ()>` cannot be sent between threads safely
 }
diff --git a/tests/ui/async-await/issues/issue-67893.stderr b/tests/ui/async-await/issues/issue-67893.stderr
index c01237255b8..34f28dd53c7 100644
--- a/tests/ui/async-await/issues/issue-67893.stderr
+++ b/tests/ui/async-await/issues/issue-67893.stderr
@@ -1,8 +1,8 @@
-error[E0277]: `MutexGuard<'_, ()>` cannot be sent between threads safely
+error[E0277]: `std::sync::MutexGuard<'_, ()>` cannot be sent between threads safely
   --> $DIR/issue-67893.rs:9:7
    |
 LL |     g(issue_67893::run())
-   |     - ^^^^^^^^^^^^^^^^^^ `MutexGuard<'_, ()>` cannot be sent between threads safely
+   |     - ^^^^^^^^^^^^^^^^^^ `std::sync::MutexGuard<'_, ()>` cannot be sent between threads safely
    |     |
    |     required by a bound introduced by this call
    |
@@ -11,7 +11,7 @@ LL |     g(issue_67893::run())
 LL | pub async fn run() {
    | ------------------ within this `impl Future<Output = ()>`
    |
-   = help: within `impl Future<Output = ()>`, the trait `Send` is not implemented for `MutexGuard<'_, ()>`
+   = help: within `impl Future<Output = ()>`, the trait `Send` is not implemented for `std::sync::MutexGuard<'_, ()>`
 note: required because it's used within this `async` fn body
   --> $DIR/auxiliary/issue_67893.rs:9:20
    |
diff --git a/tests/ui/compiletest-self-test/ui-testing-optout.stderr b/tests/ui/compiletest-self-test/ui-testing-optout.stderr
index 652c472c0bc..f1d03eab14a 100644
--- a/tests/ui/compiletest-self-test/ui-testing-optout.stderr
+++ b/tests/ui/compiletest-self-test/ui-testing-optout.stderr
@@ -16,7 +16,7 @@ error[E0412]: cannot find type `D` in this scope
 error[E0412]: cannot find type `F` in this scope
   --> $DIR/ui-testing-optout.rs:92:10
    |
-4  | type A = B;
+ 4 | type A = B;
    | ----------- similarly named type alias `A` defined here
 ...
 92 | type E = F;
diff --git a/tests/ui/const-generics/generic_const_exprs/auxiliary/feature-attribute-missing-in-dependent-crate-ice-aux.rs b/tests/ui/const-generics/generic_const_exprs/auxiliary/feature-attribute-missing-in-dependent-crate-ice-aux.rs
new file mode 100644
index 00000000000..3902454c14c
--- /dev/null
+++ b/tests/ui/const-generics/generic_const_exprs/auxiliary/feature-attribute-missing-in-dependent-crate-ice-aux.rs
@@ -0,0 +1,9 @@
+#![feature(generic_const_exprs)]
+
+pub struct Error(());
+
+pub trait FromSlice: Sized {
+    const SIZE: usize = std::mem::size_of::<Self>();
+
+    fn validate_slice(bytes: &[[u8; Self::SIZE]]) -> Result<(), Error>;
+}
diff --git a/tests/ui/const-generics/generic_const_exprs/feature-attribute-missing-in-dependent-crate-ice.rs b/tests/ui/const-generics/generic_const_exprs/feature-attribute-missing-in-dependent-crate-ice.rs
new file mode 100644
index 00000000000..b9537014767
--- /dev/null
+++ b/tests/ui/const-generics/generic_const_exprs/feature-attribute-missing-in-dependent-crate-ice.rs
@@ -0,0 +1,19 @@
+//! Regression test to ensure that using the `generic_const_exprs` feature in a library crate
+//! without enabling it in a dependent crate does not lead to an ICE.
+//!
+//! Issue: <https://github.com/rust-lang/rust/issues/129882>
+
+//@ aux-build:feature-attribute-missing-in-dependent-crate-ice-aux.rs
+
+extern crate feature_attribute_missing_in_dependent_crate_ice_aux as aux;
+
+struct Wrapper<const F: usize>(i64);
+
+impl<const F: usize> aux::FromSlice for Wrapper<F> {
+    fn validate_slice(_: &[[u8; Self::SIZE]]) -> Result<(), aux::Error> {
+        //~^ ERROR generic `Self` types are currently not permitted in anonymous constants
+        Ok(())
+    }
+}
+
+fn main() {}
diff --git a/tests/ui/const-generics/generic_const_exprs/feature-attribute-missing-in-dependent-crate-ice.stderr b/tests/ui/const-generics/generic_const_exprs/feature-attribute-missing-in-dependent-crate-ice.stderr
new file mode 100644
index 00000000000..5c330665142
--- /dev/null
+++ b/tests/ui/const-generics/generic_const_exprs/feature-attribute-missing-in-dependent-crate-ice.stderr
@@ -0,0 +1,14 @@
+error: generic `Self` types are currently not permitted in anonymous constants
+  --> $DIR/feature-attribute-missing-in-dependent-crate-ice.rs:13:33
+   |
+LL |     fn validate_slice(_: &[[u8; Self::SIZE]]) -> Result<(), aux::Error> {
+   |                                 ^^^^
+   |
+note: not a concrete type
+  --> $DIR/feature-attribute-missing-in-dependent-crate-ice.rs:12:41
+   |
+LL | impl<const F: usize> aux::FromSlice for Wrapper<F> {
+   |                                         ^^^^^^^^^^
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui/lint/must_not_suspend/mutex.rs b/tests/ui/lint/must_not_suspend/mutex.rs
index d14f7130b4c..8dd4cc17615 100644
--- a/tests/ui/lint/must_not_suspend/mutex.rs
+++ b/tests/ui/lint/must_not_suspend/mutex.rs
@@ -5,7 +5,7 @@
 async fn other() {}
 
 pub async fn uhoh(m: std::sync::Mutex<()>) {
-    let _guard = m.lock().unwrap(); //~ ERROR `MutexGuard` held across
+    let _guard = m.lock().unwrap(); //~ ERROR `std::sync::MutexGuard` held across
     other().await;
 }
 
diff --git a/tests/ui/lint/must_not_suspend/mutex.stderr b/tests/ui/lint/must_not_suspend/mutex.stderr
index ca53a753150..0db1f2575b1 100644
--- a/tests/ui/lint/must_not_suspend/mutex.stderr
+++ b/tests/ui/lint/must_not_suspend/mutex.stderr
@@ -1,4 +1,4 @@
-error: `MutexGuard` held across a suspend point, but should not be
+error: `std::sync::MutexGuard` held across a suspend point, but should not be
   --> $DIR/mutex.rs:8:9
    |
 LL |     let _guard = m.lock().unwrap();
diff --git a/tests/ui/loop-match/upvar-scrutinee.rs b/tests/ui/loop-match/upvar-scrutinee.rs
new file mode 100644
index 00000000000..a93e3a0e59a
--- /dev/null
+++ b/tests/ui/loop-match/upvar-scrutinee.rs
@@ -0,0 +1,81 @@
+#![allow(incomplete_features)]
+#![feature(loop_match)]
+
+#[derive(Clone, Copy)]
+enum State {
+    A,
+    B,
+}
+
+fn main() {
+    let mut state = State::A;
+
+    #[loop_match]
+    loop {
+        state = 'blk: {
+            match state {
+                State::A => {
+                    #[const_continue]
+                    break 'blk State::B;
+                }
+                State::B => {
+                    return;
+                }
+            }
+        }
+    }
+
+    || {
+        #[loop_match]
+        loop {
+            state = 'blk: {
+                match state {
+                    //~^ ERROR invalid match on `#[loop_match]` state
+                    State::A => {
+                        #[const_continue]
+                        break 'blk State::B;
+                    }
+                    State::B => {
+                        return;
+                    }
+                }
+            }
+        }
+    };
+
+    || {
+        let mut state = state;
+        #[loop_match]
+        loop {
+            state = 'blk: {
+                match state {
+                    State::A => {
+                        #[const_continue]
+                        break 'blk State::B;
+                    }
+                    State::B => {
+                        return;
+                    }
+                }
+            }
+        }
+    };
+
+    move || {
+        #[loop_match]
+        loop {
+            state = 'blk: {
+                match state {
+                    //~^ ERROR invalid match on `#[loop_match]` state
+                    State::A => {
+                        #[const_continue]
+                        break 'blk State::B;
+                    }
+                    State::B => {
+                        return;
+                    }
+                }
+            }
+        }
+    };
+}
diff --git a/tests/ui/loop-match/upvar-scrutinee.stderr b/tests/ui/loop-match/upvar-scrutinee.stderr
new file mode 100644
index 00000000000..b7a0a90193d
--- /dev/null
+++ b/tests/ui/loop-match/upvar-scrutinee.stderr
@@ -0,0 +1,18 @@
+error: invalid match on `#[loop_match]` state
+  --> $DIR/upvar-scrutinee.rs:32:23
+   |
+LL |                 match state {
+   |                       ^^^^^
+   |
+   = note: a local variable must be the scrutinee within a `#[loop_match]`
+
+error: invalid match on `#[loop_match]` state
+  --> $DIR/upvar-scrutinee.rs:68:23
+   |
+LL |                 match state {
+   |                       ^^^^^
+   |
+   = note: a local variable must be the scrutinee within a `#[loop_match]`
+
+error: aborting due to 2 previous errors
+
diff --git a/tests/ui/modules/issue-107649.stderr b/tests/ui/modules/issue-107649.stderr
index 802ac669a10..49d7cb4e0aa 100644
--- a/tests/ui/modules/issue-107649.stderr
+++ b/tests/ui/modules/issue-107649.stderr
@@ -9,8 +9,8 @@ error[E0277]: `Dummy` doesn't implement `Debug`
 help: consider annotating `Dummy` with `#[derive(Debug)]`
    --> $DIR/auxiliary/dummy_lib.rs:2:1
     |
-2   + #[derive(Debug)]
-3   | pub struct Dummy;
+  2 + #[derive(Debug)]
+  3 | pub struct Dummy;
     |
 
 error: aborting due to 1 previous error
diff --git a/tests/ui/privacy/private-field-access-in-mutex-54062.rs b/tests/ui/privacy/private-field-access-in-mutex-54062.rs
index f145f9c3e52..c957e0bc7e8 100644
--- a/tests/ui/privacy/private-field-access-in-mutex-54062.rs
+++ b/tests/ui/privacy/private-field-access-in-mutex-54062.rs
@@ -8,7 +8,7 @@ fn main() {}
 
 fn testing(test: Test) {
     let _ = test.comps.inner.try_lock();
-    //~^ ERROR: field `inner` of struct `Mutex` is private
+    //~^ ERROR: field `inner` of struct `std::sync::Mutex` is private
 }
 
 // https://github.com/rust-lang/rust/issues/54062
diff --git a/tests/ui/privacy/private-field-access-in-mutex-54062.stderr b/tests/ui/privacy/private-field-access-in-mutex-54062.stderr
index 14244107597..f7f84640648 100644
--- a/tests/ui/privacy/private-field-access-in-mutex-54062.stderr
+++ b/tests/ui/privacy/private-field-access-in-mutex-54062.stderr
@@ -1,4 +1,4 @@
-error[E0616]: field `inner` of struct `Mutex` is private
+error[E0616]: field `inner` of struct `std::sync::Mutex` is private
   --> $DIR/private-field-access-in-mutex-54062.rs:10:24
    |
 LL |     let _ = test.comps.inner.try_lock();
diff --git a/tests/ui/runtime/out-of-stack.rs b/tests/ui/runtime/out-of-stack.rs
index 6be34afb560..913d3637c8f 100644
--- a/tests/ui/runtime/out-of-stack.rs
+++ b/tests/ui/runtime/out-of-stack.rs
@@ -19,7 +19,7 @@ extern crate libc;
 use std::env;
 use std::hint::black_box;
 use std::process::Command;
-use std::thread;
+use std::thread::Builder;
 
 fn silent_recurse() {
     let buf = [0u8; 1000];
@@ -56,9 +56,9 @@ fn main() {
     } else if args.len() > 1 && args[1] == "loud" {
         loud_recurse();
     } else if args.len() > 1 && args[1] == "silent-thread" {
-        thread::spawn(silent_recurse).join();
+        Builder::new().name("ferris".to_string()).spawn(silent_recurse).unwrap().join();
     } else if args.len() > 1 && args[1] == "loud-thread" {
-        thread::spawn(loud_recurse).join();
+        Builder::new().name("ferris".to_string()).spawn(loud_recurse).unwrap().join();
     } else {
         let mut modes = vec![
             "silent-thread",
@@ -82,6 +82,12 @@ fn main() {
             let error = String::from_utf8_lossy(&silent.stderr);
             assert!(error.contains("has overflowed its stack"),
                     "missing overflow message: {}", error);
+
+            if mode.contains("thread") {
+                assert!(error.contains("ferris"), "missing thread name: {}", error);
+            } else {
+                assert!(error.contains("main"), "missing thread name: {}", error);
+            }
         }
     }
 }
diff --git a/tests/ui/suggestions/inner_type.fixed b/tests/ui/suggestions/inner_type.fixed
index 3dc939d6b5c..175a2a02acd 100644
--- a/tests/ui/suggestions/inner_type.fixed
+++ b/tests/ui/suggestions/inner_type.fixed
@@ -25,7 +25,7 @@ fn main() {
     let another_item = std::sync::Mutex::new(Struct { p: 42_u32 });
 
     another_item.lock().unwrap().method();
-    //~^ ERROR no method named `method` found for struct `Mutex` in the current scope [E0599]
+    //~^ ERROR no method named `method` found for struct `std::sync::Mutex` in the current scope [E0599]
     //~| HELP use `.lock().unwrap()` to borrow the `Struct<u32>`, blocking the current thread until it can be acquired
 
     let another_item = std::sync::RwLock::new(Struct { p: 42_u32 });
diff --git a/tests/ui/suggestions/inner_type.rs b/tests/ui/suggestions/inner_type.rs
index 81a05c25311..ab021414f56 100644
--- a/tests/ui/suggestions/inner_type.rs
+++ b/tests/ui/suggestions/inner_type.rs
@@ -25,7 +25,7 @@ fn main() {
     let another_item = std::sync::Mutex::new(Struct { p: 42_u32 });
 
     another_item.method();
-    //~^ ERROR no method named `method` found for struct `Mutex` in the current scope [E0599]
+    //~^ ERROR no method named `method` found for struct `std::sync::Mutex` in the current scope [E0599]
     //~| HELP use `.lock().unwrap()` to borrow the `Struct<u32>`, blocking the current thread until it can be acquired
 
     let another_item = std::sync::RwLock::new(Struct { p: 42_u32 });
diff --git a/tests/ui/suggestions/inner_type.stderr b/tests/ui/suggestions/inner_type.stderr
index 5ac3d04f104..67ebb5789b7 100644
--- a/tests/ui/suggestions/inner_type.stderr
+++ b/tests/ui/suggestions/inner_type.stderr
@@ -30,11 +30,11 @@ help: use `.borrow_mut()` to mutably borrow the `Struct<u32>`, panicking if any
 LL |     other_item.borrow_mut().some_mutable_method();
    |               +++++++++++++
 
-error[E0599]: no method named `method` found for struct `Mutex` in the current scope
+error[E0599]: no method named `method` found for struct `std::sync::Mutex` in the current scope
   --> $DIR/inner_type.rs:27:18
    |
 LL |     another_item.method();
-   |                  ^^^^^^ method not found in `Mutex<Struct<u32>>`
+   |                  ^^^^^^ method not found in `std::sync::Mutex<Struct<u32>>`
    |
 note: the method `method` exists on the type `Struct<u32>`
   --> $DIR/inner_type.rs:9:5
diff --git a/tests/ui/sync/mutexguard-sync.stderr b/tests/ui/sync/mutexguard-sync.stderr
index 1501a793d5e..ab9983c1f2c 100644
--- a/tests/ui/sync/mutexguard-sync.stderr
+++ b/tests/ui/sync/mutexguard-sync.stderr
@@ -8,7 +8,7 @@ LL |     test_sync(guard);
    |
    = help: the trait `Sync` is not implemented for `Cell<i32>`
    = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicI32` instead
-   = note: required for `MutexGuard<'_, Cell<i32>>` to implement `Sync`
+   = note: required for `std::sync::MutexGuard<'_, Cell<i32>>` to implement `Sync`
 note: required by a bound in `test_sync`
   --> $DIR/mutexguard-sync.rs:5:17
    |
diff --git a/tests/ui/traits/const-traits/span-bug-issue-121418.stderr b/tests/ui/traits/const-traits/span-bug-issue-121418.stderr
index 92cfecd0540..f31129d9cb7 100644
--- a/tests/ui/traits/const-traits/span-bug-issue-121418.stderr
+++ b/tests/ui/traits/const-traits/span-bug-issue-121418.stderr
@@ -14,8 +14,8 @@ error[E0277]: the size for values of type `(dyn T + 'static)` cannot be known at
 LL |     pub const fn new() -> std::sync::Mutex<dyn T> {}
    |                           ^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
    |
-   = help: within `Mutex<(dyn T + 'static)>`, the trait `Sized` is not implemented for `(dyn T + 'static)`
-note: required because it appears within the type `Mutex<(dyn T + 'static)>`
+   = help: within `std::sync::Mutex<(dyn T + 'static)>`, the trait `Sized` is not implemented for `(dyn T + 'static)`
+note: required because it appears within the type `std::sync::Mutex<(dyn T + 'static)>`
   --> $SRC_DIR/std/src/sync/poison/mutex.rs:LL:COL
    = note: the return type of a function must have a statically known size
 
@@ -27,7 +27,7 @@ LL |     pub const fn new() -> std::sync::Mutex<dyn T> {}
    |                  |
    |                  implicitly returns `()` as its body has no tail or `return` expression
    |
-   = note: expected struct `Mutex<(dyn T + 'static)>`
+   = note: expected struct `std::sync::Mutex<(dyn T + 'static)>`
            found unit type `()`
 
 error: aborting due to 3 previous errors
diff --git a/tests/ui/typeck/assign-non-lval-derefmut.fixed b/tests/ui/typeck/assign-non-lval-derefmut.fixed
index 6ecec574f2e..e6f97a9e86c 100644
--- a/tests/ui/typeck/assign-non-lval-derefmut.fixed
+++ b/tests/ui/typeck/assign-non-lval-derefmut.fixed
@@ -5,11 +5,11 @@ fn main() {
     *x.lock().unwrap() = 2;
     //~^ ERROR invalid left-hand side of assignment
     *x.lock().unwrap() += 1;
-    //~^ ERROR binary assignment operation `+=` cannot be applied to type `MutexGuard<'_, usize>`
+    //~^ ERROR binary assignment operation `+=` cannot be applied to type `std::sync::MutexGuard<'_, usize>`
 
     let mut y = x.lock().unwrap();
     *y = 2;
     //~^ ERROR mismatched types
     *y += 1;
-    //~^ ERROR binary assignment operation `+=` cannot be applied to type `MutexGuard<'_, usize>`
+    //~^ ERROR binary assignment operation `+=` cannot be applied to type `std::sync::MutexGuard<'_, usize>`
 }
diff --git a/tests/ui/typeck/assign-non-lval-derefmut.rs b/tests/ui/typeck/assign-non-lval-derefmut.rs
index ac1be913e2a..a53a52c7e4d 100644
--- a/tests/ui/typeck/assign-non-lval-derefmut.rs
+++ b/tests/ui/typeck/assign-non-lval-derefmut.rs
@@ -5,11 +5,11 @@ fn main() {
     x.lock().unwrap() = 2;
     //~^ ERROR invalid left-hand side of assignment
     x.lock().unwrap() += 1;
-    //~^ ERROR binary assignment operation `+=` cannot be applied to type `MutexGuard<'_, usize>`
+    //~^ ERROR binary assignment operation `+=` cannot be applied to type `std::sync::MutexGuard<'_, usize>`
 
     let mut y = x.lock().unwrap();
     y = 2;
     //~^ ERROR mismatched types
     y += 1;
-    //~^ ERROR binary assignment operation `+=` cannot be applied to type `MutexGuard<'_, usize>`
+    //~^ ERROR binary assignment operation `+=` cannot be applied to type `std::sync::MutexGuard<'_, usize>`
 }
diff --git a/tests/ui/typeck/assign-non-lval-derefmut.stderr b/tests/ui/typeck/assign-non-lval-derefmut.stderr
index 16fb1e9c5c3..f57b5abe2ee 100644
--- a/tests/ui/typeck/assign-non-lval-derefmut.stderr
+++ b/tests/ui/typeck/assign-non-lval-derefmut.stderr
@@ -11,15 +11,15 @@ help: consider dereferencing here to assign to the mutably borrowed value
 LL |     *x.lock().unwrap() = 2;
    |     +
 
-error[E0368]: binary assignment operation `+=` cannot be applied to type `MutexGuard<'_, usize>`
+error[E0368]: binary assignment operation `+=` cannot be applied to type `std::sync::MutexGuard<'_, usize>`
   --> $DIR/assign-non-lval-derefmut.rs:7:5
    |
 LL |     x.lock().unwrap() += 1;
    |     -----------------^^^^^
    |     |
-   |     cannot use `+=` on type `MutexGuard<'_, usize>`
+   |     cannot use `+=` on type `std::sync::MutexGuard<'_, usize>`
    |
-note: the foreign item type `MutexGuard<'_, usize>` doesn't implement `AddAssign<{integer}>`
+note: the foreign item type `std::sync::MutexGuard<'_, usize>` doesn't implement `AddAssign<{integer}>`
   --> $SRC_DIR/std/src/sync/poison/mutex.rs:LL:COL
    |
    = note: not implement `AddAssign<{integer}>`
@@ -36,22 +36,22 @@ LL |     let mut y = x.lock().unwrap();
 LL |     y = 2;
    |         ^ expected `MutexGuard<'_, usize>`, found integer
    |
-   = note: expected struct `MutexGuard<'_, usize>`
+   = note: expected struct `std::sync::MutexGuard<'_, usize>`
                 found type `{integer}`
 help: consider dereferencing here to assign to the mutably borrowed value
    |
 LL |     *y = 2;
    |     +
 
-error[E0368]: binary assignment operation `+=` cannot be applied to type `MutexGuard<'_, usize>`
+error[E0368]: binary assignment operation `+=` cannot be applied to type `std::sync::MutexGuard<'_, usize>`
   --> $DIR/assign-non-lval-derefmut.rs:13:5
    |
 LL |     y += 1;
    |     -^^^^^
    |     |
-   |     cannot use `+=` on type `MutexGuard<'_, usize>`
+   |     cannot use `+=` on type `std::sync::MutexGuard<'_, usize>`
    |
-note: the foreign item type `MutexGuard<'_, usize>` doesn't implement `AddAssign<{integer}>`
+note: the foreign item type `std::sync::MutexGuard<'_, usize>` doesn't implement `AddAssign<{integer}>`
   --> $SRC_DIR/std/src/sync/poison/mutex.rs:LL:COL
    |
    = note: not implement `AddAssign<{integer}>`
diff --git a/tests/ui/typeck/deref-multi.stderr b/tests/ui/typeck/deref-multi.stderr
index 02513853c48..c4fa49e43ef 100644
--- a/tests/ui/typeck/deref-multi.stderr
+++ b/tests/ui/typeck/deref-multi.stderr
@@ -63,7 +63,7 @@ LL |     x.lock().unwrap()
    |     ^^^^^^^^^^^^^^^^^ expected `i32`, found `MutexGuard<'_, &i32>`
    |
    = note: expected type `i32`
-            found struct `MutexGuard<'_, &i32>`
+            found struct `std::sync::MutexGuard<'_, &i32>`
 help: consider dereferencing the type
    |
 LL |     **x.lock().unwrap()